summaryrefslogtreecommitdiffstats
path: root/src/bin/d2
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 11:36:04 +0000
commit040eee1aa49b49df4698d83a05af57c220127fd1 (patch)
treef635435954e6ccde5eee9893889e24f30ca68346 /src/bin/d2
parentInitial commit. (diff)
downloadisc-kea-upstream.tar.xz
isc-kea-upstream.zip
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/bin/d2/Makefile.am103
-rw-r--r--src/bin/d2/Makefile.in1030
-rw-r--r--src/bin/d2/d2.dox316
-rw-r--r--src/bin/d2/d2_controller.cc156
-rw-r--r--src/bin/d2/d2_controller.h93
-rw-r--r--src/bin/d2/d2_hooks.dox81
-rw-r--r--src/bin/d2/d2_lexer.cc3676
-rw-r--r--src/bin/d2/d2_lexer.ll916
-rw-r--r--src/bin/d2/d2_parser.cc2858
-rw-r--r--src/bin/d2/d2_parser.h2645
-rw-r--r--src/bin/d2/d2_parser.yy993
-rw-r--r--src/bin/d2/d2_process.cc502
-rw-r--r--src/bin/d2/d2_process.h324
-rw-r--r--src/bin/d2/d2_queue_mgr.cc264
-rw-r--r--src/bin/d2/d2_queue_mgr.h348
-rw-r--r--src/bin/d2/d2_update_mgr.cc295
-rw-r--r--src/bin/d2/d2_update_mgr.h248
-rw-r--r--src/bin/d2/images/abstract_app_classes.svg222
-rw-r--r--src/bin/d2/images/add_state_model.svg301
-rw-r--r--src/bin/d2/images/config_data_classes.svg299
-rw-r--r--src/bin/d2/images/config_from_file_sequence.svg83
-rw-r--r--src/bin/d2/images/config_parser_classes.svg262
-rw-r--r--src/bin/d2/images/cpl_signal_classes.svg393
-rw-r--r--src/bin/d2/images/cpl_signal_sequence.svg318
-rw-r--r--src/bin/d2/images/d2_app_classes.svg345
-rw-r--r--src/bin/d2/images/nc_trans_sequence.svg230
-rw-r--r--src/bin/d2/images/remove_state_model.svg304
-rw-r--r--src/bin/d2/images/request_mgt_classes.svg316
-rw-r--r--src/bin/d2/images/state_model_classes.svg246
-rw-r--r--src/bin/d2/images/trans_classes.svg208
-rw-r--r--src/bin/d2/images/update_exec_classes.svg387
-rw-r--r--src/bin/d2/location.hh306
-rw-r--r--src/bin/d2/main.cc58
-rw-r--r--src/bin/d2/nc_add.cc707
-rw-r--r--src/bin/d2/nc_add.h445
-rw-r--r--src/bin/d2/nc_remove.cc701
-rw-r--r--src/bin/d2/nc_remove.h429
-rw-r--r--src/bin/d2/parser_context.cc221
-rw-r--r--src/bin/d2/parser_context.h351
-rw-r--r--src/bin/d2/parser_context_decl.h20
-rw-r--r--src/bin/d2/simple_add.cc536
-rw-r--r--src/bin/d2/simple_add.h355
-rw-r--r--src/bin/d2/simple_remove.cc519
-rw-r--r--src/bin/d2/simple_remove.h351
-rw-r--r--src/bin/d2/tests/Makefile.am133
-rw-r--r--src/bin/d2/tests/Makefile.in1427
-rw-r--r--src/bin/d2/tests/callout_library.cc72
-rw-r--r--src/bin/d2/tests/configured_library.cc63
-rw-r--r--src/bin/d2/tests/d2_cfg_mgr_unittests.cc1080
-rw-r--r--src/bin/d2/tests/d2_command_unittest.cc1391
-rw-r--r--src/bin/d2/tests/d2_controller_unittests.cc303
-rw-r--r--src/bin/d2/tests/d2_process_tests.sh.in332
-rw-r--r--src/bin/d2/tests/d2_process_unittests.cc693
-rw-r--r--src/bin/d2/tests/d2_queue_mgr_unittests.cc457
-rw-r--r--src/bin/d2/tests/d2_simple_parser_unittest.cc1194
-rw-r--r--src/bin/d2/tests/d2_unittests.cc28
-rw-r--r--src/bin/d2/tests/d2_update_mgr_unittests.cc989
-rw-r--r--src/bin/d2/tests/get_config_unittest.cc293
-rw-r--r--src/bin/d2/tests/nc_add_unittests.cc1713
-rw-r--r--src/bin/d2/tests/nc_remove_unittests.cc1796
-rw-r--r--src/bin/d2/tests/parser_unittest.cc871
-rw-r--r--src/bin/d2/tests/parser_unittest.h36
-rw-r--r--src/bin/d2/tests/simple_add_unittests.cc1155
-rw-r--r--src/bin/d2/tests/simple_remove_unittests.cc1238
-rw-r--r--src/bin/d2/tests/test_callout_libraries.h.in24
-rw-r--r--src/bin/d2/tests/test_configured_libraries.h.in26
-rw-r--r--src/bin/d2/tests/test_data_files_config.h.in10
-rw-r--r--src/bin/d2/tests/testdata/d2_cfg_tests.json1577
-rw-r--r--src/bin/d2/tests/testdata/get_config.json106
69 files changed, 40768 insertions, 0 deletions
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
new file mode 100644
index 0000000..5328297
--- /dev/null
+++ b/src/bin/d2/Makefile.am
@@ -0,0 +1,103 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+EXTRA_DIST = d2.dox d2_hooks.dox
+EXTRA_DIST += d2_parser.yy
+
+EXTRA_DIST += images/abstract_app_classes.svg images/add_state_model.svg
+EXTRA_DIST += images/config_data_classes.svg images/config_from_file_sequence.svg
+EXTRA_DIST += images/config_parser_classes.svg images/cpl_signal_classes.svg
+EXTRA_DIST += images/cpl_signal_sequence.svg images/d2_app_classes.svg
+EXTRA_DIST += images/nc_trans_sequence.svg images/remove_state_model.svg
+EXTRA_DIST += images/request_mgt_classes.svg images/state_model_classes.svg
+EXTRA_DIST += images/trans_classes.svg images/update_exec_classes.svg
+
+
+# convenience archive
+
+noinst_LTLIBRARIES = libd2.la
+
+libd2_la_SOURCES =
+libd2_la_SOURCES += d2_process.cc d2_process.h
+libd2_la_SOURCES += d2_lexer.ll location.hh
+libd2_la_SOURCES += d2_parser.cc d2_parser.h
+libd2_la_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
+libd2_la_SOURCES += d2_update_mgr.cc d2_update_mgr.h
+libd2_la_SOURCES += nc_add.cc nc_add.h
+libd2_la_SOURCES += nc_remove.cc nc_remove.h
+libd2_la_SOURCES += d2_controller.cc d2_controller.h
+libd2_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
+libd2_la_SOURCES += simple_add.cc simple_add.h
+libd2_la_SOURCES += simple_remove.cc simple_remove.h
+
+sbin_PROGRAMS = kea-dhcp-ddns
+
+kea_dhcp_ddns_SOURCES = main.cc
+
+kea_dhcp_ddns_LDADD = libd2.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/d2srv/libkea-d2srv.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/eval/libkea-eval.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+kea_dhcp_ddns_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS)
+
+kea_dhcp_ddns_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+
+kea_dhcp_ddnsdir = $(pkgdatadir)
+
+if GENERATE_PARSER
+
+# Generate parser first.
+all-recursive: d2_lexer.cc location.hh d2_parser.cc d2_parser.h
+
+parser: d2_lexer.cc location.hh d2_parser.cc d2_parser.h
+ @echo "Flex/bison files regenerated"
+
+# --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
+# Call flex with -s to check that the default rule can be suppressed
+# Call bison with -W to get warnings like unmarked empty rules
+# Note C++11 deprecated register still used by flex < 2.6.0
+location.hh d2_parser.cc d2_parser.h: d2_parser.yy
+ $(YACC) -Wno-yacc --defines=d2_parser.h --report=all \
+ --report-file=d2_parser.report -o d2_parser.cc d2_parser.yy
+
+d2_lexer.cc: d2_lexer.ll
+ $(LEX) --prefix d2_parser_ -o d2_lexer.cc d2_lexer.ll
+
+else
+
+parser location.hh d2_parser.cc d2_parser.h d2_lexer.cc:
+ @echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+
+endif
diff --git a/src/bin/d2/Makefile.in b/src/bin/d2/Makefile.in
new file mode 100644
index 0000000..a876038
--- /dev/null
+++ b/src/bin/d2/Makefile.in
@@ -0,0 +1,1030 @@
+# 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@
+sbin_PROGRAMS = kea-dhcp-ddns$(EXEEXT)
+subdir = src/bin/d2
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(sbindir)"
+PROGRAMS = $(sbin_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libd2_la_LIBADD =
+am_libd2_la_OBJECTS = d2_process.lo d2_lexer.lo d2_parser.lo \
+ d2_queue_mgr.lo d2_update_mgr.lo nc_add.lo nc_remove.lo \
+ d2_controller.lo parser_context.lo simple_add.lo \
+ simple_remove.lo
+libd2_la_OBJECTS = $(am_libd2_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 =
+am_kea_dhcp_ddns_OBJECTS = main.$(OBJEXT)
+kea_dhcp_ddns_OBJECTS = $(am_kea_dhcp_ddns_OBJECTS)
+am__DEPENDENCIES_1 =
+kea_dhcp_ddns_DEPENDENCIES = libd2.la \
+ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+ $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/eval/libkea-eval.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.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)
+kea_dhcp_ddns_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(AM_CXXFLAGS) $(CXXFLAGS) $(kea_dhcp_ddns_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)/d2_controller.Plo \
+ ./$(DEPDIR)/d2_lexer.Plo ./$(DEPDIR)/d2_parser.Plo \
+ ./$(DEPDIR)/d2_process.Plo ./$(DEPDIR)/d2_queue_mgr.Plo \
+ ./$(DEPDIR)/d2_update_mgr.Plo ./$(DEPDIR)/main.Po \
+ ./$(DEPDIR)/nc_add.Plo ./$(DEPDIR)/nc_remove.Plo \
+ ./$(DEPDIR)/parser_context.Plo ./$(DEPDIR)/simple_add.Plo \
+ ./$(DEPDIR)/simple_remove.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 =
+LEXCOMPILE = $(LEX) $(AM_LFLAGS) $(LFLAGS)
+LTLEXCOMPILE = $(LIBTOOL) $(AM_V_lt) $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(LEX) $(AM_LFLAGS) $(LFLAGS)
+AM_V_LEX = $(am__v_LEX_@AM_V@)
+am__v_LEX_ = $(am__v_LEX_@AM_DEFAULT_V@)
+am__v_LEX_0 = @echo " LEX " $@;
+am__v_LEX_1 =
+YLWRAP = $(top_srcdir)/ylwrap
+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 = $(libd2_la_SOURCES) $(kea_dhcp_ddns_SOURCES)
+DIST_SOURCES = $(libd2_la_SOURCES) $(kea_dhcp_ddns_SOURCES)
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp \
+ $(top_srcdir)/ylwrap d2_lexer.cc
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BOOST_INCLUDES = @BOOST_INCLUDES@
+BOOST_LIBS = @BOOST_LIBS@
+BOTAN_TOOL = @BOTAN_TOOL@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CONTRIB_DIR = @CONTRIB_DIR@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CRYPTO_CFLAGS = @CRYPTO_CFLAGS@
+CRYPTO_INCLUDES = @CRYPTO_INCLUDES@
+CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@
+CRYPTO_LIBS = @CRYPTO_LIBS@
+CRYPTO_PACKAGE = @CRYPTO_PACKAGE@
+CRYPTO_RPATH = @CRYPTO_RPATH@
+CXX = @CXX@
+CXXCPP = @CXXCPP@
+CXXDEPMODE = @CXXDEPMODE@
+CXXFLAGS = @CXXFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@
+DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@
+DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@
+DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = . tests
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \
+ -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin \
+ $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+CLEANFILES = *.gcno *.gcda
+EXTRA_DIST = d2.dox d2_hooks.dox d2_parser.yy \
+ images/abstract_app_classes.svg images/add_state_model.svg \
+ images/config_data_classes.svg \
+ images/config_from_file_sequence.svg \
+ images/config_parser_classes.svg images/cpl_signal_classes.svg \
+ images/cpl_signal_sequence.svg images/d2_app_classes.svg \
+ images/nc_trans_sequence.svg images/remove_state_model.svg \
+ images/request_mgt_classes.svg images/state_model_classes.svg \
+ images/trans_classes.svg images/update_exec_classes.svg
+
+# convenience archive
+noinst_LTLIBRARIES = libd2.la
+libd2_la_SOURCES = d2_process.cc d2_process.h d2_lexer.ll location.hh \
+ d2_parser.cc d2_parser.h d2_queue_mgr.cc d2_queue_mgr.h \
+ d2_update_mgr.cc d2_update_mgr.h nc_add.cc nc_add.h \
+ nc_remove.cc nc_remove.h d2_controller.cc d2_controller.h \
+ parser_context.cc parser_context.h parser_context_decl.h \
+ simple_add.cc simple_add.h simple_remove.cc simple_remove.h
+kea_dhcp_ddns_SOURCES = main.cc
+kea_dhcp_ddns_LDADD = libd2.la \
+ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+ $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+ $(top_builddir)/src/lib/process/libkea-process.la \
+ $(top_builddir)/src/lib/eval/libkea-eval.la \
+ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+ $(top_builddir)/src/lib/stats/libkea-stats.la \
+ $(top_builddir)/src/lib/config/libkea-cfgclient.la \
+ $(top_builddir)/src/lib/http/libkea-http.la \
+ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \
+ $(top_builddir)/src/lib/database/libkea-database.la \
+ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+ $(top_builddir)/src/lib/cc/libkea-cc.la \
+ $(top_builddir)/src/lib/dns/libkea-dns++.la \
+ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \
+ $(top_builddir)/src/lib/hooks/libkea-hooks.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)
+kea_dhcp_ddns_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+kea_dhcp_ddnsdir = $(pkgdatadir)
+all: all-recursive
+
+.SUFFIXES:
+.SUFFIXES: .cc .ll .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/bin/d2/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/d2/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-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_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
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libd2.la: $(libd2_la_OBJECTS) $(libd2_la_DEPENDENCIES) $(EXTRA_libd2_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(CXXLINK) $(libd2_la_OBJECTS) $(libd2_la_LIBADD) $(LIBS)
+
+kea-dhcp-ddns$(EXEEXT): $(kea_dhcp_ddns_OBJECTS) $(kea_dhcp_ddns_DEPENDENCIES) $(EXTRA_kea_dhcp_ddns_DEPENDENCIES)
+ @rm -f kea-dhcp-ddns$(EXEEXT)
+ $(AM_V_CXXLD)$(kea_dhcp_ddns_LINK) $(kea_dhcp_ddns_OBJECTS) $(kea_dhcp_ddns_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_controller.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_lexer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_process.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_queue_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_update_mgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/main.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nc_add.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/nc_remove.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/parser_context.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_add.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_remove.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 $@ $<
+
+.ll.cc:
+ $(AM_V_LEX)$(am__skiplex) $(SHELL) $(YLWRAP) $< $(LEX_OUTPUT_ROOT).c $@ -- $(LEXCOMPILE)
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-recursive
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-recursive
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES)
+installdirs: installdirs-recursive
+installdirs-am:
+ for dir in "$(DESTDIR)$(sbindir)"; 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."
+ -rm -f d2_lexer.cc
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \
+ clean-sbinPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/d2_controller.Plo
+ -rm -f ./$(DEPDIR)/d2_lexer.Plo
+ -rm -f ./$(DEPDIR)/d2_parser.Plo
+ -rm -f ./$(DEPDIR)/d2_process.Plo
+ -rm -f ./$(DEPDIR)/d2_queue_mgr.Plo
+ -rm -f ./$(DEPDIR)/d2_update_mgr.Plo
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/nc_add.Plo
+ -rm -f ./$(DEPDIR)/nc_remove.Plo
+ -rm -f ./$(DEPDIR)/parser_context.Plo
+ -rm -f ./$(DEPDIR)/simple_add.Plo
+ -rm -f ./$(DEPDIR)/simple_remove.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am: install-sbinPROGRAMS
+
+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)/d2_controller.Plo
+ -rm -f ./$(DEPDIR)/d2_lexer.Plo
+ -rm -f ./$(DEPDIR)/d2_parser.Plo
+ -rm -f ./$(DEPDIR)/d2_process.Plo
+ -rm -f ./$(DEPDIR)/d2_queue_mgr.Plo
+ -rm -f ./$(DEPDIR)/d2_update_mgr.Plo
+ -rm -f ./$(DEPDIR)/main.Po
+ -rm -f ./$(DEPDIR)/nc_add.Plo
+ -rm -f ./$(DEPDIR)/nc_remove.Plo
+ -rm -f ./$(DEPDIR)/parser_context.Plo
+ -rm -f ./$(DEPDIR)/simple_add.Plo
+ -rm -f ./$(DEPDIR)/simple_remove.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am: uninstall-sbinPROGRAMS
+
+.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-libtool \
+ clean-noinstLTLIBRARIES clean-sbinPROGRAMS 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-sbinPROGRAMS install-strip \
+ installcheck installcheck-am installdirs installdirs-am \
+ maintainer-clean maintainer-clean-generic mostlyclean \
+ mostlyclean-compile mostlyclean-generic mostlyclean-libtool \
+ pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \
+ uninstall-sbinPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Generate parser first.
+@GENERATE_PARSER_TRUE@all-recursive: d2_lexer.cc location.hh d2_parser.cc d2_parser.h
+
+@GENERATE_PARSER_TRUE@parser: d2_lexer.cc location.hh d2_parser.cc d2_parser.h
+@GENERATE_PARSER_TRUE@ @echo "Flex/bison files regenerated"
+
+# --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
+# Call flex with -s to check that the default rule can be suppressed
+# Call bison with -W to get warnings like unmarked empty rules
+# Note C++11 deprecated register still used by flex < 2.6.0
+@GENERATE_PARSER_TRUE@location.hh d2_parser.cc d2_parser.h: d2_parser.yy
+@GENERATE_PARSER_TRUE@ $(YACC) -Wno-yacc --defines=d2_parser.h --report=all \
+@GENERATE_PARSER_TRUE@ --report-file=d2_parser.report -o d2_parser.cc d2_parser.yy
+
+@GENERATE_PARSER_TRUE@d2_lexer.cc: d2_lexer.ll
+@GENERATE_PARSER_TRUE@ $(LEX) --prefix d2_parser_ -o d2_lexer.cc d2_lexer.ll
+
+@GENERATE_PARSER_FALSE@parser location.hh d2_parser.cc d2_parser.h d2_lexer.cc:
+@GENERATE_PARSER_FALSE@ @echo Parser generation disabled. Configure with --enable-generate-parser 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/bin/d2/d2.dox b/src/bin/d2/d2.dox
new file mode 100644
index 0000000..8a41e52
--- /dev/null
+++ b/src/bin/d2/d2.dox
@@ -0,0 +1,316 @@
+// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ @page d2 DHCP-DDNS Component
+
+Kea is capable of sending dynamic DNS updates to DNS Servers, based on lease
+changes made by Kea's DHCP servers. When DDNS updating is enabled,
+the DHCP servers generate requests to update DNS as they make lease changes.
+These requests, implemented by isc::dhcp_ddns::NameChangeRequest (NCR), are sent
+to a separate process, informally known as D2. D2 processes these requests by
+carrying out DDNS conversations with the appropriate DNS servers such that they
+update the DNS data.
+
+The design documentation for D2 can be found here:
+<a href="https://gitlab.isc.org/isc-projects/kea/wikis/designs/ddns-design">D2 Design</a>.
+
+The implementation is split in several libraries which can be used separately
+(linked only when required):
+- src/lib/dns (libkea-dns++) - contains classes and enumerations which define
+DNS update message, message content, EDNS, RData, RR Types, RCODEs, OPCODEs,
+DNS Name, TSIG key and TSIG record, exception types, etc.
+- src/lib/dhcp_ddns (libkea-dhcp_ddns) - contains classes to send and receive,
+encapsulate and decapsulate DNS messages, etc.
+- src/lib/d2srv (libkea-d2srv) - contains classes to handle NCR transactions,
+the DNS client implementation, statistics, configuration parser and manager,
+logger, DNS Zone, etc.
+- src/bin/d2 (kea-dhcp-ddns) - contains classes which handle message queues,
+D2 process and D2 controller internals, etc.
+
+This document contains several UML diagrams, and a few conventions used within
+these diagrams are worth noting:
+
+- If a class is appearing on class diagram for the first time (within this
+document) its background color will be yellow. If it has been presented
+already, its background color will be blue and/or its details may not be shown.
+
+- Associations between classes in D2 are implemented in most cases via typedefs,
+sometimes through a small chain of typedefs. These typedefs are shown for
+accuracy but are unimportant to a general discussion.
+
+@section d2ProcessDerivation D2's CPL Derivations
+
+D2's core application classes are DDNS-specific derivations of the CPL as show
+in the diagram below:
+
+@image html d2_app_classes.svg "D2's CPL Derivations"
+
+- isc::d2::D2Controller - entry point for running D2, it processes command line
+options, starts and controls the application process, @c D2Process
+(see @ref src/bin/d2/d2_controller.h).
+
+- isc::d2::D2Process - creates and manages D2's primary resources and
+implements the main event loop described in @ref d2EventLoop section
+(see @ref src/bin/d2/d2_process.h).
+
+- isc::d2::D2CfgMgr - creates, updates, and provides access to D2's application
+configuration which is embodied by @c D2CfgContext
+(see @ref src/lib/d2srv/d2_cfg_mgr.h).
+
+- isc::d2::D2CfgContext - warehouses D2's application configuration
+(see @ref src/lib/d2srv/d2_cfg_mgr.h).
+
+@section d2ConfigMgt Configuration Management
+
+D2's configuration management uses the same underlying mechanisms as Kea's DHCP
+servers. It's configuration information is organized into a hierarchical data
+model which is mirrored in the implementation by a hierarchy of parser classes
+and the runtime classes they instantiate.
+
+D2's data model is organized as follows:
+
+- A set of application level values such as the D2's IP address, port
+
+- Two lists of "domains": one for Forward DNS and one for Reverse DNS. @n
+ Each domain is described by a zone name and a list of DNS servers that can
+ update that zone.
+
+- A list of TSIG keys for conducting signed DDNS exchanges with DNS servers
+
+The runtime classes that embody this model are shown in the following diagram:
+
+@image html config_data_classes.svg "D2's Configuration Data Classes"
+
+- isc::d2::D2CfgContext - D2-specific derivation of @c DCfgContextBase. It
+houses the "global" configuration for an instance of D2
+(see @ref src/lib/d2srv/d2_cfg_mgr.h).
+
+- isc::d2::DdnsDomainListMgr - manages a list of domains. Currently there is
+one manager instance for the list of forward domains, and one for the list of
+reverse domains. In addition the domain list, it will may house other values
+specific to that list of domains (e.g. enable flag)
+(see @ref src/lib/d2srv/d2_config.h).
+
+- isc::d2::DdnsDomain - represents a DNS domain (really a zone). When requests
+are received they are matched to a domain by comparing their FQDN to the
+domain's name (see @ref src/lib/d2srv/d2_config.h).
+
+- isc::d2::DnsServerInfo - describes a DNS server which supports DDNS for a
+given domain (see @ref src/lib/d2srv/d2_config.h).
+
+- isc::d2::TSIGKeyInfo - describes a TSIG key used for authenticated DDNS for a
+given domain (see @ref src/lib/d2srv/d2_config.h).
+
+The parsing classes, as one would expect, parallel the runtime classes quite
+closely. The parsers are named for the runtime class they instantiate and are
+either designed to parse a single occurrence of that class or list of that
+class. The parser classes are shown in the diagram below:
+
+@image html config_parser_classes.svg "D2's Configuration Parsers"
+
+- isc::d2::DdnsDomainListMgrParser - parser for a domain list manager, it
+creates a domain list parser.
+
+- isc::d2::DdnsDomainListParser - parser for a list of domains, it creates a
+domain parser for domain described in a list domains.
+
+- isc::d2::DdnsDomainParser - Parser for a domain, it creates a DNS server list
+parser.
+
+- isc::d2::DnsServerInfoListParser - parser for a list of DNS servers, it
+creates a DNS server parser for server described in a list of servers.
+
+- isc::d2::DnsServerInfoParser - parser for DNS server.
+
+- isc::d2::TSIGKeyInfoListParser - parser for a list of TSIG keys, it creates a
+parser for key described in a list of keys.
+
+- isc::d2::TSIGKeyInfoParser - parser for TSIG key.
+
+For more details on the parsers see @ref src/lib/d2srv/d2_config.h.
+
+The underlying common libraries for configuration parsing support configuration
+input in JSON format, that complies with a fixed set of generic constructs that
+may be described by a spec file (also JSON).
+
+@section d2NCRReceipt Request Reception and Queuing
+
+D2 is designed to receive requests from Kea's DHCP servers, asynchronously and
+store them in queue to be processed. The classes responsible for this are
+shown in the diagram below:
+
+@image html request_mgt_classes.svg "Request Management Classes"
+
+- isc::d2::D2QueueMgr - owned by @c D2Process, it listens for
+@c NameChangeRequests and queues them for processing. It also provides services
+for adding, finding, and removing queue entries. It owns the interface used to
+receive requests and thus shields the remainder of D2 from any specific
+knowledge or interaction with this interface
+(see @ref src/bin/d2/d2_queue_mgr.h).
+
+- isc::d2::RequestQueue - storage container for the received requests
+(see @ref src/bin/d2/d2_queue_mgr.h).
+
+- isc::dhcp_ddns::NameChangeListener - Abstract asynchronous interface for
+receiving requests which uses ASIO to process IO and invoke a callback upon
+request receipt (see @ref src/lib/dhcp_ddns/ncr_io.h).
+
+- isc::dhcp_ddns::NameChangeUDPListener - Derivation of NameChangeListener
+which supports receiving request via UDP socket
+(see @ref src/lib/dhcp_ddns/ncr_udp.h).
+
+- isc::dhcp_ddns::NameChangeRequest - embodies a request to update DNS entries
+based upon a DHCP lease change (see @ref src/lib/dhcp_ddns/ncr_msg.h).
+
+D2QueueMgr is state-driven, albeit with a very simple state model. The states
+are defined by isc::d2::D2QueueMgr::State (see @ref src/bin/d2/d2_queue_mgr.h).
+
+@section d2DDNSUpdateExecution Update Execution
+
+The DDNS protocol can lead to a multiple step conversation between the updater
+and the DNS server to update entries for a single client. In addition,
+@c NameChangeRequests can request changes be made for both forward and reverse
+DNS. In order to carry out the appropriate conversation, D2 wraps each request
+in a stateful transaction.
+
+Working such transactions serially can be inefficient, especially if those
+requests involve different DNS servers. Therefore, D2 has been designed to
+work on more than one transaction at a time by creating and managing a list of
+transactions.
+
+The classes which are responsible for carrying out this work are shown in the
+following diagram:
+
+@image html update_exec_classes.svg "Update Execution Classes"
+
+- isc::d2::D2UpdateMgr owned by @c D2Process, orchestrates the fulfillment of
+each request by managing the execution of its transaction. Its high level
+method @ref isc::d2::D2UpdateMgr::sweep() is meant to be called whenever IO
+events occur (see @ref src/bin/d2/d2_update_mgr.h). The following steps are
+performed each time the method is called:
+ - Any transaction which has been completed, is logged and culled from the
+ transaction list.
+ - Start a new transaction for the next queued request (if any).
+
+- isc::d2::NameChangeTransaction - abstract state-driven class which carries
+out the steps necessary to fulfill a single request. Fulfilling a request is
+achieved as IO events in response it DDNS requests drive the transaction through
+its state model (see @ref src/lib/d2srv/nc_trans.h). The type of transaction is
+based on the nature of the request, this is discussed further on
+@ref d2TransDetail section.
+
+- isc::d2::DNSClient - an asynchronous worker which atomically performs a
+single DDNS packet exchange with a given server, providing the response via a
+callback mechanism. Each time a transaction's state model calls for a packet
+exchange with a DNS server, it uses an instance of this class to do it
+(see @ref src/lib/d2srv/dns_client.h).
+
+- isc::d2::D2UpdateMessage - container for sending and receiving DDNS packets
+(see @ref src/lib/d2srv/d2_update_message.h).
+
+@section d2EventLoop Main Event Loop
+
+Now that all of the primary components have been introduced it is worth while
+discussing D2's main event loop. As mentioned earlier D2 is constructed around
+the CPL which is designed to be driven by asynchronous IO processed by a common
+IO service thread (isc::asiolink::io_service). Any IO that needs to be
+performed by the application thread uses this service to do so. This organizes
+the IO event processing into a single event loop centered around the service.
+(This does not preclude spinning off worker threads to conduct other tasks, with
+their own io_service instances). D2's main event loop, implemented in
+@ref isc::d2::D2Process::run() may be paraphrased as follows:
+
+@code
+ As long as we should not shutdown repeat the following steps:
+ 1. Check on the state of the request queue. Take any actions necessary
+ regarding it. For example, it may be in the process of shutting down
+ its listener prior to applying a configuration change.
+
+ 2. Give update manager a "time slice" to cull finished transactions and
+ start new ones.
+
+ 3. Block until one or more of the following IO events occur:
+ a. NCR message has been received
+ b. Transaction IO has completed
+ c. Interval timer expired
+ d. A process command has been received
+ e. Something has stopped the IO service (most likely a fatal error)
+@endcode
+
+@section d2TransDetail Transactions
+
+There are two types of @c NameChangeRequests: an "Add" that is issued when DNS
+entries need to be added for new or updated lease, and a "Remove" that is
+issued when DNS entries need to be removed for obsolete or expired lease. The
+DDNS protocol dictates the steps that should be followed in both cases.
+
+D2's design addresses this by calling for two types of transactions: one for
+adding entries and one for removing them, each with their own state model.
+The transaction classes are shown in the following diagram:
+
+@image html trans_classes.svg "NameChangeTransaction Derivations"
+
+- isc::d2::NameAddTransaction - carries out a @c NameChangeRequest to add
+entries (see @ref src/bin/d2/nc_add.h).
+
+- isc::d2::NameRemoveTransaction - carries out a @c NameChangeRequest to remove
+entries (see @ref src/bin/d2/nc_remove.h).
+
+- isc::util::StateModel - abstract state model described in @ref d2StateModel
+section (see @ref src/lib/util/state_model.h).
+
+The state models for these two transactions implement DDNS with conflict
+resolution as described in <a href="https://tools.ietf.org/html/rfc4703">RFC 4703</a>.
+
+The state model for isc::d2::NameAddTransaction is diagrammed below:
+
+@image html add_state_model.svg "State Model for NameAddTransaction"
+
+The state model for isc::d2::NameRemoveTransaction is depicted next:
+
+@image html remove_state_model.svg "State Model for NameRemoveTransaction"
+
+@subsection d2StateModel State Model Implementation
+
+D2 implements a abstract state-machine through a light weight set of classes.
+At construction, the model's dictionary of events and states is initialized.
+This allows, the deriving class the ability to bind a method of its choosing
+to each state as that state's handler. Each handler contains the knowledge
+of how to respond to the "posted" event and including posting other events and
+transitioning to other states.
+
+Executing the model consists of beginning at the current state with the posted
+event and continuing until the model needs to wait for an IO-based event or
+it has reached the end of the state model. These classes will likely move to
+a common library.
+
+@image html state_model_classes.svg "State Model Classes"
+
+- isc::util::StateModel - provides the mechanics for executing a state model
+described by a dictionary events and states. It provides methods to:
+ - initialize the model - constructs the dictionary of events and states.
+ - start the model - sets the model to its initial state, posts a "start"
+ event and starts the model "running".
+ - run the model - process the posted event (if one) until the model reaches
+ a wait state or reaches the end state.
+ - transition from one state to another.
+- isc::d2::Event - Defines an event used to stimulate the model.
+- isc::d2::State - Defines a state with the model, each state has a handler
+method that is executed upon transition "into" that state from another state.
+- isc::d2::LabeledValue - An abstract that associates a integer value with a
+text label.
+- isc::d2::LabeledValueSet - A collection of LabeledValues for which the integer
+values must be unique.
+
+@subsection d2TransExecExample Transaction Execution Example
+
+The following sequence chart depicts the typically sequence of events that occur
+when D2UpdateMgr creates and starts executing a transaction:
+
+@image html nc_trans_sequence.svg "Transaction Execution Sequence"
+
+*/
diff --git a/src/bin/d2/d2_controller.cc b/src/bin/d2/d2_controller.cc
new file mode 100644
index 0000000..af2c6df
--- /dev/null
+++ b/src/bin/d2/d2_controller.cc
@@ -0,0 +1,156 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cfgrpt/config_report.h>
+#include <config/command_mgr.h>
+#include <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2/parser_context.h>
+#include <stats/stats_mgr.h>
+
+#include <stdlib.h>
+
+using namespace isc::config;
+using namespace isc::process;
+using namespace isc::stats;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines the application name, this is passed into base class
+/// it may be used to locate configuration data and appears in log statement.
+const char* D2Controller::d2_app_name_ = "DhcpDdns";
+
+/// @brief Defines the executable name. This is passed into the base class
+const char* D2Controller::d2_bin_name_ = "kea-dhcp-ddns";
+
+DControllerBasePtr&
+D2Controller::instance() {
+ // If the instance hasn't been created yet, create it. Note this method
+ // must use the base class singleton instance methods.
+ if (!getController()) {
+ DControllerBasePtr controller_ptr(new D2Controller());
+ setController(controller_ptr);
+ }
+
+ return (getController());
+}
+
+DProcessBase* D2Controller::createProcess() {
+ // Instantiate and return an instance of the D2 application process. Note
+ // that the process is passed the controller's io_service.
+ return (new D2Process(getAppName().c_str(), getIOService()));
+}
+
+D2Controller::D2Controller()
+ : DControllerBase(d2_app_name_, d2_bin_name_) {
+}
+
+void
+D2Controller::registerCommands() {
+ // These are the commands always supported by the D2 server.
+ // Please keep the list in alphabetic order.
+ CommandMgr::instance().registerCommand(BUILD_REPORT_COMMAND,
+ std::bind(&D2Controller::buildReportHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand(CONFIG_GET_COMMAND,
+ std::bind(&D2Controller::configGetHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand(CONFIG_RELOAD_COMMAND,
+ std::bind(&D2Controller::configReloadHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand(CONFIG_SET_COMMAND,
+ std::bind(&D2Controller::configSetHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand(CONFIG_TEST_COMMAND,
+ std::bind(&D2Controller::configTestHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand(CONFIG_WRITE_COMMAND,
+ std::bind(&D2Controller::configWriteHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand(SHUT_DOWN_COMMAND,
+ std::bind(&D2Controller::shutdownHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand(STATUS_GET_COMMAND,
+ std::bind(&DControllerBase::statusGetHandler, this, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand(VERSION_GET_COMMAND,
+ std::bind(&D2Controller::versionGetHandler, this, ph::_1, ph::_2));
+
+ // Register statistic related commands.
+ CommandMgr::instance().registerCommand("statistic-get",
+ std::bind(&StatsMgr::statisticGetHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-get-all",
+ std::bind(&StatsMgr::statisticGetAllHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-reset",
+ std::bind(&StatsMgr::statisticResetHandler, ph::_1, ph::_2));
+
+ CommandMgr::instance().registerCommand("statistic-reset-all",
+ std::bind(&StatsMgr::statisticResetAllHandler, ph::_1, ph::_2));
+}
+
+void
+D2Controller::deregisterCommands() {
+ try {
+ // Close the command socket (if it exists).
+ CommandMgr::instance().closeCommandSocket();
+
+ // Deregister any registered commands (please keep in alphabetic order)
+ CommandMgr::instance().deregisterCommand(BUILD_REPORT_COMMAND);
+ CommandMgr::instance().deregisterCommand(CONFIG_GET_COMMAND);
+ CommandMgr::instance().deregisterCommand(CONFIG_RELOAD_COMMAND);
+ CommandMgr::instance().deregisterCommand(CONFIG_SET_COMMAND);
+ CommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND);
+ CommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND);
+ CommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND);
+ CommandMgr::instance().deregisterCommand("statistic-get");
+ CommandMgr::instance().deregisterCommand("statistic-get-all");
+ CommandMgr::instance().deregisterCommand("statistic-reset");
+ CommandMgr::instance().deregisterCommand("statistic-reset-all");
+ CommandMgr::instance().deregisterCommand(STATUS_GET_COMMAND);
+ CommandMgr::instance().deregisterCommand(VERSION_GET_COMMAND);
+
+ } catch (...) {
+ // What to do? Simply ignore...
+ }
+}
+
+isc::data::ConstElementPtr
+D2Controller::parseFile(const std::string& file_name) {
+ isc::data::ConstElementPtr elements;
+
+ // Read contents of the file and parse it as JSON
+ D2ParserContext parser;
+ elements = parser.parseFile(file_name, D2ParserContext::PARSER_DHCPDDNS);
+ if (!elements) {
+ isc_throw(isc::BadValue, "no configuration found in file");
+ }
+
+ return (elements);
+}
+
+D2Controller::~D2Controller() {
+}
+
+// Refer to config_report so it will be embedded in the binary.
+const char* const* d2_config_report = isc::detail::config_report;
+
+std::string
+D2Controller::getVersionAddendum() {
+ std::stringstream stream;
+ // Currently the only dependency D2 adds to base is cryptolink
+ stream << isc::cryptolink::CryptoLink::getVersion() << std::endl;
+ return (stream.str());
+
+}
+
+} // end namespace isc::d2
+} // end namespace isc
diff --git a/src/bin/d2/d2_controller.h b/src/bin/d2/d2_controller.h
new file mode 100644
index 0000000..6f61735
--- /dev/null
+++ b/src/bin/d2/d2_controller.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_CONTROLLER_H
+#define D2_CONTROLLER_H
+
+#include <process/d_controller.h>
+
+namespace isc {
+namespace d2 {
+
+class D2Controller;
+/// @brief Pointer to a process controller.
+typedef boost::shared_ptr<D2Controller> D2ControllerPtr;
+
+/// @brief Process Controller for D2 Process
+/// This class is the DHCP-DDNS specific derivation of DControllerBase. It
+/// creates and manages an instance of the DHCP-DDNS application process,
+/// D2Process.
+/// @todo Currently, this class provides only the minimum required specialized
+/// behavior to run the DHCP-DDNS service. It may very well expand as the
+/// service implementation evolves. Some thought was given to making
+/// DControllerBase a templated class but the labor savings versus the
+/// potential number of virtual methods which may be overridden didn't seem
+/// worth the clutter at this point.
+class D2Controller : public process::DControllerBase {
+public:
+ /// @brief Static singleton instance method. This method returns the
+ /// base class singleton instance member. It instantiates the singleton
+ /// and sets the base class instance member upon first invocation.
+ ///
+ /// @return returns the pointer reference to the singleton instance.
+ static process::DControllerBasePtr& instance();
+
+ /// @brief Destructor.
+ virtual ~D2Controller();
+
+ /// @brief Defines the application name, this is passed into base class
+ /// and appears in log statements.
+ static const char* d2_app_name_;
+
+ /// @brief Defines the executable name. This is passed into the base class
+ /// by convention this should match the executable name.
+ static const char* d2_bin_name_;
+
+ /// @brief Register commands.
+ void registerCommands();
+
+ /// @brief Deregister commands.
+ /// @note Does not throw.
+ void deregisterCommands();
+
+protected:
+ /// @brief Returns version info specific to D2
+ virtual std::string getVersionAddendum();
+
+private:
+ /// @brief Creates an instance of the DHCP-DDNS specific application
+ /// process. This method is invoked during the process initialization
+ /// step of the controller launch.
+ ///
+ /// @return returns a DProcessBase* to the application process created.
+ /// Note the caller is responsible for destructing the process. This
+ /// is handled by the base class, which wraps this pointer with a smart
+ /// pointer.
+ virtual process::DProcessBase* createProcess();
+
+ ///@brief Parse a given file into Elements
+ ///
+ /// Uses bison parsing to parse a JSON configuration file into an
+ /// a element map.
+ ///
+ /// @param file_name pathname of the file to parse
+ ///
+ /// @return pointer to the map of elements created
+ /// @throw BadValue if the file is empty
+ virtual isc::data::ConstElementPtr parseFile(const std::string& file_name);
+
+ /// @brief Constructor is declared private to maintain the integrity of
+ /// the singleton instance.
+ D2Controller();
+
+ /// To facilitate unit testing.
+ friend class NakedD2Controller;
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/d2_hooks.dox b/src/bin/d2/d2_hooks.dox
new file mode 100644
index 0000000..0c33820
--- /dev/null
+++ b/src/bin/d2/d2_hooks.dox
@@ -0,0 +1,81 @@
+// 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/.
+
+/**
+ @page d2Hooks The Hooks API for the Kea DHCP DDNS (D2)
+
+ @section d2HooksIntroduction Introduction
+ Kea features an API (the "Hooks" API) that allows user-written code to
+ be integrated into Kea and called at specific points in its processing.
+ An overview of the API and a tutorial for writing such code can be found in
+ the @ref hooksdgDevelopersGuide. It also includes information how hooks
+ framework can be used to implement additional control commands for
+ Kea DHCP DDNS. Information for Kea maintainers can be found in the
+ @ref hooksComponentDeveloperGuide.
+
+ This manual is more specialized and is aimed at developers of hook
+ code for Kea DHCP DDNS. It describes each hook point, what the callouts
+ attached to the hook are able to do, and the arguments passed to the
+ callouts. Each entry in this manual has the following information:
+
+ - Name of the hook point.
+ - Arguments for the callout. As well as the argument name and data type, the
+ information includes the direction, which can be one of:
+ - @b in - the server passes values to the callout but ignored any data
+ returned.
+ - @b out - the callout is expected to set this value.
+ - <b>in/out</b> - the server passes a value to the callout and uses whatever
+ value the callout sends back. Note that the callout may choose not to
+ do any modification, in which case the server will use whatever value
+ it sent to the callout.
+ - Description of the hook. This explains where in the processing the hook
+ is located, the possible actions a callout attached to this hook could take,
+ and a description of the data passed to the callouts.
+ - Next step status: the action taken by the server when a callout chooses to set
+ status to specified value. Actions not listed explicitly are not supported.
+ If a callout sets status to unsupported value, this specific value will be
+ ignored and treated as if the status was CONTINUE.
+
+@section d2HooksHookPoints Hooks in Kea DHCP DDNS
+
+The following list is roughly ordered by appearance of specific hook points during
+packet processing, but the exact order depends on the actual processing. Hook points
+that are not specific to packet processing (e.g. lease expiration) will be added
+to the end of this list.
+
+ @subsection d2HooksD2SrvConfigured d2_srv_configured
+ - @b Arguments:
+ - name: @b io_context, type: isc::asiolink::IOServicePtr, direction: <b>in</b>
+ - name: @b json_config, type: isc::data::ConstElementPtr, direction: <b>in</b>
+ - name: @b server_config, type: isc::d2::D2CfgContextPtr, direction: <b>in</b>
+ - name: @b error, type: std::string, direction: <b>out</b>
+
+ - @b Description: this callout is executed when the server has completed
+ its (re)configuration. The server provides received and parsed configuration
+ structures to the hook library. It also provides a pointer to the IOService
+ object which is used by the server to run asynchronous operations. The hooks
+ libraries can use this IOService object to schedule asynchronous tasks which
+ are triggered by the Kea DHCP DDNS's main loop. The hook library should hold
+ the provided pointer until the library is unloaded. The D2CfgContext
+ object provides access to the D2 running configuration.
+
+ - <b>Next step status</b>: If any callout sets the status to DROP, the server
+ considers the configuration is incorrect and rejects it using the error
+ string as an error message.
+
+ @subsection d2HooksSelectKey select_key
+ - @b Arguments:
+ - name: @b current_server, type: isc::d2::DnsServerInfoPtr, direction: <b>in</b>
+ - name: @b tsig_key, type: isc::d2::D2TsigKeyPtr, direction: <b>in/out</b>
+
+ - @b Description: this callout is executed when the D2 is selecting for
+ a TSIG key to protect the next DNS update to the already chosen DNS
+ server. Pointers to the current DNS server and TSIG key are provided.
+ If a hook library wants to use another TSIG key the pointer must be updated.
+
+ - <b>Next step status</b>: If any callout sets the status to a different
+ value than CONTINUE the current server is skipped.
+*/
diff --git a/src/bin/d2/d2_lexer.cc b/src/bin/d2/d2_lexer.cc
new file mode 100644
index 0000000..df67bfd
--- /dev/null
+++ b/src/bin/d2/d2_lexer.cc
@@ -0,0 +1,3676 @@
+#line 1 "d2_lexer.cc"
+
+#line 3 "d2_lexer.cc"
+
+#define YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+/* %not-for-header */
+/* %if-c-only */
+/* %if-not-reentrant */
+#define yy_create_buffer d2_parser__create_buffer
+#define yy_delete_buffer d2_parser__delete_buffer
+#define yy_scan_buffer d2_parser__scan_buffer
+#define yy_scan_string d2_parser__scan_string
+#define yy_scan_bytes d2_parser__scan_bytes
+#define yy_init_buffer d2_parser__init_buffer
+#define yy_flush_buffer d2_parser__flush_buffer
+#define yy_load_buffer_state d2_parser__load_buffer_state
+#define yy_switch_to_buffer d2_parser__switch_to_buffer
+#define yypush_buffer_state d2_parser_push_buffer_state
+#define yypop_buffer_state d2_parser_pop_buffer_state
+#define yyensure_buffer_stack d2_parser_ensure_buffer_stack
+#define yy_flex_debug d2_parser__flex_debug
+#define yyin d2_parser_in
+#define yyleng d2_parser_leng
+#define yylex d2_parser_lex
+#define yylineno d2_parser_lineno
+#define yyout d2_parser_out
+#define yyrestart d2_parser_restart
+#define yytext d2_parser_text
+#define yywrap d2_parser_wrap
+#define yyalloc d2_parser_alloc
+#define yyrealloc d2_parser_realloc
+#define yyfree d2_parser_free
+
+/* %endif */
+/* %endif */
+/* %ok-for-header */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 6
+#define YY_FLEX_SUBMINOR_VERSION 4
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* %if-c++-only */
+/* %endif */
+
+/* %if-c-only */
+#ifdef yy_create_buffer
+#define d2_parser__create_buffer_ALREADY_DEFINED
+#else
+#define yy_create_buffer d2_parser__create_buffer
+#endif
+
+#ifdef yy_delete_buffer
+#define d2_parser__delete_buffer_ALREADY_DEFINED
+#else
+#define yy_delete_buffer d2_parser__delete_buffer
+#endif
+
+#ifdef yy_scan_buffer
+#define d2_parser__scan_buffer_ALREADY_DEFINED
+#else
+#define yy_scan_buffer d2_parser__scan_buffer
+#endif
+
+#ifdef yy_scan_string
+#define d2_parser__scan_string_ALREADY_DEFINED
+#else
+#define yy_scan_string d2_parser__scan_string
+#endif
+
+#ifdef yy_scan_bytes
+#define d2_parser__scan_bytes_ALREADY_DEFINED
+#else
+#define yy_scan_bytes d2_parser__scan_bytes
+#endif
+
+#ifdef yy_init_buffer
+#define d2_parser__init_buffer_ALREADY_DEFINED
+#else
+#define yy_init_buffer d2_parser__init_buffer
+#endif
+
+#ifdef yy_flush_buffer
+#define d2_parser__flush_buffer_ALREADY_DEFINED
+#else
+#define yy_flush_buffer d2_parser__flush_buffer
+#endif
+
+#ifdef yy_load_buffer_state
+#define d2_parser__load_buffer_state_ALREADY_DEFINED
+#else
+#define yy_load_buffer_state d2_parser__load_buffer_state
+#endif
+
+#ifdef yy_switch_to_buffer
+#define d2_parser__switch_to_buffer_ALREADY_DEFINED
+#else
+#define yy_switch_to_buffer d2_parser__switch_to_buffer
+#endif
+
+#ifdef yypush_buffer_state
+#define d2_parser_push_buffer_state_ALREADY_DEFINED
+#else
+#define yypush_buffer_state d2_parser_push_buffer_state
+#endif
+
+#ifdef yypop_buffer_state
+#define d2_parser_pop_buffer_state_ALREADY_DEFINED
+#else
+#define yypop_buffer_state d2_parser_pop_buffer_state
+#endif
+
+#ifdef yyensure_buffer_stack
+#define d2_parser_ensure_buffer_stack_ALREADY_DEFINED
+#else
+#define yyensure_buffer_stack d2_parser_ensure_buffer_stack
+#endif
+
+#ifdef yylex
+#define d2_parser_lex_ALREADY_DEFINED
+#else
+#define yylex d2_parser_lex
+#endif
+
+#ifdef yyrestart
+#define d2_parser_restart_ALREADY_DEFINED
+#else
+#define yyrestart d2_parser_restart
+#endif
+
+#ifdef yylex_init
+#define d2_parser_lex_init_ALREADY_DEFINED
+#else
+#define yylex_init d2_parser_lex_init
+#endif
+
+#ifdef yylex_init_extra
+#define d2_parser_lex_init_extra_ALREADY_DEFINED
+#else
+#define yylex_init_extra d2_parser_lex_init_extra
+#endif
+
+#ifdef yylex_destroy
+#define d2_parser_lex_destroy_ALREADY_DEFINED
+#else
+#define yylex_destroy d2_parser_lex_destroy
+#endif
+
+#ifdef yyget_debug
+#define d2_parser_get_debug_ALREADY_DEFINED
+#else
+#define yyget_debug d2_parser_get_debug
+#endif
+
+#ifdef yyset_debug
+#define d2_parser_set_debug_ALREADY_DEFINED
+#else
+#define yyset_debug d2_parser_set_debug
+#endif
+
+#ifdef yyget_extra
+#define d2_parser_get_extra_ALREADY_DEFINED
+#else
+#define yyget_extra d2_parser_get_extra
+#endif
+
+#ifdef yyset_extra
+#define d2_parser_set_extra_ALREADY_DEFINED
+#else
+#define yyset_extra d2_parser_set_extra
+#endif
+
+#ifdef yyget_in
+#define d2_parser_get_in_ALREADY_DEFINED
+#else
+#define yyget_in d2_parser_get_in
+#endif
+
+#ifdef yyset_in
+#define d2_parser_set_in_ALREADY_DEFINED
+#else
+#define yyset_in d2_parser_set_in
+#endif
+
+#ifdef yyget_out
+#define d2_parser_get_out_ALREADY_DEFINED
+#else
+#define yyget_out d2_parser_get_out
+#endif
+
+#ifdef yyset_out
+#define d2_parser_set_out_ALREADY_DEFINED
+#else
+#define yyset_out d2_parser_set_out
+#endif
+
+#ifdef yyget_leng
+#define d2_parser_get_leng_ALREADY_DEFINED
+#else
+#define yyget_leng d2_parser_get_leng
+#endif
+
+#ifdef yyget_text
+#define d2_parser_get_text_ALREADY_DEFINED
+#else
+#define yyget_text d2_parser_get_text
+#endif
+
+#ifdef yyget_lineno
+#define d2_parser_get_lineno_ALREADY_DEFINED
+#else
+#define yyget_lineno d2_parser_get_lineno
+#endif
+
+#ifdef yyset_lineno
+#define d2_parser_set_lineno_ALREADY_DEFINED
+#else
+#define yyset_lineno d2_parser_set_lineno
+#endif
+
+#ifdef yywrap
+#define d2_parser_wrap_ALREADY_DEFINED
+#else
+#define yywrap d2_parser_wrap
+#endif
+
+/* %endif */
+
+#ifdef yyalloc
+#define d2_parser_alloc_ALREADY_DEFINED
+#else
+#define yyalloc d2_parser_alloc
+#endif
+
+#ifdef yyrealloc
+#define d2_parser_realloc_ALREADY_DEFINED
+#else
+#define yyrealloc d2_parser_realloc
+#endif
+
+#ifdef yyfree
+#define d2_parser_free_ALREADY_DEFINED
+#else
+#define yyfree d2_parser_free
+#endif
+
+/* %if-c-only */
+
+#ifdef yytext
+#define d2_parser_text_ALREADY_DEFINED
+#else
+#define yytext d2_parser_text
+#endif
+
+#ifdef yyleng
+#define d2_parser_leng_ALREADY_DEFINED
+#else
+#define yyleng d2_parser_leng
+#endif
+
+#ifdef yyin
+#define d2_parser_in_ALREADY_DEFINED
+#else
+#define yyin d2_parser_in
+#endif
+
+#ifdef yyout
+#define d2_parser_out_ALREADY_DEFINED
+#else
+#define yyout d2_parser_out
+#endif
+
+#ifdef yy_flex_debug
+#define d2_parser__flex_debug_ALREADY_DEFINED
+#else
+#define yy_flex_debug d2_parser__flex_debug
+#endif
+
+#ifdef yylineno
+#define d2_parser_lineno_ALREADY_DEFINED
+#else
+#define yylineno d2_parser_lineno
+#endif
+
+/* %endif */
+
+/* First, we deal with platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+/* %if-c-only */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+/* %endif */
+
+/* %if-tables-serialization */
+/* %endif */
+/* end standard C headers. */
+
+/* %if-c-or-c++ */
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+#ifndef SIZE_MAX
+#define SIZE_MAX (~(size_t)0)
+#endif
+
+#endif /* ! C99 */
+
+#endif /* ! FLEXINT_H */
+
+/* %endif */
+
+/* begin standard C++ headers. */
+/* %if-c++-only */
+/* %endif */
+
+/* TODO: this is always defined, so inline it */
+#define yyconst const
+
+#if defined(__GNUC__) && __GNUC__ >= 3
+#define yynoreturn __attribute__((__noreturn__))
+#else
+#define yynoreturn
+#endif
+
+/* %not-for-header */
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+/* %ok-for-header */
+
+/* %not-for-header */
+/* Promotes a possibly negative, possibly signed char to an
+ * integer in range [0..255] for use as an array index.
+ */
+#define YY_SC_TO_UI(c) ((YY_CHAR) (c))
+/* %ok-for-header */
+
+/* %if-reentrant */
+/* %endif */
+
+/* %if-not-reentrant */
+
+/* %endif */
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN (yy_start) = 1 + 2 *
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state. The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START (((yy_start) - 1) / 2)
+#define YYSTATE YY_START
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart( yyin )
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k.
+ * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case.
+ * Ditto for the __ia64__ case accordingly.
+ */
+#define YY_BUF_SIZE 32768
+#else
+#define YY_BUF_SIZE 16384
+#endif /* __ia64__ */
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+/* %if-not-reentrant */
+extern int yyleng;
+/* %endif */
+
+/* %if-c-only */
+/* %if-not-reentrant */
+extern FILE *yyin, *yyout;
+/* %endif */
+/* %endif */
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+ #define YY_LESS_LINENO(n)
+ #define YY_LINENO_REWIND_TO(ptr)
+
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ *yy_cp = (yy_hold_char); \
+ YY_RESTORE_YY_MORE_OFFSET \
+ (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+#define unput(c) yyunput( c, (yytext_ptr) )
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+ {
+/* %if-c-only */
+ FILE *yy_input_file;
+/* %endif */
+
+/* %if-c++-only */
+/* %endif */
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ int yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ int yy_bs_lineno; /**< The line count. */
+ int yy_bs_column; /**< The column count. */
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+ /* When an EOF's been seen but there's still some text to process
+ * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+ * shouldn't try reading from the input source any more. We might
+ * still have a bunch of tokens to match, though, because of
+ * possible backing-up.
+ *
+ * When we actually see the EOF, we change the status to "new"
+ * (via yyrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
+ */
+#define YY_BUFFER_EOF_PENDING 2
+
+ };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* %if-c-only Standard (non-C++) definition */
+/* %not-for-header */
+/* %if-not-reentrant */
+
+/* Stack of input buffers. */
+static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */
+static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */
+static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */
+/* %endif */
+/* %ok-for-header */
+
+/* %endif */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
+ ? (yy_buffer_stack)[(yy_buffer_stack_top)] \
+ : NULL)
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)]
+
+/* %if-c-only Standard (non-C++) definition */
+
+/* %if-not-reentrant */
+/* %not-for-header */
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+static int yy_n_chars; /* number of characters read into yy_ch_buf */
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = NULL;
+static int yy_init = 0; /* whether we need to initialize */
+static int yy_start = 0; /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+/* %ok-for-header */
+
+/* %endif */
+
+void yyrestart ( FILE *input_file );
+void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer );
+YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size );
+void yy_delete_buffer ( YY_BUFFER_STATE b );
+void yy_flush_buffer ( YY_BUFFER_STATE b );
+void yypush_buffer_state ( YY_BUFFER_STATE new_buffer );
+void yypop_buffer_state ( void );
+
+static void yyensure_buffer_stack ( void );
+static void yy_load_buffer_state ( void );
+static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file );
+#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER )
+
+YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size );
+YY_BUFFER_STATE yy_scan_string ( const char *yy_str );
+YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len );
+
+/* %endif */
+
+void *yyalloc ( yy_size_t );
+void *yyrealloc ( void *, yy_size_t );
+void yyfree ( void * );
+
+#define yy_new_buffer yy_create_buffer
+#define yy_set_interactive(is_interactive) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){ \
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+ }
+#define yy_set_bol(at_bol) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){\
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer( yyin, YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+ }
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* %% [1.0] yytext/yyin/yyout/yy_state_type/yylineno etc. def's & init go here */
+/* Begin user sect3 */
+
+#define d2_parser_wrap() (/*CONSTCOND*/1)
+#define YY_SKIP_YYWRAP
+
+#define FLEX_DEBUG
+typedef flex_uint8_t YY_CHAR;
+
+FILE *yyin = NULL, *yyout = NULL;
+
+typedef int yy_state_type;
+
+extern int yylineno;
+int yylineno = 1;
+
+extern char *yytext;
+#ifdef yytext_ptr
+#undef yytext_ptr
+#endif
+#define yytext_ptr yytext
+
+/* %% [1.5] DFA */
+
+/* %if-c-only Standard (non-C++) definition */
+
+static yy_state_type yy_get_previous_state ( void );
+static yy_state_type yy_try_NUL_trans ( yy_state_type current_state );
+static int yy_get_next_buffer ( void );
+static void yynoreturn yy_fatal_error ( const char* msg );
+
+/* %endif */
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ (yytext_ptr) = yy_bp; \
+/* %% [2.0] code to fiddle yytext and yyleng for yymore() goes here \ */\
+ yyleng = (int) (yy_cp - yy_bp); \
+ (yy_hold_char) = *yy_cp; \
+ *yy_cp = '\0'; \
+/* %% [3.0] code to copy yytext_ptr to yytext[] goes here, if %array \ */\
+ (yy_c_buf_p) = yy_cp;
+/* %% [4.0] data tables for the DFA and the user's section 1 definitions go here */
+#define YY_NUM_RULES 68
+#define YY_END_OF_BUFFER 69
+/* This struct is not used in this scanner,
+ but its presence is necessary. */
+struct yy_trans_info
+ {
+ flex_int32_t yy_verify;
+ flex_int32_t yy_nxt;
+ };
+static const flex_int16_t yy_accept[425] =
+ { 0,
+ 61, 61, 0, 0, 0, 0, 0, 0, 0, 0,
+ 69, 67, 10, 11, 67, 1, 61, 58, 61, 61,
+ 67, 60, 59, 67, 67, 67, 67, 67, 54, 55,
+ 67, 67, 67, 56, 57, 5, 5, 5, 67, 67,
+ 67, 10, 11, 0, 0, 49, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 61, 61,
+ 0, 60, 61, 3, 2, 6, 0, 61, 0, 0,
+ 0, 0, 0, 0, 4, 0, 0, 9, 0, 50,
+ 0, 0, 0, 0, 0, 0, 52, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
+ 8, 0, 0, 0, 0, 0, 51, 53, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 66, 64,
+ 0, 63, 62, 0, 0, 0, 19, 18, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 65, 62,
+ 0, 0, 20, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 46, 0, 0, 0, 0, 0, 14, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 42, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 44, 0, 0, 41, 0, 0, 0, 0, 32,
+
+ 0, 0, 0, 0, 0, 0, 22, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 38, 39, 43,
+ 0, 0, 0, 0, 45, 0, 0, 0, 0, 0,
+ 0, 12, 0, 0, 0, 0, 0, 0, 0, 0,
+ 28, 0, 26, 0, 0, 0, 0, 0, 48, 0,
+ 0, 0, 0, 30, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 29,
+ 0, 0, 0, 47, 0, 0, 0, 0, 0, 13,
+ 17, 0, 0, 37, 0, 0, 0, 0, 0, 0,
+ 31, 0, 27, 0, 0, 0, 0, 0, 35, 34,
+
+ 0, 0, 25, 0, 23, 0, 16, 0, 24, 21,
+ 0, 0, 0, 0, 33, 0, 0, 40, 0, 36,
+ 0, 0, 15, 0
+ } ;
+
+static const YY_CHAR yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
+ 1, 1, 2, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 4, 5, 6, 7, 5, 5, 5, 5, 5,
+ 5, 8, 9, 10, 11, 12, 13, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 15, 5, 16,
+ 5, 17, 18, 5, 19, 20, 21, 22, 23, 24,
+ 5, 5, 5, 25, 5, 26, 5, 27, 28, 29,
+ 5, 30, 31, 32, 33, 5, 5, 5, 5, 5,
+ 34, 35, 36, 5, 37, 5, 38, 39, 40, 41,
+
+ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 5, 54, 55, 56, 57, 58, 59, 60,
+ 61, 62, 63, 5, 64, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5
+ } ;
+
+static const YY_CHAR yy_meta[65] =
+ { 0,
+ 1, 1, 2, 1, 1, 3, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1
+ } ;
+
+static const flex_int16_t yy_base[433] =
+ { 0,
+ 0, 63, 17, 27, 35, 42, 46, 51, 80, 89,
+ 677, 678, 24, 673, 127, 0, 180, 678, 181, 66,
+ 9, 184, 678, 657, 101, 21, 14, 31, 678, 678,
+ 22, 61, 66, 678, 678, 678, 98, 661, 622, 0,
+ 655, 99, 668, 27, 205, 678, 625, 166, 83, 177,
+ 228, 620, 616, 159, 64, 615, 613, 623, 65, 626,
+ 84, 606, 164, 620, 16, 185, 187, 0, 194, 252,
+ 259, 260, 197, 678, 0, 678, 647, 646, 186, 198,
+ 232, 243, 250, 244, 678, 619, 652, 678, 209, 678,
+ 274, 617, 253, 257, 259, 650, 0, 326, 611, 163,
+
+ 603, 614, 608, 596, 593, 595, 177, 637, 586, 607,
+ 601, 584, 593, 588, 585, 177, 586, 581, 238, 598,
+ 591, 594, 0, 259, 267, 277, 260, 269, 280, 586,
+ 678, 581, 270, 627, 626, 625, 678, 678, 356, 578,
+ 579, 572, 572, 569, 583, 613, 568, 563, 573, 564,
+ 581, 607, 563, 572, 249, 573, 603, 560, 574, 555,
+ 554, 567, 554, 565, 558, 561, 550, 285, 678, 678,
+ 297, 678, 678, 546, 580, 595, 678, 678, 386, 546,
+ 557, 544, 586, 552, 540, 539, 548, 554, 536, 539,
+ 548, 537, 549, 544, 539, 542, 577, 280, 525, 531,
+
+ 538, 573, 524, 535, 522, 533, 563, 562, 678, 678,
+ 531, 530, 678, 416, 524, 518, 516, 526, 517, 509,
+ 522, 557, 508, 550, 522, 518, 520, 503, 502, 493,
+ 500, 678, 501, 498, 495, 508, 495, 678, 493, 491,
+ 500, 489, 496, 503, 500, 490, 484, 483, 489, 485,
+ 494, 524, 480, 678, 492, 483, 481, 476, 479, 467,
+ 472, 484, 519, 470, 471, 292, 466, 470, 478, 513,
+ 462, 506, 474, 463, 678, 459, 468, 506, 500, 460,
+ 451, 469, 449, 495, 459, 462, 461, 460, 495, 494,
+ 493, 678, 448, 441, 678, 444, 453, 488, 482, 678,
+
+ 431, 255, 430, 439, 483, 438, 678, 432, 448, 443,
+ 438, 441, 441, 442, 470, 412, 447, 678, 678, 678,
+ 414, 399, 397, 395, 678, 407, 441, 408, 384, 389,
+ 387, 678, 436, 389, 388, 384, 376, 377, 382, 368,
+ 678, 366, 678, 364, 379, 362, 362, 375, 678, 365,
+ 361, 407, 370, 678, 371, 353, 397, 347, 326, 350,
+ 355, 386, 385, 338, 343, 382, 336, 344, 343, 678,
+ 324, 335, 327, 678, 368, 317, 366, 316, 309, 678,
+ 678, 313, 309, 678, 305, 353, 352, 301, 314, 349,
+ 678, 308, 678, 347, 306, 345, 293, 337, 678, 678,
+
+ 336, 285, 678, 288, 678, 294, 678, 280, 678, 678,
+ 328, 284, 269, 243, 678, 167, 119, 678, 64, 678,
+ 53, 2, 678, 678, 459, 462, 465, 0, 468, 471,
+ 474, 477
+ } ;
+
+static const flex_int16_t yy_def[433] =
+ { 0,
+ 425, 425, 426, 426, 425, 425, 425, 425, 425, 425,
+ 424, 424, 424, 424, 424, 427, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 428,
+ 424, 424, 424, 429, 15, 424, 45, 45, 45, 45,
+ 430, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 427, 424, 424,
+ 424, 424, 424, 424, 431, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 428, 424, 429, 424,
+ 424, 45, 45, 45, 45, 432, 45, 430, 45, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 431, 424, 424, 424, 424, 424, 424, 424,
+ 424, 45, 45, 45, 45, 432, 424, 424, 98, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 424, 424, 424,
+ 424, 424, 424, 424, 45, 45, 424, 424, 98, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+
+ 45, 45, 45, 45, 45, 45, 45, 45, 424, 424,
+ 424, 45, 424, 98, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 424, 45, 45, 45, 45, 45, 424, 45, 45,
+ 45, 45, 45, 45, 424, 45, 45, 45, 45, 45,
+ 45, 45, 45, 424, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 424, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 424, 45, 45, 424, 45, 45, 45, 45, 424,
+
+ 45, 45, 45, 45, 45, 45, 424, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 424, 424, 424,
+ 45, 45, 45, 45, 424, 45, 45, 45, 45, 45,
+ 45, 424, 45, 45, 45, 45, 45, 45, 45, 45,
+ 424, 45, 424, 45, 45, 45, 45, 45, 424, 45,
+ 45, 45, 45, 424, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 424,
+ 45, 45, 45, 424, 45, 45, 45, 45, 45, 424,
+ 424, 45, 45, 424, 45, 45, 45, 45, 45, 45,
+ 424, 45, 424, 45, 45, 45, 45, 45, 424, 424,
+
+ 45, 45, 424, 45, 424, 45, 424, 45, 424, 424,
+ 45, 45, 45, 45, 424, 45, 45, 424, 45, 424,
+ 45, 45, 424, 0, 424, 424, 424, 424, 424, 424,
+ 424, 424
+ } ;
+
+static const flex_int16_t yy_nxt[743] =
+ { 0,
+ 87, 13, 14, 13, 424, 15, 16, 423, 17, 18,
+ 19, 20, 21, 22, 23, 24, 74, 424, 37, 14,
+ 37, 75, 25, 26, 38, 42, 27, 42, 37, 14,
+ 37, 28, 90, 29, 38, 30, 13, 14, 13, 79,
+ 79, 25, 31, 13, 14, 13, 80, 13, 14, 13,
+ 32, 40, 13, 14, 13, 33, 40, 119, 79, 82,
+ 81, 91, 34, 35, 13, 14, 13, 120, 15, 16,
+ 80, 17, 18, 19, 20, 21, 22, 23, 24, 73,
+ 39, 13, 14, 13, 81, 25, 26, 39, 71, 27,
+ 13, 14, 13, 80, 28, 81, 29, 41, 30, 42,
+
+ 42, 42, 42, 94, 25, 31, 41, 71, 422, 77,
+ 110, 77, 105, 32, 78, 106, 111, 83, 33, 84,
+ 421, 113, 94, 114, 420, 34, 35, 44, 44, 44,
+ 45, 45, 46, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 47, 45,
+ 45, 48, 45, 45, 45, 45, 45, 45, 49, 50,
+ 45, 51, 45, 45, 52, 45, 53, 54, 45, 55,
+ 45, 56, 57, 48, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 45, 45, 45, 45, 45, 45,
+ 45, 69, 69, 70, 72, 69, 93, 72, 95, 101,
+
+ 102, 116, 71, 71, 103, 94, 71, 73, 95, 104,
+ 73, 124, 141, 142, 90, 117, 71, 95, 419, 71,
+ 93, 71, 71, 125, 94, 71, 45, 95, 149, 45,
+ 159, 150, 160, 97, 124, 71, 45, 45, 71, 121,
+ 45, 122, 45, 91, 45, 45, 125, 45, 418, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 69, 126, 70, 45, 77, 124, 77,
+ 45, 69, 78, 72, 71, 125, 126, 163, 45, 89,
+ 133, 45, 71, 45, 98, 134, 89, 135, 126, 168,
+ 168, 127, 169, 71, 169, 164, 176, 295, 128, 170,
+
+ 129, 71, 170, 195, 133, 328, 196, 209, 89, 134,
+ 329, 135, 89, 168, 171, 169, 89, 172, 170, 209,
+ 176, 173, 233, 417, 89, 416, 209, 89, 296, 89,
+ 89, 138, 234, 415, 414, 413, 376, 412, 210, 139,
+ 411, 410, 409, 408, 139, 139, 139, 139, 139, 139,
+ 407, 406, 405, 404, 403, 402, 401, 400, 399, 398,
+ 397, 396, 395, 139, 139, 139, 139, 139, 139, 179,
+ 394, 393, 392, 391, 179, 179, 179, 179, 179, 179,
+ 377, 390, 389, 388, 387, 386, 385, 384, 383, 382,
+ 381, 380, 379, 179, 179, 179, 179, 179, 179, 214,
+
+ 378, 375, 374, 373, 214, 214, 214, 214, 214, 214,
+ 372, 371, 370, 369, 368, 367, 366, 365, 364, 363,
+ 362, 361, 360, 214, 214, 214, 214, 214, 214, 45,
+ 359, 358, 357, 356, 45, 45, 45, 45, 45, 45,
+ 355, 354, 353, 352, 351, 350, 349, 348, 347, 346,
+ 345, 344, 343, 45, 45, 45, 45, 45, 45, 12,
+ 12, 12, 36, 36, 36, 68, 342, 68, 89, 89,
+ 89, 96, 96, 96, 123, 341, 123, 136, 136, 136,
+ 340, 339, 338, 337, 336, 335, 334, 333, 332, 331,
+ 330, 327, 326, 325, 324, 323, 322, 321, 320, 319,
+
+ 318, 317, 316, 315, 314, 313, 312, 311, 310, 309,
+ 308, 307, 306, 305, 304, 303, 302, 301, 300, 299,
+ 298, 297, 294, 293, 292, 291, 290, 289, 288, 287,
+ 286, 285, 284, 283, 282, 281, 280, 279, 278, 277,
+ 276, 275, 274, 273, 272, 271, 270, 269, 268, 267,
+ 266, 265, 264, 263, 262, 261, 260, 259, 258, 257,
+ 256, 255, 254, 253, 252, 251, 250, 249, 248, 247,
+ 246, 245, 244, 243, 242, 241, 240, 239, 238, 237,
+ 236, 235, 232, 231, 230, 229, 228, 227, 226, 225,
+ 224, 223, 222, 221, 220, 219, 218, 217, 216, 215,
+
+ 213, 212, 211, 208, 207, 206, 205, 204, 203, 202,
+ 201, 200, 199, 198, 197, 194, 193, 192, 191, 190,
+ 189, 188, 187, 186, 185, 184, 183, 182, 181, 180,
+ 137, 178, 177, 175, 174, 167, 166, 165, 162, 161,
+ 158, 157, 156, 155, 154, 153, 152, 151, 148, 147,
+ 146, 145, 144, 143, 140, 137, 132, 131, 130, 78,
+ 78, 118, 115, 112, 109, 108, 107, 100, 99, 92,
+ 43, 88, 86, 85, 76, 43, 424, 11, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424
+ } ;
+
+static const flex_int16_t yy_chk[743] =
+ { 0,
+ 428, 1, 1, 1, 0, 1, 1, 422, 1, 1,
+ 1, 1, 1, 1, 1, 1, 21, 0, 3, 3,
+ 3, 21, 1, 1, 3, 13, 1, 13, 4, 4,
+ 4, 1, 44, 1, 4, 1, 5, 5, 5, 26,
+ 31, 1, 1, 6, 6, 6, 27, 7, 7, 7,
+ 1, 7, 8, 8, 8, 1, 8, 65, 26, 31,
+ 28, 44, 1, 1, 2, 2, 2, 65, 2, 2,
+ 27, 2, 2, 2, 2, 2, 2, 2, 2, 20,
+ 5, 9, 9, 9, 28, 2, 2, 6, 20, 2,
+ 10, 10, 10, 32, 2, 33, 2, 9, 2, 37,
+
+ 42, 37, 42, 49, 2, 2, 10, 20, 421, 25,
+ 59, 25, 55, 2, 25, 55, 59, 32, 2, 33,
+ 419, 61, 49, 61, 417, 2, 2, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 17, 19, 17, 19, 22, 48, 22, 50, 54,
+
+ 54, 63, 17, 19, 54, 66, 22, 69, 67, 54,
+ 73, 79, 100, 100, 89, 63, 69, 50, 416, 73,
+ 48, 17, 19, 80, 66, 22, 45, 67, 107, 45,
+ 116, 107, 116, 51, 79, 69, 45, 45, 73, 66,
+ 51, 67, 45, 89, 45, 45, 80, 45, 414, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 51, 70, 81, 70, 51, 71, 82, 71,
+ 51, 72, 71, 72, 70, 83, 84, 119, 51, 91,
+ 93, 51, 72, 51, 51, 94, 91, 95, 81, 124,
+ 127, 82, 125, 70, 128, 119, 133, 266, 83, 126,
+
+ 84, 72, 129, 155, 93, 302, 155, 168, 91, 94,
+ 302, 95, 91, 124, 127, 125, 91, 128, 126, 171,
+ 133, 129, 198, 413, 91, 412, 168, 91, 266, 91,
+ 91, 98, 198, 411, 408, 406, 359, 404, 171, 98,
+ 402, 401, 398, 397, 98, 98, 98, 98, 98, 98,
+ 396, 395, 394, 392, 390, 389, 388, 387, 386, 385,
+ 383, 382, 379, 98, 98, 98, 98, 98, 98, 139,
+ 378, 377, 376, 375, 139, 139, 139, 139, 139, 139,
+ 359, 373, 372, 371, 369, 368, 367, 366, 365, 364,
+ 363, 362, 361, 139, 139, 139, 139, 139, 139, 179,
+
+ 360, 358, 357, 356, 179, 179, 179, 179, 179, 179,
+ 355, 353, 352, 351, 350, 348, 347, 346, 345, 344,
+ 342, 340, 339, 179, 179, 179, 179, 179, 179, 214,
+ 338, 337, 336, 335, 214, 214, 214, 214, 214, 214,
+ 334, 333, 331, 330, 329, 328, 327, 326, 324, 323,
+ 322, 321, 317, 214, 214, 214, 214, 214, 214, 425,
+ 425, 425, 426, 426, 426, 427, 316, 427, 429, 429,
+ 429, 430, 430, 430, 431, 315, 431, 432, 432, 432,
+ 314, 313, 312, 311, 310, 309, 308, 306, 305, 304,
+ 303, 301, 299, 298, 297, 296, 294, 293, 291, 290,
+
+ 289, 288, 287, 286, 285, 284, 283, 282, 281, 280,
+ 279, 278, 277, 276, 274, 273, 272, 271, 270, 269,
+ 268, 267, 265, 264, 263, 262, 261, 260, 259, 258,
+ 257, 256, 255, 253, 252, 251, 250, 249, 248, 247,
+ 246, 245, 244, 243, 242, 241, 240, 239, 237, 236,
+ 235, 234, 233, 231, 230, 229, 228, 227, 226, 225,
+ 224, 223, 222, 221, 220, 219, 218, 217, 216, 215,
+ 212, 211, 208, 207, 206, 205, 204, 203, 202, 201,
+ 200, 199, 197, 196, 195, 194, 193, 192, 191, 190,
+ 189, 188, 187, 186, 185, 184, 183, 182, 181, 180,
+
+ 176, 175, 174, 167, 166, 165, 164, 163, 162, 161,
+ 160, 159, 158, 157, 156, 154, 153, 152, 151, 150,
+ 149, 148, 147, 146, 145, 144, 143, 142, 141, 140,
+ 136, 135, 134, 132, 130, 122, 121, 120, 118, 117,
+ 115, 114, 113, 112, 111, 110, 109, 108, 106, 105,
+ 104, 103, 102, 101, 99, 96, 92, 87, 86, 78,
+ 77, 64, 62, 60, 58, 57, 56, 53, 52, 47,
+ 43, 41, 39, 38, 24, 14, 11, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424,
+ 424, 424
+ } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+extern int yy_flex_debug;
+int yy_flex_debug = 1;
+
+static const flex_int16_t yy_rule_linenum[68] =
+ { 0,
+ 136, 138, 140, 145, 146, 151, 152, 153, 165, 168,
+ 173, 179, 188, 199, 210, 219, 228, 237, 247, 257,
+ 267, 284, 301, 310, 319, 329, 341, 351, 362, 371,
+ 381, 391, 401, 410, 419, 428, 437, 446, 455, 464,
+ 473, 482, 491, 500, 509, 518, 531, 540, 549, 650,
+ 666, 715, 723, 738, 739, 740, 741, 742, 743, 745,
+ 763, 776, 781, 785, 787, 789, 791
+ } ;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "d2_lexer.ll"
+/* 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/. */
+#line 8 "d2_lexer.ll"
+
+/* Generated files do not make clang static analyser so happy */
+#ifndef __clang_analyzer__
+
+#include <cctype>
+#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <d2/parser_context.h>
+#include <asiolink/io_address.h>
+#include <boost/lexical_cast.hpp>
+#include <exceptions/exceptions.h>
+
+/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */
+
+/* Work around an incompatibility in flex (at least versions
+ 2.5.31 through 2.5.33): it generates code that does
+ not conform to C89. See Debian bug 333231
+ <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */
+# undef yywrap
+# define yywrap() 1
+
+namespace {
+
+bool start_token_flag = false;
+
+isc::d2::D2ParserContext::ParserType start_token_value;
+unsigned int comment_start_line = 0;
+
+};
+
+/* To avoid the call to exit... oops! */
+#define YY_FATAL_ERROR(msg) isc::d2::D2ParserContext::fatal(msg)
+#line 1139 "d2_lexer.cc"
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+ always parse only a single string, there's no need to do any wraps. And
+ using yywrap requires linking with -lfl, which provides the default yywrap
+ implementation that always returns 1 anyway. */
+/* nounput simplifies the lexer, by removing support for putting a character
+ back into the input stream. We never use such capability anyway. */
+/* batch means that we'll never use the generated lexer interactively. */
+/* avoid to get static global variables to remain with C++. */
+/* in last resort %option reentrant */
+/* Enables debug mode. To see the debug messages, one needs to also set
+ yy_flex_debug to 1, then the debug messages will be printed on stderr. */
+/* I have no idea what this option does, except it was specified in the bison
+ examples and Postgres folks added it to remove gcc 4.3 warnings. Let's
+ be on the safe side and keep it. */
+#define YY_NO_INPUT 1
+
+/* These are not token expressions yet, just convenience expressions that
+ can be used during actual token definitions. Note some can match
+ incorrect inputs (e.g., IP addresses) which must be checked. */
+/* for errors */
+#line 93 "d2_lexer.ll"
+/* This code run each time a pattern is matched. It updates the location
+ by moving it ahead by yyleng bytes. yyleng specifies the length of the
+ currently matched token. */
+#define YY_USER_ACTION driver.loc_.columns(yyleng);
+#line 1165 "d2_lexer.cc"
+#line 1166 "d2_lexer.cc"
+
+#define INITIAL 0
+#define COMMENT 1
+#define DIR_ENTER 2
+#define DIR_INCLUDE 3
+#define DIR_EXIT 4
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+/* %if-c-only */
+#include <unistd.h>
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* %if-c-only Reentrant structure and macros (non-C++). */
+/* %if-reentrant */
+/* %if-c-only */
+
+static int yy_init_globals ( void );
+
+/* %endif */
+/* %if-reentrant */
+/* %endif */
+/* %endif End reentrant structures and macros. */
+
+/* Accessor methods to globals.
+ These are made visible to non-reentrant scanners for convenience. */
+
+int yylex_destroy ( void );
+
+int yyget_debug ( void );
+
+void yyset_debug ( int debug_flag );
+
+YY_EXTRA_TYPE yyget_extra ( void );
+
+void yyset_extra ( YY_EXTRA_TYPE user_defined );
+
+FILE *yyget_in ( void );
+
+void yyset_in ( FILE * _in_str );
+
+FILE *yyget_out ( void );
+
+void yyset_out ( FILE * _out_str );
+
+ int yyget_leng ( void );
+
+char *yyget_text ( void );
+
+int yyget_lineno ( void );
+
+void yyset_lineno ( int _line_number );
+
+/* %if-bison-bridge */
+/* %endif */
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap ( void );
+#else
+extern int yywrap ( void );
+#endif
+#endif
+
+/* %not-for-header */
+#ifndef YY_NO_UNPUT
+
+#endif
+/* %ok-for-header */
+
+/* %endif */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy ( char *, const char *, int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen ( const char * );
+#endif
+
+#ifndef YY_NO_INPUT
+/* %if-c-only Standard (non-C++) definition */
+/* %not-for-header */
+#ifdef __cplusplus
+static int yyinput ( void );
+#else
+static int input ( void );
+#endif
+/* %ok-for-header */
+
+/* %endif */
+#endif
+
+/* %if-c-only */
+
+/* %endif */
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#ifdef __ia64__
+/* On IA-64, the buffer size is 16k, not 8k */
+#define YY_READ_BUF_SIZE 16384
+#else
+#define YY_READ_BUF_SIZE 8192
+#endif /* __ia64__ */
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* %if-c-only Standard (non-C++) definition */
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0)
+/* %endif */
+/* %if-c++-only C++ definition */
+/* %endif */
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+/* %% [5.0] fread()/read() definition of YY_INPUT goes here unless we're doing C++ \ */\
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+ { \
+ int c = '*'; \
+ int n; \
+ for ( n = 0; n < max_size && \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+ buf[n] = (char) c; \
+ if ( c == '\n' ) \
+ buf[n++] = (char) c; \
+ if ( c == EOF && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ result = n; \
+ } \
+ else \
+ { \
+ errno=0; \
+ while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \
+ { \
+ if( errno != EINTR) \
+ { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno=0; \
+ clearerr(yyin); \
+ } \
+ }\
+\
+/* %if-c++-only C++ definition \ */\
+/* %endif */
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+/* %if-c-only */
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+#endif
+
+/* %if-tables-serialization structures and prototypes */
+/* %not-for-header */
+/* %ok-for-header */
+
+/* %not-for-header */
+/* %tables-yydmap generated elements */
+/* %endif */
+/* end tables serialization structures and prototypes */
+
+/* %ok-for-header */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+/* %if-c-only Standard (non-C++) definition */
+
+extern int yylex (void);
+
+#define YY_DECL int yylex (void)
+/* %endif */
+/* %if-c++-only C++ definition */
+/* %endif */
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK /*LINTED*/break;
+#endif
+
+/* %% [6.0] YY_RULE_SETUP definition goes here */
+#define YY_RULE_SETUP \
+ YY_USER_ACTION
+
+/* %not-for-header */
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+ yy_state_type yy_current_state;
+ char *yy_cp, *yy_bp;
+ int yy_act;
+
+ if ( !(yy_init) )
+ {
+ (yy_init) = 1;
+
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! (yy_start) )
+ (yy_start) = 1; /* first start state */
+
+ if ( ! yyin )
+/* %if-c-only */
+ yyin = stdin;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+ if ( ! yyout )
+/* %if-c-only */
+ yyout = stdout;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+ if ( ! YY_CURRENT_BUFFER ) {
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+ }
+
+ yy_load_buffer_state( );
+ }
+
+ {
+/* %% [7.0] user's declarations go here */
+#line 99 "d2_lexer.ll"
+
+
+
+#line 103 "d2_lexer.ll"
+ /* This part of the code is copied over to the verbatim to the top
+ of the generated yylex function. Explanation:
+ http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html */
+
+ /* Code run each time yylex is called. */
+ driver.loc_.step();
+
+ if (start_token_flag) {
+ start_token_flag = false;
+ switch (start_token_value) {
+ case D2ParserContext::PARSER_JSON:
+ default:
+ return isc::d2::D2Parser::make_TOPLEVEL_JSON(driver.loc_);
+ case D2ParserContext::PARSER_DHCPDDNS:
+ return isc::d2::D2Parser::make_TOPLEVEL_DHCPDDNS(driver.loc_);
+ case D2ParserContext::PARSER_SUB_DHCPDDNS:
+ return isc::d2::D2Parser::make_SUB_DHCPDDNS(driver.loc_);
+ case D2ParserContext::PARSER_TSIG_KEY:
+ return isc::d2::D2Parser::make_SUB_TSIG_KEY(driver.loc_);
+ case D2ParserContext::PARSER_TSIG_KEYS:
+ return isc::d2::D2Parser::make_SUB_TSIG_KEYS(driver.loc_);
+ case D2ParserContext::PARSER_DDNS_DOMAIN:
+ return isc::d2::D2Parser::make_SUB_DDNS_DOMAIN(driver.loc_);
+ case D2ParserContext::PARSER_DDNS_DOMAINS:
+ return isc::d2::D2Parser::make_SUB_DDNS_DOMAINS(driver.loc_);
+ case D2ParserContext::PARSER_DNS_SERVER:
+ return isc::d2::D2Parser::make_SUB_DNS_SERVER(driver.loc_);
+ case D2ParserContext::PARSER_HOOKS_LIBRARY:
+ return isc::d2::D2Parser::make_SUB_HOOKS_LIBRARY(driver.loc_);
+ }
+ }
+
+
+#line 1486 "d2_lexer.cc"
+
+ while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */
+ {
+/* %% [8.0] yymore()-related code goes here */
+ yy_cp = (yy_c_buf_p);
+
+ /* Support of yytext. */
+ *yy_cp = (yy_hold_char);
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+/* %% [9.0] code to set up and find next match goes here */
+ yy_current_state = (yy_start);
+yy_match:
+ do
+ {
+ YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 425 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ ++yy_cp;
+ }
+ while ( yy_current_state != 424 );
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+
+yy_find_action:
+/* %% [10.0] code to find the action number goes here */
+ yy_act = yy_accept[yy_current_state];
+
+ YY_DO_BEFORE_ACTION;
+
+/* %% [11.0] code for yylineno update goes here */
+
+do_action: /* This label is used only to access EOF actions. */
+
+/* %% [12.0] debug code goes here */
+ if ( yy_flex_debug )
+ {
+ if ( yy_act == 0 )
+ fprintf( stderr, "--scanner backing up\n" );
+ else if ( yy_act < 68 )
+ fprintf( stderr, "--accepting rule at line %ld (\"%s\")\n",
+ (long)yy_rule_linenum[yy_act], yytext );
+ else if ( yy_act == 68 )
+ fprintf( stderr, "--accepting default rule (\"%s\")\n",
+ yytext );
+ else if ( yy_act == 69 )
+ fprintf( stderr, "--(end of buffer or a NUL)\n" );
+ else
+ fprintf( stderr, "--EOF (start condition %d)\n", YY_START );
+ }
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+/* %% [13.0] actions go here */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = (yy_hold_char);
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 136 "d2_lexer.ll"
+;
+ YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 138 "d2_lexer.ll"
+;
+ YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 140 "d2_lexer.ll"
+{
+ BEGIN(COMMENT);
+ comment_start_line = driver.loc_.end.line;;
+}
+ YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 145 "d2_lexer.ll"
+BEGIN(INITIAL);
+ YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 146 "d2_lexer.ll"
+;
+ YY_BREAK
+case YY_STATE_EOF(COMMENT):
+#line 147 "d2_lexer.ll"
+{
+ isc_throw(D2ParseError, "Comment not closed. (/* in line " << comment_start_line);
+}
+ YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 151 "d2_lexer.ll"
+BEGIN(DIR_ENTER);
+ YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 152 "d2_lexer.ll"
+BEGIN(DIR_INCLUDE);
+ YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 153 "d2_lexer.ll"
+{
+ /* Include directive. */
+
+ /* Extract the filename. */
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+
+ driver.includeFile(tmp);
+}
+ YY_BREAK
+case YY_STATE_EOF(DIR_ENTER):
+case YY_STATE_EOF(DIR_INCLUDE):
+case YY_STATE_EOF(DIR_EXIT):
+#line 162 "d2_lexer.ll"
+{
+ isc_throw(D2ParseError, "Directive not closed.");
+}
+ YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 165 "d2_lexer.ll"
+BEGIN(INITIAL);
+ YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 168 "d2_lexer.ll"
+{
+ /* Ok, we found a with space. Let's ignore it and update loc variable. */
+ driver.loc_.step();
+}
+ YY_BREAK
+case 11:
+/* rule 11 can match eol */
+YY_RULE_SETUP
+#line 173 "d2_lexer.ll"
+{
+ /* Newline found. Let's update the location and continue. */
+ driver.loc_.lines(yyleng);
+ driver.loc_.step();
+}
+ YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 179 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::CONFIG:
+ return isc::d2::D2Parser::make_DHCPDDNS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("DhcpDdns", driver.loc_);
+ }
+}
+ YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 188 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ return isc::d2::D2Parser::make_IP_ADDRESS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("ip-address", driver.loc_);
+ }
+}
+ YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 199 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ return isc::d2::D2Parser::make_PORT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("port", driver.loc_);
+ }
+}
+ YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 210 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_DNS_SERVER_TIMEOUT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("dns-server-timeout", driver.loc_);
+ }
+}
+ YY_BREAK
+case 16:
+YY_RULE_SETUP
+#line 219 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_NCR_PROTOCOL(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("ncr-protocol", driver.loc_);
+ }
+}
+ YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 228 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_NCR_FORMAT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("ncr-format", driver.loc_);
+ }
+}
+ YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 237 "d2_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::d2::D2ParserContext::NCR_PROTOCOL) {
+ return isc::d2::D2Parser::make_UDP(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 19:
+YY_RULE_SETUP
+#line 247 "d2_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::d2::D2ParserContext::NCR_PROTOCOL) {
+ return isc::d2::D2Parser::make_TCP(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 20:
+YY_RULE_SETUP
+#line 257 "d2_lexer.ll"
+{
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::d2::D2ParserContext::NCR_FORMAT) {
+ return isc::d2::D2Parser::make_JSON(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+ YY_BREAK
+case 21:
+YY_RULE_SETUP
+#line 267 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ case isc::d2::D2ParserContext::CONTROL_SOCKET:
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_USER_CONTEXT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("user-context", driver.loc_);
+ }
+}
+ YY_BREAK
+case 22:
+YY_RULE_SETUP
+#line 284 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ case isc::d2::D2ParserContext::CONTROL_SOCKET:
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_COMMENT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("comment", driver.loc_);
+ }
+}
+ YY_BREAK
+case 23:
+YY_RULE_SETUP
+#line 301 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_FORWARD_DDNS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("forward-ddns", driver.loc_);
+ }
+}
+ YY_BREAK
+case 24:
+YY_RULE_SETUP
+#line 310 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_REVERSE_DDNS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("reverse-ddns", driver.loc_);
+ }
+}
+ YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 319 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::FORWARD_DDNS:
+ case isc::d2::D2ParserContext::REVERSE_DDNS:
+ return isc::d2::D2Parser::make_DDNS_DOMAINS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("ddns-domains", driver.loc_);
+ }
+}
+ YY_BREAK
+case 26:
+YY_RULE_SETUP
+#line 329 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ return isc::d2::D2Parser::make_KEY_NAME(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("key-name", driver.loc_);
+ }
+}
+ YY_BREAK
+case 27:
+YY_RULE_SETUP
+#line 341 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ return isc::d2::D2Parser::make_DNS_SERVERS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("dns-servers", driver.loc_);
+ }
+}
+ YY_BREAK
+case 28:
+YY_RULE_SETUP
+#line 351 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ return isc::d2::D2Parser::make_HOSTNAME(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("hostname", driver.loc_);
+ }
+}
+ YY_BREAK
+case 29:
+YY_RULE_SETUP
+#line 362 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_TSIG_KEYS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("tsig-keys", driver.loc_);
+ }
+}
+ YY_BREAK
+case 30:
+YY_RULE_SETUP
+#line 371 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ return isc::d2::D2Parser::make_ALGORITHM(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("algorithm", driver.loc_);
+ }
+}
+ YY_BREAK
+case 31:
+YY_RULE_SETUP
+#line 381 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ return isc::d2::D2Parser::make_DIGEST_BITS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("digest-bits", driver.loc_);
+ }
+}
+ YY_BREAK
+case 32:
+YY_RULE_SETUP
+#line 391 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ return isc::d2::D2Parser::make_SECRET(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("secret", driver.loc_);
+ }
+}
+ YY_BREAK
+case 33:
+YY_RULE_SETUP
+#line 401 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_CONTROL_SOCKET(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("control-socket", driver.loc_);
+ }
+}
+ YY_BREAK
+case 34:
+YY_RULE_SETUP
+#line 410 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::CONTROL_SOCKET:
+ return isc::d2::D2Parser::make_SOCKET_TYPE(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("socket-type", driver.loc_);
+ }
+}
+ YY_BREAK
+case 35:
+YY_RULE_SETUP
+#line 419 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::CONTROL_SOCKET:
+ return isc::d2::D2Parser::make_SOCKET_NAME(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("socket-name", driver.loc_);
+ }
+}
+ YY_BREAK
+case 36:
+YY_RULE_SETUP
+#line 428 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_HOOKS_LIBRARIES(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("hooks-libraries", driver.loc_);
+ }
+}
+ YY_BREAK
+case 37:
+YY_RULE_SETUP
+#line 437 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::HOOKS_LIBRARIES:
+ return isc::d2::D2Parser::make_PARAMETERS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("parameters", driver.loc_);
+ }
+}
+ YY_BREAK
+case 38:
+YY_RULE_SETUP
+#line 446 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::HOOKS_LIBRARIES:
+ return isc::d2::D2Parser::make_LIBRARY(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("library", driver.loc_);
+ }
+}
+ YY_BREAK
+case 39:
+YY_RULE_SETUP
+#line 455 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_LOGGERS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("loggers", driver.loc_);
+ }
+}
+ YY_BREAK
+case 40:
+YY_RULE_SETUP
+#line 464 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_OUTPUT_OPTIONS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("output_options", driver.loc_);
+ }
+}
+ YY_BREAK
+case 41:
+YY_RULE_SETUP
+#line 473 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_OUTPUT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("output", driver.loc_);
+ }
+}
+ YY_BREAK
+case 42:
+YY_RULE_SETUP
+#line 482 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_FLUSH(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("flush", driver.loc_);
+ }
+}
+ YY_BREAK
+case 43:
+YY_RULE_SETUP
+#line 491 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_MAXSIZE(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("maxsize", driver.loc_);
+ }
+}
+ YY_BREAK
+case 44:
+YY_RULE_SETUP
+#line 500 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_MAXVER(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("maxver", driver.loc_);
+ }
+}
+ YY_BREAK
+case 45:
+YY_RULE_SETUP
+#line 509 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_PATTERN(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("pattern", driver.loc_);
+ }
+}
+ YY_BREAK
+case 46:
+YY_RULE_SETUP
+#line 518 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::LOGGERS:
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ return isc::d2::D2Parser::make_NAME(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("name", driver.loc_);
+ }
+}
+ YY_BREAK
+case 47:
+YY_RULE_SETUP
+#line 531 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_DEBUGLEVEL(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("debuglevel", driver.loc_);
+ }
+}
+ YY_BREAK
+case 48:
+YY_RULE_SETUP
+#line 540 "d2_lexer.ll"
+{
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_SEVERITY(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("severity", driver.loc_);
+ }
+}
+ YY_BREAK
+case 49:
+YY_RULE_SETUP
+#line 549 "d2_lexer.ll"
+{
+ /* A string has been matched. It contains the actual string and single quotes.
+ We need to get those quotes out of the way and just use its content, e.g.
+ for 'foo' we should get foo */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ raw.resize(len);
+ std::string decoded;
+ decoded.reserve(len);
+ for (size_t pos = 0; pos < len; ++pos) {
+ int b = 0;
+ char c = raw[pos];
+ switch (c) {
+ case '"':
+ /* impossible condition */
+ driver.error(driver.loc_, "Bad quote in \"" + raw + "\"");
+ break;
+ case '\\':
+ ++pos;
+ if (pos >= len) {
+ /* impossible condition */
+ driver.error(driver.loc_, "Overflow escape in \"" + raw + "\"");
+ }
+ c = raw[pos];
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ decoded.push_back(c);
+ break;
+ case 'b':
+ decoded.push_back('\b');
+ break;
+ case 'f':
+ decoded.push_back('\f');
+ break;
+ case 'n':
+ decoded.push_back('\n');
+ break;
+ case 'r':
+ decoded.push_back('\r');
+ break;
+ case 't':
+ decoded.push_back('\t');
+ break;
+ case 'u':
+ /* support only \u0000 to \u00ff */
+ ++pos;
+ if (pos + 4 > len) {
+ /* impossible condition */
+ driver.error(driver.loc_,
+ "Overflow unicode escape in \"" + raw + "\"");
+ }
+ if ((raw[pos] != '0') || (raw[pos + 1] != '0')) {
+ driver.error(driver.loc_,
+ "Unsupported unicode escape in \"" + raw + "\"",
+ pos + 1);
+ }
+ pos += 2;
+ c = raw[pos];
+ if ((c >= '0') && (c <= '9')) {
+ b = (c - '0') << 4;
+ } else if ((c >= 'A') && (c <= 'F')) {
+ b = (c - 'A' + 10) << 4;
+ } else if ((c >= 'a') && (c <= 'f')) {
+ b = (c - 'a' + 10) << 4;
+ } else {
+ /* impossible condition */
+ driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+ }
+ pos++;
+ c = raw[pos];
+ if ((c >= '0') && (c <= '9')) {
+ b |= c - '0';
+ } else if ((c >= 'A') && (c <= 'F')) {
+ b |= c - 'A' + 10;
+ } else if ((c >= 'a') && (c <= 'f')) {
+ b |= c - 'a' + 10;
+ } else {
+ /* impossible condition */
+ driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+ }
+ decoded.push_back(static_cast<char>(b & 0xff));
+ break;
+ default:
+ /* impossible condition */
+ driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
+ }
+ break;
+ default:
+ if ((c >= 0) && (c < 0x20)) {
+ /* impossible condition */
+ driver.error(driver.loc_, "Invalid control in \"" + raw + "\"");
+ }
+ decoded.push_back(c);
+ }
+ }
+
+ return isc::d2::D2Parser::make_STRING(decoded, driver.loc_);
+}
+ YY_BREAK
+case 50:
+/* rule 50 can match eol */
+YY_RULE_SETUP
+#line 650 "d2_lexer.ll"
+{
+ /* Bad string with a forbidden control character inside */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ size_t pos = 0;
+ for (; pos < len; ++pos) {
+ char c = raw[pos];
+ if ((c >= 0) && (c < 0x20)) {
+ break;
+ }
+ }
+ driver.error(driver.loc_,
+ "Invalid control in " + std::string(yytext),
+ pos + 1);
+}
+ YY_BREAK
+case 51:
+/* rule 51 can match eol */
+YY_RULE_SETUP
+#line 666 "d2_lexer.ll"
+{
+ /* Bad string with a bad escape inside */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ size_t pos = 0;
+ bool found = false;
+ for (; pos < len; ++pos) {
+ if (found) {
+ break;
+ }
+ char c = raw[pos];
+ if (c == '\\') {
+ ++pos;
+ c = raw[pos];
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ break;
+ case 'u':
+ if ((pos + 4 > len) ||
+ !std::isxdigit(raw[pos + 1]) ||
+ !std::isxdigit(raw[pos + 2]) ||
+ !std::isxdigit(raw[pos + 3]) ||
+ !std::isxdigit(raw[pos + 4])) {
+ found = true;
+ }
+ break;
+ default:
+ found = true;
+ break;
+ }
+ }
+ }
+ /* The rule stops on the first " including on \" so add ... in this case */
+ std::string trailer = "";
+ if (raw[len - 1] == '\\') {
+ trailer = "...";
+ }
+ driver.error(driver.loc_,
+ "Bad escape in " + std::string(yytext) + trailer,
+ pos);
+}
+ YY_BREAK
+case 52:
+YY_RULE_SETUP
+#line 715 "d2_lexer.ll"
+{
+ /* Bad string with an open escape at the end */
+ std::string raw(yytext+1);
+ driver.error(driver.loc_,
+ "Overflow escape in " + std::string(yytext),
+ raw.size() + 1);
+}
+ YY_BREAK
+case 53:
+YY_RULE_SETUP
+#line 723 "d2_lexer.ll"
+{
+ /* Bad string with an open unicode escape at the end */
+ std::string raw(yytext+1);
+ size_t pos = raw.size() - 1;
+ for (; pos > 0; --pos) {
+ char c = raw[pos];
+ if (c == 'u') {
+ break;
+ }
+ }
+ driver.error(driver.loc_,
+ "Overflow unicode escape in " + std::string(yytext),
+ pos + 1);
+}
+ YY_BREAK
+case 54:
+YY_RULE_SETUP
+#line 738 "d2_lexer.ll"
+{ return isc::d2::D2Parser::make_LSQUARE_BRACKET(driver.loc_); }
+ YY_BREAK
+case 55:
+YY_RULE_SETUP
+#line 739 "d2_lexer.ll"
+{ return isc::d2::D2Parser::make_RSQUARE_BRACKET(driver.loc_); }
+ YY_BREAK
+case 56:
+YY_RULE_SETUP
+#line 740 "d2_lexer.ll"
+{ return isc::d2::D2Parser::make_LCURLY_BRACKET(driver.loc_); }
+ YY_BREAK
+case 57:
+YY_RULE_SETUP
+#line 741 "d2_lexer.ll"
+{ return isc::d2::D2Parser::make_RCURLY_BRACKET(driver.loc_); }
+ YY_BREAK
+case 58:
+YY_RULE_SETUP
+#line 742 "d2_lexer.ll"
+{ return isc::d2::D2Parser::make_COMMA(driver.loc_); }
+ YY_BREAK
+case 59:
+YY_RULE_SETUP
+#line 743 "d2_lexer.ll"
+{ return isc::d2::D2Parser::make_COLON(driver.loc_); }
+ YY_BREAK
+case 60:
+YY_RULE_SETUP
+#line 745 "d2_lexer.ll"
+{
+ /* An integer was found. */
+ std::string tmp(yytext);
+ int64_t integer = 0;
+ try {
+ /* In substring we want to use negative values (e.g. -1).
+ In enterprise-id we need to use values up to 0xffffffff.
+ To cover both of those use cases, we need at least
+ int64_t. */
+ integer = boost::lexical_cast<int64_t>(tmp);
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(driver.loc_, "Failed to convert " + tmp + " to an integer.");
+ }
+
+ /* The parser needs the string form as double conversion is no lossless */
+ return isc::d2::D2Parser::make_INTEGER(integer, driver.loc_);
+}
+ YY_BREAK
+case 61:
+YY_RULE_SETUP
+#line 763 "d2_lexer.ll"
+{
+ /* A floating point was found. */
+ std::string tmp(yytext);
+ double fp = 0.0;
+ try {
+ fp = boost::lexical_cast<double>(tmp);
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(driver.loc_, "Failed to convert " + tmp + " to a floating point.");
+ }
+
+ return isc::d2::D2Parser::make_FLOAT(fp, driver.loc_);
+}
+ YY_BREAK
+case 62:
+YY_RULE_SETUP
+#line 776 "d2_lexer.ll"
+{
+ string tmp(yytext);
+ return isc::d2::D2Parser::make_BOOLEAN(tmp == "true", driver.loc_);
+}
+ YY_BREAK
+case 63:
+YY_RULE_SETUP
+#line 781 "d2_lexer.ll"
+{
+ return isc::d2::D2Parser::make_NULL_TYPE(driver.loc_);
+}
+ YY_BREAK
+case 64:
+YY_RULE_SETUP
+#line 785 "d2_lexer.ll"
+driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
+ YY_BREAK
+case 65:
+YY_RULE_SETUP
+#line 787 "d2_lexer.ll"
+driver.error (driver.loc_, "JSON false reserved keyword is lower case only");
+ YY_BREAK
+case 66:
+YY_RULE_SETUP
+#line 789 "d2_lexer.ll"
+driver.error (driver.loc_, "JSON null reserved keyword is lower case only");
+ YY_BREAK
+case 67:
+YY_RULE_SETUP
+#line 791 "d2_lexer.ll"
+driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
+ YY_BREAK
+case YY_STATE_EOF(INITIAL):
+#line 793 "d2_lexer.ll"
+{
+ if (driver.states_.empty()) {
+ return isc::d2::D2Parser::make_END(driver.loc_);
+ }
+ driver.loc_ = driver.locs_.back();
+ driver.locs_.pop_back();
+ driver.file_ = driver.files_.back();
+ driver.files_.pop_back();
+ if (driver.sfile_) {
+ fclose(driver.sfile_);
+ driver.sfile_ = 0;
+ }
+ if (!driver.sfiles_.empty()) {
+ driver.sfile_ = driver.sfiles_.back();
+ driver.sfiles_.pop_back();
+ }
+ d2_parser__delete_buffer(YY_CURRENT_BUFFER);
+ d2_parser__switch_to_buffer(driver.states_.back());
+ driver.states_.pop_back();
+
+ BEGIN(DIR_EXIT);
+}
+ YY_BREAK
+case 68:
+YY_RULE_SETUP
+#line 816 "d2_lexer.ll"
+ECHO;
+ YY_BREAK
+#line 2468 "d2_lexer.cc"
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = (yy_hold_char);
+ YY_RESTORE_YY_MORE_OFFSET
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+ {
+ /* We're scanning a new file or input source. It's
+ * possible that this happened because the user
+ * just pointed yyin at a new source and called
+ * yylex(). If so, then we have to assure
+ * consistency between YY_CURRENT_BUFFER and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input source.
+ */
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+/* %if-c-only */
+ YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++(yy_c_buf_p);
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+/* %% [14.0] code to do back-up for compressed tables and set up yy_cp goes here */
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ (yy_did_buffer_switch_on_eof) = 0;
+
+ if ( yywrap( ) )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) =
+ (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ (yy_c_buf_p) =
+ &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)];
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+ } /* end of user's declarations */
+} /* end of yylex */
+/* %ok-for-header */
+
+/* %if-c++-only */
+/* %not-for-header */
+/* %ok-for-header */
+
+/* %endif */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+/* %if-c-only */
+static int yy_get_next_buffer (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+ char *source = (yytext_ptr);
+ int number_to_move, i;
+ int ret_val;
+
+ if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a single character, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1);
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0;
+
+ else
+ {
+ int num_to_read =
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE;
+
+ int yy_c_buf_p_offset =
+ (int) ((yy_c_buf_p) - b->yy_ch_buf);
+
+ if ( b->yy_is_our_buffer )
+ {
+ int new_size = b->yy_buf_size * 2;
+
+ if ( new_size <= 0 )
+ b->yy_buf_size += b->yy_buf_size / 8;
+ else
+ b->yy_buf_size *= 2;
+
+ b->yy_ch_buf = (char *)
+ /* Include room in for 2 EOB chars. */
+ yyrealloc( (void *) b->yy_ch_buf,
+ (yy_size_t) (b->yy_buf_size + 2) );
+ }
+ else
+ /* Can't grow it, we don't own it. */
+ b->yy_ch_buf = NULL;
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+ number_to_move - 1;
+
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+ (yy_n_chars), num_to_read );
+
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ if ( (yy_n_chars) == 0 )
+ {
+ if ( number_to_move == YY_MORE_ADJ )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ yyrestart( yyin );
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+ YY_BUFFER_EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+ /* Extend the array by 50%, plus the number we really need. */
+ int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1);
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc(
+ (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size );
+ if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+ /* "- 2" to take care of EOB's */
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2);
+ }
+
+ (yy_n_chars) += number_to_move;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR;
+
+ (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+ return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+/* %if-c-only */
+/* %not-for-header */
+ static yy_state_type yy_get_previous_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ yy_state_type yy_current_state;
+ char *yy_cp;
+
+/* %% [15.0] code to get the start state into yy_current_state goes here */
+ yy_current_state = (yy_start);
+
+ for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
+ {
+/* %% [16.0] code to find the next state goes here */
+ YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 425 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ }
+
+ return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+/* %if-c-only */
+ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ int yy_is_jam;
+ /* %% [17.0] code to find the next state, and perhaps do backing up, goes here */
+ char *yy_cp = (yy_c_buf_p);
+
+ YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 425 )
+ yy_c = yy_meta[yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
+ yy_is_jam = (yy_current_state == 424);
+
+ return yy_is_jam ? 0 : yy_current_state;
+}
+
+#ifndef YY_NO_UNPUT
+/* %if-c-only */
+
+/* %endif */
+#endif
+
+/* %if-c-only */
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+ static int yyinput (void)
+#else
+ static int input (void)
+#endif
+
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ int c;
+
+ *(yy_c_buf_p) = (yy_hold_char);
+
+ if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ /* This was really a NUL. */
+ *(yy_c_buf_p) = '\0';
+
+ else
+ { /* need more input */
+ int offset = (int) ((yy_c_buf_p) - (yytext_ptr));
+ ++(yy_c_buf_p);
+
+ switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_LAST_MATCH:
+ /* This happens because yy_g_n_b()
+ * sees that we've accumulated a
+ * token and flags that we need to
+ * try matching the token before
+ * proceeding. But for input(),
+ * there's no matching to consider.
+ * So convert the EOB_ACT_LAST_MATCH
+ * to EOB_ACT_END_OF_FILE.
+ */
+
+ /* Reset buffer status. */
+ yyrestart( yyin );
+
+ /*FALLTHROUGH*/
+
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( yywrap( ) )
+ return 0;
+
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput();
+#else
+ return input();
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) = (yytext_ptr) + offset;
+ break;
+ }
+ }
+ }
+
+ c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */
+ *(yy_c_buf_p) = '\0'; /* preserve yytext */
+ (yy_hold_char) = *++(yy_c_buf_p);
+
+/* %% [19.0] update BOL and yylineno */
+
+ return c;
+}
+/* %if-c-only */
+#endif /* ifndef YY_NO_INPUT */
+/* %endif */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ *
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+/* %if-c-only */
+ void yyrestart (FILE * input_file )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ if ( ! YY_CURRENT_BUFFER ){
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer( yyin, YY_BUF_SIZE );
+ }
+
+ yy_init_buffer( YY_CURRENT_BUFFER, input_file );
+ yy_load_buffer_state( );
+}
+
+/* %if-c++-only */
+/* %endif */
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ *
+ */
+/* %if-c-only */
+ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ /* TODO. We should be able to replace this entire function body
+ * with
+ * yypop_buffer_state();
+ * yypush_buffer_state(new_buffer);
+ */
+ yyensure_buffer_stack ();
+ if ( YY_CURRENT_BUFFER == new_buffer )
+ return;
+
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+ yy_load_buffer_state( );
+
+ /* We don't actually know whether we did this switch during
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+
+/* %if-c-only */
+static void yy_load_buffer_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+/* %if-c-only */
+ yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ (yy_hold_char) = *(yy_c_buf_p);
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ *
+ * @return the allocated buffer state.
+ */
+/* %if-c-only */
+ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) );
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_is_our_buffer = 1;
+
+ yy_init_buffer( b, file );
+
+ return b;
+}
+
+/* %if-c++-only */
+/* %endif */
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ *
+ */
+/* %if-c-only */
+ void yy_delete_buffer (YY_BUFFER_STATE b )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+
+ if ( ! b )
+ return;
+
+ if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+ if ( b->yy_is_our_buffer )
+ yyfree( (void *) b->yy_ch_buf );
+
+ yyfree( (void *) b );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+/* %if-c-only */
+ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+{
+ int oerrno = errno;
+
+ yy_flush_buffer( b );
+
+/* %if-c-only */
+ b->yy_input_file = file;
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ b->yy_fill_buffer = 1;
+
+ /* If b is the current buffer, then yy_init_buffer was _probably_
+ * called from yyrestart() or through yy_get_next_buffer.
+ * In that case, we don't want to reset the lineno or column.
+ */
+ if (b != YY_CURRENT_BUFFER){
+ b->yy_bs_lineno = 1;
+ b->yy_bs_column = 0;
+ }
+
+/* %if-c-only */
+
+ b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+ errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ *
+ */
+/* %if-c-only */
+ void yy_flush_buffer (YY_BUFFER_STATE b )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if ( ! b )
+ return;
+
+ b->yy_n_chars = 0;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[0];
+
+ b->yy_at_bol = 1;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ if ( b == YY_CURRENT_BUFFER )
+ yy_load_buffer_state( );
+}
+
+/* %if-c-or-c++ */
+/** Pushes the new state onto the stack. The new state becomes
+ * the current state. This function will allocate the stack
+ * if necessary.
+ * @param new_buffer The new state.
+ *
+ */
+/* %if-c-only */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer )
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if (new_buffer == NULL)
+ return;
+
+ yyensure_buffer_stack();
+
+ /* This block is copied from yy_switch_to_buffer. */
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ /* Only push if top exists. Otherwise, replace top. */
+ if (YY_CURRENT_BUFFER)
+ (yy_buffer_stack_top)++;
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+ /* copied from yy_switch_to_buffer. */
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+/* %endif */
+
+/* %if-c-or-c++ */
+/** Removes and deletes the top of the stack, if present.
+ * The next element becomes the new top.
+ *
+ */
+/* %if-c-only */
+void yypop_buffer_state (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ if (!YY_CURRENT_BUFFER)
+ return;
+
+ yy_delete_buffer(YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ if ((yy_buffer_stack_top) > 0)
+ --(yy_buffer_stack_top);
+
+ if (YY_CURRENT_BUFFER) {
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+ }
+}
+/* %endif */
+
+/* %if-c-or-c++ */
+/* Allocates the stack if it does not exist.
+ * Guarantees space for at least one push.
+ */
+/* %if-c-only */
+static void yyensure_buffer_stack (void)
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+{
+ yy_size_t num_to_alloc;
+
+ if (!(yy_buffer_stack)) {
+
+ /* First allocation is just for 2 elements, since we don't know if this
+ * scanner will even need a stack. We use 2 instead of 1 to avoid an
+ * immediate realloc on the next call.
+ */
+ num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc
+ (num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+ if ( ! (yy_buffer_stack) )
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+ memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+
+ (yy_buffer_stack_max) = num_to_alloc;
+ (yy_buffer_stack_top) = 0;
+ return;
+ }
+
+ if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){
+
+ /* Increase the buffer to prepare for a possible push. */
+ yy_size_t grow_size = 8 /* arbitrary grow size */;
+
+ num_to_alloc = (yy_buffer_stack_max) + grow_size;
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc
+ ((yy_buffer_stack),
+ num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+ if ( ! (yy_buffer_stack) )
+ YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" );
+
+ /* zero only the new slots.*/
+ memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
+ (yy_buffer_stack_max) = num_to_alloc;
+ }
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ *
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size )
+{
+ YY_BUFFER_STATE b;
+
+ if ( size < 2 ||
+ base[size-2] != YY_END_OF_BUFFER_CHAR ||
+ base[size-1] != YY_END_OF_BUFFER_CHAR )
+ /* They forgot to leave room for the EOB's. */
+ return NULL;
+
+ b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" );
+
+ b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */
+ b->yy_buf_pos = b->yy_ch_buf = base;
+ b->yy_is_our_buffer = 0;
+ b->yy_input_file = NULL;
+ b->yy_n_chars = b->yy_buf_size;
+ b->yy_is_interactive = 0;
+ b->yy_at_bol = 1;
+ b->yy_fill_buffer = 0;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ yy_switch_to_buffer( b );
+
+ return b;
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan a string. The next call to yylex() will
+ * scan from a @e copy of @a str.
+ * @param yystr a NUL-terminated string to scan
+ *
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ * yy_scan_bytes() instead.
+ */
+YY_BUFFER_STATE yy_scan_string (const char * yystr )
+{
+
+ return yy_scan_bytes( yystr, (int) strlen(yystr) );
+}
+/* %endif */
+
+/* %if-c-only */
+/** Setup the input buffer state to scan the given bytes. The next call to yylex() will
+ * scan from a @e copy of @a bytes.
+ * @param yybytes the byte buffer to scan
+ * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes.
+ *
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len )
+{
+ YY_BUFFER_STATE b;
+ char *buf;
+ yy_size_t n;
+ int i;
+
+ /* Get memory for full buffer, including space for trailing EOB's. */
+ n = (yy_size_t) (_yybytes_len + 2);
+ buf = (char *) yyalloc( n );
+ if ( ! buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" );
+
+ for ( i = 0; i < _yybytes_len; ++i )
+ buf[i] = yybytes[i];
+
+ buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+ b = yy_scan_buffer( buf, n );
+ if ( ! b )
+ YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" );
+
+ /* It's okay to grow etc. this buffer, and we should throw it
+ * away when we're done.
+ */
+ b->yy_is_our_buffer = 1;
+
+ return b;
+}
+/* %endif */
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+/* %if-c-only */
+static void yynoreturn yy_fatal_error (const char* msg )
+{
+ fprintf( stderr, "%s\n", msg );
+ exit( YY_EXIT_FAILURE );
+}
+/* %endif */
+/* %if-c++-only */
+/* %endif */
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ yytext[yyleng] = (yy_hold_char); \
+ (yy_c_buf_p) = yytext + yyless_macro_arg; \
+ (yy_hold_char) = *(yy_c_buf_p); \
+ *(yy_c_buf_p) = '\0'; \
+ yyleng = yyless_macro_arg; \
+ } \
+ while ( 0 )
+
+/* Accessor methods (get/set functions) to struct members. */
+
+/* %if-c-only */
+/* %if-reentrant */
+/* %endif */
+
+/** Get the current line number.
+ *
+ */
+int yyget_lineno (void)
+{
+
+ return yylineno;
+}
+
+/** Get the input stream.
+ *
+ */
+FILE *yyget_in (void)
+{
+ return yyin;
+}
+
+/** Get the output stream.
+ *
+ */
+FILE *yyget_out (void)
+{
+ return yyout;
+}
+
+/** Get the length of the current token.
+ *
+ */
+int yyget_leng (void)
+{
+ return yyleng;
+}
+
+/** Get the current token.
+ *
+ */
+
+char *yyget_text (void)
+{
+ return yytext;
+}
+
+/* %if-reentrant */
+/* %endif */
+
+/** Set the current line number.
+ * @param _line_number line number
+ *
+ */
+void yyset_lineno (int _line_number )
+{
+
+ yylineno = _line_number;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param _in_str A readable stream.
+ *
+ * @see yy_switch_to_buffer
+ */
+void yyset_in (FILE * _in_str )
+{
+ yyin = _in_str ;
+}
+
+void yyset_out (FILE * _out_str )
+{
+ yyout = _out_str ;
+}
+
+int yyget_debug (void)
+{
+ return yy_flex_debug;
+}
+
+void yyset_debug (int _bdebug )
+{
+ yy_flex_debug = _bdebug ;
+}
+
+/* %endif */
+
+/* %if-reentrant */
+/* %if-bison-bridge */
+/* %endif */
+/* %endif if-c-only */
+
+/* %if-c-only */
+static int yy_init_globals (void)
+{
+ /* Initialization is the same as for the non-reentrant scanner.
+ * This function is called from yylex_destroy(), so don't allocate here.
+ */
+
+ (yy_buffer_stack) = NULL;
+ (yy_buffer_stack_top) = 0;
+ (yy_buffer_stack_max) = 0;
+ (yy_c_buf_p) = NULL;
+ (yy_init) = 0;
+ (yy_start) = 0;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+ yyin = stdin;
+ yyout = stdout;
+#else
+ yyin = NULL;
+ yyout = NULL;
+#endif
+
+ /* For future reference: Set errno on error, since we are called by
+ * yylex_init()
+ */
+ return 0;
+}
+/* %endif */
+
+/* %if-c-only SNIP! this currently causes conflicts with the c++ scanner */
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy (void)
+{
+
+ /* Pop the buffer stack, destroying each element. */
+ while(YY_CURRENT_BUFFER){
+ yy_delete_buffer( YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ yypop_buffer_state();
+ }
+
+ /* Destroy the stack itself. */
+ yyfree((yy_buffer_stack) );
+ (yy_buffer_stack) = NULL;
+
+ /* Reset the globals. This is important in a non-reentrant scanner so the next time
+ * yylex() is called, initialization will occur. */
+ yy_init_globals( );
+
+/* %if-reentrant */
+/* %endif */
+ return 0;
+}
+/* %endif */
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, const char * s2, int n )
+{
+
+ int i;
+ for ( i = 0; i < n; ++i )
+ s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (const char * s )
+{
+ int n;
+ for ( n = 0; s[n]; ++n )
+ ;
+
+ return n;
+}
+#endif
+
+void *yyalloc (yy_size_t size )
+{
+ return malloc(size);
+}
+
+void *yyrealloc (void * ptr, yy_size_t size )
+{
+
+ /* The cast to (char *) in the following accommodates both
+ * implementations that use char* generic pointers, and those
+ * that use void* generic pointers. It works with the latter
+ * because both ANSI C and C++ allow castless assignment from
+ * any pointer type to void*, and deal with argument conversions
+ * as though doing an assignment.
+ */
+ return realloc(ptr, size);
+}
+
+void yyfree (void * ptr )
+{
+ free( (char *) ptr ); /* see yyrealloc() for (char *) cast */
+}
+
+/* %if-tables-serialization definitions */
+/* %define-yytables The name for this specific scanner's tables. */
+#define YYTABLES_NAME "yytables"
+/* %endif */
+
+/* %ok-for-header */
+
+#line 816 "d2_lexer.ll"
+
+
+using namespace isc::dhcp;
+
+void
+D2ParserContext::scanStringBegin(const std::string& str, ParserType parser_type)
+{
+ start_token_flag = true;
+ start_token_value = parser_type;
+
+ file_ = "<string>";
+ sfile_ = 0;
+ loc_.initialize(&file_);
+ yy_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+ buffer = d2_parser__scan_bytes(str.c_str(), str.size());
+ if (!buffer) {
+ fatal("cannot scan string");
+ /* fatal() throws an exception so this can't be reached */
+ }
+}
+
+void
+D2ParserContext::scanFileBegin(FILE * f,
+ const std::string& filename,
+ ParserType parser_type)
+{
+ start_token_flag = true;
+ start_token_value = parser_type;
+
+ file_ = filename;
+ sfile_ = f;
+ loc_.initialize(&file_);
+ yy_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+
+ /* See d2_lexer.cc header for available definitions */
+ buffer = d2_parser__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal("cannot scan file " + filename);
+ }
+ d2_parser__switch_to_buffer(buffer);
+}
+
+void
+D2ParserContext::scanEnd() {
+ if (sfile_)
+ fclose(sfile_);
+ sfile_ = 0;
+ static_cast<void>(d2_parser_lex_destroy());
+ /* Close files */
+ while (!sfiles_.empty()) {
+ FILE* f = sfiles_.back();
+ if (f) {
+ fclose(f);
+ }
+ sfiles_.pop_back();
+ }
+ /* Delete states */
+ while (!states_.empty()) {
+ d2_parser__delete_buffer(states_.back());
+ states_.pop_back();
+ }
+}
+
+void
+D2ParserContext::includeFile(const std::string& filename) {
+ if (states_.size() > 10) {
+ fatal("Too many nested include.");
+ }
+
+ FILE* f = fopen(filename.c_str(), "r");
+ if (!f) {
+ fatal("Can't open include file " + filename);
+ }
+ if (sfile_) {
+ sfiles_.push_back(sfile_);
+ }
+ sfile_ = f;
+ states_.push_back(YY_CURRENT_BUFFER);
+ YY_BUFFER_STATE buffer;
+ buffer = d2_parser__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal( "Can't scan include file " + filename);
+ }
+ d2_parser__switch_to_buffer(buffer);
+ files_.push_back(file_);
+ file_ = filename;
+ locs_.push_back(loc_);
+ loc_.initialize(&file_);
+
+ BEGIN(INITIAL);
+}
+
+namespace {
+/** To avoid unused function error */
+class Dummy {
+ /* cppcheck-suppress unusedPrivateFunction */
+ void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
+};
+}
+#endif /* !__clang_analyzer__ */
+
diff --git a/src/bin/d2/d2_lexer.ll b/src/bin/d2/d2_lexer.ll
new file mode 100644
index 0000000..14c3411
--- /dev/null
+++ b/src/bin/d2/d2_lexer.ll
@@ -0,0 +1,916 @@
+/* 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/. */
+
+%{ /* -*- C++ -*- */
+
+/* Generated files do not make clang static analyser so happy */
+#ifndef __clang_analyzer__
+
+#include <cctype>
+#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <d2/parser_context.h>
+#include <asiolink/io_address.h>
+#include <boost/lexical_cast.hpp>
+#include <exceptions/exceptions.h>
+
+/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */
+
+/* Work around an incompatibility in flex (at least versions
+ 2.5.31 through 2.5.33): it generates code that does
+ not conform to C89. See Debian bug 333231
+ <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */
+# undef yywrap
+# define yywrap() 1
+
+namespace {
+
+bool start_token_flag = false;
+
+isc::d2::D2ParserContext::ParserType start_token_value;
+unsigned int comment_start_line = 0;
+
+};
+
+/* To avoid the call to exit... oops! */
+#define YY_FATAL_ERROR(msg) isc::d2::D2ParserContext::fatal(msg)
+%}
+
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+ always parse only a single string, there's no need to do any wraps. And
+ using yywrap requires linking with -lfl, which provides the default yywrap
+ implementation that always returns 1 anyway. */
+%option noyywrap
+
+/* nounput simplifies the lexer, by removing support for putting a character
+ back into the input stream. We never use such capability anyway. */
+%option nounput
+
+/* batch means that we'll never use the generated lexer interactively. */
+%option batch
+
+/* avoid to get static global variables to remain with C++. */
+/* in last resort %option reentrant */
+
+/* Enables debug mode. To see the debug messages, one needs to also set
+ yy_flex_debug to 1, then the debug messages will be printed on stderr. */
+%option debug
+
+/* I have no idea what this option does, except it was specified in the bison
+ examples and Postgres folks added it to remove gcc 4.3 warnings. Let's
+ be on the safe side and keep it. */
+%option noinput
+
+%x COMMENT
+%x DIR_ENTER DIR_INCLUDE DIR_EXIT
+
+/* These are not token expressions yet, just convenience expressions that
+ can be used during actual token definitions. Note some can match
+ incorrect inputs (e.g., IP addresses) which must be checked. */
+int \-?[0-9]+
+blank [ \t\r]
+
+UnicodeEscapeSequence u[0-9A-Fa-f]{4}
+JSONEscapeCharacter ["\\/bfnrt]
+JSONEscapeSequence {JSONEscapeCharacter}|{UnicodeEscapeSequence}
+JSONStandardCharacter [^\x00-\x1f"\\]
+JSONStringCharacter {JSONStandardCharacter}|\\{JSONEscapeSequence}
+JSONString \"{JSONStringCharacter}*\"
+
+/* for errors */
+
+BadUnicodeEscapeSequence u[0-9A-Fa-f]{0,3}[^0-9A-Fa-f"]
+BadJSONEscapeSequence [^"\\/bfnrtu]|{BadUnicodeEscapeSequence}
+ControlCharacter [\x00-\x1f]
+ControlCharacterFill [^"\\]|\\["\\/bfnrtu]
+
+%{
+/* This code run each time a pattern is matched. It updates the location
+ by moving it ahead by yyleng bytes. yyleng specifies the length of the
+ currently matched token. */
+#define YY_USER_ACTION driver.loc_.columns(yyleng);
+%}
+
+%%
+
+%{
+ /* This part of the code is copied over to the verbatim to the top
+ of the generated yylex function. Explanation:
+ http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html */
+
+ /* Code run each time yylex is called. */
+ driver.loc_.step();
+
+ if (start_token_flag) {
+ start_token_flag = false;
+ switch (start_token_value) {
+ case D2ParserContext::PARSER_JSON:
+ default:
+ return isc::d2::D2Parser::make_TOPLEVEL_JSON(driver.loc_);
+ case D2ParserContext::PARSER_DHCPDDNS:
+ return isc::d2::D2Parser::make_TOPLEVEL_DHCPDDNS(driver.loc_);
+ case D2ParserContext::PARSER_SUB_DHCPDDNS:
+ return isc::d2::D2Parser::make_SUB_DHCPDDNS(driver.loc_);
+ case D2ParserContext::PARSER_TSIG_KEY:
+ return isc::d2::D2Parser::make_SUB_TSIG_KEY(driver.loc_);
+ case D2ParserContext::PARSER_TSIG_KEYS:
+ return isc::d2::D2Parser::make_SUB_TSIG_KEYS(driver.loc_);
+ case D2ParserContext::PARSER_DDNS_DOMAIN:
+ return isc::d2::D2Parser::make_SUB_DDNS_DOMAIN(driver.loc_);
+ case D2ParserContext::PARSER_DDNS_DOMAINS:
+ return isc::d2::D2Parser::make_SUB_DDNS_DOMAINS(driver.loc_);
+ case D2ParserContext::PARSER_DNS_SERVER:
+ return isc::d2::D2Parser::make_SUB_DNS_SERVER(driver.loc_);
+ case D2ParserContext::PARSER_HOOKS_LIBRARY:
+ return isc::d2::D2Parser::make_SUB_HOOKS_LIBRARY(driver.loc_);
+ }
+ }
+%}
+
+#.* ;
+
+"//"(.*) ;
+
+"/*" {
+ BEGIN(COMMENT);
+ comment_start_line = driver.loc_.end.line;;
+}
+
+<COMMENT>"*/" BEGIN(INITIAL);
+<COMMENT>. ;
+<COMMENT><<EOF>> {
+ isc_throw(D2ParseError, "Comment not closed. (/* in line " << comment_start_line);
+}
+
+"<?" BEGIN(DIR_ENTER);
+<DIR_ENTER>"include" BEGIN(DIR_INCLUDE);
+<DIR_INCLUDE>\"([^\"\n])+\" {
+ /* Include directive. */
+
+ /* Extract the filename. */
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+
+ driver.includeFile(tmp);
+}
+<DIR_ENTER,DIR_INCLUDE,DIR_EXIT><<EOF>> {
+ isc_throw(D2ParseError, "Directive not closed.");
+}
+<DIR_EXIT>"?>" BEGIN(INITIAL);
+
+
+<*>{blank}+ {
+ /* Ok, we found a with space. Let's ignore it and update loc variable. */
+ driver.loc_.step();
+}
+
+<*>[\n]+ {
+ /* Newline found. Let's update the location and continue. */
+ driver.loc_.lines(yyleng);
+ driver.loc_.step();
+}
+
+\"DhcpDdns\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::CONFIG:
+ return isc::d2::D2Parser::make_DHCPDDNS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("DhcpDdns", driver.loc_);
+ }
+}
+
+\"ip-address\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ return isc::d2::D2Parser::make_IP_ADDRESS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("ip-address", driver.loc_);
+ }
+}
+
+\"port\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ return isc::d2::D2Parser::make_PORT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("port", driver.loc_);
+ }
+}
+
+\"dns-server-timeout\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_DNS_SERVER_TIMEOUT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("dns-server-timeout", driver.loc_);
+ }
+}
+
+\"ncr-protocol\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_NCR_PROTOCOL(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("ncr-protocol", driver.loc_);
+ }
+}
+
+\"ncr-format\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_NCR_FORMAT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("ncr-format", driver.loc_);
+ }
+}
+
+(?i:\"UDP\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::d2::D2ParserContext::NCR_PROTOCOL) {
+ return isc::d2::D2Parser::make_UDP(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"TCP\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::d2::D2ParserContext::NCR_PROTOCOL) {
+ return isc::d2::D2Parser::make_TCP(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"JSON\") {
+ /* dhcp-ddns value keywords are case insensitive */
+ if (driver.ctx_ == isc::d2::D2ParserContext::NCR_FORMAT) {
+ return isc::d2::D2Parser::make_JSON(driver.loc_);
+ }
+ std::string tmp(yytext+1);
+ tmp.resize(tmp.size() - 1);
+ return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+\"user-context\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ case isc::d2::D2ParserContext::CONTROL_SOCKET:
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_USER_CONTEXT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("user-context", driver.loc_);
+ }
+}
+
+\"comment\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ case isc::d2::D2ParserContext::CONTROL_SOCKET:
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_COMMENT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("comment", driver.loc_);
+ }
+}
+
+\"forward-ddns\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_FORWARD_DDNS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("forward-ddns", driver.loc_);
+ }
+}
+
+\"reverse-ddns\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_REVERSE_DDNS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("reverse-ddns", driver.loc_);
+ }
+}
+
+\"ddns-domains\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::FORWARD_DDNS:
+ case isc::d2::D2ParserContext::REVERSE_DDNS:
+ return isc::d2::D2Parser::make_DDNS_DOMAINS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("ddns-domains", driver.loc_);
+ }
+}
+
+\"key-name\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ return isc::d2::D2Parser::make_KEY_NAME(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("key-name", driver.loc_);
+ }
+}
+
+\"dns-servers\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ return isc::d2::D2Parser::make_DNS_SERVERS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("dns-servers", driver.loc_);
+ }
+}
+
+\"hostname\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DNS_SERVER:
+ case isc::d2::D2ParserContext::DNS_SERVERS:
+ return isc::d2::D2Parser::make_HOSTNAME(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("hostname", driver.loc_);
+ }
+}
+
+
+\"tsig-keys\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_TSIG_KEYS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("tsig-keys", driver.loc_);
+ }
+}
+
+\"algorithm\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ return isc::d2::D2Parser::make_ALGORITHM(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("algorithm", driver.loc_);
+ }
+}
+
+\"digest-bits\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ return isc::d2::D2Parser::make_DIGEST_BITS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("digest-bits", driver.loc_);
+ }
+}
+
+\"secret\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ return isc::d2::D2Parser::make_SECRET(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("secret", driver.loc_);
+ }
+}
+
+\"control-socket\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_CONTROL_SOCKET(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("control-socket", driver.loc_);
+ }
+}
+
+\"socket-type\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::CONTROL_SOCKET:
+ return isc::d2::D2Parser::make_SOCKET_TYPE(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("socket-type", driver.loc_);
+ }
+}
+
+\"socket-name\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::CONTROL_SOCKET:
+ return isc::d2::D2Parser::make_SOCKET_NAME(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("socket-name", driver.loc_);
+ }
+}
+
+\"hooks-libraries\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_HOOKS_LIBRARIES(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("hooks-libraries", driver.loc_);
+ }
+}
+
+\"parameters\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::HOOKS_LIBRARIES:
+ return isc::d2::D2Parser::make_PARAMETERS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("parameters", driver.loc_);
+ }
+}
+
+\"library\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::HOOKS_LIBRARIES:
+ return isc::d2::D2Parser::make_LIBRARY(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("library", driver.loc_);
+ }
+}
+
+\"loggers\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::DHCPDDNS:
+ return isc::d2::D2Parser::make_LOGGERS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("loggers", driver.loc_);
+ }
+}
+
+\"output_options\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_OUTPUT_OPTIONS(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("output_options", driver.loc_);
+ }
+}
+
+\"output\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_OUTPUT(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("output", driver.loc_);
+ }
+}
+
+\"flush\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_FLUSH(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("flush", driver.loc_);
+ }
+}
+
+\"maxsize\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_MAXSIZE(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("maxsize", driver.loc_);
+ }
+}
+
+\"maxver\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_MAXVER(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("maxver", driver.loc_);
+ }
+}
+
+\"pattern\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+ return isc::d2::D2Parser::make_PATTERN(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("pattern", driver.loc_);
+ }
+}
+
+\"name\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::LOGGERS:
+ case isc::d2::D2ParserContext::TSIG_KEY:
+ case isc::d2::D2ParserContext::TSIG_KEYS:
+ case isc::d2::D2ParserContext::DDNS_DOMAIN:
+ case isc::d2::D2ParserContext::DDNS_DOMAINS:
+ return isc::d2::D2Parser::make_NAME(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("name", driver.loc_);
+ }
+}
+
+\"debuglevel\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_DEBUGLEVEL(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("debuglevel", driver.loc_);
+ }
+}
+
+\"severity\" {
+ switch(driver.ctx_) {
+ case isc::d2::D2ParserContext::LOGGERS:
+ return isc::d2::D2Parser::make_SEVERITY(driver.loc_);
+ default:
+ return isc::d2::D2Parser::make_STRING("severity", driver.loc_);
+ }
+}
+
+{JSONString} {
+ /* A string has been matched. It contains the actual string and single quotes.
+ We need to get those quotes out of the way and just use its content, e.g.
+ for 'foo' we should get foo */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ raw.resize(len);
+ std::string decoded;
+ decoded.reserve(len);
+ for (size_t pos = 0; pos < len; ++pos) {
+ int b = 0;
+ char c = raw[pos];
+ switch (c) {
+ case '"':
+ /* impossible condition */
+ driver.error(driver.loc_, "Bad quote in \"" + raw + "\"");
+ break;
+ case '\\':
+ ++pos;
+ if (pos >= len) {
+ /* impossible condition */
+ driver.error(driver.loc_, "Overflow escape in \"" + raw + "\"");
+ }
+ c = raw[pos];
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ decoded.push_back(c);
+ break;
+ case 'b':
+ decoded.push_back('\b');
+ break;
+ case 'f':
+ decoded.push_back('\f');
+ break;
+ case 'n':
+ decoded.push_back('\n');
+ break;
+ case 'r':
+ decoded.push_back('\r');
+ break;
+ case 't':
+ decoded.push_back('\t');
+ break;
+ case 'u':
+ /* support only \u0000 to \u00ff */
+ ++pos;
+ if (pos + 4 > len) {
+ /* impossible condition */
+ driver.error(driver.loc_,
+ "Overflow unicode escape in \"" + raw + "\"");
+ }
+ if ((raw[pos] != '0') || (raw[pos + 1] != '0')) {
+ driver.error(driver.loc_,
+ "Unsupported unicode escape in \"" + raw + "\"",
+ pos + 1);
+ }
+ pos += 2;
+ c = raw[pos];
+ if ((c >= '0') && (c <= '9')) {
+ b = (c - '0') << 4;
+ } else if ((c >= 'A') && (c <= 'F')) {
+ b = (c - 'A' + 10) << 4;
+ } else if ((c >= 'a') && (c <= 'f')) {
+ b = (c - 'a' + 10) << 4;
+ } else {
+ /* impossible condition */
+ driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+ }
+ pos++;
+ c = raw[pos];
+ if ((c >= '0') && (c <= '9')) {
+ b |= c - '0';
+ } else if ((c >= 'A') && (c <= 'F')) {
+ b |= c - 'A' + 10;
+ } else if ((c >= 'a') && (c <= 'f')) {
+ b |= c - 'a' + 10;
+ } else {
+ /* impossible condition */
+ driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+ }
+ decoded.push_back(static_cast<char>(b & 0xff));
+ break;
+ default:
+ /* impossible condition */
+ driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
+ }
+ break;
+ default:
+ if ((c >= 0) && (c < 0x20)) {
+ /* impossible condition */
+ driver.error(driver.loc_, "Invalid control in \"" + raw + "\"");
+ }
+ decoded.push_back(c);
+ }
+ }
+
+ return isc::d2::D2Parser::make_STRING(decoded, driver.loc_);
+}
+
+\"{JSONStringCharacter}*{ControlCharacter}{ControlCharacterFill}*\" {
+ /* Bad string with a forbidden control character inside */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ size_t pos = 0;
+ for (; pos < len; ++pos) {
+ char c = raw[pos];
+ if ((c >= 0) && (c < 0x20)) {
+ break;
+ }
+ }
+ driver.error(driver.loc_,
+ "Invalid control in " + std::string(yytext),
+ pos + 1);
+}
+
+\"{JSONStringCharacter}*\\{BadJSONEscapeSequence}[^"]*\" {
+ /* Bad string with a bad escape inside */
+ std::string raw(yytext+1);
+ size_t len = raw.size() - 1;
+ size_t pos = 0;
+ bool found = false;
+ for (; pos < len; ++pos) {
+ if (found) {
+ break;
+ }
+ char c = raw[pos];
+ if (c == '\\') {
+ ++pos;
+ c = raw[pos];
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ case 'b':
+ case 'f':
+ case 'n':
+ case 'r':
+ case 't':
+ break;
+ case 'u':
+ if ((pos + 4 > len) ||
+ !std::isxdigit(raw[pos + 1]) ||
+ !std::isxdigit(raw[pos + 2]) ||
+ !std::isxdigit(raw[pos + 3]) ||
+ !std::isxdigit(raw[pos + 4])) {
+ found = true;
+ }
+ break;
+ default:
+ found = true;
+ break;
+ }
+ }
+ }
+ /* The rule stops on the first " including on \" so add ... in this case */
+ std::string trailer = "";
+ if (raw[len - 1] == '\\') {
+ trailer = "...";
+ }
+ driver.error(driver.loc_,
+ "Bad escape in " + std::string(yytext) + trailer,
+ pos);
+}
+
+\"{JSONStringCharacter}*\\\" {
+ /* Bad string with an open escape at the end */
+ std::string raw(yytext+1);
+ driver.error(driver.loc_,
+ "Overflow escape in " + std::string(yytext),
+ raw.size() + 1);
+}
+
+\"{JSONStringCharacter}*\\u[0-9A-Fa-f]{0,3}\" {
+ /* Bad string with an open unicode escape at the end */
+ std::string raw(yytext+1);
+ size_t pos = raw.size() - 1;
+ for (; pos > 0; --pos) {
+ char c = raw[pos];
+ if (c == 'u') {
+ break;
+ }
+ }
+ driver.error(driver.loc_,
+ "Overflow unicode escape in " + std::string(yytext),
+ pos + 1);
+}
+
+"[" { return isc::d2::D2Parser::make_LSQUARE_BRACKET(driver.loc_); }
+"]" { return isc::d2::D2Parser::make_RSQUARE_BRACKET(driver.loc_); }
+"{" { return isc::d2::D2Parser::make_LCURLY_BRACKET(driver.loc_); }
+"}" { return isc::d2::D2Parser::make_RCURLY_BRACKET(driver.loc_); }
+"," { return isc::d2::D2Parser::make_COMMA(driver.loc_); }
+":" { return isc::d2::D2Parser::make_COLON(driver.loc_); }
+
+{int} {
+ /* An integer was found. */
+ std::string tmp(yytext);
+ int64_t integer = 0;
+ try {
+ /* In substring we want to use negative values (e.g. -1).
+ In enterprise-id we need to use values up to 0xffffffff.
+ To cover both of those use cases, we need at least
+ int64_t. */
+ integer = boost::lexical_cast<int64_t>(tmp);
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(driver.loc_, "Failed to convert " + tmp + " to an integer.");
+ }
+
+ /* The parser needs the string form as double conversion is no lossless */
+ return isc::d2::D2Parser::make_INTEGER(integer, driver.loc_);
+}
+
+[-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)? {
+ /* A floating point was found. */
+ std::string tmp(yytext);
+ double fp = 0.0;
+ try {
+ fp = boost::lexical_cast<double>(tmp);
+ } catch (const boost::bad_lexical_cast &) {
+ driver.error(driver.loc_, "Failed to convert " + tmp + " to a floating point.");
+ }
+
+ return isc::d2::D2Parser::make_FLOAT(fp, driver.loc_);
+}
+
+true|false {
+ string tmp(yytext);
+ return isc::d2::D2Parser::make_BOOLEAN(tmp == "true", driver.loc_);
+}
+
+null {
+ return isc::d2::D2Parser::make_NULL_TYPE(driver.loc_);
+}
+
+(?i:true) driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
+
+(?i:false) driver.error (driver.loc_, "JSON false reserved keyword is lower case only");
+
+(?i:null) driver.error (driver.loc_, "JSON null reserved keyword is lower case only");
+
+<*>. driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
+
+<<EOF>> {
+ if (driver.states_.empty()) {
+ return isc::d2::D2Parser::make_END(driver.loc_);
+ }
+ driver.loc_ = driver.locs_.back();
+ driver.locs_.pop_back();
+ driver.file_ = driver.files_.back();
+ driver.files_.pop_back();
+ if (driver.sfile_) {
+ fclose(driver.sfile_);
+ driver.sfile_ = 0;
+ }
+ if (!driver.sfiles_.empty()) {
+ driver.sfile_ = driver.sfiles_.back();
+ driver.sfiles_.pop_back();
+ }
+ d2_parser__delete_buffer(YY_CURRENT_BUFFER);
+ d2_parser__switch_to_buffer(driver.states_.back());
+ driver.states_.pop_back();
+
+ BEGIN(DIR_EXIT);
+}
+
+%%
+
+using namespace isc::dhcp;
+
+void
+D2ParserContext::scanStringBegin(const std::string& str, ParserType parser_type)
+{
+ start_token_flag = true;
+ start_token_value = parser_type;
+
+ file_ = "<string>";
+ sfile_ = 0;
+ loc_.initialize(&file_);
+ yy_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+ buffer = d2_parser__scan_bytes(str.c_str(), str.size());
+ if (!buffer) {
+ fatal("cannot scan string");
+ /* fatal() throws an exception so this can't be reached */
+ }
+}
+
+void
+D2ParserContext::scanFileBegin(FILE * f,
+ const std::string& filename,
+ ParserType parser_type)
+{
+ start_token_flag = true;
+ start_token_value = parser_type;
+
+ file_ = filename;
+ sfile_ = f;
+ loc_.initialize(&file_);
+ yy_flex_debug = trace_scanning_;
+ YY_BUFFER_STATE buffer;
+
+ /* See d2_lexer.cc header for available definitions */
+ buffer = d2_parser__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal("cannot scan file " + filename);
+ }
+ d2_parser__switch_to_buffer(buffer);
+}
+
+void
+D2ParserContext::scanEnd() {
+ if (sfile_)
+ fclose(sfile_);
+ sfile_ = 0;
+ static_cast<void>(d2_parser_lex_destroy());
+ /* Close files */
+ while (!sfiles_.empty()) {
+ FILE* f = sfiles_.back();
+ if (f) {
+ fclose(f);
+ }
+ sfiles_.pop_back();
+ }
+ /* Delete states */
+ while (!states_.empty()) {
+ d2_parser__delete_buffer(states_.back());
+ states_.pop_back();
+ }
+}
+
+void
+D2ParserContext::includeFile(const std::string& filename) {
+ if (states_.size() > 10) {
+ fatal("Too many nested include.");
+ }
+
+ FILE* f = fopen(filename.c_str(), "r");
+ if (!f) {
+ fatal("Can't open include file " + filename);
+ }
+ if (sfile_) {
+ sfiles_.push_back(sfile_);
+ }
+ sfile_ = f;
+ states_.push_back(YY_CURRENT_BUFFER);
+ YY_BUFFER_STATE buffer;
+ buffer = d2_parser__create_buffer(f, 65536 /*buffer size*/);
+ if (!buffer) {
+ fatal( "Can't scan include file " + filename);
+ }
+ d2_parser__switch_to_buffer(buffer);
+ files_.push_back(file_);
+ file_ = filename;
+ locs_.push_back(loc_);
+ loc_.initialize(&file_);
+
+ BEGIN(INITIAL);
+}
+
+namespace {
+/** To avoid unused function error */
+class Dummy {
+ /* cppcheck-suppress unusedPrivateFunction */
+ void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
+};
+}
+#endif /* !__clang_analyzer__ */
diff --git a/src/bin/d2/d2_parser.cc b/src/bin/d2/d2_parser.cc
new file mode 100644
index 0000000..ba6a862
--- /dev/null
+++ b/src/bin/d2/d2_parser.cc
@@ -0,0 +1,2858 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Skeleton implementation for Bison LALR(1) parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+// DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+// especially those whose name start with YY_ or yy_. They are
+// private implementation details that can be changed or removed.
+
+
+// Take the name prefix into account.
+#define yylex d2_parser_lex
+
+
+
+#include "d2_parser.h"
+
+
+// Unqualified %code blocks.
+#line 34 "d2_parser.yy"
+
+#include <d2/parser_context.h>
+
+#line 52 "d2_parser.cc"
+
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> // FIXME: INFRINGES ON USER NAME SPACE.
+# define YY_(msgid) dgettext ("bison-runtime", msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(msgid) msgid
+# endif
+#endif
+
+
+// Whether we are compiled with exception support.
+#ifndef YY_EXCEPTIONS
+# if defined __GNUC__ && !defined __EXCEPTIONS
+# define YY_EXCEPTIONS 0
+# else
+# define YY_EXCEPTIONS 1
+# endif
+#endif
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K].location)
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+ If N is 0, then set CURRENT to the empty location which ends
+ the previous symbol: RHS[0] (always defined). */
+
+# ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do \
+ if (N) \
+ { \
+ (Current).begin = YYRHSLOC (Rhs, 1).begin; \
+ (Current).end = YYRHSLOC (Rhs, N).end; \
+ } \
+ else \
+ { \
+ (Current).begin = (Current).end = YYRHSLOC (Rhs, 0).end; \
+ } \
+ while (false)
+# endif
+
+
+// Enable debugging if requested.
+#if D2_PARSER_DEBUG
+
+// A pseudo ostream that takes yydebug_ into account.
+# define YYCDEBUG if (yydebug_) (*yycdebug_)
+
+# define YY_SYMBOL_PRINT(Title, Symbol) \
+ do { \
+ if (yydebug_) \
+ { \
+ *yycdebug_ << Title << ' '; \
+ yy_print_ (*yycdebug_, Symbol); \
+ *yycdebug_ << '\n'; \
+ } \
+ } while (false)
+
+# define YY_REDUCE_PRINT(Rule) \
+ do { \
+ if (yydebug_) \
+ yy_reduce_print_ (Rule); \
+ } while (false)
+
+# define YY_STACK_PRINT() \
+ do { \
+ if (yydebug_) \
+ yy_stack_print_ (); \
+ } while (false)
+
+#else // !D2_PARSER_DEBUG
+
+# define YYCDEBUG if (false) std::cerr
+# define YY_SYMBOL_PRINT(Title, Symbol) YY_USE (Symbol)
+# define YY_REDUCE_PRINT(Rule) static_cast<void> (0)
+# define YY_STACK_PRINT() static_cast<void> (0)
+
+#endif // !D2_PARSER_DEBUG
+
+#define yyerrok (yyerrstatus_ = 0)
+#define yyclearin (yyla.clear ())
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+#define YYRECOVERING() (!!yyerrstatus_)
+
+#line 14 "d2_parser.yy"
+namespace isc { namespace d2 {
+#line 145 "d2_parser.cc"
+
+ /// Build a parser object.
+ D2Parser::D2Parser (isc::d2::D2ParserContext& ctx_yyarg)
+#if D2_PARSER_DEBUG
+ : yydebug_ (false),
+ yycdebug_ (&std::cerr),
+#else
+ :
+#endif
+ ctx (ctx_yyarg)
+ {}
+
+ D2Parser::~D2Parser ()
+ {}
+
+ D2Parser::syntax_error::~syntax_error () YY_NOEXCEPT YY_NOTHROW
+ {}
+
+ /*---------.
+ | symbol. |
+ `---------*/
+
+
+
+ // by_state.
+ D2Parser::by_state::by_state () YY_NOEXCEPT
+ : state (empty_state)
+ {}
+
+ D2Parser::by_state::by_state (const by_state& that) YY_NOEXCEPT
+ : state (that.state)
+ {}
+
+ void
+ D2Parser::by_state::clear () YY_NOEXCEPT
+ {
+ state = empty_state;
+ }
+
+ void
+ D2Parser::by_state::move (by_state& that)
+ {
+ state = that.state;
+ that.clear ();
+ }
+
+ D2Parser::by_state::by_state (state_type s) YY_NOEXCEPT
+ : state (s)
+ {}
+
+ D2Parser::symbol_kind_type
+ D2Parser::by_state::kind () const YY_NOEXCEPT
+ {
+ if (state == empty_state)
+ return symbol_kind::S_YYEMPTY;
+ else
+ return YY_CAST (symbol_kind_type, yystos_[+state]);
+ }
+
+ D2Parser::stack_symbol_type::stack_symbol_type ()
+ {}
+
+ D2Parser::stack_symbol_type::stack_symbol_type (YY_RVREF (stack_symbol_type) that)
+ : super_type (YY_MOVE (that.state), YY_MOVE (that.location))
+ {
+ switch (that.kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.YY_MOVE_OR_COPY< ElementPtr > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.YY_MOVE_OR_COPY< bool > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.YY_MOVE_OR_COPY< double > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.YY_MOVE_OR_COPY< int64_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.YY_MOVE_OR_COPY< std::string > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+#if 201103L <= YY_CPLUSPLUS
+ // that is emptied.
+ that.state = empty_state;
+#endif
+ }
+
+ D2Parser::stack_symbol_type::stack_symbol_type (state_type s, YY_MOVE_REF (symbol_type) that)
+ : super_type (s, YY_MOVE (that.location))
+ {
+ switch (that.kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.move< ElementPtr > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.move< bool > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.move< double > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.move< int64_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.move< std::string > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ // that is emptied.
+ that.kind_ = symbol_kind::S_YYEMPTY;
+ }
+
+#if YY_CPLUSPLUS < 201103L
+ D2Parser::stack_symbol_type&
+ D2Parser::stack_symbol_type::operator= (const stack_symbol_type& that)
+ {
+ state = that.state;
+ switch (that.kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.copy< ElementPtr > (that.value);
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.copy< bool > (that.value);
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.copy< double > (that.value);
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.copy< int64_t > (that.value);
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.copy< std::string > (that.value);
+ break;
+
+ default:
+ break;
+ }
+
+ location = that.location;
+ return *this;
+ }
+
+ D2Parser::stack_symbol_type&
+ D2Parser::stack_symbol_type::operator= (stack_symbol_type& that)
+ {
+ state = that.state;
+ switch (that.kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.move< ElementPtr > (that.value);
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.move< bool > (that.value);
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.move< double > (that.value);
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.move< int64_t > (that.value);
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.move< std::string > (that.value);
+ break;
+
+ default:
+ break;
+ }
+
+ location = that.location;
+ // that is emptied.
+ that.state = empty_state;
+ return *this;
+ }
+#endif
+
+ template <typename Base>
+ void
+ D2Parser::yy_destroy_ (const char* yymsg, basic_symbol<Base>& yysym) const
+ {
+ if (yymsg)
+ YY_SYMBOL_PRINT (yymsg, yysym);
+ }
+
+#if D2_PARSER_DEBUG
+ template <typename Base>
+ void
+ D2Parser::yy_print_ (std::ostream& yyo, const basic_symbol<Base>& yysym) const
+ {
+ std::ostream& yyoutput = yyo;
+ YY_USE (yyoutput);
+ if (yysym.empty ())
+ yyo << "empty symbol";
+ else
+ {
+ symbol_kind_type yykind = yysym.kind ();
+ yyo << (yykind < YYNTOKENS ? "token" : "nterm")
+ << ' ' << yysym.name () << " ("
+ << yysym.location << ": ";
+ switch (yykind)
+ {
+ case symbol_kind::S_STRING: // "constant string"
+#line 116 "d2_parser.yy"
+ { yyoutput << yysym.value.template as < std::string > (); }
+#line 384 "d2_parser.cc"
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+#line 116 "d2_parser.yy"
+ { yyoutput << yysym.value.template as < int64_t > (); }
+#line 390 "d2_parser.cc"
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+#line 116 "d2_parser.yy"
+ { yyoutput << yysym.value.template as < double > (); }
+#line 396 "d2_parser.cc"
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+#line 116 "d2_parser.yy"
+ { yyoutput << yysym.value.template as < bool > (); }
+#line 402 "d2_parser.cc"
+ break;
+
+ case symbol_kind::S_value: // value
+#line 116 "d2_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 408 "d2_parser.cc"
+ break;
+
+ case symbol_kind::S_map_value: // map_value
+#line 116 "d2_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 414 "d2_parser.cc"
+ break;
+
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+#line 116 "d2_parser.yy"
+ { yyoutput << yysym.value.template as < ElementPtr > (); }
+#line 420 "d2_parser.cc"
+ break;
+
+ default:
+ break;
+ }
+ yyo << ')';
+ }
+ }
+#endif
+
+ void
+ D2Parser::yypush_ (const char* m, YY_MOVE_REF (stack_symbol_type) sym)
+ {
+ if (m)
+ YY_SYMBOL_PRINT (m, sym);
+ yystack_.push (YY_MOVE (sym));
+ }
+
+ void
+ D2Parser::yypush_ (const char* m, state_type s, YY_MOVE_REF (symbol_type) sym)
+ {
+#if 201103L <= YY_CPLUSPLUS
+ yypush_ (m, stack_symbol_type (s, std::move (sym)));
+#else
+ stack_symbol_type ss (s, sym);
+ yypush_ (m, ss);
+#endif
+ }
+
+ void
+ D2Parser::yypop_ (int n) YY_NOEXCEPT
+ {
+ yystack_.pop (n);
+ }
+
+#if D2_PARSER_DEBUG
+ std::ostream&
+ D2Parser::debug_stream () const
+ {
+ return *yycdebug_;
+ }
+
+ void
+ D2Parser::set_debug_stream (std::ostream& o)
+ {
+ yycdebug_ = &o;
+ }
+
+
+ D2Parser::debug_level_type
+ D2Parser::debug_level () const
+ {
+ return yydebug_;
+ }
+
+ void
+ D2Parser::set_debug_level (debug_level_type l)
+ {
+ yydebug_ = l;
+ }
+#endif // D2_PARSER_DEBUG
+
+ D2Parser::state_type
+ D2Parser::yy_lr_goto_state_ (state_type yystate, int yysym)
+ {
+ int yyr = yypgoto_[yysym - YYNTOKENS] + yystate;
+ if (0 <= yyr && yyr <= yylast_ && yycheck_[yyr] == yystate)
+ return yytable_[yyr];
+ else
+ return yydefgoto_[yysym - YYNTOKENS];
+ }
+
+ bool
+ D2Parser::yy_pact_value_is_default_ (int yyvalue) YY_NOEXCEPT
+ {
+ return yyvalue == yypact_ninf_;
+ }
+
+ bool
+ D2Parser::yy_table_value_is_error_ (int yyvalue) YY_NOEXCEPT
+ {
+ return yyvalue == yytable_ninf_;
+ }
+
+ int
+ D2Parser::operator() ()
+ {
+ return parse ();
+ }
+
+ int
+ D2Parser::parse ()
+ {
+ int yyn;
+ /// Length of the RHS of the rule being reduced.
+ int yylen = 0;
+
+ // Error handling.
+ int yynerrs_ = 0;
+ int yyerrstatus_ = 0;
+
+ /// The lookahead symbol.
+ symbol_type yyla;
+
+ /// The locations where the error started and ended.
+ stack_symbol_type yyerror_range[3];
+
+ /// The return value of parse ().
+ int yyresult;
+
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ YYCDEBUG << "Starting parse\n";
+
+
+ /* Initialize the stack. The initial state will be set in
+ yynewstate, since the latter expects the semantical and the
+ location values to have been already stored, initialize these
+ stacks with a primary value. */
+ yystack_.clear ();
+ yypush_ (YY_NULLPTR, 0, YY_MOVE (yyla));
+
+ /*-----------------------------------------------.
+ | yynewstate -- push a new symbol on the stack. |
+ `-----------------------------------------------*/
+ yynewstate:
+ YYCDEBUG << "Entering state " << int (yystack_[0].state) << '\n';
+ YY_STACK_PRINT ();
+
+ // Accept?
+ if (yystack_[0].state == yyfinal_)
+ YYACCEPT;
+
+ goto yybackup;
+
+
+ /*-----------.
+ | yybackup. |
+ `-----------*/
+ yybackup:
+ // Try to take a decision without lookahead.
+ yyn = yypact_[+yystack_[0].state];
+ if (yy_pact_value_is_default_ (yyn))
+ goto yydefault;
+
+ // Read a lookahead token.
+ if (yyla.empty ())
+ {
+ YYCDEBUG << "Reading a token\n";
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ symbol_type yylookahead (yylex (ctx));
+ yyla.move (yylookahead);
+ }
+#if YY_EXCEPTIONS
+ catch (const syntax_error& yyexc)
+ {
+ YYCDEBUG << "Caught exception: " << yyexc.what() << '\n';
+ error (yyexc);
+ goto yyerrlab1;
+ }
+#endif // YY_EXCEPTIONS
+ }
+ YY_SYMBOL_PRINT ("Next token is", yyla);
+
+ if (yyla.kind () == symbol_kind::S_YYerror)
+ {
+ // The scanner already issued an error message, process directly
+ // to error recovery. But do not keep the error token as
+ // lookahead, it is too special and may lead us to an endless
+ // loop in error recovery. */
+ yyla.kind_ = symbol_kind::S_YYUNDEF;
+ goto yyerrlab1;
+ }
+
+ /* If the proper action on seeing token YYLA.TYPE is to reduce or
+ to detect an error, take that action. */
+ yyn += yyla.kind ();
+ if (yyn < 0 || yylast_ < yyn || yycheck_[yyn] != yyla.kind ())
+ {
+ goto yydefault;
+ }
+
+ // Reduce or error.
+ yyn = yytable_[yyn];
+ if (yyn <= 0)
+ {
+ if (yy_table_value_is_error_ (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ // Count tokens shifted since error; after three, turn off error status.
+ if (yyerrstatus_)
+ --yyerrstatus_;
+
+ // Shift the lookahead token.
+ yypush_ ("Shifting", state_type (yyn), YY_MOVE (yyla));
+ goto yynewstate;
+
+
+ /*-----------------------------------------------------------.
+ | yydefault -- do the default action for the current state. |
+ `-----------------------------------------------------------*/
+ yydefault:
+ yyn = yydefact_[+yystack_[0].state];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+ /*-----------------------------.
+ | yyreduce -- do a reduction. |
+ `-----------------------------*/
+ yyreduce:
+ yylen = yyr2_[yyn];
+ {
+ stack_symbol_type yylhs;
+ yylhs.state = yy_lr_goto_state_ (yystack_[yylen].state, yyr1_[yyn]);
+ /* Variants are always initialized to an empty instance of the
+ correct type. The default '$$ = $1' action is NOT applied
+ when using variants. */
+ switch (yyr1_[yyn])
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ yylhs.value.emplace< ElementPtr > ();
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ yylhs.value.emplace< bool > ();
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ yylhs.value.emplace< double > ();
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ yylhs.value.emplace< int64_t > ();
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ yylhs.value.emplace< std::string > ();
+ break;
+
+ default:
+ break;
+ }
+
+
+ // Default location.
+ {
+ stack_type::slice range (yystack_, yylen);
+ YYLLOC_DEFAULT (yylhs.location, range, yylen);
+ yyerror_range[1].location = yylhs.location;
+ }
+
+ // Perform the reduction.
+ YY_REDUCE_PRINT (yyn);
+#if YY_EXCEPTIONS
+ try
+#endif // YY_EXCEPTIONS
+ {
+ switch (yyn)
+ {
+ case 2: // $@1: %empty
+#line 125 "d2_parser.yy"
+ { ctx.ctx_ = ctx.NO_KEYWORD; }
+#line 695 "d2_parser.cc"
+ break;
+
+ case 4: // $@2: %empty
+#line 126 "d2_parser.yy"
+ { ctx.ctx_ = ctx.CONFIG; }
+#line 701 "d2_parser.cc"
+ break;
+
+ case 6: // $@3: %empty
+#line 127 "d2_parser.yy"
+ { ctx.ctx_ = ctx.DHCPDDNS; }
+#line 707 "d2_parser.cc"
+ break;
+
+ case 8: // $@4: %empty
+#line 128 "d2_parser.yy"
+ { ctx.ctx_ = ctx.TSIG_KEY; }
+#line 713 "d2_parser.cc"
+ break;
+
+ case 10: // $@5: %empty
+#line 129 "d2_parser.yy"
+ { ctx.ctx_ = ctx.TSIG_KEYS; }
+#line 719 "d2_parser.cc"
+ break;
+
+ case 12: // $@6: %empty
+#line 130 "d2_parser.yy"
+ { ctx.ctx_ = ctx.DDNS_DOMAIN; }
+#line 725 "d2_parser.cc"
+ break;
+
+ case 14: // $@7: %empty
+#line 131 "d2_parser.yy"
+ { ctx.ctx_ = ctx.DDNS_DOMAINS; }
+#line 731 "d2_parser.cc"
+ break;
+
+ case 16: // $@8: %empty
+#line 132 "d2_parser.yy"
+ { ctx.ctx_ = ctx.DNS_SERVERS; }
+#line 737 "d2_parser.cc"
+ break;
+
+ case 18: // $@9: %empty
+#line 133 "d2_parser.yy"
+ { ctx.ctx_ = ctx.DNS_SERVERS; }
+#line 743 "d2_parser.cc"
+ break;
+
+ case 20: // $@10: %empty
+#line 134 "d2_parser.yy"
+ { ctx.ctx_ = ctx.HOOKS_LIBRARIES; }
+#line 749 "d2_parser.cc"
+ break;
+
+ case 22: // value: "integer"
+#line 142 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location))); }
+#line 755 "d2_parser.cc"
+ break;
+
+ case 23: // value: "floating point"
+#line 143 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new DoubleElement(yystack_[0].value.as < double > (), ctx.loc2pos(yystack_[0].location))); }
+#line 761 "d2_parser.cc"
+ break;
+
+ case 24: // value: "boolean"
+#line 144 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location))); }
+#line 767 "d2_parser.cc"
+ break;
+
+ case 25: // value: "constant string"
+#line 145 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location))); }
+#line 773 "d2_parser.cc"
+ break;
+
+ case 26: // value: "null"
+#line 146 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new NullElement(ctx.loc2pos(yystack_[0].location))); }
+#line 779 "d2_parser.cc"
+ break;
+
+ case 27: // value: map2
+#line 147 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ctx.stack_.back(); ctx.stack_.pop_back(); }
+#line 785 "d2_parser.cc"
+ break;
+
+ case 28: // value: list_generic
+#line 148 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ctx.stack_.back(); ctx.stack_.pop_back(); }
+#line 791 "d2_parser.cc"
+ break;
+
+ case 29: // sub_json: value
+#line 151 "d2_parser.yy"
+ {
+ // Push back the JSON value on the stack
+ ctx.stack_.push_back(yystack_[0].value.as < ElementPtr > ());
+}
+#line 800 "d2_parser.cc"
+ break;
+
+ case 30: // $@11: %empty
+#line 156 "d2_parser.yy"
+ {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 811 "d2_parser.cc"
+ break;
+
+ case 31: // map2: "{" $@11 map_content "}"
+#line 161 "d2_parser.yy"
+ {
+ // map parsing completed. If we ever want to do any wrap up
+ // (maybe some sanity checking), this would be the best place
+ // for it.
+}
+#line 821 "d2_parser.cc"
+ break;
+
+ case 32: // map_value: map2
+#line 167 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ctx.stack_.back(); ctx.stack_.pop_back(); }
+#line 827 "d2_parser.cc"
+ break;
+
+ case 35: // not_empty_map: "constant string" ":" value
+#line 174 "d2_parser.yy"
+ {
+ // map containing a single entry
+ ctx.unique(yystack_[2].value.as < std::string > (), ctx.loc2pos(yystack_[2].location));
+ ctx.stack_.back()->set(yystack_[2].value.as < std::string > (), yystack_[0].value.as < ElementPtr > ());
+ }
+#line 837 "d2_parser.cc"
+ break;
+
+ case 36: // not_empty_map: not_empty_map "," "constant string" ":" value
+#line 179 "d2_parser.yy"
+ {
+ // map consisting of a shorter map followed by
+ // comma and string:value
+ ctx.unique(yystack_[2].value.as < std::string > (), ctx.loc2pos(yystack_[2].location));
+ ctx.stack_.back()->set(yystack_[2].value.as < std::string > (), yystack_[0].value.as < ElementPtr > ());
+ }
+#line 848 "d2_parser.cc"
+ break;
+
+ case 37: // not_empty_map: not_empty_map ","
+#line 185 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 856 "d2_parser.cc"
+ break;
+
+ case 38: // $@12: %empty
+#line 190 "d2_parser.yy"
+ {
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(l);
+}
+#line 865 "d2_parser.cc"
+ break;
+
+ case 39: // list_generic: "[" $@12 list_content "]"
+#line 193 "d2_parser.yy"
+ {
+ // list parsing complete. Put any sanity checking here
+}
+#line 873 "d2_parser.cc"
+ break;
+
+ case 42: // not_empty_list: value
+#line 201 "d2_parser.yy"
+ {
+ // List consisting of a single element.
+ ctx.stack_.back()->add(yystack_[0].value.as < ElementPtr > ());
+ }
+#line 882 "d2_parser.cc"
+ break;
+
+ case 43: // not_empty_list: not_empty_list "," value
+#line 205 "d2_parser.yy"
+ {
+ // List ending with , and a value.
+ ctx.stack_.back()->add(yystack_[0].value.as < ElementPtr > ());
+ }
+#line 891 "d2_parser.cc"
+ break;
+
+ case 44: // not_empty_list: not_empty_list ","
+#line 209 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 899 "d2_parser.cc"
+ break;
+
+ case 45: // unknown_map_entry: "constant string" ":"
+#line 219 "d2_parser.yy"
+ {
+ const std::string& where = ctx.contextName();
+ const std::string& keyword = yystack_[1].value.as < std::string > ();
+ error(yystack_[1].location,
+ "got unexpected keyword \"" + keyword + "\" in " + where + " map.");
+}
+#line 910 "d2_parser.cc"
+ break;
+
+ case 46: // $@13: %empty
+#line 228 "d2_parser.yy"
+ {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 921 "d2_parser.cc"
+ break;
+
+ case 47: // syntax_map: "{" $@13 global_object "}"
+#line 233 "d2_parser.yy"
+ {
+ // map parsing completed. If we ever want to do any wrap up
+ // (maybe some sanity checking), this would be the best place
+ // for it.
+}
+#line 931 "d2_parser.cc"
+ break;
+
+ case 48: // $@14: %empty
+#line 241 "d2_parser.yy"
+ {
+ ctx.unique("DhcpDdns", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("DhcpDdns", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.DHCPDDNS);
+}
+#line 943 "d2_parser.cc"
+ break;
+
+ case 49: // global_object: "DhcpDdns" $@14 ":" "{" dhcpddns_params "}"
+#line 247 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 952 "d2_parser.cc"
+ break;
+
+ case 51: // global_object_comma: global_object ","
+#line 254 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+}
+#line 960 "d2_parser.cc"
+ break;
+
+ case 52: // $@15: %empty
+#line 258 "d2_parser.yy"
+ {
+ // Parse the dhcpddns map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 970 "d2_parser.cc"
+ break;
+
+ case 53: // sub_dhcpddns: "{" $@15 dhcpddns_params "}"
+#line 262 "d2_parser.yy"
+ {
+ // parsing completed
+}
+#line 978 "d2_parser.cc"
+ break;
+
+ case 56: // dhcpddns_params: dhcpddns_params ","
+#line 268 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 986 "d2_parser.cc"
+ break;
+
+ case 71: // $@16: %empty
+#line 290 "d2_parser.yy"
+ {
+ ctx.unique("ip-address", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 995 "d2_parser.cc"
+ break;
+
+ case 72: // ip_address: "ip-address" $@16 ":" "constant string"
+#line 293 "d2_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ip-address", s);
+ ctx.leave();
+}
+#line 1005 "d2_parser.cc"
+ break;
+
+ case 73: // port: "port" ":" "integer"
+#line 299 "d2_parser.yy"
+ {
+ ctx.unique("port", ctx.loc2pos(yystack_[2].location));
+ if (yystack_[0].value.as < int64_t > () <= 0 || yystack_[0].value.as < int64_t > () >= 65536 ) {
+ error(yystack_[0].location, "port must be greater than zero but less than 65536");
+ }
+ ElementPtr i(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("port", i);
+}
+#line 1018 "d2_parser.cc"
+ break;
+
+ case 74: // dns_server_timeout: "dns-server-timeout" ":" "integer"
+#line 308 "d2_parser.yy"
+ {
+ ctx.unique("dns-server-timeout", ctx.loc2pos(yystack_[2].location));
+ if (yystack_[0].value.as < int64_t > () <= 0) {
+ error(yystack_[0].location, "dns-server-timeout must be greater than zero");
+ } else {
+ ElementPtr i(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("dns-server-timeout", i);
+ }
+}
+#line 1032 "d2_parser.cc"
+ break;
+
+ case 75: // $@17: %empty
+#line 318 "d2_parser.yy"
+ {
+ ctx.unique("ncr-protocol", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NCR_PROTOCOL);
+}
+#line 1041 "d2_parser.cc"
+ break;
+
+ case 76: // ncr_protocol: "ncr-protocol" $@17 ":" ncr_protocol_value
+#line 321 "d2_parser.yy"
+ {
+ ctx.stack_.back()->set("ncr-protocol", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 1050 "d2_parser.cc"
+ break;
+
+ case 77: // ncr_protocol_value: "UDP"
+#line 327 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("UDP", ctx.loc2pos(yystack_[0].location))); }
+#line 1056 "d2_parser.cc"
+ break;
+
+ case 78: // ncr_protocol_value: "TCP"
+#line 328 "d2_parser.yy"
+ { yylhs.value.as < ElementPtr > () = ElementPtr(new StringElement("TCP", ctx.loc2pos(yystack_[0].location))); }
+#line 1062 "d2_parser.cc"
+ break;
+
+ case 79: // $@18: %empty
+#line 331 "d2_parser.yy"
+ {
+ ctx.unique("ncr-format", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NCR_FORMAT);
+}
+#line 1071 "d2_parser.cc"
+ break;
+
+ case 80: // ncr_format: "ncr-format" $@18 ":" "JSON"
+#line 334 "d2_parser.yy"
+ {
+ ElementPtr json(new StringElement("JSON", ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ncr-format", json);
+ ctx.leave();
+}
+#line 1081 "d2_parser.cc"
+ break;
+
+ case 81: // $@19: %empty
+#line 340 "d2_parser.yy"
+ {
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1089 "d2_parser.cc"
+ break;
+
+ case 82: // user_context: "user-context" $@19 ":" map_value
+#line 342 "d2_parser.yy"
+ {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context = yystack_[0].value.as < ElementPtr > ();
+ ConstElementPtr old = parent->get("user-context");
+
+ // Handle already existing user context
+ if (old) {
+ // Check if it was a comment or a duplicate
+ if ((old->size() != 1) || !old->contains("comment")) {
+ std::stringstream msg;
+ msg << "duplicate user-context entries (previous at "
+ << old->getPosition().str() << ")";
+ error(yystack_[3].location, msg.str());
+ }
+ // Merge the comment
+ user_context->set("comment", old->get("comment"));
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+}
+#line 1116 "d2_parser.cc"
+ break;
+
+ case 83: // $@20: %empty
+#line 365 "d2_parser.yy"
+ {
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1124 "d2_parser.cc"
+ break;
+
+ case 84: // comment: "comment" $@20 ":" "constant string"
+#line 367 "d2_parser.yy"
+ {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context(new MapElement(ctx.loc2pos(yystack_[3].location)));
+ ElementPtr comment(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ user_context->set("comment", comment);
+
+ // Handle already existing user context
+ ConstElementPtr old = parent->get("user-context");
+ if (old) {
+ // Check for duplicate comment
+ if (old->contains("comment")) {
+ std::stringstream msg;
+ msg << "duplicate user-context/comment entries (previous at "
+ << old->getPosition().str() << ")";
+ error(yystack_[3].location, msg.str());
+ }
+ // Merge the user context in the comment
+ merge(user_context, old);
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+}
+#line 1153 "d2_parser.cc"
+ break;
+
+ case 85: // $@21: %empty
+#line 392 "d2_parser.yy"
+ {
+ ctx.unique("forward-ddns", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("forward-ddns", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.FORWARD_DDNS);
+}
+#line 1165 "d2_parser.cc"
+ break;
+
+ case 86: // forward_ddns: "forward-ddns" $@21 ":" "{" ddns_mgr_params "}"
+#line 398 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1174 "d2_parser.cc"
+ break;
+
+ case 87: // $@22: %empty
+#line 403 "d2_parser.yy"
+ {
+ ctx.unique("reverse-ddns", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("reverse-ddns", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.REVERSE_DDNS);
+}
+#line 1186 "d2_parser.cc"
+ break;
+
+ case 88: // reverse_ddns: "reverse-ddns" $@22 ":" "{" ddns_mgr_params "}"
+#line 409 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1195 "d2_parser.cc"
+ break;
+
+ case 93: // not_empty_ddns_mgr_params: ddns_mgr_params ","
+#line 420 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1203 "d2_parser.cc"
+ break;
+
+ case 96: // $@23: %empty
+#line 431 "d2_parser.yy"
+ {
+ ctx.unique("ddns-domains", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ddns-domains", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.DDNS_DOMAINS);
+}
+#line 1215 "d2_parser.cc"
+ break;
+
+ case 97: // ddns_domains: "ddns-domains" $@23 ":" "[" ddns_domain_list "]"
+#line 437 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1224 "d2_parser.cc"
+ break;
+
+ case 98: // $@24: %empty
+#line 442 "d2_parser.yy"
+ {
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(l);
+}
+#line 1233 "d2_parser.cc"
+ break;
+
+ case 99: // sub_ddns_domains: "[" $@24 ddns_domain_list "]"
+#line 445 "d2_parser.yy"
+ {
+ // parsing completed
+}
+#line 1241 "d2_parser.cc"
+ break;
+
+ case 104: // not_empty_ddns_domain_list: not_empty_ddns_domain_list ","
+#line 455 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1249 "d2_parser.cc"
+ break;
+
+ case 105: // $@25: %empty
+#line 460 "d2_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 1259 "d2_parser.cc"
+ break;
+
+ case 106: // ddns_domain: "{" $@25 ddns_domain_params "}"
+#line 464 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+}
+#line 1267 "d2_parser.cc"
+ break;
+
+ case 107: // $@26: %empty
+#line 468 "d2_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 1276 "d2_parser.cc"
+ break;
+
+ case 108: // sub_ddns_domain: "{" $@26 ddns_domain_params "}"
+#line 471 "d2_parser.yy"
+ {
+ // parsing completed
+}
+#line 1284 "d2_parser.cc"
+ break;
+
+ case 111: // ddns_domain_params: ddns_domain_params ","
+#line 477 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1292 "d2_parser.cc"
+ break;
+
+ case 118: // $@27: %empty
+#line 491 "d2_parser.yy"
+ {
+ ctx.unique("name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1301 "d2_parser.cc"
+ break;
+
+ case 119: // ddns_domain_name: "name" $@27 ":" "constant string"
+#line 494 "d2_parser.yy"
+ {
+ if (yystack_[0].value.as < std::string > () == "") {
+ error(yystack_[1].location, "Ddns domain name cannot be blank");
+ }
+ ElementPtr elem(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ElementPtr name(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+}
+#line 1315 "d2_parser.cc"
+ break;
+
+ case 120: // $@28: %empty
+#line 504 "d2_parser.yy"
+ {
+ ctx.unique("key-name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1324 "d2_parser.cc"
+ break;
+
+ case 121: // ddns_key_name: "key-name" $@28 ":" "constant string"
+#line 507 "d2_parser.yy"
+ {
+ ElementPtr elem(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ElementPtr name(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("key-name", name);
+ ctx.leave();
+}
+#line 1335 "d2_parser.cc"
+ break;
+
+ case 122: // $@29: %empty
+#line 517 "d2_parser.yy"
+ {
+ ctx.unique("dns-servers", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("dns-servers", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.DNS_SERVERS);
+}
+#line 1347 "d2_parser.cc"
+ break;
+
+ case 123: // dns_servers: "dns-servers" $@29 ":" "[" dns_server_list "]"
+#line 523 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1356 "d2_parser.cc"
+ break;
+
+ case 124: // $@30: %empty
+#line 528 "d2_parser.yy"
+ {
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(l);
+}
+#line 1365 "d2_parser.cc"
+ break;
+
+ case 125: // sub_dns_servers: "[" $@30 dns_server_list "]"
+#line 531 "d2_parser.yy"
+ {
+ // parsing completed
+}
+#line 1373 "d2_parser.cc"
+ break;
+
+ case 128: // dns_server_list: dns_server_list ","
+#line 537 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1381 "d2_parser.cc"
+ break;
+
+ case 129: // $@31: %empty
+#line 542 "d2_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 1391 "d2_parser.cc"
+ break;
+
+ case 130: // dns_server: "{" $@31 dns_server_params "}"
+#line 546 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+}
+#line 1399 "d2_parser.cc"
+ break;
+
+ case 131: // $@32: %empty
+#line 550 "d2_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 1408 "d2_parser.cc"
+ break;
+
+ case 132: // sub_dns_server: "{" $@32 dns_server_params "}"
+#line 553 "d2_parser.yy"
+ {
+ // parsing completed
+}
+#line 1416 "d2_parser.cc"
+ break;
+
+ case 135: // dns_server_params: dns_server_params ","
+#line 559 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1424 "d2_parser.cc"
+ break;
+
+ case 143: // $@33: %empty
+#line 573 "d2_parser.yy"
+ {
+ ctx.unique("hostname", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1433 "d2_parser.cc"
+ break;
+
+ case 144: // dns_server_hostname: "hostname" $@33 ":" "constant string"
+#line 576 "d2_parser.yy"
+ {
+ if (yystack_[0].value.as < std::string > () != "") {
+ error(yystack_[1].location, "hostname is not yet supported");
+ }
+ ElementPtr elem(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ElementPtr name(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hostname", name);
+ ctx.leave();
+}
+#line 1447 "d2_parser.cc"
+ break;
+
+ case 145: // $@34: %empty
+#line 586 "d2_parser.yy"
+ {
+ ctx.unique("ip-address", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1456 "d2_parser.cc"
+ break;
+
+ case 146: // dns_server_ip_address: "ip-address" $@34 ":" "constant string"
+#line 589 "d2_parser.yy"
+ {
+ ElementPtr s(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("ip-address", s);
+ ctx.leave();
+}
+#line 1466 "d2_parser.cc"
+ break;
+
+ case 147: // dns_server_port: "port" ":" "integer"
+#line 595 "d2_parser.yy"
+ {
+ ctx.unique("port", ctx.loc2pos(yystack_[2].location));
+ if (yystack_[0].value.as < int64_t > () <= 0 || yystack_[0].value.as < int64_t > () >= 65536 ) {
+ error(yystack_[0].location, "port must be greater than zero but less than 65536");
+ }
+ ElementPtr i(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("port", i);
+}
+#line 1479 "d2_parser.cc"
+ break;
+
+ case 148: // $@35: %empty
+#line 610 "d2_parser.yy"
+ {
+ ctx.unique("tsig-keys", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("tsig-keys", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.TSIG_KEYS);
+}
+#line 1491 "d2_parser.cc"
+ break;
+
+ case 149: // tsig_keys: "tsig-keys" $@35 ":" "[" tsig_keys_list "]"
+#line 616 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1500 "d2_parser.cc"
+ break;
+
+ case 150: // $@36: %empty
+#line 621 "d2_parser.yy"
+ {
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(l);
+}
+#line 1509 "d2_parser.cc"
+ break;
+
+ case 151: // sub_tsig_keys: "[" $@36 tsig_keys_list "]"
+#line 624 "d2_parser.yy"
+ {
+ // parsing completed
+}
+#line 1517 "d2_parser.cc"
+ break;
+
+ case 156: // not_empty_tsig_keys_list: not_empty_tsig_keys_list ","
+#line 634 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1525 "d2_parser.cc"
+ break;
+
+ case 157: // $@37: %empty
+#line 639 "d2_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 1535 "d2_parser.cc"
+ break;
+
+ case 158: // tsig_key: "{" $@37 tsig_key_params "}"
+#line 643 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+}
+#line 1543 "d2_parser.cc"
+ break;
+
+ case 159: // $@38: %empty
+#line 647 "d2_parser.yy"
+ {
+ // Parse tsig key list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 1553 "d2_parser.cc"
+ break;
+
+ case 160: // sub_tsig_key: "{" $@38 tsig_key_params "}"
+#line 651 "d2_parser.yy"
+ {
+ // parsing completed
+}
+#line 1561 "d2_parser.cc"
+ break;
+
+ case 163: // tsig_key_params: tsig_key_params ","
+#line 658 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1569 "d2_parser.cc"
+ break;
+
+ case 171: // $@39: %empty
+#line 672 "d2_parser.yy"
+ {
+ ctx.unique("name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1578 "d2_parser.cc"
+ break;
+
+ case 172: // tsig_key_name: "name" $@39 ":" "constant string"
+#line 675 "d2_parser.yy"
+ {
+ if (yystack_[0].value.as < std::string > () == "") {
+ error(yystack_[1].location, "TSIG key name cannot be blank");
+ }
+ ElementPtr elem(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ElementPtr name(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+}
+#line 1592 "d2_parser.cc"
+ break;
+
+ case 173: // $@40: %empty
+#line 685 "d2_parser.yy"
+ {
+ ctx.unique("algorithm", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1601 "d2_parser.cc"
+ break;
+
+ case 174: // tsig_key_algorithm: "algorithm" $@40 ":" "constant string"
+#line 688 "d2_parser.yy"
+ {
+ if (yystack_[0].value.as < std::string > () == "") {
+ error(yystack_[1].location, "TSIG key algorithm cannot be blank");
+ }
+ ElementPtr elem(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("algorithm", elem);
+ ctx.leave();
+}
+#line 1614 "d2_parser.cc"
+ break;
+
+ case 175: // tsig_key_digest_bits: "digest-bits" ":" "integer"
+#line 697 "d2_parser.yy"
+ {
+ ctx.unique("digest-bits", ctx.loc2pos(yystack_[2].location));
+ if (yystack_[0].value.as < int64_t > () < 0 || (yystack_[0].value.as < int64_t > () > 0 && (yystack_[0].value.as < int64_t > () % 8 != 0))) {
+ error(yystack_[0].location, "TSIG key digest-bits must either be zero or a positive, multiple of eight");
+ }
+ ElementPtr elem(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("digest-bits", elem);
+}
+#line 1627 "d2_parser.cc"
+ break;
+
+ case 176: // $@41: %empty
+#line 706 "d2_parser.yy"
+ {
+ ctx.unique("secret", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1636 "d2_parser.cc"
+ break;
+
+ case 177: // tsig_key_secret: "secret" $@41 ":" "constant string"
+#line 709 "d2_parser.yy"
+ {
+ if (yystack_[0].value.as < std::string > () == "") {
+ error(yystack_[1].location, "TSIG key secret cannot be blank");
+ }
+ ElementPtr elem(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("secret", elem);
+ ctx.leave();
+}
+#line 1649 "d2_parser.cc"
+ break;
+
+ case 178: // $@42: %empty
+#line 723 "d2_parser.yy"
+ {
+ ctx.unique("control-socket", ctx.loc2pos(yystack_[0].location));
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("control-socket", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.CONTROL_SOCKET);
+}
+#line 1661 "d2_parser.cc"
+ break;
+
+ case 179: // control_socket: "control-socket" $@42 ":" "{" control_socket_params "}"
+#line 729 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1670 "d2_parser.cc"
+ break;
+
+ case 182: // control_socket_params: control_socket_params ","
+#line 736 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1678 "d2_parser.cc"
+ break;
+
+ case 188: // $@43: %empty
+#line 748 "d2_parser.yy"
+ {
+ ctx.unique("socket-type", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1687 "d2_parser.cc"
+ break;
+
+ case 189: // control_socket_type: "socket-type" $@43 ":" "constant string"
+#line 751 "d2_parser.yy"
+ {
+ ElementPtr stype(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("socket-type", stype);
+ ctx.leave();
+}
+#line 1697 "d2_parser.cc"
+ break;
+
+ case 190: // $@44: %empty
+#line 757 "d2_parser.yy"
+ {
+ ctx.unique("socket-name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1706 "d2_parser.cc"
+ break;
+
+ case 191: // control_socket_name: "socket-name" $@44 ":" "constant string"
+#line 760 "d2_parser.yy"
+ {
+ ElementPtr name(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("socket-name", name);
+ ctx.leave();
+}
+#line 1716 "d2_parser.cc"
+ break;
+
+ case 192: // $@45: %empty
+#line 768 "d2_parser.yy"
+ {
+ ctx.unique("hooks-libraries", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("hooks-libraries", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.HOOKS_LIBRARIES);
+}
+#line 1728 "d2_parser.cc"
+ break;
+
+ case 193: // hooks_libraries: "hooks-libraries" $@45 ":" "[" hooks_libraries_list "]"
+#line 774 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1737 "d2_parser.cc"
+ break;
+
+ case 198: // not_empty_hooks_libraries_list: not_empty_hooks_libraries_list ","
+#line 785 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1745 "d2_parser.cc"
+ break;
+
+ case 199: // $@46: %empty
+#line 790 "d2_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 1755 "d2_parser.cc"
+ break;
+
+ case 200: // hooks_library: "{" $@46 hooks_params "}"
+#line 794 "d2_parser.yy"
+ {
+ // The library hooks parameter is required
+ ctx.require("library", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ ctx.stack_.pop_back();
+}
+#line 1765 "d2_parser.cc"
+ break;
+
+ case 201: // $@47: %empty
+#line 800 "d2_parser.yy"
+ {
+ // Parse the hooks-libraries list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.push_back(m);
+}
+#line 1775 "d2_parser.cc"
+ break;
+
+ case 202: // sub_hooks_library: "{" $@47 hooks_params "}"
+#line 804 "d2_parser.yy"
+ {
+ // The library hooks parameter is required
+ ctx.require("library", ctx.loc2pos(yystack_[3].location), ctx.loc2pos(yystack_[0].location));
+ // parsing completed
+}
+#line 1785 "d2_parser.cc"
+ break;
+
+ case 205: // hooks_params: hooks_params ","
+#line 812 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1793 "d2_parser.cc"
+ break;
+
+ case 209: // $@48: %empty
+#line 822 "d2_parser.yy"
+ {
+ ctx.unique("library", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1802 "d2_parser.cc"
+ break;
+
+ case 210: // library: "library" $@48 ":" "constant string"
+#line 825 "d2_parser.yy"
+ {
+ ElementPtr lib(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("library", lib);
+ ctx.leave();
+}
+#line 1812 "d2_parser.cc"
+ break;
+
+ case 211: // $@49: %empty
+#line 831 "d2_parser.yy"
+ {
+ ctx.unique("parameters", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1821 "d2_parser.cc"
+ break;
+
+ case 212: // parameters: "parameters" $@49 ":" map_value
+#line 834 "d2_parser.yy"
+ {
+ ctx.stack_.back()->set("parameters", yystack_[0].value.as < ElementPtr > ());
+ ctx.leave();
+}
+#line 1830 "d2_parser.cc"
+ break;
+
+ case 213: // $@50: %empty
+#line 841 "d2_parser.yy"
+ {
+ ctx.unique("loggers", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("loggers", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.LOGGERS);
+}
+#line 1842 "d2_parser.cc"
+ break;
+
+ case 214: // loggers: "loggers" $@50 ":" "[" loggers_entries "]"
+#line 847 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1851 "d2_parser.cc"
+ break;
+
+ case 217: // loggers_entries: loggers_entries ","
+#line 856 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1859 "d2_parser.cc"
+ break;
+
+ case 218: // $@51: %empty
+#line 862 "d2_parser.yy"
+ {
+ ElementPtr l(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(l);
+ ctx.stack_.push_back(l);
+}
+#line 1869 "d2_parser.cc"
+ break;
+
+ case 219: // logger_entry: "{" $@51 logger_params "}"
+#line 866 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+}
+#line 1877 "d2_parser.cc"
+ break;
+
+ case 222: // logger_params: logger_params ","
+#line 872 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1885 "d2_parser.cc"
+ break;
+
+ case 230: // $@52: %empty
+#line 886 "d2_parser.yy"
+ {
+ ctx.unique("name", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1894 "d2_parser.cc"
+ break;
+
+ case 231: // name: "name" $@52 ":" "constant string"
+#line 889 "d2_parser.yy"
+ {
+ ElementPtr name(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+}
+#line 1904 "d2_parser.cc"
+ break;
+
+ case 232: // debuglevel: "debuglevel" ":" "integer"
+#line 895 "d2_parser.yy"
+ {
+ ctx.unique("debuglevel", ctx.loc2pos(yystack_[2].location));
+ ElementPtr dl(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("debuglevel", dl);
+}
+#line 1914 "d2_parser.cc"
+ break;
+
+ case 233: // $@53: %empty
+#line 901 "d2_parser.yy"
+ {
+ ctx.unique("severity", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1923 "d2_parser.cc"
+ break;
+
+ case 234: // severity: "severity" $@53 ":" "constant string"
+#line 904 "d2_parser.yy"
+ {
+ ElementPtr sev(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("severity", sev);
+ ctx.leave();
+}
+#line 1933 "d2_parser.cc"
+ break;
+
+ case 235: // $@54: %empty
+#line 910 "d2_parser.yy"
+ {
+ ctx.unique("output_options", ctx.loc2pos(yystack_[0].location));
+ ElementPtr l(new ListElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("output_options", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.OUTPUT_OPTIONS);
+}
+#line 1945 "d2_parser.cc"
+ break;
+
+ case 236: // output_options_list: "output_options" $@54 ":" "[" output_options_list_content "]"
+#line 916 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+#line 1954 "d2_parser.cc"
+ break;
+
+ case 239: // output_options_list_content: output_options_list_content ","
+#line 923 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1962 "d2_parser.cc"
+ break;
+
+ case 240: // $@55: %empty
+#line 928 "d2_parser.yy"
+ {
+ ElementPtr m(new MapElement(ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+}
+#line 1972 "d2_parser.cc"
+ break;
+
+ case 241: // output_entry: "{" $@55 output_params_list "}"
+#line 932 "d2_parser.yy"
+ {
+ ctx.stack_.pop_back();
+}
+#line 1980 "d2_parser.cc"
+ break;
+
+ case 244: // output_params_list: output_params_list ","
+#line 938 "d2_parser.yy"
+ {
+ ctx.warnAboutExtraCommas(yystack_[0].location);
+ }
+#line 1988 "d2_parser.cc"
+ break;
+
+ case 250: // $@56: %empty
+#line 950 "d2_parser.yy"
+ {
+ ctx.unique("output", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 1997 "d2_parser.cc"
+ break;
+
+ case 251: // output: "output" $@56 ":" "constant string"
+#line 953 "d2_parser.yy"
+ {
+ ElementPtr sev(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("output", sev);
+ ctx.leave();
+}
+#line 2007 "d2_parser.cc"
+ break;
+
+ case 252: // flush: "flush" ":" "boolean"
+#line 959 "d2_parser.yy"
+ {
+ ctx.unique("flush", ctx.loc2pos(yystack_[2].location));
+ ElementPtr flush(new BoolElement(yystack_[0].value.as < bool > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("flush", flush);
+}
+#line 2017 "d2_parser.cc"
+ break;
+
+ case 253: // maxsize: "maxsize" ":" "integer"
+#line 965 "d2_parser.yy"
+ {
+ ctx.unique("maxsize", ctx.loc2pos(yystack_[2].location));
+ ElementPtr maxsize(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("maxsize", maxsize);
+}
+#line 2027 "d2_parser.cc"
+ break;
+
+ case 254: // maxver: "maxver" ":" "integer"
+#line 971 "d2_parser.yy"
+ {
+ ctx.unique("maxver", ctx.loc2pos(yystack_[2].location));
+ ElementPtr maxver(new IntElement(yystack_[0].value.as < int64_t > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("maxver", maxver);
+}
+#line 2037 "d2_parser.cc"
+ break;
+
+ case 255: // $@57: %empty
+#line 977 "d2_parser.yy"
+ {
+ ctx.unique("pattern", ctx.loc2pos(yystack_[0].location));
+ ctx.enter(ctx.NO_KEYWORD);
+}
+#line 2046 "d2_parser.cc"
+ break;
+
+ case 256: // pattern: "pattern" $@57 ":" "constant string"
+#line 980 "d2_parser.yy"
+ {
+ ElementPtr sev(new StringElement(yystack_[0].value.as < std::string > (), ctx.loc2pos(yystack_[0].location)));
+ ctx.stack_.back()->set("pattern", sev);
+ ctx.leave();
+}
+#line 2056 "d2_parser.cc"
+ break;
+
+
+#line 2060 "d2_parser.cc"
+
+ default:
+ break;
+ }
+ }
+#if YY_EXCEPTIONS
+ catch (const syntax_error& yyexc)
+ {
+ YYCDEBUG << "Caught exception: " << yyexc.what() << '\n';
+ error (yyexc);
+ YYERROR;
+ }
+#endif // YY_EXCEPTIONS
+ YY_SYMBOL_PRINT ("-> $$ =", yylhs);
+ yypop_ (yylen);
+ yylen = 0;
+
+ // Shift the result of the reduction.
+ yypush_ (YY_NULLPTR, YY_MOVE (yylhs));
+ }
+ goto yynewstate;
+
+
+ /*--------------------------------------.
+ | yyerrlab -- here on detecting error. |
+ `--------------------------------------*/
+ yyerrlab:
+ // If not already recovering from an error, report this error.
+ if (!yyerrstatus_)
+ {
+ ++yynerrs_;
+ context yyctx (*this, yyla);
+ std::string msg = yysyntax_error_ (yyctx);
+ error (yyla.location, YY_MOVE (msg));
+ }
+
+
+ yyerror_range[1].location = yyla.location;
+ if (yyerrstatus_ == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ // Return failure if at end of input.
+ if (yyla.kind () == symbol_kind::S_YYEOF)
+ YYABORT;
+ else if (!yyla.empty ())
+ {
+ yy_destroy_ ("Error: discarding", yyla);
+ yyla.clear ();
+ }
+ }
+
+ // Else will try to reuse lookahead token after shifting the error token.
+ goto yyerrlab1;
+
+
+ /*---------------------------------------------------.
+ | yyerrorlab -- error raised explicitly by YYERROR. |
+ `---------------------------------------------------*/
+ yyerrorlab:
+ /* Pacify compilers when the user code never invokes YYERROR and
+ the label yyerrorlab therefore never appears in user code. */
+ if (false)
+ YYERROR;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ yypop_ (yylen);
+ yylen = 0;
+ YY_STACK_PRINT ();
+ goto yyerrlab1;
+
+
+ /*-------------------------------------------------------------.
+ | yyerrlab1 -- common code for both syntax error and YYERROR. |
+ `-------------------------------------------------------------*/
+ yyerrlab1:
+ yyerrstatus_ = 3; // Each real token shifted decrements this.
+ // Pop stack until we find a state that shifts the error token.
+ for (;;)
+ {
+ yyn = yypact_[+yystack_[0].state];
+ if (!yy_pact_value_is_default_ (yyn))
+ {
+ yyn += symbol_kind::S_YYerror;
+ if (0 <= yyn && yyn <= yylast_
+ && yycheck_[yyn] == symbol_kind::S_YYerror)
+ {
+ yyn = yytable_[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ // Pop the current state because it cannot handle the error token.
+ if (yystack_.size () == 1)
+ YYABORT;
+
+ yyerror_range[1].location = yystack_[0].location;
+ yy_destroy_ ("Error: popping", yystack_[0]);
+ yypop_ ();
+ YY_STACK_PRINT ();
+ }
+ {
+ stack_symbol_type error_token;
+
+ yyerror_range[2].location = yyla.location;
+ YYLLOC_DEFAULT (error_token.location, yyerror_range, 2);
+
+ // Shift the error token.
+ error_token.state = state_type (yyn);
+ yypush_ ("Shifting", YY_MOVE (error_token));
+ }
+ goto yynewstate;
+
+
+ /*-------------------------------------.
+ | yyacceptlab -- YYACCEPT comes here. |
+ `-------------------------------------*/
+ yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+
+ /*-----------------------------------.
+ | yyabortlab -- YYABORT comes here. |
+ `-----------------------------------*/
+ yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+
+ /*-----------------------------------------------------.
+ | yyreturn -- parsing is finished, return the result. |
+ `-----------------------------------------------------*/
+ yyreturn:
+ if (!yyla.empty ())
+ yy_destroy_ ("Cleanup: discarding lookahead", yyla);
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ yypop_ (yylen);
+ YY_STACK_PRINT ();
+ while (1 < yystack_.size ())
+ {
+ yy_destroy_ ("Cleanup: popping", yystack_[0]);
+ yypop_ ();
+ }
+
+ return yyresult;
+ }
+#if YY_EXCEPTIONS
+ catch (...)
+ {
+ YYCDEBUG << "Exception caught: cleaning lookahead and stack\n";
+ // Do not try to display the values of the reclaimed symbols,
+ // as their printers might throw an exception.
+ if (!yyla.empty ())
+ yy_destroy_ (YY_NULLPTR, yyla);
+
+ while (1 < yystack_.size ())
+ {
+ yy_destroy_ (YY_NULLPTR, yystack_[0]);
+ yypop_ ();
+ }
+ throw;
+ }
+#endif // YY_EXCEPTIONS
+ }
+
+ void
+ D2Parser::error (const syntax_error& yyexc)
+ {
+ error (yyexc.location, yyexc.what ());
+ }
+
+ /* Return YYSTR after stripping away unnecessary quotes and
+ backslashes, so that it's suitable for yyerror. The heuristic is
+ that double-quoting is unnecessary unless the string contains an
+ apostrophe, a comma, or backslash (other than backslash-backslash).
+ YYSTR is taken from yytname. */
+ std::string
+ D2Parser::yytnamerr_ (const char *yystr)
+ {
+ if (*yystr == '"')
+ {
+ std::string yyr;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ else
+ goto append;
+
+ append:
+ default:
+ yyr += *yyp;
+ break;
+
+ case '"':
+ return yyr;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ return yystr;
+ }
+
+ std::string
+ D2Parser::symbol_name (symbol_kind_type yysymbol)
+ {
+ return yytnamerr_ (yytname_[yysymbol]);
+ }
+
+
+
+ // D2Parser::context.
+ D2Parser::context::context (const D2Parser& yyparser, const symbol_type& yyla)
+ : yyparser_ (yyparser)
+ , yyla_ (yyla)
+ {}
+
+ int
+ D2Parser::context::expected_tokens (symbol_kind_type yyarg[], int yyargn) const
+ {
+ // Actual number of expected tokens
+ int yycount = 0;
+
+ const int yyn = yypact_[+yyparser_.yystack_[0].state];
+ if (!yy_pact_value_is_default_ (yyn))
+ {
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. In other words, skip the first -YYN actions for
+ this state because they are default actions. */
+ const int yyxbegin = yyn < 0 ? -yyn : 0;
+ // Stay within bounds of both yycheck and yytname.
+ const int yychecklim = yylast_ - yyn + 1;
+ const int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ for (int yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck_[yyx + yyn] == yyx && yyx != symbol_kind::S_YYerror
+ && !yy_table_value_is_error_ (yytable_[yyx + yyn]))
+ {
+ if (!yyarg)
+ ++yycount;
+ else if (yycount == yyargn)
+ return 0;
+ else
+ yyarg[yycount++] = YY_CAST (symbol_kind_type, yyx);
+ }
+ }
+
+ if (yyarg && yycount == 0 && 0 < yyargn)
+ yyarg[0] = symbol_kind::S_YYEMPTY;
+ return yycount;
+ }
+
+
+
+
+
+
+ int
+ D2Parser::yy_syntax_error_arguments_ (const context& yyctx,
+ symbol_kind_type yyarg[], int yyargn) const
+ {
+ /* There are many possibilities here to consider:
+ - If this state is a consistent state with a default action, then
+ the only way this function was invoked is if the default action
+ is an error action. In that case, don't check for expected
+ tokens because there are none.
+ - The only way there can be no lookahead present (in yyla) is
+ if this state is a consistent state with a default action.
+ Thus, detecting the absence of a lookahead is sufficient to
+ determine that there is no unexpected or expected token to
+ report. In that case, just report a simple "syntax error".
+ - Don't assume there isn't a lookahead just because this state is
+ a consistent state with a default action. There might have
+ been a previous inconsistent state, consistent state with a
+ non-default action, or user semantic action that manipulated
+ yyla. (However, yyla is currently not documented for users.)
+ - Of course, the expected token list depends on states to have
+ correct lookahead information, and it depends on the parser not
+ to perform extra reductions after fetching a lookahead from the
+ scanner and before detecting a syntax error. Thus, state merging
+ (from LALR or IELR) and default reductions corrupt the expected
+ token list. However, the list is correct for canonical LR with
+ one exception: it will still contain any token that will not be
+ accepted due to an error action in a later state.
+ */
+
+ if (!yyctx.lookahead ().empty ())
+ {
+ if (yyarg)
+ yyarg[0] = yyctx.token ();
+ int yyn = yyctx.expected_tokens (yyarg ? yyarg + 1 : yyarg, yyargn - 1);
+ return yyn + 1;
+ }
+ return 0;
+ }
+
+ // Generate an error message.
+ std::string
+ D2Parser::yysyntax_error_ (const context& yyctx) const
+ {
+ // Its maximum.
+ enum { YYARGS_MAX = 5 };
+ // Arguments of yyformat.
+ symbol_kind_type yyarg[YYARGS_MAX];
+ int yycount = yy_syntax_error_arguments_ (yyctx, yyarg, YYARGS_MAX);
+
+ char const* yyformat = YY_NULLPTR;
+ switch (yycount)
+ {
+#define YYCASE_(N, S) \
+ case N: \
+ yyformat = S; \
+ break
+ default: // Avoid compiler warnings.
+ YYCASE_ (0, YY_("syntax error"));
+ YYCASE_ (1, YY_("syntax error, unexpected %s"));
+ YYCASE_ (2, YY_("syntax error, unexpected %s, expecting %s"));
+ YYCASE_ (3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+ YYCASE_ (4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+ YYCASE_ (5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+#undef YYCASE_
+ }
+
+ std::string yyres;
+ // Argument number.
+ std::ptrdiff_t yyi = 0;
+ for (char const* yyp = yyformat; *yyp; ++yyp)
+ if (yyp[0] == '%' && yyp[1] == 's' && yyi < yycount)
+ {
+ yyres += symbol_name (yyarg[yyi++]);
+ ++yyp;
+ }
+ else
+ yyres += *yyp;
+ return yyres;
+ }
+
+
+ const short D2Parser::yypact_ninf_ = -212;
+
+ const signed char D2Parser::yytable_ninf_ = -1;
+
+ const short
+ D2Parser::yypact_[] =
+ {
+ 49, -212, -212, -212, -212, -212, -212, -212, -212, -212,
+ -212, 10, 8, 24, 30, 42, 48, 64, 128, 74,
+ 136, 127, -212, -212, -212, -212, -212, -212, -212, -212,
+ -212, -212, -212, -212, -212, -212, -212, -212, -212, -212,
+ -212, -212, -212, -212, -212, -212, -212, -212, -212, -212,
+ -212, -212, 8, -22, 33, 7, 31, 146, 38, 156,
+ 28, 160, 37, -212, 137, 163, 166, 164, 168, -212,
+ 22, -212, -212, 169, 170, -212, -212, -212, -212, -212,
+ -212, -212, -212, -212, -212, 171, -212, 76, -212, -212,
+ -212, -212, -212, -212, -212, -212, -212, -212, -212, -212,
+ -212, -212, -212, 172, -212, -212, -212, -212, -212, 79,
+ -212, -212, -212, -212, -212, -212, 173, 174, -212, -212,
+ -212, -212, -212, -212, -212, 103, -212, -212, -212, -212,
+ -212, 175, 177, -212, -212, 178, -212, -212, -212, -212,
+ -212, 104, -212, -212, -212, -212, -212, 77, -212, -212,
+ -212, -212, 105, -212, -212, -212, -212, 8, 8, -212,
+ 121, 179, -212, -212, 180, 130, 131, 181, 182, 183,
+ 186, 187, 188, 189, 190, 191, 192, -212, 7, -212,
+ 193, 140, 195, 196, 31, -212, 31, -212, 146, 197,
+ 198, 201, 38, -212, 38, -212, 156, 205, 154, 206,
+ 28, -212, 28, 160, -212, 207, 209, -13, -212, -212,
+ -212, 210, 208, 162, -212, -212, 153, 199, 213, 165,
+ 214, 216, 211, 217, 220, 221, -212, 176, -212, 184,
+ 185, -212, 106, -212, 203, 222, 204, -212, 107, -212,
+ 215, -212, 218, -212, 115, -212, 219, 213, -212, 8,
+ 7, -212, -212, -212, -212, -212, -212, -212, -212, -15,
+ -15, 146, 13, 223, 224, -212, -212, -212, -212, -212,
+ 160, -212, -212, -212, -212, -212, -212, -212, -212, 116,
+ -212, -212, 117, -212, -212, -212, 118, 226, -212, -212,
+ -212, -212, -212, 119, -212, -212, -212, -212, 228, 225,
+ -212, -212, 129, -212, 158, -212, 231, -15, -212, -212,
+ -212, 232, 233, 13, -212, 37, -212, 223, 36, 224,
+ -212, -212, 234, -212, 227, 229, -212, 149, -212, -212,
+ -212, 236, -212, -212, -212, -212, 151, -212, -212, -212,
+ -212, -212, -212, 156, -212, -212, -212, 239, 240, 194,
+ 241, 36, -212, 242, 230, 244, -212, 235, -212, -212,
+ -212, 243, -212, -212, 159, -212, 46, 243, -212, -212,
+ 249, 250, 251, -212, 152, -212, -212, -212, -212, -212,
+ -212, -212, 252, 237, 212, 245, 260, 46, -212, 247,
+ -212, -212, -212, 248, -212, -212, -212
+ };
+
+ const short
+ D2Parser::yydefact_[] =
+ {
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18,
+ 20, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 38, 30, 26, 25, 22, 23, 24,
+ 29, 3, 27, 28, 46, 5, 52, 7, 159, 9,
+ 150, 11, 107, 13, 98, 15, 131, 17, 124, 19,
+ 201, 21, 40, 33, 0, 0, 0, 152, 0, 100,
+ 0, 0, 0, 42, 0, 41, 0, 0, 34, 48,
+ 0, 50, 71, 0, 0, 75, 79, 81, 83, 85,
+ 87, 148, 178, 192, 213, 0, 70, 0, 54, 57,
+ 58, 59, 60, 61, 68, 69, 62, 63, 64, 65,
+ 66, 67, 173, 0, 176, 171, 170, 168, 169, 0,
+ 161, 164, 165, 166, 167, 157, 0, 153, 154, 120,
+ 122, 118, 117, 115, 116, 0, 109, 112, 113, 114,
+ 105, 0, 101, 102, 145, 0, 143, 142, 140, 141,
+ 139, 0, 133, 136, 137, 138, 129, 0, 126, 209,
+ 211, 206, 0, 203, 207, 208, 39, 44, 0, 31,
+ 37, 0, 51, 47, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 45, 56, 53,
+ 0, 0, 0, 0, 163, 160, 0, 151, 156, 0,
+ 0, 0, 111, 108, 0, 99, 104, 0, 0, 0,
+ 135, 132, 0, 128, 125, 0, 0, 205, 202, 43,
+ 35, 0, 0, 0, 73, 74, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 55, 0, 175, 0,
+ 0, 162, 0, 155, 0, 0, 0, 110, 0, 103,
+ 0, 147, 0, 134, 0, 127, 0, 0, 204, 0,
+ 0, 72, 77, 78, 76, 80, 32, 82, 84, 89,
+ 89, 152, 0, 194, 0, 174, 177, 172, 158, 121,
+ 0, 119, 106, 146, 144, 130, 210, 212, 36, 0,
+ 96, 95, 0, 90, 91, 94, 0, 0, 188, 190,
+ 187, 185, 186, 0, 180, 183, 184, 199, 0, 195,
+ 196, 218, 0, 215, 0, 49, 0, 93, 86, 88,
+ 149, 0, 0, 182, 179, 0, 193, 198, 0, 217,
+ 214, 123, 0, 92, 0, 0, 181, 0, 197, 230,
+ 235, 0, 233, 229, 227, 228, 0, 220, 223, 225,
+ 226, 224, 216, 100, 189, 191, 200, 0, 0, 0,
+ 0, 222, 219, 0, 0, 0, 232, 0, 221, 97,
+ 231, 0, 234, 240, 0, 237, 0, 239, 236, 250,
+ 0, 0, 0, 255, 0, 242, 245, 246, 247, 248,
+ 249, 238, 0, 0, 0, 0, 0, 244, 241, 0,
+ 252, 253, 254, 0, 243, 251, 256
+ };
+
+ const short
+ D2Parser::yypgoto_[] =
+ {
+ -212, -212, -212, -212, -212, -212, -212, -212, -212, -212,
+ -212, -212, -41, -212, -211, -212, -18, -212, -212, -212,
+ -212, -212, -212, -56, -212, -212, -212, -212, -212, -212,
+ -212, -12, 68, -212, -212, -212, -212, -212, -212, -212,
+ -212, -212, -55, -212, -44, -212, -212, -212, -212, -212,
+ 5, -212, -60, -212, -212, -212, -212, -77, -212, 71,
+ -212, -212, -212, 83, 81, -212, -212, -51, -212, -212,
+ -212, -212, -212, -2, 75, -212, -212, -212, 69, 80,
+ -212, -212, -212, -212, -212, -212, -212, -212, -212, 18,
+ -212, 93, -212, -212, -212, 96, 99, -212, -212, -212,
+ -212, -212, -212, -212, -212, -212, -212, -28, -212, -212,
+ -212, -212, -212, -212, -212, -212, -29, -212, -212, -212,
+ -26, 84, -212, -212, -212, -212, -212, -212, -212, -25,
+ -212, -212, -61, -212, -212, -212, -212, -212, -212, -212,
+ -212, -74, -212, -212, -89, -212, -212, -212, -212, -212,
+ -212, -212
+ };
+
+ const short
+ D2Parser::yydefgoto_[] =
+ {
+ 0, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 30, 31, 32, 53, 257, 67, 68, 33,
+ 52, 64, 65, 86, 35, 54, 70, 161, 71, 37,
+ 55, 87, 88, 89, 164, 90, 91, 92, 167, 254,
+ 93, 168, 94, 169, 95, 170, 96, 171, 97, 172,
+ 282, 283, 284, 285, 306, 45, 59, 131, 132, 133,
+ 194, 43, 58, 125, 126, 127, 191, 128, 189, 129,
+ 190, 49, 61, 147, 148, 202, 47, 60, 141, 142,
+ 143, 199, 144, 197, 145, 98, 173, 41, 57, 116,
+ 117, 118, 186, 39, 56, 109, 110, 111, 183, 112,
+ 180, 113, 114, 182, 99, 174, 293, 294, 295, 311,
+ 296, 312, 100, 175, 298, 299, 300, 315, 51, 62,
+ 152, 153, 154, 205, 155, 206, 101, 176, 302, 303,
+ 318, 336, 337, 338, 347, 339, 340, 350, 341, 348,
+ 364, 365, 366, 374, 375, 376, 382, 377, 378, 379,
+ 380, 386
+ };
+
+ const short
+ D2Parser::yytable_[] =
+ {
+ 106, 107, 122, 123, 137, 138, 151, 256, 280, 140,
+ 22, 63, 108, 23, 124, 24, 139, 25, 72, 73,
+ 74, 75, 149, 150, 76, 162, 77, 78, 79, 80,
+ 163, 34, 77, 78, 81, 66, 256, 36, 82, 134,
+ 135, 83, 85, 69, 84, 288, 289, 77, 78, 38,
+ 77, 78, 119, 40, 136, 77, 78, 77, 78, 102,
+ 103, 104, 119, 120, 85, 26, 27, 28, 29, 105,
+ 85, 42, 149, 150, 329, 330, 121, 331, 332, 178,
+ 203, 46, 184, 204, 179, 85, 369, 185, 85, 370,
+ 371, 372, 373, 85, 85, 85, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 192, 200, 207, 184,
+ 192, 193, 201, 208, 268, 272, 209, 210, 200, 178,
+ 307, 307, 313, 275, 305, 308, 309, 314, 106, 107,
+ 106, 107, 319, 44, 50, 320, 122, 123, 122, 123,
+ 108, 48, 108, 156, 137, 138, 137, 138, 124, 140,
+ 124, 140, 207, 115, 351, 387, 139, 346, 139, 352,
+ 388, 203, 367, 130, 321, 368, 157, 146, 252, 253,
+ 158, 160, 159, 165, 166, 177, 181, 188, 211, 187,
+ 196, 195, 198, 212, 213, 216, 217, 218, 214, 215,
+ 219, 220, 221, 222, 223, 224, 225, 227, 228, 229,
+ 230, 234, 235, 281, 281, 236, 290, 291, 278, 240,
+ 242, 246, 241, 247, 249, 250, 261, 255, 292, 251,
+ 24, 259, 258, 260, 262, 263, 264, 270, 317, 277,
+ 297, 301, 310, 265, 316, 322, 324, 325, 279, 343,
+ 349, 266, 267, 354, 355, 357, 226, 323, 359, 361,
+ 363, 281, 356, 383, 384, 385, 389, 290, 291, 151,
+ 269, 271, 333, 334, 393, 286, 353, 239, 304, 292,
+ 391, 244, 273, 237, 335, 274, 276, 238, 245, 287,
+ 243, 233, 232, 231, 344, 326, 345, 360, 328, 327,
+ 358, 248, 362, 381, 342, 333, 334, 390, 394, 0,
+ 0, 0, 0, 392, 395, 396, 0, 335
+ };
+
+ const short
+ D2Parser::yycheck_[] =
+ {
+ 56, 56, 58, 58, 60, 60, 62, 218, 23, 60,
+ 0, 52, 56, 5, 58, 7, 60, 9, 11, 12,
+ 13, 14, 35, 36, 17, 3, 19, 20, 21, 22,
+ 8, 7, 19, 20, 27, 57, 247, 7, 31, 11,
+ 12, 34, 57, 10, 37, 32, 33, 19, 20, 7,
+ 19, 20, 24, 5, 26, 19, 20, 19, 20, 28,
+ 29, 30, 24, 25, 57, 57, 58, 59, 60, 38,
+ 57, 7, 35, 36, 38, 39, 38, 41, 42, 3,
+ 3, 7, 3, 6, 8, 57, 40, 8, 57, 43,
+ 44, 45, 46, 57, 57, 57, 47, 48, 49, 50,
+ 51, 52, 53, 54, 55, 56, 3, 3, 3, 3,
+ 3, 8, 8, 8, 8, 8, 157, 158, 3, 3,
+ 3, 3, 3, 8, 8, 8, 8, 8, 184, 184,
+ 186, 186, 3, 5, 7, 6, 192, 192, 194, 194,
+ 184, 5, 186, 6, 200, 200, 202, 202, 192, 200,
+ 194, 202, 3, 7, 3, 3, 200, 8, 202, 8,
+ 8, 3, 3, 7, 6, 6, 3, 7, 15, 16,
+ 4, 3, 8, 4, 4, 4, 4, 3, 57, 6,
+ 3, 6, 4, 4, 4, 4, 4, 4, 58, 58,
+ 4, 4, 4, 4, 4, 4, 4, 4, 58, 4,
+ 4, 4, 4, 259, 260, 4, 262, 262, 249, 4,
+ 4, 4, 58, 4, 4, 7, 5, 18, 262, 57,
+ 7, 7, 57, 7, 7, 5, 5, 5, 3, 247,
+ 7, 7, 6, 57, 6, 4, 4, 4, 250, 5,
+ 4, 57, 57, 4, 4, 4, 178, 307, 6, 5,
+ 7, 307, 58, 4, 4, 4, 4, 313, 313, 315,
+ 57, 57, 318, 318, 4, 260, 343, 196, 270, 313,
+ 58, 202, 57, 192, 318, 57, 57, 194, 203, 261,
+ 200, 188, 186, 184, 57, 313, 57, 57, 317, 315,
+ 351, 207, 57, 367, 319, 351, 351, 60, 387, -1,
+ -1, -1, -1, 58, 57, 57, -1, 351
+ };
+
+ const unsigned char
+ D2Parser::yystos_[] =
+ {
+ 0, 47, 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 62, 63, 64, 65, 66, 67, 68, 69, 70,
+ 71, 72, 0, 5, 7, 9, 57, 58, 59, 60,
+ 73, 74, 75, 80, 7, 85, 7, 90, 7, 154,
+ 5, 148, 7, 122, 5, 116, 7, 137, 5, 132,
+ 7, 179, 81, 76, 86, 91, 155, 149, 123, 117,
+ 138, 133, 180, 73, 82, 83, 57, 78, 79, 10,
+ 87, 89, 11, 12, 13, 14, 17, 19, 20, 21,
+ 22, 27, 31, 34, 37, 57, 84, 92, 93, 94,
+ 96, 97, 98, 101, 103, 105, 107, 109, 146, 165,
+ 173, 187, 28, 29, 30, 38, 84, 103, 105, 156,
+ 157, 158, 160, 162, 163, 7, 150, 151, 152, 24,
+ 25, 38, 84, 103, 105, 124, 125, 126, 128, 130,
+ 7, 118, 119, 120, 11, 12, 26, 84, 103, 105,
+ 128, 139, 140, 141, 143, 145, 7, 134, 135, 35,
+ 36, 84, 181, 182, 183, 185, 6, 3, 4, 8,
+ 3, 88, 3, 8, 95, 4, 4, 99, 102, 104,
+ 106, 108, 110, 147, 166, 174, 188, 4, 3, 8,
+ 161, 4, 164, 159, 3, 8, 153, 6, 3, 129,
+ 131, 127, 3, 8, 121, 6, 3, 144, 4, 142,
+ 3, 8, 136, 3, 6, 184, 186, 3, 8, 73,
+ 73, 57, 4, 4, 58, 58, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 93, 4, 58, 4,
+ 4, 157, 156, 152, 4, 4, 4, 125, 124, 120,
+ 4, 58, 4, 140, 139, 135, 4, 4, 182, 4,
+ 7, 57, 15, 16, 100, 18, 75, 77, 57, 7,
+ 7, 5, 7, 5, 5, 57, 57, 57, 8, 57,
+ 5, 57, 8, 57, 57, 8, 57, 77, 73, 92,
+ 23, 84, 111, 112, 113, 114, 111, 150, 32, 33,
+ 84, 103, 105, 167, 168, 169, 171, 7, 175, 176,
+ 177, 7, 189, 190, 134, 8, 115, 3, 8, 8,
+ 6, 170, 172, 3, 8, 178, 6, 3, 191, 3,
+ 6, 6, 4, 113, 4, 4, 168, 181, 177, 38,
+ 39, 41, 42, 84, 103, 105, 192, 193, 194, 196,
+ 197, 199, 190, 5, 57, 57, 8, 195, 200, 4,
+ 198, 3, 8, 118, 4, 4, 58, 4, 193, 6,
+ 57, 5, 57, 7, 201, 202, 203, 3, 6, 40,
+ 43, 44, 45, 46, 204, 205, 206, 208, 209, 210,
+ 211, 202, 207, 4, 4, 4, 212, 3, 8, 4,
+ 60, 58, 58, 4, 205, 57, 57
+ };
+
+ const unsigned char
+ D2Parser::yyr1_[] =
+ {
+ 0, 61, 63, 62, 64, 62, 65, 62, 66, 62,
+ 67, 62, 68, 62, 69, 62, 70, 62, 71, 62,
+ 72, 62, 73, 73, 73, 73, 73, 73, 73, 74,
+ 76, 75, 77, 78, 78, 79, 79, 79, 81, 80,
+ 82, 82, 83, 83, 83, 84, 86, 85, 88, 87,
+ 87, 89, 91, 90, 92, 92, 92, 93, 93, 93,
+ 93, 93, 93, 93, 93, 93, 93, 93, 93, 93,
+ 93, 95, 94, 96, 97, 99, 98, 100, 100, 102,
+ 101, 104, 103, 106, 105, 108, 107, 110, 109, 111,
+ 111, 112, 112, 112, 113, 113, 115, 114, 117, 116,
+ 118, 118, 119, 119, 119, 121, 120, 123, 122, 124,
+ 124, 124, 125, 125, 125, 125, 125, 125, 127, 126,
+ 129, 128, 131, 130, 133, 132, 134, 134, 134, 136,
+ 135, 138, 137, 139, 139, 139, 140, 140, 140, 140,
+ 140, 140, 140, 142, 141, 144, 143, 145, 147, 146,
+ 149, 148, 150, 150, 151, 151, 151, 153, 152, 155,
+ 154, 156, 156, 156, 157, 157, 157, 157, 157, 157,
+ 157, 159, 158, 161, 160, 162, 164, 163, 166, 165,
+ 167, 167, 167, 168, 168, 168, 168, 168, 170, 169,
+ 172, 171, 174, 173, 175, 175, 176, 176, 176, 178,
+ 177, 180, 179, 181, 181, 181, 181, 182, 182, 184,
+ 183, 186, 185, 188, 187, 189, 189, 189, 191, 190,
+ 192, 192, 192, 193, 193, 193, 193, 193, 193, 193,
+ 195, 194, 196, 198, 197, 200, 199, 201, 201, 201,
+ 203, 202, 204, 204, 204, 205, 205, 205, 205, 205,
+ 207, 206, 208, 209, 210, 212, 211
+ };
+
+ const signed char
+ D2Parser::yyr2_[] =
+ {
+ 0, 2, 0, 3, 0, 3, 0, 3, 0, 3,
+ 0, 3, 0, 3, 0, 3, 0, 3, 0, 3,
+ 0, 3, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 4, 1, 0, 1, 3, 5, 2, 0, 4,
+ 0, 1, 1, 3, 2, 2, 0, 4, 0, 6,
+ 1, 2, 0, 4, 1, 3, 2, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 0, 4, 3, 3, 0, 4, 1, 1, 0,
+ 4, 0, 4, 0, 4, 0, 6, 0, 6, 0,
+ 1, 1, 3, 2, 1, 1, 0, 6, 0, 4,
+ 0, 1, 1, 3, 2, 0, 4, 0, 4, 1,
+ 3, 2, 1, 1, 1, 1, 1, 1, 0, 4,
+ 0, 4, 0, 6, 0, 4, 1, 3, 2, 0,
+ 4, 0, 4, 1, 3, 2, 1, 1, 1, 1,
+ 1, 1, 1, 0, 4, 0, 4, 3, 0, 6,
+ 0, 4, 0, 1, 1, 3, 2, 0, 4, 0,
+ 4, 1, 3, 2, 1, 1, 1, 1, 1, 1,
+ 1, 0, 4, 0, 4, 3, 0, 4, 0, 6,
+ 1, 3, 2, 1, 1, 1, 1, 1, 0, 4,
+ 0, 4, 0, 6, 0, 1, 1, 3, 2, 0,
+ 4, 0, 4, 1, 3, 2, 1, 1, 1, 0,
+ 4, 0, 4, 0, 6, 1, 3, 2, 0, 4,
+ 1, 3, 2, 1, 1, 1, 1, 1, 1, 1,
+ 0, 4, 3, 0, 4, 0, 6, 1, 3, 2,
+ 0, 4, 1, 3, 2, 1, 1, 1, 1, 1,
+ 0, 4, 3, 3, 3, 0, 4
+ };
+
+
+#if D2_PARSER_DEBUG || 1
+ // YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ // First, the terminals, then, starting at \a YYNTOKENS, nonterminals.
+ const char*
+ const D2Parser::yytname_[] =
+ {
+ "\"end of file\"", "error", "\"invalid token\"", "\",\"", "\":\"",
+ "\"[\"", "\"]\"", "\"{\"", "\"}\"", "\"null\"", "\"DhcpDdns\"",
+ "\"ip-address\"", "\"port\"", "\"dns-server-timeout\"",
+ "\"ncr-protocol\"", "\"UDP\"", "\"TCP\"", "\"ncr-format\"", "\"JSON\"",
+ "\"user-context\"", "\"comment\"", "\"forward-ddns\"",
+ "\"reverse-ddns\"", "\"ddns-domains\"", "\"key-name\"",
+ "\"dns-servers\"", "\"hostname\"", "\"tsig-keys\"", "\"algorithm\"",
+ "\"digest-bits\"", "\"secret\"", "\"control-socket\"", "\"socket-type\"",
+ "\"socket-name\"", "\"hooks-libraries\"", "\"library\"",
+ "\"parameters\"", "\"loggers\"", "\"name\"", "\"output_options\"",
+ "\"output\"", "\"debuglevel\"", "\"severity\"", "\"flush\"",
+ "\"maxsize\"", "\"maxver\"", "\"pattern\"", "TOPLEVEL_JSON",
+ "TOPLEVEL_DHCPDDNS", "SUB_DHCPDDNS", "SUB_TSIG_KEY", "SUB_TSIG_KEYS",
+ "SUB_DDNS_DOMAIN", "SUB_DDNS_DOMAINS", "SUB_DNS_SERVER",
+ "SUB_DNS_SERVERS", "SUB_HOOKS_LIBRARY", "\"constant string\"",
+ "\"integer\"", "\"floating point\"", "\"boolean\"", "$accept", "start",
+ "$@1", "$@2", "$@3", "$@4", "$@5", "$@6", "$@7", "$@8", "$@9", "$@10",
+ "value", "sub_json", "map2", "$@11", "map_value", "map_content",
+ "not_empty_map", "list_generic", "$@12", "list_content",
+ "not_empty_list", "unknown_map_entry", "syntax_map", "$@13",
+ "global_object", "$@14", "global_object_comma", "sub_dhcpddns", "$@15",
+ "dhcpddns_params", "dhcpddns_param", "ip_address", "$@16", "port",
+ "dns_server_timeout", "ncr_protocol", "$@17", "ncr_protocol_value",
+ "ncr_format", "$@18", "user_context", "$@19", "comment", "$@20",
+ "forward_ddns", "$@21", "reverse_ddns", "$@22", "ddns_mgr_params",
+ "not_empty_ddns_mgr_params", "ddns_mgr_param", "ddns_domains", "$@23",
+ "sub_ddns_domains", "$@24", "ddns_domain_list",
+ "not_empty_ddns_domain_list", "ddns_domain", "$@25", "sub_ddns_domain",
+ "$@26", "ddns_domain_params", "ddns_domain_param", "ddns_domain_name",
+ "$@27", "ddns_key_name", "$@28", "dns_servers", "$@29",
+ "sub_dns_servers", "$@30", "dns_server_list", "dns_server", "$@31",
+ "sub_dns_server", "$@32", "dns_server_params", "dns_server_param",
+ "dns_server_hostname", "$@33", "dns_server_ip_address", "$@34",
+ "dns_server_port", "tsig_keys", "$@35", "sub_tsig_keys", "$@36",
+ "tsig_keys_list", "not_empty_tsig_keys_list", "tsig_key", "$@37",
+ "sub_tsig_key", "$@38", "tsig_key_params", "tsig_key_param",
+ "tsig_key_name", "$@39", "tsig_key_algorithm", "$@40",
+ "tsig_key_digest_bits", "tsig_key_secret", "$@41", "control_socket",
+ "$@42", "control_socket_params", "control_socket_param",
+ "control_socket_type", "$@43", "control_socket_name", "$@44",
+ "hooks_libraries", "$@45", "hooks_libraries_list",
+ "not_empty_hooks_libraries_list", "hooks_library", "$@46",
+ "sub_hooks_library", "$@47", "hooks_params", "hooks_param", "library",
+ "$@48", "parameters", "$@49", "loggers", "$@50", "loggers_entries",
+ "logger_entry", "$@51", "logger_params", "logger_param", "name", "$@52",
+ "debuglevel", "severity", "$@53", "output_options_list", "$@54",
+ "output_options_list_content", "output_entry", "$@55",
+ "output_params_list", "output_params", "output", "$@56", "flush",
+ "maxsize", "maxver", "pattern", "$@57", YY_NULLPTR
+ };
+#endif
+
+
+#if D2_PARSER_DEBUG
+ const short
+ D2Parser::yyrline_[] =
+ {
+ 0, 125, 125, 125, 126, 126, 127, 127, 128, 128,
+ 129, 129, 130, 130, 131, 131, 132, 132, 133, 133,
+ 134, 134, 142, 143, 144, 145, 146, 147, 148, 151,
+ 156, 156, 167, 170, 171, 174, 179, 185, 190, 190,
+ 197, 198, 201, 205, 209, 219, 228, 228, 241, 241,
+ 251, 254, 258, 258, 266, 267, 268, 274, 275, 276,
+ 277, 278, 279, 280, 281, 282, 283, 284, 285, 286,
+ 287, 290, 290, 299, 308, 318, 318, 327, 328, 331,
+ 331, 340, 340, 365, 365, 392, 392, 403, 403, 414,
+ 415, 418, 419, 420, 425, 426, 431, 431, 442, 442,
+ 449, 450, 453, 454, 455, 460, 460, 468, 468, 475,
+ 476, 477, 482, 483, 484, 485, 486, 487, 491, 491,
+ 504, 504, 517, 517, 528, 528, 535, 536, 537, 542,
+ 542, 550, 550, 557, 558, 559, 564, 565, 566, 567,
+ 568, 569, 570, 573, 573, 586, 586, 595, 610, 610,
+ 621, 621, 628, 629, 632, 633, 634, 639, 639, 647,
+ 647, 656, 657, 658, 663, 664, 665, 666, 667, 668,
+ 669, 672, 672, 685, 685, 697, 706, 706, 723, 723,
+ 734, 735, 736, 741, 742, 743, 744, 745, 748, 748,
+ 757, 757, 768, 768, 779, 780, 783, 784, 785, 790,
+ 790, 800, 800, 810, 811, 812, 815, 818, 819, 822,
+ 822, 831, 831, 841, 841, 854, 855, 856, 862, 862,
+ 870, 871, 872, 877, 878, 879, 880, 881, 882, 883,
+ 886, 886, 895, 901, 901, 910, 910, 921, 922, 923,
+ 928, 928, 936, 937, 938, 943, 944, 945, 946, 947,
+ 950, 950, 959, 965, 971, 977, 977
+ };
+
+ void
+ D2Parser::yy_stack_print_ () const
+ {
+ *yycdebug_ << "Stack now";
+ for (stack_type::const_iterator
+ i = yystack_.begin (),
+ i_end = yystack_.end ();
+ i != i_end; ++i)
+ *yycdebug_ << ' ' << int (i->state);
+ *yycdebug_ << '\n';
+ }
+
+ void
+ D2Parser::yy_reduce_print_ (int yyrule) const
+ {
+ int yylno = yyrline_[yyrule];
+ int yynrhs = yyr2_[yyrule];
+ // Print the symbols being reduced, and their result.
+ *yycdebug_ << "Reducing stack by rule " << yyrule - 1
+ << " (line " << yylno << "):\n";
+ // The symbols being reduced.
+ for (int yyi = 0; yyi < yynrhs; yyi++)
+ YY_SYMBOL_PRINT (" $" << yyi + 1 << " =",
+ yystack_[(yynrhs) - (yyi + 1)]);
+ }
+#endif // D2_PARSER_DEBUG
+
+
+#line 14 "d2_parser.yy"
+} } // isc::d2
+#line 2849 "d2_parser.cc"
+
+#line 986 "d2_parser.yy"
+
+
+void
+isc::d2::D2Parser::error(const location_type& loc,
+ const std::string& what)
+{
+ ctx.error(loc, what);
+}
diff --git a/src/bin/d2/d2_parser.h b/src/bin/d2/d2_parser.h
new file mode 100644
index 0000000..352e62f
--- /dev/null
+++ b/src/bin/d2/d2_parser.h
@@ -0,0 +1,2645 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Skeleton interface for Bison LALR(1) parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+
+/**
+ ** \file d2_parser.h
+ ** Define the isc::d2::parser class.
+ */
+
+// C++ LALR(1) parser skeleton written by Akim Demaille.
+
+// DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+// especially those whose name start with YY_ or yy_. They are
+// private implementation details that can be changed or removed.
+
+#ifndef YY_D2_PARSER_D2_PARSER_H_INCLUDED
+# define YY_D2_PARSER_D2_PARSER_H_INCLUDED
+// "%code requires" blocks.
+#line 17 "d2_parser.yy"
+
+#include <string>
+#include <cc/data.h>
+#include <d2srv/d2_config.h>
+#include <boost/lexical_cast.hpp>
+#include <d2/parser_context_decl.h>
+
+using namespace isc::d2;
+using namespace isc::data;
+using namespace std;
+
+#line 61 "d2_parser.h"
+
+# include <cassert>
+# include <cstdlib> // std::abort
+# include <iostream>
+# include <stdexcept>
+# include <string>
+# include <vector>
+
+#if defined __cplusplus
+# define YY_CPLUSPLUS __cplusplus
+#else
+# define YY_CPLUSPLUS 199711L
+#endif
+
+// Support move semantics when possible.
+#if 201103L <= YY_CPLUSPLUS
+# define YY_MOVE std::move
+# define YY_MOVE_OR_COPY move
+# define YY_MOVE_REF(Type) Type&&
+# define YY_RVREF(Type) Type&&
+# define YY_COPY(Type) Type
+#else
+# define YY_MOVE
+# define YY_MOVE_OR_COPY copy
+# define YY_MOVE_REF(Type) Type&
+# define YY_RVREF(Type) const Type&
+# define YY_COPY(Type) const Type&
+#endif
+
+// Support noexcept when possible.
+#if 201103L <= YY_CPLUSPLUS
+# define YY_NOEXCEPT noexcept
+# define YY_NOTHROW
+#else
+# define YY_NOEXCEPT
+# define YY_NOTHROW throw ()
+#endif
+
+// Support constexpr when possible.
+#if 201703 <= YY_CPLUSPLUS
+# define YY_CONSTEXPR constexpr
+#else
+# define YY_CONSTEXPR
+#endif
+# include "location.hh"
+#include <typeinfo>
+#ifndef D2_PARSER__ASSERT
+# include <cassert>
+# define D2_PARSER__ASSERT assert
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__
+# if __GNUC__ * 100 + __GNUC_MINOR__ < 407
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")
+# else
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# endif
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+/* Debug traces. */
+#ifndef D2_PARSER_DEBUG
+# if defined YYDEBUG
+#if YYDEBUG
+# define D2_PARSER_DEBUG 1
+# else
+# define D2_PARSER_DEBUG 0
+# endif
+# else /* ! defined YYDEBUG */
+# define D2_PARSER_DEBUG 1
+# endif /* ! defined YYDEBUG */
+#endif /* ! defined D2_PARSER_DEBUG */
+
+#line 14 "d2_parser.yy"
+namespace isc { namespace d2 {
+#line 210 "d2_parser.h"
+
+
+
+
+ /// A Bison parser.
+ class D2Parser
+ {
+ public:
+#ifdef D2_PARSER_STYPE
+# ifdef __GNUC__
+# pragma GCC message "bison: do not #define D2_PARSER_STYPE in C++, use %define api.value.type"
+# endif
+ typedef D2_PARSER_STYPE value_type;
+#else
+ /// A buffer to store and retrieve objects.
+ ///
+ /// Sort of a variant, but does not keep track of the nature
+ /// of the stored data, since that knowledge is available
+ /// via the current parser state.
+ class value_type
+ {
+ public:
+ /// Type of *this.
+ typedef value_type self_type;
+
+ /// Empty construction.
+ value_type () YY_NOEXCEPT
+ : yyraw_ ()
+ , yytypeid_ (YY_NULLPTR)
+ {}
+
+ /// Construct and fill.
+ template <typename T>
+ value_type (YY_RVREF (T) t)
+ : yytypeid_ (&typeid (T))
+ {
+ D2_PARSER__ASSERT (sizeof (T) <= size);
+ new (yyas_<T> ()) T (YY_MOVE (t));
+ }
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ value_type (const self_type&) = delete;
+ /// Non copyable.
+ self_type& operator= (const self_type&) = delete;
+#endif
+
+ /// Destruction, allowed only if empty.
+ ~value_type () YY_NOEXCEPT
+ {
+ D2_PARSER__ASSERT (!yytypeid_);
+ }
+
+# if 201103L <= YY_CPLUSPLUS
+ /// Instantiate a \a T in here from \a t.
+ template <typename T, typename... U>
+ T&
+ emplace (U&&... u)
+ {
+ D2_PARSER__ASSERT (!yytypeid_);
+ D2_PARSER__ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T (std::forward <U>(u)...);
+ }
+# else
+ /// Instantiate an empty \a T in here.
+ template <typename T>
+ T&
+ emplace ()
+ {
+ D2_PARSER__ASSERT (!yytypeid_);
+ D2_PARSER__ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T ();
+ }
+
+ /// Instantiate a \a T in here from \a t.
+ template <typename T>
+ T&
+ emplace (const T& t)
+ {
+ D2_PARSER__ASSERT (!yytypeid_);
+ D2_PARSER__ASSERT (sizeof (T) <= size);
+ yytypeid_ = & typeid (T);
+ return *new (yyas_<T> ()) T (t);
+ }
+# endif
+
+ /// Instantiate an empty \a T in here.
+ /// Obsolete, use emplace.
+ template <typename T>
+ T&
+ build ()
+ {
+ return emplace<T> ();
+ }
+
+ /// Instantiate a \a T in here from \a t.
+ /// Obsolete, use emplace.
+ template <typename T>
+ T&
+ build (const T& t)
+ {
+ return emplace<T> (t);
+ }
+
+ /// Accessor to a built \a T.
+ template <typename T>
+ T&
+ as () YY_NOEXCEPT
+ {
+ D2_PARSER__ASSERT (yytypeid_);
+ D2_PARSER__ASSERT (*yytypeid_ == typeid (T));
+ D2_PARSER__ASSERT (sizeof (T) <= size);
+ return *yyas_<T> ();
+ }
+
+ /// Const accessor to a built \a T (for %printer).
+ template <typename T>
+ const T&
+ as () const YY_NOEXCEPT
+ {
+ D2_PARSER__ASSERT (yytypeid_);
+ D2_PARSER__ASSERT (*yytypeid_ == typeid (T));
+ D2_PARSER__ASSERT (sizeof (T) <= size);
+ return *yyas_<T> ();
+ }
+
+ /// Swap the content with \a that, of same type.
+ ///
+ /// Both variants must be built beforehand, because swapping the actual
+ /// data requires reading it (with as()), and this is not possible on
+ /// unconstructed variants: it would require some dynamic testing, which
+ /// should not be the variant's responsibility.
+ /// Swapping between built and (possibly) non-built is done with
+ /// self_type::move ().
+ template <typename T>
+ void
+ swap (self_type& that) YY_NOEXCEPT
+ {
+ D2_PARSER__ASSERT (yytypeid_);
+ D2_PARSER__ASSERT (*yytypeid_ == *that.yytypeid_);
+ std::swap (as<T> (), that.as<T> ());
+ }
+
+ /// Move the content of \a that to this.
+ ///
+ /// Destroys \a that.
+ template <typename T>
+ void
+ move (self_type& that)
+ {
+# if 201103L <= YY_CPLUSPLUS
+ emplace<T> (std::move (that.as<T> ()));
+# else
+ emplace<T> ();
+ swap<T> (that);
+# endif
+ that.destroy<T> ();
+ }
+
+# if 201103L <= YY_CPLUSPLUS
+ /// Move the content of \a that to this.
+ template <typename T>
+ void
+ move (self_type&& that)
+ {
+ emplace<T> (std::move (that.as<T> ()));
+ that.destroy<T> ();
+ }
+#endif
+
+ /// Copy the content of \a that to this.
+ template <typename T>
+ void
+ copy (const self_type& that)
+ {
+ emplace<T> (that.as<T> ());
+ }
+
+ /// Destroy the stored \a T.
+ template <typename T>
+ void
+ destroy ()
+ {
+ as<T> ().~T ();
+ yytypeid_ = YY_NULLPTR;
+ }
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ value_type (const self_type&);
+ /// Non copyable.
+ self_type& operator= (const self_type&);
+#endif
+
+ /// Accessor to raw memory as \a T.
+ template <typename T>
+ T*
+ yyas_ () YY_NOEXCEPT
+ {
+ void *yyp = yyraw_;
+ return static_cast<T*> (yyp);
+ }
+
+ /// Const accessor to raw memory as \a T.
+ template <typename T>
+ const T*
+ yyas_ () const YY_NOEXCEPT
+ {
+ const void *yyp = yyraw_;
+ return static_cast<const T*> (yyp);
+ }
+
+ /// An auxiliary type to compute the largest semantic type.
+ union union_type
+ {
+ // value
+ // map_value
+ // ncr_protocol_value
+ char dummy1[sizeof (ElementPtr)];
+
+ // "boolean"
+ char dummy2[sizeof (bool)];
+
+ // "floating point"
+ char dummy3[sizeof (double)];
+
+ // "integer"
+ char dummy4[sizeof (int64_t)];
+
+ // "constant string"
+ char dummy5[sizeof (std::string)];
+ };
+
+ /// The size of the largest semantic type.
+ enum { size = sizeof (union_type) };
+
+ /// A buffer to store semantic values.
+ union
+ {
+ /// Strongest alignment constraints.
+ long double yyalign_me_;
+ /// A buffer large enough to store any of the semantic values.
+ char yyraw_[size];
+ };
+
+ /// Whether the content is built: if defined, the name of the stored type.
+ const std::type_info *yytypeid_;
+ };
+
+#endif
+ /// Backward compatibility (Bison 3.8).
+ typedef value_type semantic_type;
+
+ /// Symbol locations.
+ typedef location location_type;
+
+ /// Syntax errors thrown from user actions.
+ struct syntax_error : std::runtime_error
+ {
+ syntax_error (const location_type& l, const std::string& m)
+ : std::runtime_error (m)
+ , location (l)
+ {}
+
+ syntax_error (const syntax_error& s)
+ : std::runtime_error (s.what ())
+ , location (s.location)
+ {}
+
+ ~syntax_error () YY_NOEXCEPT YY_NOTHROW;
+
+ location_type location;
+ };
+
+ /// Token kinds.
+ struct token
+ {
+ enum token_kind_type
+ {
+ TOKEN_D2_PARSER_EMPTY = -2,
+ TOKEN_END = 0, // "end of file"
+ TOKEN_D2_PARSER_error = 256, // error
+ TOKEN_D2_PARSER_UNDEF = 257, // "invalid token"
+ TOKEN_COMMA = 258, // ","
+ TOKEN_COLON = 259, // ":"
+ TOKEN_LSQUARE_BRACKET = 260, // "["
+ TOKEN_RSQUARE_BRACKET = 261, // "]"
+ TOKEN_LCURLY_BRACKET = 262, // "{"
+ TOKEN_RCURLY_BRACKET = 263, // "}"
+ TOKEN_NULL_TYPE = 264, // "null"
+ TOKEN_DHCPDDNS = 265, // "DhcpDdns"
+ TOKEN_IP_ADDRESS = 266, // "ip-address"
+ TOKEN_PORT = 267, // "port"
+ TOKEN_DNS_SERVER_TIMEOUT = 268, // "dns-server-timeout"
+ TOKEN_NCR_PROTOCOL = 269, // "ncr-protocol"
+ TOKEN_UDP = 270, // "UDP"
+ TOKEN_TCP = 271, // "TCP"
+ TOKEN_NCR_FORMAT = 272, // "ncr-format"
+ TOKEN_JSON = 273, // "JSON"
+ TOKEN_USER_CONTEXT = 274, // "user-context"
+ TOKEN_COMMENT = 275, // "comment"
+ TOKEN_FORWARD_DDNS = 276, // "forward-ddns"
+ TOKEN_REVERSE_DDNS = 277, // "reverse-ddns"
+ TOKEN_DDNS_DOMAINS = 278, // "ddns-domains"
+ TOKEN_KEY_NAME = 279, // "key-name"
+ TOKEN_DNS_SERVERS = 280, // "dns-servers"
+ TOKEN_HOSTNAME = 281, // "hostname"
+ TOKEN_TSIG_KEYS = 282, // "tsig-keys"
+ TOKEN_ALGORITHM = 283, // "algorithm"
+ TOKEN_DIGEST_BITS = 284, // "digest-bits"
+ TOKEN_SECRET = 285, // "secret"
+ TOKEN_CONTROL_SOCKET = 286, // "control-socket"
+ TOKEN_SOCKET_TYPE = 287, // "socket-type"
+ TOKEN_SOCKET_NAME = 288, // "socket-name"
+ TOKEN_HOOKS_LIBRARIES = 289, // "hooks-libraries"
+ TOKEN_LIBRARY = 290, // "library"
+ TOKEN_PARAMETERS = 291, // "parameters"
+ TOKEN_LOGGERS = 292, // "loggers"
+ TOKEN_NAME = 293, // "name"
+ TOKEN_OUTPUT_OPTIONS = 294, // "output_options"
+ TOKEN_OUTPUT = 295, // "output"
+ TOKEN_DEBUGLEVEL = 296, // "debuglevel"
+ TOKEN_SEVERITY = 297, // "severity"
+ TOKEN_FLUSH = 298, // "flush"
+ TOKEN_MAXSIZE = 299, // "maxsize"
+ TOKEN_MAXVER = 300, // "maxver"
+ TOKEN_PATTERN = 301, // "pattern"
+ TOKEN_TOPLEVEL_JSON = 302, // TOPLEVEL_JSON
+ TOKEN_TOPLEVEL_DHCPDDNS = 303, // TOPLEVEL_DHCPDDNS
+ TOKEN_SUB_DHCPDDNS = 304, // SUB_DHCPDDNS
+ TOKEN_SUB_TSIG_KEY = 305, // SUB_TSIG_KEY
+ TOKEN_SUB_TSIG_KEYS = 306, // SUB_TSIG_KEYS
+ TOKEN_SUB_DDNS_DOMAIN = 307, // SUB_DDNS_DOMAIN
+ TOKEN_SUB_DDNS_DOMAINS = 308, // SUB_DDNS_DOMAINS
+ TOKEN_SUB_DNS_SERVER = 309, // SUB_DNS_SERVER
+ TOKEN_SUB_DNS_SERVERS = 310, // SUB_DNS_SERVERS
+ TOKEN_SUB_HOOKS_LIBRARY = 311, // SUB_HOOKS_LIBRARY
+ TOKEN_STRING = 312, // "constant string"
+ TOKEN_INTEGER = 313, // "integer"
+ TOKEN_FLOAT = 314, // "floating point"
+ TOKEN_BOOLEAN = 315 // "boolean"
+ };
+ /// Backward compatibility alias (Bison 3.6).
+ typedef token_kind_type yytokentype;
+ };
+
+ /// Token kind, as returned by yylex.
+ typedef token::token_kind_type token_kind_type;
+
+ /// Backward compatibility alias (Bison 3.6).
+ typedef token_kind_type token_type;
+
+ /// Symbol kinds.
+ struct symbol_kind
+ {
+ enum symbol_kind_type
+ {
+ YYNTOKENS = 61, ///< Number of tokens.
+ S_YYEMPTY = -2,
+ S_YYEOF = 0, // "end of file"
+ S_YYerror = 1, // error
+ S_YYUNDEF = 2, // "invalid token"
+ S_COMMA = 3, // ","
+ S_COLON = 4, // ":"
+ S_LSQUARE_BRACKET = 5, // "["
+ S_RSQUARE_BRACKET = 6, // "]"
+ S_LCURLY_BRACKET = 7, // "{"
+ S_RCURLY_BRACKET = 8, // "}"
+ S_NULL_TYPE = 9, // "null"
+ S_DHCPDDNS = 10, // "DhcpDdns"
+ S_IP_ADDRESS = 11, // "ip-address"
+ S_PORT = 12, // "port"
+ S_DNS_SERVER_TIMEOUT = 13, // "dns-server-timeout"
+ S_NCR_PROTOCOL = 14, // "ncr-protocol"
+ S_UDP = 15, // "UDP"
+ S_TCP = 16, // "TCP"
+ S_NCR_FORMAT = 17, // "ncr-format"
+ S_JSON = 18, // "JSON"
+ S_USER_CONTEXT = 19, // "user-context"
+ S_COMMENT = 20, // "comment"
+ S_FORWARD_DDNS = 21, // "forward-ddns"
+ S_REVERSE_DDNS = 22, // "reverse-ddns"
+ S_DDNS_DOMAINS = 23, // "ddns-domains"
+ S_KEY_NAME = 24, // "key-name"
+ S_DNS_SERVERS = 25, // "dns-servers"
+ S_HOSTNAME = 26, // "hostname"
+ S_TSIG_KEYS = 27, // "tsig-keys"
+ S_ALGORITHM = 28, // "algorithm"
+ S_DIGEST_BITS = 29, // "digest-bits"
+ S_SECRET = 30, // "secret"
+ S_CONTROL_SOCKET = 31, // "control-socket"
+ S_SOCKET_TYPE = 32, // "socket-type"
+ S_SOCKET_NAME = 33, // "socket-name"
+ S_HOOKS_LIBRARIES = 34, // "hooks-libraries"
+ S_LIBRARY = 35, // "library"
+ S_PARAMETERS = 36, // "parameters"
+ S_LOGGERS = 37, // "loggers"
+ S_NAME = 38, // "name"
+ S_OUTPUT_OPTIONS = 39, // "output_options"
+ S_OUTPUT = 40, // "output"
+ S_DEBUGLEVEL = 41, // "debuglevel"
+ S_SEVERITY = 42, // "severity"
+ S_FLUSH = 43, // "flush"
+ S_MAXSIZE = 44, // "maxsize"
+ S_MAXVER = 45, // "maxver"
+ S_PATTERN = 46, // "pattern"
+ S_TOPLEVEL_JSON = 47, // TOPLEVEL_JSON
+ S_TOPLEVEL_DHCPDDNS = 48, // TOPLEVEL_DHCPDDNS
+ S_SUB_DHCPDDNS = 49, // SUB_DHCPDDNS
+ S_SUB_TSIG_KEY = 50, // SUB_TSIG_KEY
+ S_SUB_TSIG_KEYS = 51, // SUB_TSIG_KEYS
+ S_SUB_DDNS_DOMAIN = 52, // SUB_DDNS_DOMAIN
+ S_SUB_DDNS_DOMAINS = 53, // SUB_DDNS_DOMAINS
+ S_SUB_DNS_SERVER = 54, // SUB_DNS_SERVER
+ S_SUB_DNS_SERVERS = 55, // SUB_DNS_SERVERS
+ S_SUB_HOOKS_LIBRARY = 56, // SUB_HOOKS_LIBRARY
+ S_STRING = 57, // "constant string"
+ S_INTEGER = 58, // "integer"
+ S_FLOAT = 59, // "floating point"
+ S_BOOLEAN = 60, // "boolean"
+ S_YYACCEPT = 61, // $accept
+ S_start = 62, // start
+ S_63_1 = 63, // $@1
+ S_64_2 = 64, // $@2
+ S_65_3 = 65, // $@3
+ S_66_4 = 66, // $@4
+ S_67_5 = 67, // $@5
+ S_68_6 = 68, // $@6
+ S_69_7 = 69, // $@7
+ S_70_8 = 70, // $@8
+ S_71_9 = 71, // $@9
+ S_72_10 = 72, // $@10
+ S_value = 73, // value
+ S_sub_json = 74, // sub_json
+ S_map2 = 75, // map2
+ S_76_11 = 76, // $@11
+ S_map_value = 77, // map_value
+ S_map_content = 78, // map_content
+ S_not_empty_map = 79, // not_empty_map
+ S_list_generic = 80, // list_generic
+ S_81_12 = 81, // $@12
+ S_list_content = 82, // list_content
+ S_not_empty_list = 83, // not_empty_list
+ S_unknown_map_entry = 84, // unknown_map_entry
+ S_syntax_map = 85, // syntax_map
+ S_86_13 = 86, // $@13
+ S_global_object = 87, // global_object
+ S_88_14 = 88, // $@14
+ S_global_object_comma = 89, // global_object_comma
+ S_sub_dhcpddns = 90, // sub_dhcpddns
+ S_91_15 = 91, // $@15
+ S_dhcpddns_params = 92, // dhcpddns_params
+ S_dhcpddns_param = 93, // dhcpddns_param
+ S_ip_address = 94, // ip_address
+ S_95_16 = 95, // $@16
+ S_port = 96, // port
+ S_dns_server_timeout = 97, // dns_server_timeout
+ S_ncr_protocol = 98, // ncr_protocol
+ S_99_17 = 99, // $@17
+ S_ncr_protocol_value = 100, // ncr_protocol_value
+ S_ncr_format = 101, // ncr_format
+ S_102_18 = 102, // $@18
+ S_user_context = 103, // user_context
+ S_104_19 = 104, // $@19
+ S_comment = 105, // comment
+ S_106_20 = 106, // $@20
+ S_forward_ddns = 107, // forward_ddns
+ S_108_21 = 108, // $@21
+ S_reverse_ddns = 109, // reverse_ddns
+ S_110_22 = 110, // $@22
+ S_ddns_mgr_params = 111, // ddns_mgr_params
+ S_not_empty_ddns_mgr_params = 112, // not_empty_ddns_mgr_params
+ S_ddns_mgr_param = 113, // ddns_mgr_param
+ S_ddns_domains = 114, // ddns_domains
+ S_115_23 = 115, // $@23
+ S_sub_ddns_domains = 116, // sub_ddns_domains
+ S_117_24 = 117, // $@24
+ S_ddns_domain_list = 118, // ddns_domain_list
+ S_not_empty_ddns_domain_list = 119, // not_empty_ddns_domain_list
+ S_ddns_domain = 120, // ddns_domain
+ S_121_25 = 121, // $@25
+ S_sub_ddns_domain = 122, // sub_ddns_domain
+ S_123_26 = 123, // $@26
+ S_ddns_domain_params = 124, // ddns_domain_params
+ S_ddns_domain_param = 125, // ddns_domain_param
+ S_ddns_domain_name = 126, // ddns_domain_name
+ S_127_27 = 127, // $@27
+ S_ddns_key_name = 128, // ddns_key_name
+ S_129_28 = 129, // $@28
+ S_dns_servers = 130, // dns_servers
+ S_131_29 = 131, // $@29
+ S_sub_dns_servers = 132, // sub_dns_servers
+ S_133_30 = 133, // $@30
+ S_dns_server_list = 134, // dns_server_list
+ S_dns_server = 135, // dns_server
+ S_136_31 = 136, // $@31
+ S_sub_dns_server = 137, // sub_dns_server
+ S_138_32 = 138, // $@32
+ S_dns_server_params = 139, // dns_server_params
+ S_dns_server_param = 140, // dns_server_param
+ S_dns_server_hostname = 141, // dns_server_hostname
+ S_142_33 = 142, // $@33
+ S_dns_server_ip_address = 143, // dns_server_ip_address
+ S_144_34 = 144, // $@34
+ S_dns_server_port = 145, // dns_server_port
+ S_tsig_keys = 146, // tsig_keys
+ S_147_35 = 147, // $@35
+ S_sub_tsig_keys = 148, // sub_tsig_keys
+ S_149_36 = 149, // $@36
+ S_tsig_keys_list = 150, // tsig_keys_list
+ S_not_empty_tsig_keys_list = 151, // not_empty_tsig_keys_list
+ S_tsig_key = 152, // tsig_key
+ S_153_37 = 153, // $@37
+ S_sub_tsig_key = 154, // sub_tsig_key
+ S_155_38 = 155, // $@38
+ S_tsig_key_params = 156, // tsig_key_params
+ S_tsig_key_param = 157, // tsig_key_param
+ S_tsig_key_name = 158, // tsig_key_name
+ S_159_39 = 159, // $@39
+ S_tsig_key_algorithm = 160, // tsig_key_algorithm
+ S_161_40 = 161, // $@40
+ S_tsig_key_digest_bits = 162, // tsig_key_digest_bits
+ S_tsig_key_secret = 163, // tsig_key_secret
+ S_164_41 = 164, // $@41
+ S_control_socket = 165, // control_socket
+ S_166_42 = 166, // $@42
+ S_control_socket_params = 167, // control_socket_params
+ S_control_socket_param = 168, // control_socket_param
+ S_control_socket_type = 169, // control_socket_type
+ S_170_43 = 170, // $@43
+ S_control_socket_name = 171, // control_socket_name
+ S_172_44 = 172, // $@44
+ S_hooks_libraries = 173, // hooks_libraries
+ S_174_45 = 174, // $@45
+ S_hooks_libraries_list = 175, // hooks_libraries_list
+ S_not_empty_hooks_libraries_list = 176, // not_empty_hooks_libraries_list
+ S_hooks_library = 177, // hooks_library
+ S_178_46 = 178, // $@46
+ S_sub_hooks_library = 179, // sub_hooks_library
+ S_180_47 = 180, // $@47
+ S_hooks_params = 181, // hooks_params
+ S_hooks_param = 182, // hooks_param
+ S_library = 183, // library
+ S_184_48 = 184, // $@48
+ S_parameters = 185, // parameters
+ S_186_49 = 186, // $@49
+ S_loggers = 187, // loggers
+ S_188_50 = 188, // $@50
+ S_loggers_entries = 189, // loggers_entries
+ S_logger_entry = 190, // logger_entry
+ S_191_51 = 191, // $@51
+ S_logger_params = 192, // logger_params
+ S_logger_param = 193, // logger_param
+ S_name = 194, // name
+ S_195_52 = 195, // $@52
+ S_debuglevel = 196, // debuglevel
+ S_severity = 197, // severity
+ S_198_53 = 198, // $@53
+ S_output_options_list = 199, // output_options_list
+ S_200_54 = 200, // $@54
+ S_output_options_list_content = 201, // output_options_list_content
+ S_output_entry = 202, // output_entry
+ S_203_55 = 203, // $@55
+ S_output_params_list = 204, // output_params_list
+ S_output_params = 205, // output_params
+ S_output = 206, // output
+ S_207_56 = 207, // $@56
+ S_flush = 208, // flush
+ S_maxsize = 209, // maxsize
+ S_maxver = 210, // maxver
+ S_pattern = 211, // pattern
+ S_212_57 = 212 // $@57
+ };
+ };
+
+ /// (Internal) symbol kind.
+ typedef symbol_kind::symbol_kind_type symbol_kind_type;
+
+ /// The number of tokens.
+ static const symbol_kind_type YYNTOKENS = symbol_kind::YYNTOKENS;
+
+ /// A complete symbol.
+ ///
+ /// Expects its Base type to provide access to the symbol kind
+ /// via kind ().
+ ///
+ /// Provide access to semantic value and location.
+ template <typename Base>
+ struct basic_symbol : Base
+ {
+ /// Alias to Base.
+ typedef Base super_type;
+
+ /// Default constructor.
+ basic_symbol () YY_NOEXCEPT
+ : value ()
+ , location ()
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Move constructor.
+ basic_symbol (basic_symbol&& that)
+ : Base (std::move (that))
+ , value ()
+ , location (std::move (that.location))
+ {
+ switch (this->kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.move< ElementPtr > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.move< bool > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.move< double > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.move< int64_t > (std::move (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.move< std::string > (std::move (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ }
+#endif
+
+ /// Copy constructor.
+ basic_symbol (const basic_symbol& that);
+
+ /// Constructors for typed symbols.
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, location_type&& l)
+ : Base (t)
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const location_type& l)
+ : Base (t)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, ElementPtr&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const ElementPtr& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, bool&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const bool& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, double&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const double& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, int64_t&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const int64_t& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+#if 201103L <= YY_CPLUSPLUS
+ basic_symbol (typename Base::kind_type t, std::string&& v, location_type&& l)
+ : Base (t)
+ , value (std::move (v))
+ , location (std::move (l))
+ {}
+#else
+ basic_symbol (typename Base::kind_type t, const std::string& v, const location_type& l)
+ : Base (t)
+ , value (v)
+ , location (l)
+ {}
+#endif
+
+ /// Destroy the symbol.
+ ~basic_symbol ()
+ {
+ clear ();
+ }
+
+
+
+ /// Destroy contents, and record that is empty.
+ void clear () YY_NOEXCEPT
+ {
+ // User destructor.
+ symbol_kind_type yykind = this->kind ();
+ basic_symbol<Base>& yysym = *this;
+ (void) yysym;
+ switch (yykind)
+ {
+ default:
+ break;
+ }
+
+ // Value type destructor.
+switch (yykind)
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.template destroy< ElementPtr > ();
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.template destroy< bool > ();
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.template destroy< double > ();
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.template destroy< int64_t > ();
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.template destroy< std::string > ();
+ break;
+
+ default:
+ break;
+ }
+
+ Base::clear ();
+ }
+
+ /// The user-facing name of this symbol.
+ std::string name () const YY_NOEXCEPT
+ {
+ return D2Parser::symbol_name (this->kind ());
+ }
+
+ /// Backward compatibility (Bison 3.6).
+ symbol_kind_type type_get () const YY_NOEXCEPT;
+
+ /// Whether empty.
+ bool empty () const YY_NOEXCEPT;
+
+ /// Destructive move, \a s is emptied into this.
+ void move (basic_symbol& s);
+
+ /// The semantic value.
+ value_type value;
+
+ /// The location.
+ location_type location;
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Assignment operator.
+ basic_symbol& operator= (const basic_symbol& that);
+#endif
+ };
+
+ /// Type access provider for token (enum) based symbols.
+ struct by_kind
+ {
+ /// The symbol kind as needed by the constructor.
+ typedef token_kind_type kind_type;
+
+ /// Default constructor.
+ by_kind () YY_NOEXCEPT;
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Move constructor.
+ by_kind (by_kind&& that) YY_NOEXCEPT;
+#endif
+
+ /// Copy constructor.
+ by_kind (const by_kind& that) YY_NOEXCEPT;
+
+ /// Constructor from (external) token numbers.
+ by_kind (kind_type t) YY_NOEXCEPT;
+
+
+
+ /// Record that this symbol is empty.
+ void clear () YY_NOEXCEPT;
+
+ /// Steal the symbol kind from \a that.
+ void move (by_kind& that);
+
+ /// The (internal) type number (corresponding to \a type).
+ /// \a empty when empty.
+ symbol_kind_type kind () const YY_NOEXCEPT;
+
+ /// Backward compatibility (Bison 3.6).
+ symbol_kind_type type_get () const YY_NOEXCEPT;
+
+ /// The symbol kind.
+ /// \a S_YYEMPTY when empty.
+ symbol_kind_type kind_;
+ };
+
+ /// Backward compatibility for a private implementation detail (Bison 3.6).
+ typedef by_kind by_type;
+
+ /// "External" symbols: returned by the scanner.
+ struct symbol_type : basic_symbol<by_kind>
+ {
+ /// Superclass.
+ typedef basic_symbol<by_kind> super_type;
+
+ /// Empty symbol.
+ symbol_type () YY_NOEXCEPT {}
+
+ /// Constructor for valueless symbols, and symbols from each type.
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, location_type l)
+ : super_type (token_kind_type (tok), std::move (l))
+#else
+ symbol_type (int tok, const location_type& l)
+ : super_type (token_kind_type (tok), l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ D2_PARSER__ASSERT (tok == token::TOKEN_END
+ || (token::TOKEN_D2_PARSER_error <= tok && tok <= token::TOKEN_SUB_HOOKS_LIBRARY));
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, bool v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const bool& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ D2_PARSER__ASSERT (tok == token::TOKEN_BOOLEAN);
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, double v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const double& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ D2_PARSER__ASSERT (tok == token::TOKEN_FLOAT);
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, int64_t v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const int64_t& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ D2_PARSER__ASSERT (tok == token::TOKEN_INTEGER);
+#endif
+ }
+#if 201103L <= YY_CPLUSPLUS
+ symbol_type (int tok, std::string v, location_type l)
+ : super_type (token_kind_type (tok), std::move (v), std::move (l))
+#else
+ symbol_type (int tok, const std::string& v, const location_type& l)
+ : super_type (token_kind_type (tok), v, l)
+#endif
+ {
+#if !defined _MSC_VER || defined __clang__
+ D2_PARSER__ASSERT (tok == token::TOKEN_STRING);
+#endif
+ }
+ };
+
+ /// Build a parser object.
+ D2Parser (isc::d2::D2ParserContext& ctx_yyarg);
+ virtual ~D2Parser ();
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ D2Parser (const D2Parser&) = delete;
+ /// Non copyable.
+ D2Parser& operator= (const D2Parser&) = delete;
+#endif
+
+ /// Parse. An alias for parse ().
+ /// \returns 0 iff parsing succeeded.
+ int operator() ();
+
+ /// Parse.
+ /// \returns 0 iff parsing succeeded.
+ virtual int parse ();
+
+#if D2_PARSER_DEBUG
+ /// The current debugging stream.
+ std::ostream& debug_stream () const YY_ATTRIBUTE_PURE;
+ /// Set the current debugging stream.
+ void set_debug_stream (std::ostream &);
+
+ /// Type for debugging levels.
+ typedef int debug_level_type;
+ /// The current debugging level.
+ debug_level_type debug_level () const YY_ATTRIBUTE_PURE;
+ /// Set the current debugging level.
+ void set_debug_level (debug_level_type l);
+#endif
+
+ /// Report a syntax error.
+ /// \param loc where the syntax error is found.
+ /// \param msg a description of the syntax error.
+ virtual void error (const location_type& loc, const std::string& msg);
+
+ /// Report a syntax error.
+ void error (const syntax_error& err);
+
+ /// The user-facing name of the symbol whose (internal) number is
+ /// YYSYMBOL. No bounds checking.
+ static std::string symbol_name (symbol_kind_type yysymbol);
+
+ // Implementation of make_symbol for each token kind.
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_END (location_type l)
+ {
+ return symbol_type (token::TOKEN_END, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_END (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_END, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_D2_PARSER_error (location_type l)
+ {
+ return symbol_type (token::TOKEN_D2_PARSER_error, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_D2_PARSER_error (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_D2_PARSER_error, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_D2_PARSER_UNDEF (location_type l)
+ {
+ return symbol_type (token::TOKEN_D2_PARSER_UNDEF, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_D2_PARSER_UNDEF (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_D2_PARSER_UNDEF, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_COMMA (location_type l)
+ {
+ return symbol_type (token::TOKEN_COMMA, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_COMMA (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_COMMA, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_COLON (location_type l)
+ {
+ return symbol_type (token::TOKEN_COLON, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_COLON (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_COLON, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LSQUARE_BRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_LSQUARE_BRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LSQUARE_BRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LSQUARE_BRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RSQUARE_BRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_RSQUARE_BRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RSQUARE_BRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RSQUARE_BRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LCURLY_BRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_LCURLY_BRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LCURLY_BRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LCURLY_BRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_RCURLY_BRACKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_RCURLY_BRACKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_RCURLY_BRACKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_RCURLY_BRACKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NULL_TYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_NULL_TYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NULL_TYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NULL_TYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DHCPDDNS (location_type l)
+ {
+ return symbol_type (token::TOKEN_DHCPDDNS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DHCPDDNS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DHCPDDNS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_IP_ADDRESS (location_type l)
+ {
+ return symbol_type (token::TOKEN_IP_ADDRESS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_IP_ADDRESS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_IP_ADDRESS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PORT (location_type l)
+ {
+ return symbol_type (token::TOKEN_PORT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PORT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PORT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DNS_SERVER_TIMEOUT (location_type l)
+ {
+ return symbol_type (token::TOKEN_DNS_SERVER_TIMEOUT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DNS_SERVER_TIMEOUT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DNS_SERVER_TIMEOUT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NCR_PROTOCOL (location_type l)
+ {
+ return symbol_type (token::TOKEN_NCR_PROTOCOL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NCR_PROTOCOL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NCR_PROTOCOL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_UDP (location_type l)
+ {
+ return symbol_type (token::TOKEN_UDP, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_UDP (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_UDP, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TCP (location_type l)
+ {
+ return symbol_type (token::TOKEN_TCP, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TCP (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TCP, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NCR_FORMAT (location_type l)
+ {
+ return symbol_type (token::TOKEN_NCR_FORMAT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NCR_FORMAT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NCR_FORMAT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_JSON (location_type l)
+ {
+ return symbol_type (token::TOKEN_JSON, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_JSON (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_JSON, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_USER_CONTEXT (location_type l)
+ {
+ return symbol_type (token::TOKEN_USER_CONTEXT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_USER_CONTEXT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_USER_CONTEXT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_COMMENT (location_type l)
+ {
+ return symbol_type (token::TOKEN_COMMENT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_COMMENT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_COMMENT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_FORWARD_DDNS (location_type l)
+ {
+ return symbol_type (token::TOKEN_FORWARD_DDNS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_FORWARD_DDNS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_FORWARD_DDNS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_REVERSE_DDNS (location_type l)
+ {
+ return symbol_type (token::TOKEN_REVERSE_DDNS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_REVERSE_DDNS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_REVERSE_DDNS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DDNS_DOMAINS (location_type l)
+ {
+ return symbol_type (token::TOKEN_DDNS_DOMAINS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DDNS_DOMAINS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DDNS_DOMAINS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_KEY_NAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_KEY_NAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_KEY_NAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_KEY_NAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DNS_SERVERS (location_type l)
+ {
+ return symbol_type (token::TOKEN_DNS_SERVERS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DNS_SERVERS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DNS_SERVERS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOSTNAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOSTNAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOSTNAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOSTNAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TSIG_KEYS (location_type l)
+ {
+ return symbol_type (token::TOKEN_TSIG_KEYS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TSIG_KEYS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TSIG_KEYS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_ALGORITHM (location_type l)
+ {
+ return symbol_type (token::TOKEN_ALGORITHM, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_ALGORITHM (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_ALGORITHM, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DIGEST_BITS (location_type l)
+ {
+ return symbol_type (token::TOKEN_DIGEST_BITS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DIGEST_BITS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DIGEST_BITS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SECRET (location_type l)
+ {
+ return symbol_type (token::TOKEN_SECRET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SECRET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SECRET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_CONTROL_SOCKET (location_type l)
+ {
+ return symbol_type (token::TOKEN_CONTROL_SOCKET, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_CONTROL_SOCKET (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_CONTROL_SOCKET, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SOCKET_TYPE (location_type l)
+ {
+ return symbol_type (token::TOKEN_SOCKET_TYPE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SOCKET_TYPE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SOCKET_TYPE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SOCKET_NAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_SOCKET_NAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SOCKET_NAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SOCKET_NAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_HOOKS_LIBRARIES (location_type l)
+ {
+ return symbol_type (token::TOKEN_HOOKS_LIBRARIES, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_HOOKS_LIBRARIES (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_HOOKS_LIBRARIES, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LIBRARY (location_type l)
+ {
+ return symbol_type (token::TOKEN_LIBRARY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LIBRARY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LIBRARY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PARAMETERS (location_type l)
+ {
+ return symbol_type (token::TOKEN_PARAMETERS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PARAMETERS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PARAMETERS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_LOGGERS (location_type l)
+ {
+ return symbol_type (token::TOKEN_LOGGERS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_LOGGERS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_LOGGERS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_NAME (location_type l)
+ {
+ return symbol_type (token::TOKEN_NAME, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_NAME (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_NAME, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OUTPUT_OPTIONS (location_type l)
+ {
+ return symbol_type (token::TOKEN_OUTPUT_OPTIONS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OUTPUT_OPTIONS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OUTPUT_OPTIONS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_OUTPUT (location_type l)
+ {
+ return symbol_type (token::TOKEN_OUTPUT, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_OUTPUT (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_OUTPUT, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_DEBUGLEVEL (location_type l)
+ {
+ return symbol_type (token::TOKEN_DEBUGLEVEL, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_DEBUGLEVEL (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_DEBUGLEVEL, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SEVERITY (location_type l)
+ {
+ return symbol_type (token::TOKEN_SEVERITY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SEVERITY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SEVERITY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_FLUSH (location_type l)
+ {
+ return symbol_type (token::TOKEN_FLUSH, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_FLUSH (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_FLUSH, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAXSIZE (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAXSIZE, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAXSIZE (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAXSIZE, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_MAXVER (location_type l)
+ {
+ return symbol_type (token::TOKEN_MAXVER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_MAXVER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MAXVER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_PATTERN (location_type l)
+ {
+ return symbol_type (token::TOKEN_PATTERN, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_PATTERN (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_PATTERN, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TOPLEVEL_JSON (location_type l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_JSON, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TOPLEVEL_JSON (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_JSON, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_TOPLEVEL_DHCPDDNS (location_type l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_DHCPDDNS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_TOPLEVEL_DHCPDDNS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_TOPLEVEL_DHCPDDNS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_DHCPDDNS (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_DHCPDDNS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_DHCPDDNS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_DHCPDDNS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_TSIG_KEY (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_TSIG_KEY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_TSIG_KEY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_TSIG_KEY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_TSIG_KEYS (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_TSIG_KEYS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_TSIG_KEYS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_TSIG_KEYS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_DDNS_DOMAIN (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_DDNS_DOMAIN, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_DDNS_DOMAIN (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_DDNS_DOMAIN, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_DDNS_DOMAINS (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_DDNS_DOMAINS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_DDNS_DOMAINS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_DDNS_DOMAINS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_DNS_SERVER (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_DNS_SERVER, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_DNS_SERVER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_DNS_SERVER, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_DNS_SERVERS (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_DNS_SERVERS, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_DNS_SERVERS (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_DNS_SERVERS, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_SUB_HOOKS_LIBRARY (location_type l)
+ {
+ return symbol_type (token::TOKEN_SUB_HOOKS_LIBRARY, std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_SUB_HOOKS_LIBRARY (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_SUB_HOOKS_LIBRARY, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_STRING (std::string v, location_type l)
+ {
+ return symbol_type (token::TOKEN_STRING, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_STRING (const std::string& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_STRING, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_INTEGER (int64_t v, location_type l)
+ {
+ return symbol_type (token::TOKEN_INTEGER, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_INTEGER (const int64_t& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_INTEGER, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_FLOAT (double v, location_type l)
+ {
+ return symbol_type (token::TOKEN_FLOAT, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_FLOAT (const double& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_FLOAT, v, l);
+ }
+#endif
+#if 201103L <= YY_CPLUSPLUS
+ static
+ symbol_type
+ make_BOOLEAN (bool v, location_type l)
+ {
+ return symbol_type (token::TOKEN_BOOLEAN, std::move (v), std::move (l));
+ }
+#else
+ static
+ symbol_type
+ make_BOOLEAN (const bool& v, const location_type& l)
+ {
+ return symbol_type (token::TOKEN_BOOLEAN, v, l);
+ }
+#endif
+
+
+ class context
+ {
+ public:
+ context (const D2Parser& yyparser, const symbol_type& yyla);
+ const symbol_type& lookahead () const YY_NOEXCEPT { return yyla_; }
+ symbol_kind_type token () const YY_NOEXCEPT { return yyla_.kind (); }
+ const location_type& location () const YY_NOEXCEPT { return yyla_.location; }
+
+ /// Put in YYARG at most YYARGN of the expected tokens, and return the
+ /// number of tokens stored in YYARG. If YYARG is null, return the
+ /// number of expected tokens (guaranteed to be less than YYNTOKENS).
+ int expected_tokens (symbol_kind_type yyarg[], int yyargn) const;
+
+ private:
+ const D2Parser& yyparser_;
+ const symbol_type& yyla_;
+ };
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ D2Parser (const D2Parser&);
+ /// Non copyable.
+ D2Parser& operator= (const D2Parser&);
+#endif
+
+
+ /// Stored state numbers (used for stacks).
+ typedef short state_type;
+
+ /// The arguments of the error message.
+ int yy_syntax_error_arguments_ (const context& yyctx,
+ symbol_kind_type yyarg[], int yyargn) const;
+
+ /// Generate an error message.
+ /// \param yyctx the context in which the error occurred.
+ virtual std::string yysyntax_error_ (const context& yyctx) const;
+ /// Compute post-reduction state.
+ /// \param yystate the current state
+ /// \param yysym the nonterminal to push on the stack
+ static state_type yy_lr_goto_state_ (state_type yystate, int yysym);
+
+ /// Whether the given \c yypact_ value indicates a defaulted state.
+ /// \param yyvalue the value to check
+ static bool yy_pact_value_is_default_ (int yyvalue) YY_NOEXCEPT;
+
+ /// Whether the given \c yytable_ value indicates a syntax error.
+ /// \param yyvalue the value to check
+ static bool yy_table_value_is_error_ (int yyvalue) YY_NOEXCEPT;
+
+ static const short yypact_ninf_;
+ static const signed char yytable_ninf_;
+
+ /// Convert a scanner token kind \a t to a symbol kind.
+ /// In theory \a t should be a token_kind_type, but character literals
+ /// are valid, yet not members of the token_kind_type enum.
+ static symbol_kind_type yytranslate_ (int t) YY_NOEXCEPT;
+
+ /// Convert the symbol name \a n to a form suitable for a diagnostic.
+ static std::string yytnamerr_ (const char *yystr);
+
+ /// For a symbol, its name in clear.
+ static const char* const yytname_[];
+
+
+ // Tables.
+ // YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ // STATE-NUM.
+ static const short yypact_[];
+
+ // YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ // Performed when YYTABLE does not specify something else to do. Zero
+ // means the default is an error.
+ static const short yydefact_[];
+
+ // YYPGOTO[NTERM-NUM].
+ static const short yypgoto_[];
+
+ // YYDEFGOTO[NTERM-NUM].
+ static const short yydefgoto_[];
+
+ // YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ // positive, shift that token. If negative, reduce the rule whose
+ // number is the opposite. If YYTABLE_NINF, syntax error.
+ static const short yytable_[];
+
+ static const short yycheck_[];
+
+ // YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of
+ // state STATE-NUM.
+ static const unsigned char yystos_[];
+
+ // YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM.
+ static const unsigned char yyr1_[];
+
+ // YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM.
+ static const signed char yyr2_[];
+
+
+#if D2_PARSER_DEBUG
+ // YYRLINE[YYN] -- Source line where rule number YYN was defined.
+ static const short yyrline_[];
+ /// Report on the debug stream that the rule \a r is going to be reduced.
+ virtual void yy_reduce_print_ (int r) const;
+ /// Print the state stack on the debug stream.
+ virtual void yy_stack_print_ () const;
+
+ /// Debugging level.
+ int yydebug_;
+ /// Debug stream.
+ std::ostream* yycdebug_;
+
+ /// \brief Display a symbol kind, value and location.
+ /// \param yyo The output stream.
+ /// \param yysym The symbol.
+ template <typename Base>
+ void yy_print_ (std::ostream& yyo, const basic_symbol<Base>& yysym) const;
+#endif
+
+ /// \brief Reclaim the memory associated to a symbol.
+ /// \param yymsg Why this token is reclaimed.
+ /// If null, print nothing.
+ /// \param yysym The symbol.
+ template <typename Base>
+ void yy_destroy_ (const char* yymsg, basic_symbol<Base>& yysym) const;
+
+ private:
+ /// Type access provider for state based symbols.
+ struct by_state
+ {
+ /// Default constructor.
+ by_state () YY_NOEXCEPT;
+
+ /// The symbol kind as needed by the constructor.
+ typedef state_type kind_type;
+
+ /// Constructor.
+ by_state (kind_type s) YY_NOEXCEPT;
+
+ /// Copy constructor.
+ by_state (const by_state& that) YY_NOEXCEPT;
+
+ /// Record that this symbol is empty.
+ void clear () YY_NOEXCEPT;
+
+ /// Steal the symbol kind from \a that.
+ void move (by_state& that);
+
+ /// The symbol kind (corresponding to \a state).
+ /// \a symbol_kind::S_YYEMPTY when empty.
+ symbol_kind_type kind () const YY_NOEXCEPT;
+
+ /// The state number used to denote an empty symbol.
+ /// We use the initial state, as it does not have a value.
+ enum { empty_state = 0 };
+
+ /// The state.
+ /// \a empty when empty.
+ state_type state;
+ };
+
+ /// "Internal" symbol: element of the stack.
+ struct stack_symbol_type : basic_symbol<by_state>
+ {
+ /// Superclass.
+ typedef basic_symbol<by_state> super_type;
+ /// Construct an empty symbol.
+ stack_symbol_type ();
+ /// Move or copy construction.
+ stack_symbol_type (YY_RVREF (stack_symbol_type) that);
+ /// Steal the contents from \a sym to build this.
+ stack_symbol_type (state_type s, YY_MOVE_REF (symbol_type) sym);
+#if YY_CPLUSPLUS < 201103L
+ /// Assignment, needed by push_back by some old implementations.
+ /// Moves the contents of that.
+ stack_symbol_type& operator= (stack_symbol_type& that);
+
+ /// Assignment, needed by push_back by other implementations.
+ /// Needed by some other old implementations.
+ stack_symbol_type& operator= (const stack_symbol_type& that);
+#endif
+ };
+
+ /// A stack with random access from its top.
+ template <typename T, typename S = std::vector<T> >
+ class stack
+ {
+ public:
+ // Hide our reversed order.
+ typedef typename S::iterator iterator;
+ typedef typename S::const_iterator const_iterator;
+ typedef typename S::size_type size_type;
+ typedef typename std::ptrdiff_t index_type;
+
+ stack (size_type n = 200) YY_NOEXCEPT
+ : seq_ (n)
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ /// Non copyable.
+ stack (const stack&) = delete;
+ /// Non copyable.
+ stack& operator= (const stack&) = delete;
+#endif
+
+ /// Random access.
+ ///
+ /// Index 0 returns the topmost element.
+ const T&
+ operator[] (index_type i) const
+ {
+ return seq_[size_type (size () - 1 - i)];
+ }
+
+ /// Random access.
+ ///
+ /// Index 0 returns the topmost element.
+ T&
+ operator[] (index_type i)
+ {
+ return seq_[size_type (size () - 1 - i)];
+ }
+
+ /// Steal the contents of \a t.
+ ///
+ /// Close to move-semantics.
+ void
+ push (YY_MOVE_REF (T) t)
+ {
+ seq_.push_back (T ());
+ operator[] (0).move (t);
+ }
+
+ /// Pop elements from the stack.
+ void
+ pop (std::ptrdiff_t n = 1) YY_NOEXCEPT
+ {
+ for (; 0 < n; --n)
+ seq_.pop_back ();
+ }
+
+ /// Pop all elements from the stack.
+ void
+ clear () YY_NOEXCEPT
+ {
+ seq_.clear ();
+ }
+
+ /// Number of elements on the stack.
+ index_type
+ size () const YY_NOEXCEPT
+ {
+ return index_type (seq_.size ());
+ }
+
+ /// Iterator on top of the stack (going downwards).
+ const_iterator
+ begin () const YY_NOEXCEPT
+ {
+ return seq_.begin ();
+ }
+
+ /// Bottom of the stack.
+ const_iterator
+ end () const YY_NOEXCEPT
+ {
+ return seq_.end ();
+ }
+
+ /// Present a slice of the top of a stack.
+ class slice
+ {
+ public:
+ slice (const stack& stack, index_type range) YY_NOEXCEPT
+ : stack_ (stack)
+ , range_ (range)
+ {}
+
+ const T&
+ operator[] (index_type i) const
+ {
+ return stack_[range_ - i];
+ }
+
+ private:
+ const stack& stack_;
+ index_type range_;
+ };
+
+ private:
+#if YY_CPLUSPLUS < 201103L
+ /// Non copyable.
+ stack (const stack&);
+ /// Non copyable.
+ stack& operator= (const stack&);
+#endif
+ /// The wrapped container.
+ S seq_;
+ };
+
+
+ /// Stack type.
+ typedef stack<stack_symbol_type> stack_type;
+
+ /// The stack.
+ stack_type yystack_;
+
+ /// Push a new state on the stack.
+ /// \param m a debug message to display
+ /// if null, no trace is output.
+ /// \param sym the symbol
+ /// \warning the contents of \a s.value is stolen.
+ void yypush_ (const char* m, YY_MOVE_REF (stack_symbol_type) sym);
+
+ /// Push a new look ahead token on the state on the stack.
+ /// \param m a debug message to display
+ /// if null, no trace is output.
+ /// \param s the state
+ /// \param sym the symbol (for its value and location).
+ /// \warning the contents of \a sym.value is stolen.
+ void yypush_ (const char* m, state_type s, YY_MOVE_REF (symbol_type) sym);
+
+ /// Pop \a n symbols from the stack.
+ void yypop_ (int n = 1) YY_NOEXCEPT;
+
+ /// Constants.
+ enum
+ {
+ yylast_ = 307, ///< Last index in yytable_.
+ yynnts_ = 152, ///< Number of nonterminal symbols.
+ yyfinal_ = 22 ///< Termination state number.
+ };
+
+
+ // User arguments.
+ isc::d2::D2ParserContext& ctx;
+
+ };
+
+ inline
+ D2Parser::symbol_kind_type
+ D2Parser::yytranslate_ (int t) YY_NOEXCEPT
+ {
+ // YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to
+ // TOKEN-NUM as returned by yylex.
+ static
+ const signed char
+ translate_table[] =
+ {
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60
+ };
+ // Last valid token kind.
+ const int code_max = 315;
+
+ if (t <= 0)
+ return symbol_kind::S_YYEOF;
+ else if (t <= code_max)
+ return static_cast <symbol_kind_type> (translate_table[t]);
+ else
+ return symbol_kind::S_YYUNDEF;
+ }
+
+ // basic_symbol.
+ template <typename Base>
+ D2Parser::basic_symbol<Base>::basic_symbol (const basic_symbol& that)
+ : Base (that)
+ , value ()
+ , location (that.location)
+ {
+ switch (this->kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.copy< ElementPtr > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.copy< bool > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.copy< double > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.copy< int64_t > (YY_MOVE (that.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.copy< std::string > (YY_MOVE (that.value));
+ break;
+
+ default:
+ break;
+ }
+
+ }
+
+
+
+
+ template <typename Base>
+ D2Parser::symbol_kind_type
+ D2Parser::basic_symbol<Base>::type_get () const YY_NOEXCEPT
+ {
+ return this->kind ();
+ }
+
+
+ template <typename Base>
+ bool
+ D2Parser::basic_symbol<Base>::empty () const YY_NOEXCEPT
+ {
+ return this->kind () == symbol_kind::S_YYEMPTY;
+ }
+
+ template <typename Base>
+ void
+ D2Parser::basic_symbol<Base>::move (basic_symbol& s)
+ {
+ super_type::move (s);
+ switch (this->kind ())
+ {
+ case symbol_kind::S_value: // value
+ case symbol_kind::S_map_value: // map_value
+ case symbol_kind::S_ncr_protocol_value: // ncr_protocol_value
+ value.move< ElementPtr > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_BOOLEAN: // "boolean"
+ value.move< bool > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_FLOAT: // "floating point"
+ value.move< double > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_INTEGER: // "integer"
+ value.move< int64_t > (YY_MOVE (s.value));
+ break;
+
+ case symbol_kind::S_STRING: // "constant string"
+ value.move< std::string > (YY_MOVE (s.value));
+ break;
+
+ default:
+ break;
+ }
+
+ location = YY_MOVE (s.location);
+ }
+
+ // by_kind.
+ inline
+ D2Parser::by_kind::by_kind () YY_NOEXCEPT
+ : kind_ (symbol_kind::S_YYEMPTY)
+ {}
+
+#if 201103L <= YY_CPLUSPLUS
+ inline
+ D2Parser::by_kind::by_kind (by_kind&& that) YY_NOEXCEPT
+ : kind_ (that.kind_)
+ {
+ that.clear ();
+ }
+#endif
+
+ inline
+ D2Parser::by_kind::by_kind (const by_kind& that) YY_NOEXCEPT
+ : kind_ (that.kind_)
+ {}
+
+ inline
+ D2Parser::by_kind::by_kind (token_kind_type t) YY_NOEXCEPT
+ : kind_ (yytranslate_ (t))
+ {}
+
+
+
+ inline
+ void
+ D2Parser::by_kind::clear () YY_NOEXCEPT
+ {
+ kind_ = symbol_kind::S_YYEMPTY;
+ }
+
+ inline
+ void
+ D2Parser::by_kind::move (by_kind& that)
+ {
+ kind_ = that.kind_;
+ that.clear ();
+ }
+
+ inline
+ D2Parser::symbol_kind_type
+ D2Parser::by_kind::kind () const YY_NOEXCEPT
+ {
+ return kind_;
+ }
+
+
+ inline
+ D2Parser::symbol_kind_type
+ D2Parser::by_kind::type_get () const YY_NOEXCEPT
+ {
+ return this->kind ();
+ }
+
+
+#line 14 "d2_parser.yy"
+} } // isc::d2
+#line 2641 "d2_parser.h"
+
+
+
+
+#endif // !YY_D2_PARSER_D2_PARSER_H_INCLUDED
diff --git a/src/bin/d2/d2_parser.yy b/src/bin/d2/d2_parser.yy
new file mode 100644
index 0000000..ddee8c1
--- /dev/null
+++ b/src/bin/d2/d2_parser.yy
@@ -0,0 +1,993 @@
+/* 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/. */
+
+%skeleton "lalr1.cc" /* -*- C++ -*- */
+%require "3.3.0"
+%defines
+%define api.parser.class {D2Parser}
+%define api.prefix {d2_parser_}
+%define api.token.constructor
+%define api.value.type variant
+%define api.namespace {isc::d2}
+%define parse.assert
+%code requires
+{
+#include <string>
+#include <cc/data.h>
+#include <d2srv/d2_config.h>
+#include <boost/lexical_cast.hpp>
+#include <d2/parser_context_decl.h>
+
+using namespace isc::d2;
+using namespace isc::data;
+using namespace std;
+}
+// The parsing context.
+%param { isc::d2::D2ParserContext& ctx }
+%locations
+%define parse.trace
+%define parse.error verbose
+%code
+{
+#include <d2/parser_context.h>
+}
+
+
+%define api.token.prefix {TOKEN_}
+// Tokens in an order which makes sense and related to the intended use.
+// Actual regexps for tokens are defined in d2_lexer.ll.
+%token
+ END 0 "end of file"
+ COMMA ","
+ COLON ":"
+ LSQUARE_BRACKET "["
+ RSQUARE_BRACKET "]"
+ LCURLY_BRACKET "{"
+ RCURLY_BRACKET "}"
+ NULL_TYPE "null"
+
+ DHCPDDNS "DhcpDdns"
+ IP_ADDRESS "ip-address"
+ PORT "port"
+ DNS_SERVER_TIMEOUT "dns-server-timeout"
+ NCR_PROTOCOL "ncr-protocol"
+ UDP "UDP"
+ TCP "TCP"
+ NCR_FORMAT "ncr-format"
+ JSON "JSON"
+ USER_CONTEXT "user-context"
+ COMMENT "comment"
+ FORWARD_DDNS "forward-ddns"
+ REVERSE_DDNS "reverse-ddns"
+ DDNS_DOMAINS "ddns-domains"
+ KEY_NAME "key-name"
+ DNS_SERVERS "dns-servers"
+ HOSTNAME "hostname"
+ TSIG_KEYS "tsig-keys"
+ ALGORITHM "algorithm"
+ DIGEST_BITS "digest-bits"
+ SECRET "secret"
+
+ CONTROL_SOCKET "control-socket"
+ SOCKET_TYPE "socket-type"
+ SOCKET_NAME "socket-name"
+
+ HOOKS_LIBRARIES "hooks-libraries"
+ LIBRARY "library"
+ PARAMETERS "parameters"
+
+ LOGGERS "loggers"
+ NAME "name"
+ OUTPUT_OPTIONS "output_options"
+ OUTPUT "output"
+ DEBUGLEVEL "debuglevel"
+ SEVERITY "severity"
+ FLUSH "flush"
+ MAXSIZE "maxsize"
+ MAXVER "maxver"
+ PATTERN "pattern"
+
+ // Not real tokens, just a way to signal what the parser is expected to
+ // parse.
+ TOPLEVEL_JSON
+ TOPLEVEL_DHCPDDNS
+ SUB_DHCPDDNS
+ SUB_TSIG_KEY
+ SUB_TSIG_KEYS
+ SUB_DDNS_DOMAIN
+ SUB_DDNS_DOMAINS
+ SUB_DNS_SERVER
+ SUB_DNS_SERVERS
+ SUB_HOOKS_LIBRARY
+;
+
+%token <std::string> STRING "constant string"
+%token <int64_t> INTEGER "integer"
+%token <double> FLOAT "floating point"
+%token <bool> BOOLEAN "boolean"
+
+%type <ElementPtr> value
+%type <ElementPtr> map_value
+%type <ElementPtr> ncr_protocol_value
+
+%printer { yyoutput << $$; } <*>;
+
+%%
+
+// The whole grammar starts with a map, because the config file
+// consists of Dhcp, Logger and DhcpDdns entries in one big { }.
+// We made the same for subparsers at the exception of the JSON value.
+%start start;
+
+start: TOPLEVEL_JSON { ctx.ctx_ = ctx.NO_KEYWORD; } sub_json
+ | TOPLEVEL_DHCPDDNS { ctx.ctx_ = ctx.CONFIG; } syntax_map
+ | SUB_DHCPDDNS { ctx.ctx_ = ctx.DHCPDDNS; } sub_dhcpddns
+ | SUB_TSIG_KEY { ctx.ctx_ = ctx.TSIG_KEY; } sub_tsig_key
+ | SUB_TSIG_KEYS { ctx.ctx_ = ctx.TSIG_KEYS; } sub_tsig_keys
+ | SUB_DDNS_DOMAIN { ctx.ctx_ = ctx.DDNS_DOMAIN; } sub_ddns_domain
+ | SUB_DDNS_DOMAINS { ctx.ctx_ = ctx.DDNS_DOMAINS; } sub_ddns_domains
+ | SUB_DNS_SERVER { ctx.ctx_ = ctx.DNS_SERVERS; } sub_dns_server
+ | SUB_DNS_SERVERS { ctx.ctx_ = ctx.DNS_SERVERS; } sub_dns_servers
+ | SUB_HOOKS_LIBRARY { ctx.ctx_ = ctx.HOOKS_LIBRARIES; } sub_hooks_library
+ ;
+
+// ---- generic JSON parser ---------------------------------
+
+// Note that ctx_ is NO_KEYWORD here
+
+// Values rule
+value: INTEGER { $$ = ElementPtr(new IntElement($1, ctx.loc2pos(@1))); }
+ | FLOAT { $$ = ElementPtr(new DoubleElement($1, ctx.loc2pos(@1))); }
+ | BOOLEAN { $$ = ElementPtr(new BoolElement($1, ctx.loc2pos(@1))); }
+ | STRING { $$ = ElementPtr(new StringElement($1, ctx.loc2pos(@1))); }
+ | NULL_TYPE { $$ = ElementPtr(new NullElement(ctx.loc2pos(@1))); }
+ | map2 { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+ | list_generic { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+ ;
+
+sub_json: value {
+ // Push back the JSON value on the stack
+ ctx.stack_.push_back($1);
+};
+
+map2: LCURLY_BRACKET {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} map_content RCURLY_BRACKET {
+ // map parsing completed. If we ever want to do any wrap up
+ // (maybe some sanity checking), this would be the best place
+ // for it.
+};
+
+map_value: map2 { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); };
+
+// Assignments rule
+map_content: %empty // empty map
+ | not_empty_map
+ ;
+
+not_empty_map: STRING COLON value {
+ // map containing a single entry
+ ctx.unique($1, ctx.loc2pos(@1));
+ ctx.stack_.back()->set($1, $3);
+ }
+ | not_empty_map COMMA STRING COLON value {
+ // map consisting of a shorter map followed by
+ // comma and string:value
+ ctx.unique($3, ctx.loc2pos(@3));
+ ctx.stack_.back()->set($3, $5);
+ }
+ | not_empty_map COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+list_generic: LSQUARE_BRACKET {
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(l);
+} list_content RSQUARE_BRACKET {
+ // list parsing complete. Put any sanity checking here
+};
+
+list_content: %empty // Empty list
+ | not_empty_list
+ ;
+
+not_empty_list: value {
+ // List consisting of a single element.
+ ctx.stack_.back()->add($1);
+ }
+ | not_empty_list COMMA value {
+ // List ending with , and a value.
+ ctx.stack_.back()->add($3);
+ }
+ | not_empty_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// ---- generic JSON parser ends here ----------------------------------
+
+// ---- syntax checking parser starts here -----------------------------
+
+// Unknown keyword in a map
+unknown_map_entry: STRING COLON {
+ const std::string& where = ctx.contextName();
+ const std::string& keyword = $1;
+ error(@1,
+ "got unexpected keyword \"" + keyword + "\" in " + where + " map.");
+};
+
+
+// This defines the top-level { } that holds only DhcpDdns object.
+syntax_map: LCURLY_BRACKET {
+ // This code is executed when we're about to start parsing
+ // the content of the map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} global_object RCURLY_BRACKET {
+ // map parsing completed. If we ever want to do any wrap up
+ // (maybe some sanity checking), this would be the best place
+ // for it.
+};
+
+// --- dhcp ddns ---------------------------------------------
+// This represents the single top level entry, e.g. DhcpDdns.
+global_object: DHCPDDNS {
+ ctx.unique("DhcpDdns", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("DhcpDdns", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.DHCPDDNS);
+} COLON LCURLY_BRACKET dhcpddns_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+}
+ | global_object_comma
+ ;
+
+global_object_comma: global_object COMMA {
+ ctx.warnAboutExtraCommas(@2);
+};
+
+sub_dhcpddns: LCURLY_BRACKET {
+ // Parse the dhcpddns map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} dhcpddns_params RCURLY_BRACKET {
+ // parsing completed
+};
+
+dhcpddns_params: dhcpddns_param
+ | dhcpddns_params COMMA dhcpddns_param
+ | dhcpddns_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// These are the top-level parameters allowed for DhcpDdns
+dhcpddns_param: ip_address
+ | port
+ | dns_server_timeout
+ | ncr_protocol
+ | ncr_format
+ | forward_ddns
+ | reverse_ddns
+ | tsig_keys
+ | control_socket
+ | hooks_libraries
+ | loggers
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+ip_address: IP_ADDRESS {
+ ctx.unique("ip-address", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("ip-address", s);
+ ctx.leave();
+};
+
+port: PORT COLON INTEGER {
+ ctx.unique("port", ctx.loc2pos(@1));
+ if ($3 <= 0 || $3 >= 65536 ) {
+ error(@3, "port must be greater than zero but less than 65536");
+ }
+ ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("port", i);
+};
+
+dns_server_timeout: DNS_SERVER_TIMEOUT COLON INTEGER {
+ ctx.unique("dns-server-timeout", ctx.loc2pos(@1));
+ if ($3 <= 0) {
+ error(@3, "dns-server-timeout must be greater than zero");
+ } else {
+ ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("dns-server-timeout", i);
+ }
+};
+
+ncr_protocol: NCR_PROTOCOL {
+ ctx.unique("ncr-protocol", ctx.loc2pos(@1));
+ ctx.enter(ctx.NCR_PROTOCOL);
+} COLON ncr_protocol_value {
+ ctx.stack_.back()->set("ncr-protocol", $4);
+ ctx.leave();
+};
+
+ncr_protocol_value:
+ UDP { $$ = ElementPtr(new StringElement("UDP", ctx.loc2pos(@1))); }
+ | TCP { $$ = ElementPtr(new StringElement("TCP", ctx.loc2pos(@1))); }
+ ;
+
+ncr_format: NCR_FORMAT {
+ ctx.unique("ncr-format", ctx.loc2pos(@1));
+ ctx.enter(ctx.NCR_FORMAT);
+} COLON JSON {
+ ElementPtr json(new StringElement("JSON", ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("ncr-format", json);
+ ctx.leave();
+};
+
+user_context: USER_CONTEXT {
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON map_value {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context = $4;
+ ConstElementPtr old = parent->get("user-context");
+
+ // Handle already existing user context
+ if (old) {
+ // Check if it was a comment or a duplicate
+ if ((old->size() != 1) || !old->contains("comment")) {
+ std::stringstream msg;
+ msg << "duplicate user-context entries (previous at "
+ << old->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+ // Merge the comment
+ user_context->set("comment", old->get("comment"));
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+};
+
+comment: COMMENT {
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr parent = ctx.stack_.back();
+ ElementPtr user_context(new MapElement(ctx.loc2pos(@1)));
+ ElementPtr comment(new StringElement($4, ctx.loc2pos(@4)));
+ user_context->set("comment", comment);
+
+ // Handle already existing user context
+ ConstElementPtr old = parent->get("user-context");
+ if (old) {
+ // Check for duplicate comment
+ if (old->contains("comment")) {
+ std::stringstream msg;
+ msg << "duplicate user-context/comment entries (previous at "
+ << old->getPosition().str() << ")";
+ error(@1, msg.str());
+ }
+ // Merge the user context in the comment
+ merge(user_context, old);
+ }
+
+ // Set the user context
+ parent->set("user-context", user_context);
+ ctx.leave();
+};
+
+forward_ddns : FORWARD_DDNS {
+ ctx.unique("forward-ddns", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("forward-ddns", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.FORWARD_DDNS);
+} COLON LCURLY_BRACKET ddns_mgr_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+reverse_ddns : REVERSE_DDNS {
+ ctx.unique("reverse-ddns", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("reverse-ddns", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.REVERSE_DDNS);
+} COLON LCURLY_BRACKET ddns_mgr_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+ddns_mgr_params: %empty
+ | not_empty_ddns_mgr_params
+ ;
+
+not_empty_ddns_mgr_params: ddns_mgr_param
+ | ddns_mgr_params COMMA ddns_mgr_param
+ | ddns_mgr_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+ddns_mgr_param: ddns_domains
+ | unknown_map_entry
+ ;
+
+
+// --- ddns-domains ----------------------------------------
+ddns_domains: DDNS_DOMAINS {
+ ctx.unique("ddns-domains", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("ddns-domains", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.DDNS_DOMAINS);
+} COLON LSQUARE_BRACKET ddns_domain_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+sub_ddns_domains: LSQUARE_BRACKET {
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(l);
+} ddns_domain_list RSQUARE_BRACKET {
+ // parsing completed
+}
+
+ddns_domain_list: %empty
+ | not_empty_ddns_domain_list
+ ;
+
+not_empty_ddns_domain_list: ddns_domain
+ | not_empty_ddns_domain_list COMMA ddns_domain
+ | not_empty_ddns_domain_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+ddns_domain: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} ddns_domain_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+sub_ddns_domain: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} ddns_domain_params RCURLY_BRACKET {
+ // parsing completed
+};
+
+ddns_domain_params: ddns_domain_param
+ | ddns_domain_params COMMA ddns_domain_param
+ | ddns_domain_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+ddns_domain_param: ddns_domain_name
+ | ddns_key_name
+ | dns_servers
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+// @todo NAME needs to be an FQDN sort of thing
+ddns_domain_name: NAME {
+ ctx.unique("name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ if ($4 == "") {
+ error(@3, "Ddns domain name cannot be blank");
+ }
+ ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+};
+
+ddns_key_name: KEY_NAME {
+ ctx.unique("key-name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("key-name", name);
+ ctx.leave();
+};
+
+// --- end ddns-domains ----------------------------------------
+
+// --- dns-servers ----------------------------------------
+dns_servers: DNS_SERVERS {
+ ctx.unique("dns-servers", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("dns-servers", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.DNS_SERVERS);
+} COLON LSQUARE_BRACKET dns_server_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+sub_dns_servers: LSQUARE_BRACKET {
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(l);
+} dns_server_list RSQUARE_BRACKET {
+ // parsing completed
+}
+
+dns_server_list: dns_server
+ | dns_server_list COMMA dns_server
+ | dns_server_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+dns_server: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} dns_server_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+sub_dns_server: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} dns_server_params RCURLY_BRACKET {
+ // parsing completed
+};
+
+dns_server_params: dns_server_param
+ | dns_server_params COMMA dns_server_param
+ | dns_server_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+dns_server_param: dns_server_hostname
+ | dns_server_ip_address
+ | dns_server_port
+ | ddns_key_name
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+dns_server_hostname: HOSTNAME {
+ ctx.unique("hostname", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ if ($4 != "") {
+ error(@3, "hostname is not yet supported");
+ }
+ ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("hostname", name);
+ ctx.leave();
+};
+
+dns_server_ip_address: IP_ADDRESS {
+ ctx.unique("ip-address", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("ip-address", s);
+ ctx.leave();
+};
+
+dns_server_port: PORT COLON INTEGER {
+ ctx.unique("port", ctx.loc2pos(@1));
+ if ($3 <= 0 || $3 >= 65536 ) {
+ error(@3, "port must be greater than zero but less than 65536");
+ }
+ ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("port", i);
+};
+
+// --- end of dns-servers ---------------------------------
+
+
+
+// --- tsig-keys ----------------------------------------
+// "tsig-keys" : [ ... ]
+tsig_keys: TSIG_KEYS {
+ ctx.unique("tsig-keys", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("tsig-keys", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.TSIG_KEYS);
+} COLON LSQUARE_BRACKET tsig_keys_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+sub_tsig_keys: LSQUARE_BRACKET {
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(l);
+} tsig_keys_list RSQUARE_BRACKET {
+ // parsing completed
+}
+
+tsig_keys_list: %empty
+ | not_empty_tsig_keys_list
+ ;
+
+not_empty_tsig_keys_list: tsig_key
+ | not_empty_tsig_keys_list COMMA tsig_key
+ | not_empty_tsig_keys_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+tsig_key: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} tsig_key_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+sub_tsig_key: LCURLY_BRACKET {
+ // Parse tsig key list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} tsig_key_params RCURLY_BRACKET {
+ // parsing completed
+};
+
+
+tsig_key_params: tsig_key_param
+ | tsig_key_params COMMA tsig_key_param
+ | tsig_key_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+tsig_key_param: tsig_key_name
+ | tsig_key_algorithm
+ | tsig_key_digest_bits
+ | tsig_key_secret
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+tsig_key_name: NAME {
+ ctx.unique("name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ if ($4 == "") {
+ error(@3, "TSIG key name cannot be blank");
+ }
+ ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+};
+
+tsig_key_algorithm: ALGORITHM {
+ ctx.unique("algorithm", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ if ($4 == "") {
+ error(@3, "TSIG key algorithm cannot be blank");
+ }
+ ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("algorithm", elem);
+ ctx.leave();
+};
+
+tsig_key_digest_bits: DIGEST_BITS COLON INTEGER {
+ ctx.unique("digest-bits", ctx.loc2pos(@1));
+ if ($3 < 0 || ($3 > 0 && ($3 % 8 != 0))) {
+ error(@3, "TSIG key digest-bits must either be zero or a positive, multiple of eight");
+ }
+ ElementPtr elem(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("digest-bits", elem);
+};
+
+tsig_key_secret: SECRET {
+ ctx.unique("secret", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ if ($4 == "") {
+ error(@3, "TSIG key secret cannot be blank");
+ }
+ ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("secret", elem);
+ ctx.leave();
+};
+
+
+// --- end of tsig-keys ---------------------------------
+
+// --- control socket ----------------------------------------
+
+control_socket: CONTROL_SOCKET {
+ ctx.unique("control-socket", ctx.loc2pos(@1));
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("control-socket", m);
+ ctx.stack_.push_back(m);
+ ctx.enter(ctx.CONTROL_SOCKET);
+} COLON LCURLY_BRACKET control_socket_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+control_socket_params: control_socket_param
+ | control_socket_params COMMA control_socket_param
+ | control_socket_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+control_socket_param: control_socket_type
+ | control_socket_name
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+control_socket_type: SOCKET_TYPE {
+ ctx.unique("socket-type", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr stype(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("socket-type", stype);
+ ctx.leave();
+};
+
+control_socket_name: SOCKET_NAME {
+ ctx.unique("socket-name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("socket-name", name);
+ ctx.leave();
+};
+
+// --- hooks libraries -----------------------------------------
+
+hooks_libraries: HOOKS_LIBRARIES {
+ ctx.unique("hooks-libraries", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("hooks-libraries", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.HOOKS_LIBRARIES);
+} COLON LSQUARE_BRACKET hooks_libraries_list RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+hooks_libraries_list: %empty
+ | not_empty_hooks_libraries_list
+ ;
+
+not_empty_hooks_libraries_list: hooks_library
+ | not_empty_hooks_libraries_list COMMA hooks_library
+ | not_empty_hooks_libraries_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+hooks_library: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} hooks_params RCURLY_BRACKET {
+ // The library hooks parameter is required
+ ctx.require("library", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ ctx.stack_.pop_back();
+};
+
+sub_hooks_library: LCURLY_BRACKET {
+ // Parse the hooks-libraries list entry map
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.push_back(m);
+} hooks_params RCURLY_BRACKET {
+ // The library hooks parameter is required
+ ctx.require("library", ctx.loc2pos(@1), ctx.loc2pos(@4));
+ // parsing completed
+};
+
+hooks_params: hooks_param
+ | hooks_params COMMA hooks_param
+ | hooks_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ | unknown_map_entry
+ ;
+
+hooks_param: library
+ | parameters
+ ;
+
+library: LIBRARY {
+ ctx.unique("library", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr lib(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("library", lib);
+ ctx.leave();
+};
+
+parameters: PARAMETERS {
+ ctx.unique("parameters", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON map_value {
+ ctx.stack_.back()->set("parameters", $4);
+ ctx.leave();
+};
+
+// --- loggers entry -----------------------------------------
+
+loggers: LOGGERS {
+ ctx.unique("loggers", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("loggers", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.LOGGERS);
+} COLON LSQUARE_BRACKET loggers_entries RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+// These are the parameters allowed in loggers: either one logger
+// entry or multiple entries separate by commas.
+loggers_entries: logger_entry
+ | loggers_entries COMMA logger_entry
+ | loggers_entries COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+// This defines a single entry defined in loggers.
+logger_entry: LCURLY_BRACKET {
+ ElementPtr l(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(l);
+ ctx.stack_.push_back(l);
+} logger_params RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+logger_params: logger_param
+ | logger_params COMMA logger_param
+ | logger_params COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+logger_param: name
+ | output_options_list
+ | debuglevel
+ | severity
+ | user_context
+ | comment
+ | unknown_map_entry
+ ;
+
+name: NAME {
+ ctx.unique("name", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("name", name);
+ ctx.leave();
+};
+
+debuglevel: DEBUGLEVEL COLON INTEGER {
+ ctx.unique("debuglevel", ctx.loc2pos(@1));
+ ElementPtr dl(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("debuglevel", dl);
+};
+
+severity: SEVERITY {
+ ctx.unique("severity", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("severity", sev);
+ ctx.leave();
+};
+
+output_options_list: OUTPUT_OPTIONS {
+ ctx.unique("output_options", ctx.loc2pos(@1));
+ ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("output_options", l);
+ ctx.stack_.push_back(l);
+ ctx.enter(ctx.OUTPUT_OPTIONS);
+} COLON LSQUARE_BRACKET output_options_list_content RSQUARE_BRACKET {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
+output_options_list_content: output_entry
+ | output_options_list_content COMMA output_entry
+ | output_options_list_content COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+output_entry: LCURLY_BRACKET {
+ ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->add(m);
+ ctx.stack_.push_back(m);
+} output_params_list RCURLY_BRACKET {
+ ctx.stack_.pop_back();
+};
+
+output_params_list: output_params
+ | output_params_list COMMA output_params
+ | output_params_list COMMA {
+ ctx.warnAboutExtraCommas(@2);
+ }
+ ;
+
+output_params: output
+ | flush
+ | maxsize
+ | maxver
+ | pattern
+ ;
+
+output: OUTPUT {
+ ctx.unique("output", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("output", sev);
+ ctx.leave();
+};
+
+flush: FLUSH COLON BOOLEAN {
+ ctx.unique("flush", ctx.loc2pos(@1));
+ ElementPtr flush(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("flush", flush);
+}
+
+maxsize: MAXSIZE COLON INTEGER {
+ ctx.unique("maxsize", ctx.loc2pos(@1));
+ ElementPtr maxsize(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("maxsize", maxsize);
+}
+
+maxver: MAXVER COLON INTEGER {
+ ctx.unique("maxver", ctx.loc2pos(@1));
+ ElementPtr maxver(new IntElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("maxver", maxver);
+}
+
+pattern: PATTERN {
+ ctx.unique("pattern", ctx.loc2pos(@1));
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+ ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+ ctx.stack_.back()->set("pattern", sev);
+ ctx.leave();
+};
+
+%%
+
+void
+isc::d2::D2Parser::error(const location_type& loc,
+ const std::string& what)
+{
+ ctx.error(loc, what);
+}
diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc
new file mode 100644
index 0000000..c06e0be
--- /dev/null
+++ b/src/bin/d2/d2_process.cc
@@ -0,0 +1,502 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <asiolink/asio_wrapper.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_log.h>
+#include <d2srv/d2_stats.h>
+#include <d2srv/d2_tsig_key.h>
+#include <hooks/hooks.h>
+#include <hooks/hooks_manager.h>
+
+using namespace isc::hooks;
+using namespace isc::process;
+
+namespace {
+
+/// Structure that holds registered hook indexes.
+struct D2ProcessHooks {
+ int hooks_index_d2_srv_configured_;
+
+ /// Constructor that registers hook points for the D2 server.
+ D2ProcessHooks() {
+ hooks_index_d2_srv_configured_ = HooksManager::registerHook("d2_srv_configured");
+ }
+
+};
+
+// 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.
+D2ProcessHooks Hooks;
+
+}
+
+namespace isc {
+namespace d2 {
+
+// Setting to 80% for now. This is an arbitrary choice and should probably
+// be configurable.
+const unsigned int D2Process::QUEUE_RESTART_PERCENT = 80;
+
+D2Process::D2Process(const char* name, const asiolink::IOServicePtr& io_service)
+ : DProcessBase(name, io_service, DCfgMgrBasePtr(new D2CfgMgr())),
+ reconf_queue_flag_(false), shutdown_type_(SD_NORMAL) {
+
+ // Instantiate queue manager. Note that queue manager does not start
+ // listening at this point. That can only occur after configuration has
+ // been received. This means that until we receive the configuration,
+ // D2 will neither receive nor process NameChangeRequests.
+ // Pass in IOService for NCR IO event processing.
+ queue_mgr_.reset(new D2QueueMgr(getIoService()));
+
+ // Instantiate update manager.
+ // Pass in both queue manager and configuration manager.
+ // Pass in IOService for DNS update transaction IO event processing.
+ D2CfgMgrPtr tmp = getD2CfgMgr();
+ update_mgr_.reset(new D2UpdateMgr(queue_mgr_, tmp, getIoService()));
+
+ // Initialize stats manager.
+ D2Stats::init();
+};
+
+void
+D2Process::init() {
+ // CommandMgr uses IO service to run asynchronous socket operations.
+ isc::config::CommandMgr::instance().setIOService(getIoService());
+};
+
+void
+D2Process::run() {
+ LOG_INFO(d2_logger, DHCP_DDNS_STARTED).arg(VERSION);
+ D2ControllerPtr controller =
+ boost::dynamic_pointer_cast<D2Controller>(D2Controller::instance());
+ try {
+ // Now logging was initialized so commands can be registered.
+ controller->registerCommands();
+
+ // Loop forever until we are allowed to shutdown.
+ while (!canShutdown()) {
+ // Check on the state of the request queue. Take any
+ // actions necessary regarding it.
+ checkQueueStatus();
+
+ // Give update manager a time slice to queue new jobs and
+ // process finished ones.
+ update_mgr_->sweep();
+
+ // Wait on IO event(s) - block until one or more of the following
+ // has occurred:
+ // a. NCR message has been received
+ // b. Transaction IO has completed
+ // c. Interval timer expired
+ // d. Control channel event
+ // e. Something stopped IO service (runIO returns 0)
+ if (runIO() == 0) {
+ // Pretty sure this amounts to an unexpected stop and we
+ // should bail out now. Normal shutdowns do not utilize
+ // stopping the IOService.
+ isc_throw(DProcessBaseError,
+ "Primary IO service stopped unexpectedly");
+ }
+ }
+ } catch (const std::exception& ex) {
+ LOG_FATAL(d2_logger, DHCP_DDNS_FAILED).arg(ex.what());
+ controller->deregisterCommands();
+ isc_throw (DProcessBaseError,
+ "Process run method failed: " << ex.what());
+ }
+
+ /// @todo - if queue isn't empty, we may need to persist its contents
+ /// this might be the place to do it, once there is a persistence mgr.
+ /// This may also be better in checkQueueStatus.
+
+ controller->deregisterCommands();
+
+ LOG_DEBUG(d2_logger, isc::log::DBGLVL_START_SHUT, DHCP_DDNS_RUN_EXIT);
+
+};
+
+size_t
+D2Process::runIO() {
+ // We want to block until at least one handler is called. We'll use
+ // boost::asio::io_service directly for two reasons. First off
+ // asiolink::IOService::run_one is a void and boost::asio::io_service::stopped
+ // is not present in older versions of boost. We need to know if any
+ // handlers ran or if the io_service was stopped. That latter represents
+ // some form of error and the application cannot proceed with a stopped
+ // service. Secondly, asiolink::IOService does not provide the poll
+ // method. This is a handy method which runs all ready handlers without
+ // blocking.
+ asiolink::IOServicePtr& io = getIoService();
+ boost::asio::io_service& asio_io_service = io->get_io_service();
+
+ // Poll runs all that are ready. If none are ready it returns immediately
+ // with a count of zero.
+ size_t cnt = asio_io_service.poll();
+ if (!cnt) {
+ // Poll ran no handlers either none are ready or the service has been
+ // stopped. Either way, call run_one to wait for a IO event. If the
+ // service is stopped it will return immediately with a cnt of zero.
+ cnt = asio_io_service.run_one();
+ }
+
+ return (cnt);
+}
+
+bool
+D2Process::canShutdown() const {
+ bool all_clear = false;
+
+ // If we have been told to shutdown, find out if we are ready to do so.
+ if (shouldShutdown()) {
+ switch (shutdown_type_) {
+ case SD_NORMAL:
+ // For a normal shutdown we need to stop the queue manager but
+ // wait until we have finished all the transactions in progress.
+ all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
+ (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
+ && (update_mgr_->getTransactionCount() == 0));
+ break;
+
+ case SD_DRAIN_FIRST:
+ // For a drain first shutdown we need to stop the queue manager but
+ // process all of the requests in the receive queue first.
+ all_clear = (((queue_mgr_->getMgrState() != D2QueueMgr::RUNNING) &&
+ (queue_mgr_->getMgrState() != D2QueueMgr::STOPPING))
+ && (queue_mgr_->getQueueSize() == 0)
+ && (update_mgr_->getTransactionCount() == 0));
+ break;
+
+ case SD_NOW:
+ // Get out right now, no niceties.
+ all_clear = true;
+ break;
+
+ default:
+ // shutdown_type_ is an enum and should only be one of the above.
+ // if its getting through to this, something is whacked.
+ break;
+ }
+
+ if (all_clear) {
+ LOG_DEBUG(d2_logger, isc::log::DBGLVL_START_SHUT,
+ DHCP_DDNS_CLEARED_FOR_SHUTDOWN)
+ .arg(getShutdownTypeStr(shutdown_type_));
+ }
+ }
+
+ return (all_clear);
+}
+
+isc::data::ConstElementPtr
+D2Process::shutdown(isc::data::ConstElementPtr args) {
+ LOG_DEBUG(d2_logger, isc::log::DBGLVL_START_SHUT,
+ DHCP_DDNS_SHUTDOWN_COMMAND)
+ .arg(args ? args->str() : "(no arguments)");
+
+ // Default shutdown type is normal.
+ std::string type_str(getShutdownTypeStr(SD_NORMAL));
+ shutdown_type_ = SD_NORMAL;
+
+ if (args) {
+ if ((args->getType() == isc::data::Element::map) &&
+ args->contains("type")) {
+ type_str = args->get("type")->stringValue();
+
+ if (type_str == getShutdownTypeStr(SD_NORMAL)) {
+ shutdown_type_ = SD_NORMAL;
+ } else if (type_str == getShutdownTypeStr(SD_DRAIN_FIRST)) {
+ shutdown_type_ = SD_DRAIN_FIRST;
+ } else if (type_str == getShutdownTypeStr(SD_NOW)) {
+ shutdown_type_ = SD_NOW;
+ } else {
+ setShutdownFlag(false);
+ return (isc::config::createAnswer(1, "Invalid Shutdown type: "
+ + type_str));
+ }
+ }
+ }
+
+ // Set the base class's shutdown flag.
+ setShutdownFlag(true);
+ return (isc::config::createAnswer(0, "Shutdown initiated, type is: "
+ + type_str));
+}
+
+isc::data::ConstElementPtr
+D2Process::configure(isc::data::ConstElementPtr config_set, bool check_only) {
+ LOG_DEBUG(d2_logger, isc::log::DBGLVL_TRACE_BASIC, DHCP_DDNS_CONFIGURE)
+ .arg(check_only ? "check" : "update")
+ .arg(getD2CfgMgr()->redactConfig(config_set)->str());
+
+ isc::data::ConstElementPtr answer;
+ answer = getCfgMgr()->simpleParseConfig(config_set, check_only,
+ std::bind(&D2Process::reconfigureCommandChannel, this));
+ if (check_only) {
+ return (answer);
+ }
+
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+
+ if (rcode) {
+ // Non-zero means we got an invalid configuration, take no further
+ // action. In integrated mode, this will send a failed response back
+ // to the configuration backend.
+ reconf_queue_flag_ = false;
+ return (answer);
+ }
+
+ // Set the reconf_queue_flag to indicate that we need to reconfigure
+ // the queue manager. Reconfiguring the queue manager may be asynchronous
+ // and require one or more events to occur, therefore we set a flag
+ // indicating it needs to be done but we cannot do it here. It must
+ // be done over time, while events are being processed. Remember that
+ // the method we are in now is invoked as part of the configuration event
+ // callback. This means you can't wait for events here, you are already
+ // in one.
+ /// (@todo NOTE This could be turned into a bitmask of flags if we find other
+ /// things that need reconfiguration. It might also be useful if we
+ /// did some analysis to decide what if anything we need to do.)
+ reconf_queue_flag_ = true;
+
+ // This hook point notifies hooks libraries that the configuration of the
+ // D2 server has completed. It provides the hook library with the pointer
+ // to the common IO service object, new server configuration in the JSON
+ // format and with the pointer to the configuration storage where the
+ // parsed configuration is stored.
+ std::string error("");
+ if (HooksManager::calloutsPresent(Hooks.hooks_index_d2_srv_configured_)) {
+ CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
+
+ callout_handle->setArgument("io_context", getIoService());
+ callout_handle->setArgument("json_config", config_set);
+ callout_handle->setArgument("server_config",
+ getD2CfgMgr()->getD2CfgContext());
+ callout_handle->setArgument("error", error);
+
+ HooksManager::callCallouts(Hooks.hooks_index_d2_srv_configured_,
+ *callout_handle);
+
+ // The config can be rejected by a hook.
+ if (callout_handle->getStatus() == CalloutHandle::NEXT_STEP_DROP) {
+ callout_handle->getArgument("error", error);
+ LOG_ERROR(d2_logger, DHCP_DDNS_CONFIGURED_CALLOUT_DROP)
+ .arg(error);
+ reconf_queue_flag_ = false;
+ answer = isc::config::createAnswer(1, error);
+ return (answer);
+ }
+ }
+
+ // If we are here, configuration was valid, at least it parsed correctly
+ // and therefore contained no invalid values.
+ // Return the success answer from above.
+ return (answer);
+}
+
+void
+D2Process::checkQueueStatus() {
+ switch (queue_mgr_->getMgrState()){
+ case D2QueueMgr::RUNNING:
+ if (reconf_queue_flag_ || shouldShutdown()) {
+ /// If we need to reconfigure the queue manager or we have been
+ /// told to shutdown, then stop listening first. Stopping entails
+ /// canceling active listening which may generate an IO event, so
+ /// instigate the stop and get out.
+ try {
+ LOG_DEBUG(d2_logger, isc::log::DBGLVL_START_SHUT,
+ DHCP_DDNS_QUEUE_MGR_STOPPING)
+ .arg(reconf_queue_flag_ ? "reconfiguration" : "shutdown");
+ queue_mgr_->stopListening();
+ } catch (const isc::Exception& ex) {
+ // It is very unlikely that we would experience an error
+ // here, but theoretically possible.
+ LOG_ERROR(d2_logger, DHCP_DDNS_QUEUE_MGR_STOP_ERROR)
+ .arg(ex.what());
+ }
+ }
+ break;
+
+ case D2QueueMgr::STOPPED_QUEUE_FULL: {
+ /// Resume receiving once the queue has decreased by twenty
+ /// percent. This is an arbitrary choice.
+ /// @todo this value should probably be configurable.
+ size_t threshold = (((queue_mgr_->getMaxQueueSize()
+ * QUEUE_RESTART_PERCENT)) / 100);
+ if (queue_mgr_->getQueueSize() <= threshold) {
+ LOG_INFO (d2_logger, DHCP_DDNS_QUEUE_MGR_RESUMING)
+ .arg(threshold).arg(queue_mgr_->getMaxQueueSize());
+ try {
+ queue_mgr_->startListening();
+ } catch (const isc::Exception& ex) {
+ LOG_ERROR(d2_logger, DHCP_DDNS_QUEUE_MGR_RESUME_ERROR)
+ .arg(ex.what());
+ }
+ }
+
+ break;
+ }
+
+ case D2QueueMgr::STOPPED_RECV_ERROR:
+ /// If the receive error is not due to some fallout from shutting
+ /// down then we will attempt to recover by reconfiguring the listener.
+ /// This will close and destruct the current listener and make a new
+ /// one with new resources.
+ /// @todo This may need a safety valve such as retry count or a timer
+ /// to keep from endlessly retrying over and over, with little time
+ /// in between.
+ if (!shouldShutdown()) {
+ LOG_INFO (d2_logger, DHCP_DDNS_QUEUE_MGR_RECOVERING);
+ reconfigureQueueMgr();
+ }
+ break;
+
+ case D2QueueMgr::STOPPING:
+ /// We are waiting for IO to cancel, so this is a NOP.
+ /// @todo Possible timer for self-defense? We could conceivably
+ /// get into a condition where we never get the event, which would
+ /// leave us stuck in stopping. This is hugely unlikely but possible?
+ break;
+
+ default:
+ // If the reconfigure flag is set, then we are in a state now where
+ // we can do the reconfigure. In other words, we aren't RUNNING or
+ // STOPPING.
+ if (reconf_queue_flag_) {
+ LOG_DEBUG(d2_logger, isc::log::DBGLVL_TRACE_BASIC,
+ DHCP_DDNS_QUEUE_MGR_RECONFIGURING);
+ reconfigureQueueMgr();
+ }
+ break;
+ }
+}
+
+void
+D2Process::reconfigureQueueMgr() {
+ // Set reconfigure flag to false. We are only here because we have
+ // a valid configuration to work with so if we fail below, it will be
+ // an operational issue, such as a busy IP address. That will leave
+ // queue manager in INITTED state, which is fine.
+ // What we don't want is to continually attempt to reconfigure so set
+ // the flag false now.
+ /// @todo This method assumes only 1 type of listener. This will change
+ /// to support at least a TCP version, possibly some form of RDBMS listener
+ /// as well.
+ reconf_queue_flag_ = false;
+ try {
+ // Wipe out the current listener.
+ queue_mgr_->removeListener();
+
+ // Get the configuration parameters that affect Queue Manager.
+ const D2ParamsPtr& d2_params = getD2CfgMgr()->getD2Params();
+
+ /// Warn the user if the server address is not the loopback.
+ /// @todo Remove this once we provide a secure mechanism.
+ std::string ip_address = d2_params->getIpAddress().toText();
+ if (ip_address != "127.0.0.1" && ip_address != "::1") {
+ LOG_WARN(d2_logger, DHCP_DDNS_NOT_ON_LOOPBACK).arg(ip_address);
+ }
+
+ // Instantiate the listener.
+ if (d2_params->getNcrProtocol() == dhcp_ddns::NCR_UDP) {
+ queue_mgr_->initUDPListener(d2_params->getIpAddress(),
+ d2_params->getPort(),
+ d2_params->getNcrFormat(), true);
+ } else {
+ /// @todo Add TCP/IP once it's supported
+ // We should never get this far but if we do deal with it.
+ isc_throw(DProcessBaseError, "Unsupported NCR listener protocol:"
+ << dhcp_ddns::ncrProtocolToString(d2_params->
+ getNcrProtocol()));
+ }
+
+ // Now start it. This assumes that starting is a synchronous,
+ // blocking call that executes quickly.
+ /// @todo Should that change then we will have to expand the state model
+ /// to accommodate this.
+ queue_mgr_->startListening();
+ } catch (const isc::Exception& ex) {
+ // Queue manager failed to initialize and therefore not listening.
+ // This is most likely due to an unavailable IP address or port,
+ // which is a configuration issue.
+ LOG_ERROR(d2_logger, DHCP_DDNS_QUEUE_MGR_START_ERROR).arg(ex.what());
+ }
+}
+
+D2Process::~D2Process() {
+}
+
+D2CfgMgrPtr
+D2Process::getD2CfgMgr() {
+ // The base class gives a base class pointer to our configuration manager.
+ // Since we are D2, and we need D2 specific extensions, we need a pointer
+ // to D2CfgMgr for some things.
+ return (boost::dynamic_pointer_cast<D2CfgMgr>(getCfgMgr()));
+}
+
+const char* D2Process::getShutdownTypeStr(const ShutdownType& type) {
+ const char* str = "invalid";
+ switch (type) {
+ case SD_NORMAL:
+ str = "normal";
+ break;
+ case SD_DRAIN_FIRST:
+ str = "drain_first";
+ break;
+ case SD_NOW:
+ str = "now";
+ break;
+ default:
+ break;
+ }
+
+ return (str);
+}
+
+void
+D2Process::reconfigureCommandChannel() {
+ // Get new socket configuration.
+ isc::data::ConstElementPtr sock_cfg = getD2CfgMgr()->getControlSocketInfo();
+
+ // Determine if the socket configuration has changed. It has if
+ // both old and new configuration is specified but respective
+ // data elements aren't equal.
+ bool sock_changed = (sock_cfg && current_control_socket_ &&
+ !sock_cfg->equals(*current_control_socket_));
+
+ // If the previous or new socket configuration doesn't exist or
+ // the new configuration differs from the old configuration we
+ // close the existing socket and open a new socket as appropriate.
+ // Note that closing an existing socket means the client will not
+ // receive the configuration result.
+ if (!sock_cfg || !current_control_socket_ || sock_changed) {
+ // Close the existing socket.
+ if (current_control_socket_) {
+ isc::config::CommandMgr::instance().closeCommandSocket();
+ current_control_socket_.reset();
+ }
+
+ // Open the new socket.
+ if (sock_cfg) {
+ isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
+ }
+ }
+
+ // Commit the new socket configuration.
+ current_control_socket_ = sock_cfg;
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/d2_process.h b/src/bin/d2/d2_process.h
new file mode 100644
index 0000000..5ca3422
--- /dev/null
+++ b/src/bin/d2/d2_process.h
@@ -0,0 +1,324 @@
+// Copyright (C) 2013-2019 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_PROCESS_H
+#define D2_PROCESS_H
+
+#include <d2/d2_queue_mgr.h>
+#include <d2/d2_update_mgr.h>
+#include <process/d_process.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief DHCP-DDNS Application Process
+///
+/// D2Process provides the top level application logic for DHCP-driven DDNS
+/// update processing. It provides the asynchronous event processing required
+/// to receive DNS mapping change requests and carry them out.
+/// It implements the DProcessBase interface, which structures it such that it
+/// is a managed "application", controlled by a management layer.
+class D2Process : public process::DProcessBase {
+public:
+
+ /// @brief Defines the shutdown types supported by D2Process
+ ///
+ /// * SD_NORMAL - Stops the queue manager and finishes all current
+ /// transactions before exiting. This is the default.
+ ///
+ /// * SD_DRAIN_FIRST - Stops the queue manager but continues processing
+ /// requests from the queue until it is empty.
+ ///
+ /// * SD_NOW - Exits immediately.
+ enum ShutdownType {
+ SD_NORMAL,
+ SD_DRAIN_FIRST,
+ SD_NOW
+ };
+
+ /// @brief Defines the point at which to resume receiving requests.
+ /// If the receive queue has become full, D2Process will "pause" the
+ /// reception of requests by putting the queue manager in the stopped
+ /// state. Once the number of entries has decreased to this percentage
+ /// of the maximum allowed, D2Process will "resume" receiving requests
+ /// by restarting the queue manager.
+ static const unsigned int QUEUE_RESTART_PERCENT;
+
+ /// @brief Constructor
+ ///
+ /// Construction creates the configuration manager, the queue
+ /// manager, and the update manager.
+ ///
+ /// @param name name is a text label for the process. Generally used
+ /// in log statements, but otherwise arbitrary.
+ /// @param io_service is the io_service used by the caller for
+ /// asynchronous event handling.
+ ///
+ /// @throw DProcessBaseError if io_service is NULL.
+ D2Process(const char* name, const asiolink::IOServicePtr& io_service);
+
+ /// @brief Called after instantiation to perform initialization unique to
+ /// D2.
+ ///
+ /// This is invoked by the controller after command line arguments but
+ /// PRIOR to configuration reception. The base class provides this method
+ /// as a place to perform any derivation-specific initialization steps
+ /// that are inappropriate for the constructor but necessary prior to
+ /// configure.
+ /// For D2 it is used to initialize the command manager.
+ virtual void init();
+
+ /// @brief Implements the process's event loop.
+ ///
+ /// Once entered, the main control thread remains inside this method
+ /// until shutdown. The event loop logic is as follows:
+ /// @code
+ /// while should not shutdown {
+ /// process queue manager state change
+ /// process completed jobs
+ /// dequeue new jobs
+ /// wait for IO event(s)
+ ///
+ /// ON an exception, exit with fatal error
+ /// }
+ /// @endcode
+ ///
+ /// To summarize, each pass through the event loop first checks the state
+ /// of the received queue and takes any steps required to ensure it is
+ /// operating in the manner necessary. Next the update manager is given
+ /// a chance to clean up any completed transactions and start new
+ /// transactions by dequeuing jobs from the request queue. Lastly, it
+ /// allows IOService to process until one or more event handlers are
+ /// called. Note that this last step will block until at least one
+ /// ready handler is invoked. In other words, if no IO events have occurred
+ /// since it was last called, the event loop will block at this step until
+ /// an IO event occurs. At that time we return to the top of the loop.
+ ///
+ /// @throw DProcessBaseError if an error is encountered. Note that
+ /// exceptions thrown at this point are assumed to be FATAL exceptions.
+ /// This includes exceptions generated but not caught by IO callbacks.
+ /// Services which rely on callbacks are expected to be well behaved and
+ /// any errors they encounter handled internally.
+ virtual void run();
+
+ /// @brief Initiates the D2Process shutdown process.
+ ///
+ /// This is last step in the shutdown event callback chain. It is invoked
+ /// to notify D2Process that it needs to begin its shutdown procedure.
+ /// Note that shutting down may be neither instantaneous nor synchronous,
+ /// This method records the request for and the type of shutdown desired.
+ /// Generally it will require one or more subsequent events to complete,
+ /// dependent on the type of shutdown requested. The type of shutdown is
+ /// specified as an optional argument of the shutdown command. The types
+ /// of shutdown supported are:
+ ///
+ /// * "normal" - Stops the queue manager and finishes all current
+ /// transactions before exiting. This is the default.
+ ///
+ /// * "drain_first" - Stops the queue manager but continues processing
+ /// requests from the queue until it is empty.
+ ///
+ /// * "now" - Exits immediately.
+ ///
+ /// @param args Specifies the shutdown "type" as "normal", "drain_first",
+ /// or "now"
+ ///
+ /// @return an Element that contains the results of argument processing,
+ /// consisting of an integer status value (0 means successful,
+ /// non-zero means failure), and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr
+ shutdown(isc::data::ConstElementPtr args);
+
+ /// @brief Processes the given configuration.
+ ///
+ /// This method may be called multiple times during the process lifetime.
+ /// Certainly once during process startup, and possibly later if the user
+ /// alters the configuration. This method must not throw, it should catch any
+ /// processing errors and return a success or failure answer as described
+ /// below.
+ ///
+ /// This method passes the newly received configuration to the configuration
+ /// manager instance for parsing. The configuration manager parses the
+ /// configuration and updates the necessary values within the context,
+ /// assuming it parses correctly. If that's the case this method sets the
+ /// flag to reconfigure the queue manager and returns a successful response
+ /// as described below.
+ ///
+ /// If the new configuration fails to parse, then the current configuration
+ /// is retained and a failure response is returned as described below.
+ ///
+ /// @param config_set a new configuration (JSON) for the process
+ /// @param check_only true if configuration is to be verified only, not applied
+ /// @return an Element that contains the results of configuration composed
+ /// of an integer status value (0 means successful, non-zero means failure),
+ /// and a string explanation of the outcome.
+ virtual isc::data::ConstElementPtr
+ configure(isc::data::ConstElementPtr config_set,
+ bool check_only = false);
+
+ /// @brief Destructor
+ virtual ~D2Process();
+
+protected:
+ /// @brief Monitors current queue manager state, takes action accordingly
+ ///
+ /// This method ensures that the queue manager transitions to the state
+ /// most appropriate to the operational state of the D2Process and any
+ /// events that may have occurred since it was last called. It is called
+ /// once for each iteration of the event loop. It is essentially a
+ /// switch statement based on the D2QueueMgr's current state. The logic
+ /// is as follows:
+ ///
+ /// If the state is D2QueueMgr::RUNNING, and the queue manager needs to be
+ /// reconfigured or we have been told to shutdown, then instruct the queue
+ /// manager to stop listening. Exit the method.
+ ///
+ /// If the state is D2QueueMgr::STOPPED_QUEUE_FULL, then check if the
+ /// number of entries in the queue has fallen below the "resume threshold".
+ /// If it has, then instruct the queue manager to start listening. Exit
+ /// the method.
+ ///
+ /// If the state is D2QueueMgr::STOPPED_RECV_ERROR, then attempt to recover
+ /// by calling reconfigureQueueMgr(). Exit the method.
+ ///
+ /// If the state is D2QueueMgr::STOPPING, simply exit the method. This is
+ /// a NOP condition as we are waiting for the IO cancel event
+ ///
+ /// For any other state, (NOT_INITTED,INITTED,STOPPED), if the reconfigure
+ /// queue flag is set, call reconfigureQueueMgr(). Exit the method.
+ ///
+ /// This method is exception safe.
+ virtual void checkQueueStatus();
+
+ /// @brief Initializes then starts the queue manager.
+ ///
+ /// This method initializes the queue manager with the current
+ /// configuration parameters and instructs it to start listening.
+ /// Note the existing listener instance (if it exists) is destroyed,
+ /// and that a new listener is created during initialization.
+ ///
+ /// This method is exception safe.
+ virtual void reconfigureQueueMgr();
+
+ /// @brief Allows IO processing to run until at least callback is invoked.
+ ///
+ /// This method is called from within the D2Process main event loop and is
+ /// the point at which the D2Process blocks, waiting for IO events to
+ /// cause IO event callbacks to be invoked.
+ ///
+ /// If callbacks are ready to be executed upon entry, the method will
+ /// return as soon as these callbacks have completed. If no callbacks
+ /// are ready, then it will wait (indefinitely) until at least one callback
+ /// is executed.
+ ///
+ /// @note: Should become desirable to periodically force an
+ /// event, an interval timer could be used to do so.
+ ///
+ /// @return The number of callback handlers executed, or 0 if the IO
+ /// service has been stopped.
+ ///
+ /// @throw This method does not throw directly, but the execution of
+ /// callbacks invoked in response to IO events might. If so, these
+ /// will propagate upward out of this method.
+ virtual size_t runIO();
+
+ /// @brief Indicates whether or not the process can perform a shutdown.
+ ///
+ /// Determines if the process has been instructed to shutdown and if
+ /// the criteria for performing the type of shutdown requested has been
+ /// met.
+ ///
+ /// @return Returns true if the criteria has been met, false otherwise.
+ virtual bool canShutdown() const;
+
+ /// @brief Sets queue reconfigure indicator to the given value.
+ ///
+ /// @param value is the new value to assign to the indicator
+ ///
+ /// @note this method is really only intended for testing purposes.
+ void setReconfQueueFlag(const bool value) {
+ reconf_queue_flag_ = value;
+ }
+
+ /// @brief Sets the shutdown type to the given value.
+ ///
+ /// @param value is the new value to assign to shutdown type.
+ ///
+ /// @note this method is really only intended for testing purposes.
+ void setShutdownType(const ShutdownType& value) {
+ shutdown_type_ = value;
+ }
+
+ /// @brief (Re-)Configure the command channel.
+ ///
+ /// Only close the current channel, if the new channel configuration is
+ /// different. This avoids disconnecting a client and hence not sending
+ /// them a command result, unless they specifically alter the channel
+ /// configuration. In that case the user simply has to accept they'll
+ /// be disconnected.
+ void reconfigureCommandChannel();
+
+public:
+ /// @brief Returns a pointer to the configuration manager.
+ /// Note, this method cannot return a reference as it uses dynamic
+ /// pointer casting of the base class configuration manager.
+ D2CfgMgrPtr getD2CfgMgr();
+
+ /// @brief Returns a reference to the queue manager.
+ const D2QueueMgrPtr& getD2QueueMgr() const {
+ return (queue_mgr_);
+ }
+
+ /// @brief Returns a reference to the update manager.
+ const D2UpdateMgrPtr& getD2UpdateMgr() const {
+ return (update_mgr_);
+ }
+
+ /// @brief Returns true if the queue manager should be reconfigured.
+ bool getReconfQueueFlag() const {
+ return (reconf_queue_flag_);
+ }
+
+ /// @brief Returns the type of shutdown requested.
+ ///
+ /// Note, this value is meaningless unless shouldShutdown() returns true.
+ ShutdownType getShutdownType() const {
+ return (shutdown_type_);
+ }
+
+ /// @brief Returns a text label for the given shutdown type.
+ ///
+ /// @param type the numerical shutdown type for which the label is desired.
+ ///
+ /// @return A text label corresponding the value or "invalid" if the
+ /// value is not a valid value.
+ static const char* getShutdownTypeStr(const ShutdownType& type);
+
+private:
+ /// @brief Pointer to our queue manager instance.
+ D2QueueMgrPtr queue_mgr_;
+
+ /// @brief Pointer to our update manager instance.
+ D2UpdateMgrPtr update_mgr_;
+
+ /// @brief Indicates if the queue manager should be reconfigured.
+ bool reconf_queue_flag_;
+
+ /// @brief Indicates the type of shutdown requested.
+ ShutdownType shutdown_type_;
+
+ /// @brief Current socket control configuration.
+ isc::data::ConstElementPtr current_control_socket_;
+
+};
+
+/// @brief Defines a shared pointer to D2Process.
+typedef boost::shared_ptr<D2Process> D2ProcessPtr;
+
+}; // namespace isc::d2
+}; // namespace isc
+
+#endif
diff --git a/src/bin/d2/d2_queue_mgr.cc b/src/bin/d2/d2_queue_mgr.cc
new file mode 100644
index 0000000..71949b3
--- /dev/null
+++ b/src/bin/d2/d2_queue_mgr.cc
@@ -0,0 +1,264 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2srv/d2_log.h>
+#include <dhcp_ddns/ncr_udp.h>
+
+namespace isc {
+namespace d2 {
+
+// Makes constant visible to Google test macros.
+const size_t D2QueueMgr::MAX_QUEUE_DEFAULT;
+
+D2QueueMgr::D2QueueMgr(asiolink::IOServicePtr& io_service, const size_t max_queue_size)
+ : io_service_(io_service), max_queue_size_(max_queue_size),
+ mgr_state_(NOT_INITTED), target_stop_state_(NOT_INITTED) {
+ if (!io_service_) {
+ isc_throw(D2QueueMgrError, "IOServicePtr cannot be null");
+ }
+
+ // Use setter to do validation.
+ setMaxQueueSize(max_queue_size);
+}
+
+D2QueueMgr::~D2QueueMgr() {
+}
+
+void
+D2QueueMgr::operator()(const dhcp_ddns::NameChangeListener::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr) {
+ try {
+ // Note that error conditions must be handled here without throwing
+ // exceptions. Remember this is the application level "link" in the
+ // callback chain. Throwing an exception here will "break" the
+ // io_service "run" we are operating under. With that in mind,
+ // if we hit a problem, we will stop the listener transition to
+ // the appropriate stopped state. Upper layer(s) must monitor our
+ // state as well as our queue size.
+ switch (result) {
+ case dhcp_ddns::NameChangeListener::SUCCESS:
+ // Receive was successful, attempt to queue the request.
+ if (getQueueSize() < getMaxQueueSize()) {
+ // There's room on the queue, add to the end
+ enqueue(ncr);
+
+ // Log that we got the request
+ LOG_DEBUG(dhcp_to_d2_logger,
+ isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_QUEUE_MGR_QUEUE_RECEIVE)
+ .arg(ncr->getRequestId());
+ return;
+ }
+
+ // Queue is full, stop the listener.
+ // Note that we can move straight to a STOPPED state as there
+ // is no receive in progress.
+ LOG_ERROR(dhcp_to_d2_logger, DHCP_DDNS_QUEUE_MGR_QUEUE_FULL)
+ .arg(max_queue_size_);
+ stopListening(STOPPED_QUEUE_FULL);
+ break;
+
+ case dhcp_ddns::NameChangeListener::STOPPED:
+ if (mgr_state_ == STOPPING) {
+ // This is confirmation that the listener has stopped and its
+ // callback will not be called again, unless its restarted.
+ updateStopState();
+ } else {
+ // We should not get a receive complete status of stopped
+ // unless we canceled the read as part of stopping. Therefore
+ // this is unexpected so we will treat it as a receive error.
+ // This is most likely an unforeseen programmatic issue.
+ LOG_ERROR(dhcp_to_d2_logger, DHCP_DDNS_QUEUE_MGR_UNEXPECTED_STOP)
+ .arg(mgr_state_);
+ stopListening(STOPPED_RECV_ERROR);
+ }
+
+ break;
+
+ default:
+ // Receive failed, stop the listener.
+ // Note that we can move straight to a STOPPED state as there
+ // is no receive in progress.
+ LOG_ERROR(dhcp_to_d2_logger, DHCP_DDNS_QUEUE_MGR_RECV_ERROR);
+ stopListening(STOPPED_RECV_ERROR);
+ break;
+ }
+ } catch (const std::exception& ex) {
+ // On the outside chance a throw occurs, let's log it and swallow it.
+ LOG_ERROR(dhcp_to_d2_logger, DHCP_DDNS_QUEUE_MGR_UNEXPECTED_HANDLER_ERROR)
+ .arg(ex.what());
+ }
+}
+
+void
+D2QueueMgr::initUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const dhcp_ddns::NameChangeFormat format,
+ const bool reuse_address) {
+
+ if (listener_) {
+ isc_throw(D2QueueMgrError,
+ "D2QueueMgr listener is already initialized");
+ }
+
+ // Instantiate a UDP listener and set state to INITTED.
+ // Note UDP listener constructor does not throw.
+ listener_.reset(new dhcp_ddns::
+ NameChangeUDPListener(ip_address, port, format, *this,
+ reuse_address));
+ mgr_state_ = INITTED;
+}
+
+void
+D2QueueMgr::startListening() {
+ // We can't listen if we haven't initialized the listener yet.
+ if (!listener_) {
+ isc_throw(D2QueueMgrError, "D2QueueMgr "
+ "listener is not initialized, cannot start listening");
+ }
+
+ // If we are already listening, we do not want to "reopen" the listener
+ // and really we shouldn't be trying.
+ if (mgr_state_ == RUNNING) {
+ isc_throw(D2QueueMgrError, "D2QueueMgr "
+ "cannot call startListening from the RUNNING state");
+ }
+
+ // Instruct the listener to start listening and set state accordingly.
+ try {
+ listener_->startListening(*io_service_);
+ mgr_state_ = RUNNING;
+ } catch (const isc::Exception& ex) {
+ isc_throw(D2QueueMgrError, "D2QueueMgr listener start failed: "
+ << ex.what());
+ }
+
+ LOG_DEBUG(d2_logger, isc::log::DBGLVL_START_SHUT,
+ DHCP_DDNS_QUEUE_MGR_STARTED);
+}
+
+void
+D2QueueMgr::stopListening(const State target_stop_state) {
+ if (listener_) {
+ // Enforce only valid "stop" states.
+ // This is purely a programmatic error and should never happen.
+ if (target_stop_state != STOPPED &&
+ target_stop_state != STOPPED_QUEUE_FULL &&
+ target_stop_state != STOPPED_RECV_ERROR) {
+ isc_throw(D2QueueMgrError,
+ "D2QueueMgr invalid value for stop state: "
+ << target_stop_state);
+ }
+
+ // Remember the state we want to achieve.
+ target_stop_state_ = target_stop_state;
+
+ // Instruct the listener to stop. If the listener reports that it
+ // has IO pending, then we transition to STOPPING to wait for the
+ // cancellation event. Otherwise, we can move directly to the targeted
+ // state.
+ listener_->stopListening();
+ if (listener_->isIoPending()) {
+ mgr_state_ = STOPPING;
+ } else {
+ updateStopState();
+ }
+ }
+}
+
+void
+D2QueueMgr::updateStopState() {
+ mgr_state_ = target_stop_state_;
+ LOG_DEBUG(d2_logger, isc::log::DBGLVL_TRACE_BASIC,
+ DHCP_DDNS_QUEUE_MGR_STOPPED);
+}
+
+
+void
+D2QueueMgr::removeListener() {
+ // Force our managing layer(s) to stop us properly first.
+ if (mgr_state_ == RUNNING) {
+ isc_throw(D2QueueMgrError,
+ "D2QueueMgr cannot delete listener while state is RUNNING");
+ }
+
+ listener_.reset();
+ mgr_state_ = NOT_INITTED;
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2QueueMgr::peek() const {
+ if (getQueueSize() == 0) {
+ isc_throw(D2QueueMgrQueueEmpty,
+ "D2QueueMgr peek attempted on an empty queue");
+ }
+
+ return (ncr_queue_.front());
+}
+
+const dhcp_ddns::NameChangeRequestPtr&
+D2QueueMgr::peekAt(const size_t index) const {
+ if (index >= getQueueSize()) {
+ isc_throw(D2QueueMgrInvalidIndex,
+ "D2QueueMgr peek beyond end of queue attempted"
+ << " index: " << index << " queue size: " << getQueueSize());
+ }
+
+ return (ncr_queue_.at(index));
+}
+
+void
+D2QueueMgr::dequeueAt(const size_t index) {
+ if (index >= getQueueSize()) {
+ isc_throw(D2QueueMgrInvalidIndex,
+ "D2QueueMgr dequeue beyond end of queue attempted"
+ << " index: " << index << " queue size: " << getQueueSize());
+ }
+
+ RequestQueue::iterator pos = ncr_queue_.begin() + index;
+ ncr_queue_.erase(pos);
+}
+
+
+void
+D2QueueMgr::dequeue() {
+ if (getQueueSize() == 0) {
+ isc_throw(D2QueueMgrQueueEmpty,
+ "D2QueueMgr dequeue attempted on an empty queue");
+ }
+
+ ncr_queue_.pop_front();
+}
+
+void
+D2QueueMgr::enqueue(dhcp_ddns::NameChangeRequestPtr& ncr) {
+ ncr_queue_.push_back(ncr);
+}
+
+void
+D2QueueMgr::clearQueue() {
+ ncr_queue_.clear();
+}
+
+void
+D2QueueMgr::setMaxQueueSize(const size_t new_queue_max) {
+ if (new_queue_max < 1) {
+ isc_throw(D2QueueMgrError,
+ "D2QueueMgr maximum queue size must be greater than zero");
+ }
+
+ if (new_queue_max < getQueueSize()) {
+ isc_throw(D2QueueMgrError, "D2QueueMgr maximum queue size value cannot"
+ " be less than the current queue size :" << getQueueSize());
+ }
+
+ max_queue_size_ = new_queue_max;
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/d2_queue_mgr.h b/src/bin/d2/d2_queue_mgr.h
new file mode 100644
index 0000000..bbd95ea
--- /dev/null
+++ b/src/bin/d2/d2_queue_mgr.h
@@ -0,0 +1,348 @@
+// Copyright (C) 2013-2015,2017,2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_QUEUE_MGR_H
+#define D2_QUEUE_MGR_H
+
+/// @file d2_queue_mgr.h This file defines the class D2QueueMgr.
+
+#include <asiolink/io_service.h>
+#include <exceptions/exceptions.h>
+#include <dhcp_ddns/ncr_msg.h>
+#include <dhcp_ddns/ncr_io.h>
+
+#include <boost/noncopyable.hpp>
+#include <deque>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Defines a queue of requests.
+/// @todo This may be replaced with an actual class in the future.
+typedef std::deque<dhcp_ddns::NameChangeRequestPtr> RequestQueue;
+
+/// @brief Thrown if the queue manager encounters a general error.
+class D2QueueMgrError : public isc::Exception {
+public:
+ D2QueueMgrError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if the queue manager's receive handler is passed
+/// a failure result.
+/// @todo use or remove it.
+class D2QueueMgrReceiveError : public isc::Exception {
+public:
+ D2QueueMgrReceiveError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Thrown if the request queue is full when an enqueue is attempted.
+/// @todo use or remove it.
+class D2QueueMgrQueueFull : public isc::Exception {
+public:
+ D2QueueMgrQueueFull(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if the request queue empty and a read is attempted.
+class D2QueueMgrQueueEmpty : public isc::Exception {
+public:
+ D2QueueMgrQueueEmpty(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Thrown if a queue index is beyond the end of the queue
+class D2QueueMgrInvalidIndex : public isc::Exception {
+public:
+ D2QueueMgrInvalidIndex(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief D2QueueMgr creates and manages a queue of DNS update requests.
+///
+/// D2QueueMgr is a class specifically designed as an integral part of DHCP-DDNS.
+/// Its primary responsibility is to listen for NameChangeRequests from
+/// DHCP-DDNS clients (e.g. DHCP servers) and queue them for processing. In
+/// addition it may provide a number of services to locate entries in the queue
+/// such as by FQDN or DHCID. These services may eventually be used
+/// for processing optimization. The initial implementation will support
+/// simple FIFO access.
+///
+/// D2QueueMgr uses a NameChangeListener to asynchronously receive requests.
+/// It derives from NameChangeListener::RequestReceiveHandler and supplies an
+/// implementation of the operator()(Result, NameChangeRequestPtr). It is
+/// through this operator() that D2QueueMgr is passed inbound NCRs. D2QueueMgr
+/// will add each newly received request onto the back of the request queue
+///
+/// D2QueueMgr defines a simple state model constructed around the status of
+/// its NameChangeListener, consisting of the following states:
+///
+/// * NOT_INITTED - D2QueueMgr has been constructed, but its listener has
+/// not been initialized.
+///
+/// * INITTED - The listener has been initialized, but it is not open for
+/// listening. To move from NOT_INITTED to INITTED, one of the D2QueueMgr
+/// listener initialization methods must be invoked. Currently there is
+/// only one type of listener, NameChangeUDPListener, hence there is only
+/// one listener initialization method, initUDPListener. As more listener
+/// types are created, listener initialization methods will need to be
+/// added.
+///
+/// * RUNNING - The listener is open and listening for requests.
+/// Once initialized, in order to begin listening for requests, the
+/// startListener() method must be invoked. Upon successful completion of
+/// of this call, D2QueueMgr will begin receiving requests as they arrive
+/// without any further steps. This method may be called from the INITTED
+/// or one of the STOPPED states.
+///
+/// * STOPPING - The listener is in the process of stopping active
+/// listening. This is transitory state between RUNNING and STOPPED, which
+/// is completed by IO cancellation event.
+///
+/// * STOPPED - The listener has been listening but has been stopped
+/// without error. To return to listening, startListener() must be invoked.
+///
+/// * STOPPED_QUEUE_FULL - Request queue is full, the listener has been
+/// stopped. D2QueueMgr will enter this state when the request queue
+/// reaches the maximum queue size. Once this limit is reached, the
+/// listener will be closed and no further requests will be received.
+/// To return to listening, startListener() must be invoked. Note that so
+/// long as the queue is full, any attempt to queue a request will fail.
+///
+/// * STOPPED_RECV_ERROR - The listener has experienced a receive error
+/// and has been stopped. D2QueueMgr will enter this state when it is
+/// passed a failed status into the request completion handler. To return
+/// to listening, startListener() must be invoked.
+///
+/// D2QueueMgr does not attempt to recover from stopped conditions, this is left
+/// to upper layers.
+///
+/// It is important to note that the queue contents are preserved between
+/// state transitions. In other words entries in the queue remain there
+/// until they are removed explicitly via the deque() or implicitly by
+/// via the clearQueue() method.
+///
+class D2QueueMgr : public dhcp_ddns::NameChangeListener::RequestReceiveHandler,
+ boost::noncopyable {
+public:
+ /// @brief Maximum number of entries allowed in the request queue.
+ /// NOTE that 1024 is an arbitrary choice picked for the initial
+ /// implementation.
+ static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+ /// @brief Defines the list of possible states for D2QueueMgr.
+ enum State {
+ NOT_INITTED,
+ INITTED,
+ RUNNING,
+ STOPPING,
+ STOPPED_QUEUE_FULL,
+ STOPPED_RECV_ERROR,
+ STOPPED,
+ };
+
+ /// @brief Constructor
+ ///
+ /// Creates a D2QueueMgr instance. Note that the listener is not created
+ /// in the constructor. The initial state will be NOT_INITTED.
+ ///
+ /// @param io_service IOService instance to be passed into the listener for
+ /// IO management.
+ /// @param max_queue_size the maximum number of entries allowed in the
+ /// queue.
+ /// This value must be greater than zero. It defaults to MAX_QUEUE_DEFAULT.
+ ///
+ /// @throw D2QueueMgrError if max_queue_size is zero.
+ D2QueueMgr(asiolink::IOServicePtr& io_service,
+ const size_t max_queue_size = MAX_QUEUE_DEFAULT);
+
+ /// @brief Destructor
+ virtual ~D2QueueMgr();
+
+ /// @brief Initializes the listener as a UDP listener.
+ ///
+ /// Instantiates the listener_ member as NameChangeUDPListener passing
+ /// the given parameters. Upon successful completion, the D2QueueMgr state
+ /// will be INITTED.
+ ///
+ /// @param ip_address is the network address on which to listen
+ /// @param port is the IP port on which to listen
+ /// @param format is the wire format of the inbound requests.
+ /// @param reuse_address enables IP address sharing when true
+ /// It defaults to false.
+ void initUDPListener(const isc::asiolink::IOAddress& ip_address,
+ const uint32_t port,
+ const dhcp_ddns::NameChangeFormat format,
+ const bool reuse_address = false);
+
+ /// @brief Starts actively listening for requests.
+ ///
+ /// Invokes the listener's startListening method passing in our
+ /// IOService instance.
+ ///
+ /// @throw D2QueueMgrError if the listener has not been initialized,
+ /// state is already RUNNING, or the listener fails to actually start.
+ void startListening();
+
+ /// @brief Function operator implementing the NCR receive callback.
+ ///
+ /// This method is invoked by the listener as part of its receive
+ /// completion callback and is how the inbound NameChangeRequests are
+ /// passed up to the D2QueueMgr for queuing.
+ /// If the given result indicates a successful receive completion and
+ /// there is room left in the queue, the given request is queued.
+ ///
+ /// If the queue is at maximum capacity, stopListening() is invoked and
+ /// the state is set to STOPPED_QUEUE_FULL.
+ ///
+ /// If the result indicates IO stopped, then the state is set to STOPPED.
+ /// Note this is not an error, it results from a deliberate cancellation
+ /// of listener IO as part of a normal stopListener call.
+ ///
+ /// If the result indicates a failed receive, stopListening() is invoked
+ /// and the state is set to STOPPED_RECV_ERROR.
+ ///
+ /// This method specifically avoids throwing on an error as any such throw
+ /// would surface at the io_service::run (or run variant) method invocation
+ /// site. The upper layers are expected to monitor D2QueueMgr's state and
+ /// act accordingly.
+ ///
+ /// @param result contains that receive outcome status.
+ /// @param ncr is a pointer to the newly received NameChangeRequest if
+ /// result is NameChangeListener::SUCCESS. It is indeterminate other
+ /// wise.
+ virtual void operator ()(const dhcp_ddns::NameChangeListener::Result result,
+ dhcp_ddns::NameChangeRequestPtr& ncr);
+
+ /// @brief Stops listening for requests.
+ ///
+ /// Invokes the listener's stopListening method which will cause it to
+ /// cancel any pending IO and close its IO source. It the sets target
+ /// stop state to the given value.
+ ///
+ /// If there is no IO pending, the manager state is immediately set to the
+ /// target stop state, otherwise the manager state is set to STOPPING.
+ ///
+ /// @param target_stop_state is one of the three stopped state values.
+ ///
+ /// @throw D2QueueMgrError if stop_state is a valid stop state.
+ void stopListening(const State target_stop_state = STOPPED);
+
+
+ /// @brief Deletes the current listener
+ ///
+ /// This method will delete the current listener and returns the manager
+ /// to the NOT_INITTED state. This is provided to support reconfiguring
+ /// a new listener without losing queued requests.
+ ///
+ /// @throw D2QueueMgrError if called when the manager state is RUNNING.
+ void removeListener();
+
+ /// @brief Returns the number of entries in the queue.
+ size_t getQueueSize() const {
+ return (ncr_queue_.size());
+ };
+
+ /// @brief Returns the maximum number of entries allowed in the queue.
+ size_t getMaxQueueSize() const {
+ return (max_queue_size_);
+ }
+
+ /// @brief Sets the maximum number of entries allowed in the queue.
+ ///
+ /// @param max_queue_size is the new maximum size of the queue.
+ ///
+ /// @throw D2QueueMgrError if the new value is less than one or if
+ /// the new value is less than the number of entries currently in the
+ /// queue.
+ void setMaxQueueSize(const size_t max_queue_size);
+
+ /// @brief Returns the current state.
+ State getMgrState() const {
+ return (mgr_state_);
+ }
+
+ /// @brief Returns the entry at the front of the queue.
+ ///
+ /// The entry returned is next in line to be processed, assuming a FIFO
+ /// approach to task selection. Note, the entry is not removed from the
+ /// queue.
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw D2QueueMgrQueueEmpty if there are no entries in the queue.
+ const dhcp_ddns::NameChangeRequestPtr& peek() const;
+
+ /// @brief Returns the entry at a given position in the queue.
+ ///
+ /// Note that the entry is not removed from the queue.
+ /// @param index the index of the entry in the queue to fetch.
+ /// Valid values are 0 (front of the queue) to (queue size - 1).
+ ///
+ /// @return Pointer reference to the queue entry.
+ ///
+ /// @throw D2QueueMgrInvalidIndex if the given index is beyond the
+ /// end of the queue.
+ const dhcp_ddns::NameChangeRequestPtr& peekAt(const size_t index) const;
+
+ /// @brief Removes the entry at a given position in the queue.
+ ///
+ /// @param index the index of the entry in the queue to remove.
+ /// Valid values are 0 (front of the queue) to (queue size - 1).
+ ///
+ /// @throw D2QueueMgrInvalidIndex if the given index is beyond the
+ /// end of the queue.
+ void dequeueAt(const size_t index);
+
+ /// @brief Removes the entry at the front of the queue.
+ ///
+ /// @throw D2QueueMgrQueueEmpty if there are no entries in the queue.
+ void dequeue();
+
+ /// @brief Adds a request to the end of the queue.
+ ///
+ /// @param ncr pointer to the NameChangeRequest to add to the queue.
+ void enqueue(dhcp_ddns::NameChangeRequestPtr& ncr);
+
+ /// @brief Removes all entries from the queue.
+ void clearQueue();
+
+ private:
+ /// @brief Sets the manager state to the target stop state.
+ ///
+ /// Convenience method which sets the manager state to the target stop
+ /// state and logs that the manager is stopped.
+ void updateStopState();
+
+ /// @brief IOService that our listener should use for IO management.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Dictates the maximum number of entries allowed in the queue.
+ size_t max_queue_size_;
+
+ /// @brief Queue of received NameChangeRequests.
+ RequestQueue ncr_queue_;
+
+ /// @brief Listener instance from which requests are received.
+ boost::shared_ptr<dhcp_ddns::NameChangeListener> listener_;
+
+ /// @brief Current state of the manager.
+ State mgr_state_;
+
+ /// @brief Tracks the state the manager should be in once stopped.
+ State target_stop_state_;
+};
+
+/// @brief Defines a pointer for manager instances.
+typedef boost::shared_ptr<D2QueueMgr> D2QueueMgrPtr;
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif
diff --git a/src/bin/d2/d2_update_mgr.cc b/src/bin/d2/d2_update_mgr.cc
new file mode 100644
index 0000000..223430e
--- /dev/null
+++ b/src/bin/d2/d2_update_mgr.cc
@@ -0,0 +1,295 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2/d2_update_mgr.h>
+#include <d2/nc_add.h>
+#include <d2/nc_remove.h>
+#include <d2/simple_add.h>
+#include <d2/simple_remove.h>
+
+#include <sstream>
+#include <iostream>
+#include <vector>
+
+namespace isc {
+namespace d2 {
+
+const size_t D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT;
+
+D2UpdateMgr::D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+ asiolink::IOServicePtr& io_service,
+ const size_t max_transactions)
+ :queue_mgr_(queue_mgr), cfg_mgr_(cfg_mgr), io_service_(io_service) {
+ if (!queue_mgr_) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr queue manager cannot be null");
+ }
+
+ if (!cfg_mgr_) {
+ isc_throw(D2UpdateMgrError,
+ "D2UpdateMgr configuration manager cannot be null");
+ }
+
+ if (!io_service_) {
+ isc_throw(D2UpdateMgrError, "IOServicePtr cannot be null");
+ }
+
+ // Use setter to do validation.
+ setMaxTransactions(max_transactions);
+}
+
+D2UpdateMgr::~D2UpdateMgr() {
+ transaction_list_.clear();
+}
+
+void D2UpdateMgr::sweep() {
+ // cleanup finished transactions;
+ checkFinishedTransactions();
+
+ // if the queue isn't empty, find the next suitable job and
+ // start a transaction for it.
+ // @todo - Do we want to queue max transactions? The logic here will only
+ // start one new transaction per invocation. On the other hand a busy
+ // system will generate many IO events and this method will be called
+ // frequently. It will likely achieve max transactions quickly on its own.
+ if (getQueueCount() > 0) {
+ if (getTransactionCount() >= max_transactions_) {
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_AT_MAX_TRANSACTIONS).arg(getQueueCount())
+ .arg(getMaxTransactions());
+
+ return;
+ }
+
+ // We are not at maximum transactions, so pick and start the next job.
+ pickNextJob();
+ }
+}
+
+void
+D2UpdateMgr::checkFinishedTransactions() {
+ // Cycle through transaction list and do whatever needs to be done
+ // for finished transactions.
+ // At the moment all we do is remove them from the list. This is likely
+ // to expand as DHCP_DDNS matures.
+ // NOTE: One must use postfix increments of the iterator on the calls
+ // to erase. This replaces the old iterator which becomes invalid by the
+ // erase with the next valid iterator. Prefix incrementing will not
+ // work.
+ TransactionList::iterator it = transaction_list_.begin();
+ while (it != transaction_list_.end()) {
+ NameChangeTransactionPtr trans = (*it).second;
+ if (trans->isModelDone()) {
+ // @todo Additional actions based on NCR status could be
+ // performed here.
+ transaction_list_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void D2UpdateMgr::pickNextJob() {
+ // Start at the front of the queue, looking for the first entry for
+ // which no transaction is in progress. If we find an eligible entry
+ // remove it from the queue and make a transaction for it.
+ // Requests and transactions are associated by DHCID. If a request has
+ // the same DHCID as a transaction, they are presumed to be for the same
+ // "end user".
+ size_t queue_count = getQueueCount();
+ for (size_t index = 0; index < queue_count; ++index) {
+ dhcp_ddns::NameChangeRequestPtr found_ncr = queue_mgr_->peekAt(index);
+ if (!hasTransaction(found_ncr->getDhcid())) {
+ queue_mgr_->dequeueAt(index);
+ makeTransaction(found_ncr);
+ return;
+ }
+ }
+
+ // There were no eligible jobs. All of the current DHCIDs already have
+ // transactions pending.
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_NO_ELIGIBLE_JOBS)
+ .arg(getQueueCount()).arg(getTransactionCount());
+}
+
+void
+D2UpdateMgr::makeTransaction(dhcp_ddns::NameChangeRequestPtr& next_ncr) {
+ // First lets ensure there is not a transaction in progress for this
+ // DHCID. (pickNextJob should ensure this, as it is the only real caller
+ // but for safety's sake we'll check).
+ const TransactionKey& key = next_ncr->getDhcid();
+ if (findTransaction(key) != transactionListEnd()) {
+ // This is programmatic error. Caller(s) should be checking this.
+ isc_throw(D2UpdateMgrError, "Transaction already in progress for: "
+ << key.toStr());
+ }
+
+ int direction_count = 0;
+ // If forward change is enabled, match to forward servers.
+ DdnsDomainPtr forward_domain;
+ if (next_ncr->isForwardChange()) {
+ if (!cfg_mgr_->forwardUpdatesEnabled()) {
+ next_ncr->setForwardChange(false);
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_FWD_REQUEST_IGNORED)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ } else {
+ bool matched = cfg_mgr_->matchForward(next_ncr->getFqdn(),
+ forward_domain);
+ // Could not find a match for forward DNS server. Log it and get
+ // out. This has the net affect of dropping the request on the
+ // floor.
+ if (!matched) {
+ LOG_ERROR(dhcp_to_d2_logger, DHCP_DDNS_NO_FWD_MATCH_ERROR)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ return;
+ }
+
+ ++direction_count;
+ }
+ }
+
+ // If reverse change is enabled, match to reverse servers.
+ DdnsDomainPtr reverse_domain;
+ if (next_ncr->isReverseChange()) {
+ if (!cfg_mgr_->reverseUpdatesEnabled()) {
+ next_ncr->setReverseChange(false);
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_REV_REQUEST_IGNORED)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ } else {
+ bool matched = cfg_mgr_->matchReverse(next_ncr->getIpAddress(),
+ reverse_domain);
+ // Could not find a match for reverse DNS server. Log it and get
+ // out. This has the net affect of dropping the request on the
+ // floor.
+ if (!matched) {
+ LOG_ERROR(dhcp_to_d2_logger, DHCP_DDNS_NO_REV_MATCH_ERROR)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ return;
+ }
+
+ ++direction_count;
+ }
+ }
+
+ // If there is nothing to actually do, then the request falls on the floor.
+ // Should we log this?
+ if (!direction_count) {
+ LOG_DEBUG(dhcp_to_d2_logger, isc::log::DBGLVL_TRACE_DETAIL_DATA,
+ DHCP_DDNS_REQUEST_DROPPED)
+ .arg(next_ncr->getRequestId())
+ .arg(next_ncr->toText());
+ return;
+ }
+
+ // We matched to the required servers, so construct the transaction.
+ // @todo If multi-threading is implemented, one would pass in an
+ // empty IOServicePtr, rather than our instance value. This would cause
+ // the transaction to instantiate its own, separate IOService to handle
+ // the transaction's IO.
+ NameChangeTransactionPtr trans;
+ if (next_ncr->getChangeType() == dhcp_ddns::CHG_ADD) {
+ if (next_ncr->useConflictResolution()) {
+ trans.reset(new NameAddTransaction(io_service_, next_ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr_));
+ } else {
+ trans.reset(new SimpleAddTransaction(io_service_, next_ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr_));
+ }
+ } else {
+ if (next_ncr->useConflictResolution()) {
+ trans.reset(new NameRemoveTransaction(io_service_, next_ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr_));
+ } else {
+ trans.reset(new SimpleRemoveTransaction(io_service_, next_ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr_));
+ }
+ }
+
+ // Add the new transaction to the list.
+ transaction_list_[key] = trans;
+
+ // Start it.
+ trans->startTransaction();
+}
+
+TransactionList::iterator
+D2UpdateMgr::findTransaction(const TransactionKey& key) {
+ return (transaction_list_.find(key));
+}
+
+bool
+D2UpdateMgr::hasTransaction(const TransactionKey& key) {
+ return (findTransaction(key) != transactionListEnd());
+}
+
+void
+D2UpdateMgr::removeTransaction(const TransactionKey& key) {
+ TransactionList::iterator pos = findTransaction(key);
+ if (pos != transactionListEnd()) {
+ transaction_list_.erase(pos);
+ }
+}
+
+TransactionList::iterator
+D2UpdateMgr::transactionListBegin() {
+ return (transaction_list_.begin());
+}
+
+TransactionList::iterator
+D2UpdateMgr::transactionListEnd() {
+ return (transaction_list_.end());
+}
+
+void
+D2UpdateMgr::clearTransactionList() {
+ // @todo for now this just wipes them out. We might need something
+ // more elegant, that allows a cancel first.
+ transaction_list_.clear();
+}
+
+void
+D2UpdateMgr::setMaxTransactions(const size_t new_trans_max) {
+ // Obviously we need at room for at least one transaction.
+ if (new_trans_max < 1) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr"
+ " maximum transactions limit must be greater than zero");
+ }
+
+ // Do not allow the list maximum to be set to less then current list size.
+ if (new_trans_max < getTransactionCount()) {
+ isc_throw(D2UpdateMgrError, "D2UpdateMgr maximum transaction limit "
+ "cannot be less than the current transaction count :"
+ << getTransactionCount());
+ }
+
+ max_transactions_ = new_trans_max;
+}
+
+size_t
+D2UpdateMgr::getQueueCount() const {
+ return (queue_mgr_->getQueueSize());
+}
+
+size_t
+D2UpdateMgr::getTransactionCount() const {
+ return (transaction_list_.size());
+}
+
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/d2_update_mgr.h b/src/bin/d2/d2_update_mgr.h
new file mode 100644
index 0000000..593cfd1
--- /dev/null
+++ b/src/bin/d2/d2_update_mgr.h
@@ -0,0 +1,248 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_UPDATE_MGR_H
+#define D2_UPDATE_MGR_H
+
+/// @file d2_update_mgr.h This file defines the class D2UpdateMgr.
+
+#include <asiolink/io_service.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2srv/nc_trans.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_log.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <map>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the update manager encounters a general error.
+class D2UpdateMgrError : public isc::Exception {
+public:
+ D2UpdateMgrError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines a list of transactions.
+typedef std::map<TransactionKey, NameChangeTransactionPtr> TransactionList;
+
+/// @brief D2UpdateMgr creates and manages update transactions.
+///
+/// D2UpdateMgr is the DHCP_DDNS task master, instantiating and then supervising
+/// transactions that execute the DNS updates needed to fulfill the requests
+/// (NameChangeRequests) received from DHCP_DDNS clients (e.g. DHCP servers).
+///
+/// D2UpdateMgr uses the services of D2QueueMgr to monitor the queue of
+/// NameChangeRequests and select and dequeue requests for processing.
+/// When a request is dequeued for processing it is removed from the queue and
+/// wrapped in NameChangeTransaction and added to the D2UpdateMgr's list of
+/// transactions.
+///
+/// As part of the process of forming transactions, D2UpdateMgr matches each
+/// request with the appropriate list of DNS servers. This matching is based
+/// upon request attributes, primarily the FQDN and update direction (forward
+/// or reverse). D2UpdateMgr uses the services of D2CfgMgr to match requests
+/// to DNS server lists.
+///
+/// Once created, each transaction is responsible for carrying out the steps
+/// required to fulfill its specific request. These steps typically consist of
+/// one or more DNS packet exchanges with the appropriate DNS server. As
+/// transactions complete, D2UpdateMgr removes them from the transaction list,
+/// replacing them with new transactions.
+///
+/// D2UpdateMgr carries out each of the above steps, with a method called
+/// sweep(). This method is intended to be called as IO events complete.
+/// The upper layer(s) are responsible for calling sweep in a timely and cyclic
+/// manner.
+///
+class D2UpdateMgr : public boost::noncopyable {
+public:
+ /// @brief Maximum number of concurrent transactions
+ /// NOTE that 32 is an arbitrary choice picked for the initial
+ /// implementation.
+ static const size_t MAX_TRANSACTIONS_DEFAULT = 32;
+
+ /// @brief Constructor
+ ///
+ /// @param queue_mgr reference to the queue manager receiving requests
+ /// @param cfg_mgr reference to the configuration manager
+ /// @param io_service IO service used by the upper layer(s) to manage
+ /// IO events
+ /// @param max_transactions the maximum number of concurrent transactions
+ ///
+ /// @throw D2UpdateMgrError if either the queue manager or configuration
+ /// managers are NULL, or max transactions is less than one.
+ D2UpdateMgr(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+ asiolink::IOServicePtr& io_service,
+ const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT);
+
+ /// @brief Destructor
+ virtual ~D2UpdateMgr();
+
+ /// @brief Check current transactions; start transactions for new requests.
+ ///
+ /// This method is the primary public interface used by the upper layer. It
+ /// should be called as IO events complete. During each invocation it does
+ /// the following:
+ ///
+ /// - Removes all completed transactions from the transaction list.
+ ///
+ /// - If the request queue is not empty and the number of transactions
+ /// in the transaction list has not reached maximum allowed, then select
+ /// a request from the queue.
+ ///
+ /// - If a request was selected, start a new transaction for it and
+ /// add the transaction to the list of transactions.
+ void sweep();
+
+protected:
+ /// @brief Performs post-completion cleanup on completed transactions.
+ ///
+ /// Iterates through the list of transactions and removes any that have
+ /// reached completion. This method may expand in complexity or even
+ /// disappear altogether as the implementation matures.
+ void checkFinishedTransactions();
+
+ /// @brief Starts a transaction for the next eligible request in the queue.
+ ///
+ /// This method will scan the request queue for the next request to
+ /// dequeue. The current implementation starts at the front of the queue
+ /// and looks for the first request for whose DHCID there is no current
+ /// transaction in progress.
+ ///
+ /// If a request is selected, it is removed from the queue and transaction
+ /// is constructed for it.
+ ///
+ /// It is possible that no such request exists, though this is likely to be
+ /// rather rare unless a system is frequently seeing requests for the same
+ /// clients in quick succession.
+ void pickNextJob();
+
+ /// @brief Create a new transaction for the given request.
+ ///
+ /// This method will attempt to match the request to suitable DNS servers.
+ /// If matching servers are found, it will instantiate a transaction for
+ /// the requests, add the transaction to the transaction list, and start
+ /// the transaction.
+ ///
+ /// If updates in a given direction are disabled requests for updates in
+ /// that direction will be ignored. For example: If a request is received
+ /// which asks for updates in both directions but only forward updates are
+ /// enabled; only the forward update will be attempted. Effectively, the
+ /// request will be treated as if it only asked for forward updates.
+ ///
+ /// If updates in a given direction are enabled, and a request asks for
+ /// updates in that direction, failing to match the request to a list
+ /// of servers is an error which will be logged and the request will be
+ /// discarded.
+ ///
+ /// Finally, If conflict resolution is enabled, it will instantiate either
+ /// a NameAddTransaction or a NameRemoveTransaction. If disabled it will
+ /// instantiate either a SimpleAddTransaction or a SimpleRemoveTransaction.
+ ///
+ /// @param ncr the NameChangeRequest for which to create a transaction.
+ ///
+ /// @throw D2UpdateMgrError if a transaction for this DHCID already
+ /// exists. Note this would be programmatic error.
+ void makeTransaction(isc::dhcp_ddns::NameChangeRequestPtr& ncr);
+
+public:
+ /// @brief Gets the D2UpdateMgr's IOService.
+ ///
+ /// @return returns a reference to the IOService
+ const asiolink::IOServicePtr& getIOService() {
+ return (io_service_);
+ }
+
+ /// @brief Returns the maximum number of concurrent transactions.
+ size_t getMaxTransactions() const {
+ return (max_transactions_);
+ }
+
+ /// @brief Sets the maximum number of entries allowed in the queue.
+ ///
+ /// @param max_transactions is the new maximum number of transactions
+ ///
+ /// @throw Throws D2QueueMgrError if the new value is less than one or if
+ /// the new value is less than the number of entries currently in the
+ /// queue.
+ void setMaxTransactions(const size_t max_transactions);
+
+ /// @brief Search the transaction list for the given key.
+ ///
+ /// @param key the transaction key value for which to search.
+ ///
+ /// @return Iterator pointing to the entry found. If no entry is
+ /// it will point to the list end position.
+ TransactionList::iterator findTransaction(const TransactionKey& key);
+
+ /// @brief Returns the transaction list end position.
+ TransactionList::iterator transactionListEnd();
+
+ /// @brief Returns the transaction list beg position.
+ TransactionList::iterator transactionListBegin();
+
+ /// @brief Convenience method that checks transaction list for the given key
+ ///
+ /// @param key the transaction key value for which to search.
+ ///
+ /// @return Returns true if the key is found within the list, false
+ /// otherwise.
+ bool hasTransaction(const TransactionKey& key);
+
+ /// @brief Removes the entry pointed to by key from the transaction list.
+ ///
+ /// Removes the entry referred to by key if it exists. It has no effect
+ /// if the entry is not found.
+ ///
+ /// @param key of the transaction to remove
+ void removeTransaction(const TransactionKey& key);
+
+ /// @brief Immediately discards all entries in the transaction list.
+ ///
+ /// @todo For now this just wipes them out. We might need something
+ /// more elegant, that allows a cancel first.
+ void clearTransactionList();
+
+ /// @brief Convenience method that returns the number of requests queued.
+ size_t getQueueCount() const;
+
+ /// @brief Returns the current number of transactions.
+ size_t getTransactionCount() const;
+
+private:
+ /// @brief Pointer to the queue manager.
+ D2QueueMgrPtr queue_mgr_;
+
+ /// @brief Pointer to the configuration manager.
+ D2CfgMgrPtr cfg_mgr_;
+
+ /// @brief Primary IOService instance.
+ /// This is the IOService that the upper layer(s) use for IO events, such
+ /// as shutdown and configuration commands. It is the IOService that is
+ /// passed into transactions to manager their IO events.
+ /// (For future reference, multi-threaded transactions would each use their
+ /// own IOService instance.)
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Maximum number of concurrent transactions.
+ size_t max_transactions_;
+
+ /// @brief List of transactions.
+ TransactionList transaction_list_;
+};
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<D2UpdateMgr> D2UpdateMgrPtr;
+
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/bin/d2/images/abstract_app_classes.svg b/src/bin/d2/images/abstract_app_classes.svg
new file mode 100644
index 0000000..52af2f6
--- /dev/null
+++ b/src/bin/d2/images/abstract_app_classes.svg
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="770" height="641" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="588" y="279" width="3" height="193" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="474" y="469" width="117" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="470" y="275" width="118" height="194" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="529" y="289">DCfgMgrBase</text>
+ <line stroke="black" stroke-opacity="1" x1="470" y1="291" x2="588" y2="291" />
+ <line stroke="black" stroke-opacity="1" x1="470" y1="299" x2="588" y2="299" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="313">DCfgMgrBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="327">~DCfgMgrBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="341">parseConfig()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="355">addToParseOrder()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="369">getParseOrder()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="383">getContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="397">buildParams()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="474" y="411">createConfigParser()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="474" y="425">createNewContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="439">resetContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="453">setContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="474" y="467">buildAndCommit()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="754" y="385" width="3" height="243" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="640" y="625" width="117" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="636" y="381" width="118" height="244" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="695" y="395">DCfgContextBase</text>
+ <line stroke="black" stroke-opacity="1" x1="636" y1="397" x2="754" y2="397" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="640" y="411">OPTIONAL</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="640" y="425">REQUIRED</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="439">boolean_values_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="453">uint32_values_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="467">string_values_</text>
+ <line stroke="black" stroke-opacity="1" x1="636" y1="469" x2="754" y2="469" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="483">DCfgContextBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="497">~DCfgContextBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="511">getParam()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="525">getParam()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="539">getParam()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="553">getBooleanStorage()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="567">getUint32Storage()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="581">getStringStorage()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="640" y="595">clone()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="609">DCfgContextBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="640" y="623">operator =()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="175" y="14" width="3" height="453" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="17" y="464" width="161" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="13" y="10" width="162" height="454" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="94" y="24">DControllerBase</text>
+ <line stroke="black" stroke-opacity="1" x1="13" y1="26" x2="175" y2="26" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="40">app_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="54">bin_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="68">verbose_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="82">spec_file_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="13" y1="84" x2="175" y2="84" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="98">DControllerBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="112">~DControllerBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="126">launch()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="140">updateConfig()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="154">configFromFile()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="168">executeCommand()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="182">getAppName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="196">getBinName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="210">customOption()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="17" y="224">createProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="238">customControllerCommand()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="252">getUsageText()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="266">getCustomOpts()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="280">isVerbose()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="294">setVerbose()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="308">getIOService()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="322">getSpecFileName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="336">setSpecFileName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="17" y="350">getController()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="17" y="364">setController()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="378">parseArgs()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="392">initProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="406">runProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="420">shutdownProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="434">getConfigFile()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="448">getProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="462">usage()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="274" y="165" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="198" y="209" width="79" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="194" y="161" width="80" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="234" y="175">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="234" y="191">IOServicePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="194" y1="193" x2="274" y2="193" />
+ <line stroke="black" stroke-opacity="1" x1="194" y1="201" x2="274" y2="201" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="179" y1="118" x2="236" y2="118" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="179,118 185,112 191,118 185,124" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="236" y1="160" x2="242" y2="154" />
+ <line stroke="black" stroke-opacity="1" x1="236" y1="160" x2="230" y2="154" />
+ <line stroke="black" stroke-opacity="1" x1="236" y1="118" x2="236" y2="160" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="426" y="148" width="3" height="229" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="322" y="374" width="107" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="318" y="144" width="108" height="230" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="372" y="158">DProcessBase</text>
+ <line stroke="black" stroke-opacity="1" x1="318" y1="160" x2="426" y2="160" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="174">app_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="188">shut_down_flag_</text>
+ <line stroke="black" stroke-opacity="1" x1="318" y1="190" x2="426" y2="190" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="204">DProcessBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="322" y="218">init()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="322" y="232">run()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="322" y="246">shutdown()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="322" y="260">configure()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="322" y="274">command()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="288">~DProcessBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="302">shouldShutdown()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="316">setShutdownFlag()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="330">getAppName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="344">getIoService()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="358">stopIOService()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="322" y="372">getCfgMgr()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="317" y1="260" x2="236" y2="260" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="317,260 311,266 305,260 311,254" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="236" y1="213" x2="230" y2="219" />
+ <line stroke="black" stroke-opacity="1" x1="236" y1="213" x2="242" y2="219" />
+ <line stroke="black" stroke-opacity="1" x1="236" y1="260" x2="236" y2="213" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="424" y="72" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="320" y="116" width="107" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="316" y="68" width="108" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="370" y="82">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="370" y="98">DProcessBasePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="316" y1="100" x2="424" y2="100" />
+ <line stroke="black" stroke-opacity="1" x1="316" y1="108" x2="424" y2="108" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="372" y1="143" x2="378" y2="137" />
+ <line stroke="black" stroke-opacity="1" x1="372" y1="143" x2="366" y2="137" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="372" y1="120" x2="372" y2="143" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="179" y1="39" x2="372" y2="39" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="179,39 185,33 191,39 185,45" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="372" y1="67" x2="378" y2="61" />
+ <line stroke="black" stroke-opacity="1" x1="372" y1="67" x2="366" y2="61" />
+ <line stroke="black" stroke-opacity="1" x1="372" y1="39" x2="372" y2="67" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="382" y="63">process_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="246" y="231">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="246" y="156">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="696" y="302">context_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="534" y="196">cfg_mgr_</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="747" y="311" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="625" y="355" width="125" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="621" y="307" width="126" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="684" y="321">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="684" y="337">DCfgContextBasePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="621" y1="339" x2="747" y2="339" />
+ <line stroke="black" stroke-opacity="1" x1="621" y1="347" x2="747" y2="347" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="688" y1="380" x2="693" y2="373" />
+ <line stroke="black" stroke-opacity="1" x1="688" y1="380" x2="681" y2="374" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="687" y1="359" x2="688" y2="380" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="592" y1="289" x2="686" y2="289" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="592,289 598,283 604,289 598,295" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="686" y1="306" x2="692" y2="300" />
+ <line stroke="black" stroke-opacity="1" x1="686" y1="306" x2="680" y2="300" />
+ <line stroke="black" stroke-opacity="1" x1="686" y1="289" x2="686" y2="306" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="574" y="205" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="474" y="249" width="103" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="470" y="201" width="104" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="522" y="215">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="522" y="231">DCfgMgrBasePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="470" y1="233" x2="574" y2="233" />
+ <line stroke="black" stroke-opacity="1" x1="470" y1="241" x2="574" y2="241" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="524" y1="274" x2="530" y2="268" />
+ <line stroke="black" stroke-opacity="1" x1="524" y1="274" x2="518" y2="268" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="524" y1="253" x2="524" y2="274" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="430" y1="168" x2="524" y2="168" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="430,168 436,162 442,168 436,174" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="524" y1="200" x2="530" y2="194" />
+ <line stroke="black" stroke-opacity="1" x1="524" y1="200" x2="518" y2="194" />
+ <line stroke="black" stroke-opacity="1" x1="524" y1="168" x2="524" y2="200" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/add_state_model.svg b/src/bin/d2/images/add_state_model.svg
new file mode 100644
index 0000000..c68773c
--- /dev/null
+++ b/src/bin/d2/images/add_state_model.svg
@@ -0,0 +1,301 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="840" height="777" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="365" y="49" width="74" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="361" y="45" width="74" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="398" y="61">READY_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="312" y="193" width="180" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="308" y="189" width="180" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="398" y="205">SELECTING_FWD_SERVER_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="46" y="527" width="178" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="42" y="523" width="178" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="131" y="539">SELECTING_REV_SERVER_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="324" y="299" width="156" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="320" y="295" width="156" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="398" y="311">ADDING_FWD_ADDRS_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="466" y="414" width="176" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="462" y="410" width="176" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="550" y="426">REPLACING_FWD_ADDRS_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="662" y="600" width="166" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="658" y="596" width="166" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="741" y="612">PROCESS_ADD_FAILED_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="233" y="735" width="151" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="229" y="731" width="151" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="305" y="747">PROCESS_ADD_OK_ST</text>
+</g>
+<g>
+ <ellipse fill="white" stroke="black" stroke-width="1" stroke-opacity="1" cx="743" cy="746" rx="11.5" ry="11.5" />
+ <ellipse fill="black" cx="743" cy="746" rx="8.5" ry="8.5" />
+</g>
+<ellipse fill="black" cx="137" cy="57" rx="8.5" ry="8.5" />
+<polygon fill="white" stroke="black" stroke-opacity="1" points ="291,492 300,475 309,492 300,509" />
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="575" y="473" width="118" height="43" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="634" y="489">&lt;&lt;DNS IO Callback&gt;&gt;</text>
+</g>
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="178" y="244" width="118" height="43" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="237" y="260">&lt;&lt;DNS IO Callback&gt;&gt;</text>
+</g>
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="12" y="722" width="118" height="43" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="71" y="738">&lt;&lt;DNS IO Callback&gt;&gt;</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="610" y1="436" x2="604" y2="442" />
+ <line stroke="black" stroke-opacity="1" x1="610" y1="436" x2="616" y2="441" />
+ <line stroke="black" stroke-opacity="1" x1="611" y1="472" x2="610" y2="436" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="360" y1="57" x2="354" y2="51" />
+ <line stroke="black" stroke-opacity="1" x1="360" y1="57" x2="354" y2="63" />
+ <line stroke="black" stroke-opacity="1" x1="146" y1="57" x2="360" y2="57" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="400" y1="294" x2="406" y2="288" />
+ <line stroke="black" stroke-opacity="1" x1="400" y1="294" x2="394" y2="288" />
+ <line stroke="black" stroke-opacity="1" x1="400" y1="215" x2="400" y2="294" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="355" y1="294" x2="323" y2="253" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="352" y1="215" x2="343" y2="216" />
+ <line stroke="black" stroke-opacity="1" x1="352" y1="215" x2="353" y2="223" />
+ <line stroke="black" stroke-opacity="1" x1="323" y1="253" x2="352" y2="215" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="492" y1="201" x2="743" y2="201" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="743" y1="595" x2="749" y2="589" />
+ <line stroke="black" stroke-opacity="1" x1="743" y1="595" x2="737" y2="589" />
+ <line stroke="black" stroke-opacity="1" x1="743" y1="201" x2="743" y2="595" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="480" y1="308" x2="743" y2="308" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="743" y1="595" x2="749" y2="589" />
+ <line stroke="black" stroke-opacity="1" x1="743" y1="595" x2="737" y2="589" />
+ <line stroke="black" stroke-opacity="1" x1="743" y1="308" x2="743" y2="595" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="642" y1="423" x2="743" y2="423" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="743" y1="595" x2="749" y2="589" />
+ <line stroke="black" stroke-opacity="1" x1="743" y1="595" x2="737" y2="589" />
+ <line stroke="black" stroke-opacity="1" x1="743" y1="423" x2="743" y2="595" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="370" y1="321" x2="370" y2="423" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="461" y1="423" x2="455" y2="417" />
+ <line stroke="black" stroke-opacity="1" x1="461" y1="423" x2="455" y2="429" />
+ <line stroke="black" stroke-opacity="1" x1="370" y1="423" x2="461" y2="423" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="523" y1="409" x2="522" y2="357" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="434" y1="321" x2="437" y2="328" />
+ <line stroke="black" stroke-opacity="1" x1="434" y1="321" x2="441" y2="317" />
+ <line stroke="black" stroke-opacity="1" x1="522" y1="357" x2="434" y2="321" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="587" y1="409" x2="587" y2="238" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="484" y1="215" x2="488" y2="222" />
+ <line stroke="black" stroke-opacity="1" x1="484" y1="215" x2="491" y2="210" />
+ <line stroke="black" stroke-opacity="1" x1="587" y1="238" x2="484" y2="215" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="319" y1="312" x2="300" y2="312" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="300" y1="473" x2="306" y2="467" />
+ <line stroke="black" stroke-opacity="1" x1="300" y1="473" x2="294" y2="467" />
+ <line stroke="black" stroke-opacity="1" x1="300" y1="312" x2="300" y2="473" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="506" y1="436" x2="506" y2="492" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="312" y1="492" x2="318" y2="498" />
+ <line stroke="black" stroke-opacity="1" x1="312" y1="492" x2="318" y2="486" />
+ <line stroke="black" stroke-opacity="1" x1="506" y1="492" x2="312" y2="492" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="298" y1="730" x2="304" y2="724" />
+ <line stroke="black" stroke-opacity="1" x1="298" y1="730" x2="292" y2="723" />
+ <line stroke="black" stroke-opacity="1" x1="300" y1="510" x2="298" y2="730" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="287" y1="491" x2="132" y2="491" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="132" y1="522" x2="138" y2="516" />
+ <line stroke="black" stroke-opacity="1" x1="132" y1="522" x2="126" y2="516" />
+ <line stroke="black" stroke-opacity="1" x1="132" y1="491" x2="132" y2="522" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="743" y1="733" x2="749" y2="727" />
+ <line stroke="black" stroke-opacity="1" x1="743" y1="733" x2="737" y2="727" />
+ <line stroke="black" stroke-opacity="1" x1="743" y1="622" x2="743" y2="733" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="730" y1="745" x2="724" y2="738" />
+ <line stroke="black" stroke-opacity="1" x1="730" y1="745" x2="723" y2="750" />
+ <line stroke="black" stroke-opacity="1" x1="384" y1="744" x2="730" y2="745" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="234" y1="287" x2="234" y2="303" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="319" y1="303" x2="313" y2="297" />
+ <line stroke="black" stroke-opacity="1" x1="319" y1="303" x2="313" y2="309" />
+ <line stroke="black" stroke-opacity="1" x1="234" y1="303" x2="319" y2="303" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="616" y="301">UPDATE_FAILED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="196" y="236">SERVER_IO_ERROR_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="336" y="440">FQDN_IN_USE_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="436" y="251">SERVER_IO_ERROR_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="378" y="374">FQDN_NOT_IN_USE_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="328" y="485">UPDATE_OK_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="309" y="540">No reverse change requested</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="286" y="51">START_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="530" y="740">END_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="197" y="455">UPDATE_OK_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="616" y="400">UPDATE_FAILED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="149" y="484">Reverse change requested</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="674" y="695">END_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="588" y="196">NO_MORE_SERVERS_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="420" y="291">SERVER_SELECTED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="168" y="324">IO_COMPLETED_EVT</text>
+</g>
+<polygon fill="white" stroke="black" stroke-opacity="1" points ="391,122 400,105 409,122 400,139" />
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="34" y="642" width="207" height="23" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="30" y="638" width="207" height="23" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="134" y="654">REPLACING_REV_PTRS_ST</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="400" y1="103" x2="406" y2="97" />
+ <line stroke="black" stroke-opacity="1" x1="400" y1="103" x2="394" y2="97" />
+ <line stroke="black" stroke-opacity="1" x1="400" y1="71" x2="400" y2="103" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="400" y1="188" x2="406" y2="182" />
+ <line stroke="black" stroke-opacity="1" x1="400" y1="188" x2="394" y2="182" />
+ <line stroke="black" stroke-opacity="1" x1="400" y1="140" x2="400" y2="188" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="387" y1="121" x2="132" y2="121" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="132" y1="522" x2="138" y2="516" />
+ <line stroke="black" stroke-opacity="1" x1="132" y1="522" x2="126" y2="516" />
+ <line stroke="black" stroke-opacity="1" x1="132" y1="121" x2="132" y2="522" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="410" y="156">Forward change requested</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="179" y="116">Only reverse change requested</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="175" y1="549" x2="208" y2="590" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="181" y1="637" x2="189" y2="634" />
+ <line stroke="black" stroke-opacity="1" x1="181" y1="637" x2="178" y2="628" />
+ <line stroke="black" stroke-opacity="1" x1="208" y1="590" x2="181" y2="637" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="83" y1="637" x2="52" y2="602" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="86" y1="549" x2="77" y2="550" />
+ <line stroke="black" stroke-opacity="1" x1="86" y1="549" x2="87" y2="557" />
+ <line stroke="black" stroke-opacity="1" x1="52" y1="602" x2="86" y2="549" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="147" y1="665" x2="147" y2="695" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="147" y1="695" x2="298" y2="695" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="298" y1="730" x2="304" y2="724" />
+ <line stroke="black" stroke-opacity="1" x1="298" y1="730" x2="292" y2="724" />
+ <line stroke="black" stroke-opacity="1" x1="298" y1="695" x2="298" y2="730" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="71" y1="665" x2="65" y2="671" />
+ <line stroke="black" stroke-opacity="1" x1="71" y1="665" x2="77" y2="671" />
+ <line stroke="black" stroke-opacity="1" x1="71" y1="721" x2="71" y2="665" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="31" y="577">SERVER_IO_ERROR_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="124" y="620">SERVER_SELECTED_ST</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="197" y="691">UPDATE_OK_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="714">IO_COMPLETED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="553" y="460">IO_COMPLETED_EVT</text>
+</g>
+</svg>
diff --git a/src/bin/d2/images/config_data_classes.svg b/src/bin/d2/images/config_data_classes.svg
new file mode 100644
index 0000000..7953b88
--- /dev/null
+++ b/src/bin/d2/images/config_data_classes.svg
@@ -0,0 +1,299 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="744" height="590" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="163" y="23" width="3" height="127" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="77" y="147" width="89" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="73" y="19" width="90" height="128" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="118" y="32">D2CfgContext</text>
+ <line stroke="black" stroke-opacity="1" x1="73" y1="34" x2="163" y2="34" />
+ <line stroke="black" stroke-opacity="1" x1="73" y1="42" x2="163" y2="42" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="77" y="55">D2CfgContext()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="77" y="68">~D2CfgContext()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="77" y="81">clone()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="77" y="94">getForwardMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="77" y="107">getReverseMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="77" y="120">getKeys()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="77" y="133">D2CfgContext()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="77" y="146">operator =()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="728" y="342" width="3" height="213" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="600" y="552" width="131" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="596" y="338" width="132" height="214" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="662" y="351">DnsServerInfo</text>
+ <line stroke="black" stroke-opacity="1" x1="596" y1="353" x2="728" y2="353" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="600" y="366">STANDARD_DNS_PORT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="600" y="379">EMPTY_IP_STR</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="392">hostname_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="405">ip_address_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="418">port_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="431">enabled_</text>
+ <line stroke="black" stroke-opacity="1" x1="596" y1="433" x2="728" y2="433" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="446">DnsServerInfo()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="459">~DnsServerInfo()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="472">getHostname()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="485">getPort()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="498">getIpAddress()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="511">isEnabled()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="524">enable()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="537">disable()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="600" y="550">toText()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="551" y="64" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="467" y="106" width="87" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="463" y="60" width="88" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="507" y="73">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="507" y="88">TSIGKeyInfoPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="463" y1="90" x2="551" y2="90" />
+ <line stroke="black" stroke-opacity="1" x1="463" y1="98" x2="551" y2="98" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="158" y="532" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="64" y="574" width="97" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="60" y="528" width="98" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="109" y="541">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="109" y="556">DdnsDomainMap</text>
+ <line stroke="black" stroke-opacity="1" x1="60" y1="558" x2="158" y2="558" />
+ <line stroke="black" stroke-opacity="1" x1="60" y1="566" x2="158" y2="566" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="437" y="460" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="303" y="502" width="137" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="299" y="456" width="138" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="368" y="469">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="368" y="484">DnsServerInfoStoragePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="299" y1="486" x2="437" y2="486" />
+ <line stroke="black" stroke-opacity="1" x1="299" y1="494" x2="437" y2="494" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="180" y="195" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="54" y="237" width="129" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="50" y="191" width="130" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="115" y="204">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="115" y="219">DdnsDomainListMgrPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="50" y1="221" x2="180" y2="221" />
+ <line stroke="black" stroke-opacity="1" x1="50" y1="229" x2="180" y2="229" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="548" y="355" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="454" y="397" width="97" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="450" y="351" width="98" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="499" y="364">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="499" y="379">DnsServerInfoPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="450" y1="381" x2="548" y2="381" />
+ <line stroke="black" stroke-opacity="1" x1="450" y1="389" x2="548" y2="389" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="666" y="63" width="3" height="121" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="586" y="181" width="83" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="582" y="59" width="84" height="122" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="624" y="72">TSIGKeyInfo</text>
+ <line stroke="black" stroke-opacity="1" x1="582" y1="74" x2="666" y2="74" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="586" y="87">name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="586" y="100">algorithm_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="586" y="113">secret_</text>
+ <line stroke="black" stroke-opacity="1" x1="582" y1="115" x2="666" y2="115" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="586" y="128">TSIGKeyInfo()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="586" y="141">~TSIGKeyInfo()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="586" y="154">getName()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="586" y="167">getAlgorithm()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="586" y="180">getSecret()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="152" y1="190" x2="158" y2="184" />
+ <line stroke="black" stroke-opacity="1" x1="152" y1="190" x2="146" y2="184" />
+ <line stroke="black" stroke-opacity="1" x1="152" y1="151" x2="152" y2="190" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="152,151 158,157 152,163 146,157" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="85" y1="190" x2="91" y2="184" />
+ <line stroke="black" stroke-opacity="1" x1="85" y1="190" x2="79" y2="184" />
+ <line stroke="black" stroke-opacity="1" x1="85" y1="151" x2="85" y2="190" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="85,151 91,157 85,163 79,157" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="595" y1="369" x2="589" y2="362" />
+ <line stroke="black" stroke-opacity="1" x1="595" y1="369" x2="588" y2="374" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="552" y1="368" x2="595" y2="369" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="581" y1="72" x2="575" y2="65" />
+ <line stroke="black" stroke-opacity="1" x1="581" y1="72" x2="574" y2="77" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="555" y1="71" x2="581" y2="72" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="177" y="274" width="3" height="147" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="51" y="418" width="129" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="47" y="270" width="130" height="148" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="112" y="283">DdnsDomainListMgr</text>
+ <line stroke="black" stroke-opacity="1" x1="47" y1="285" x2="177" y2="285" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="51" y="298">wildcard_domain_name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="311">name_</text>
+ <line stroke="black" stroke-opacity="1" x1="47" y1="313" x2="177" y2="313" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="326">DdnsDomainListMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="339">~DdnsDomainListMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="352">matchDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="365">getName()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="378">size()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="391">getWildcardDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="404">getDomains()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="51" y="417">setDomains()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="428" y="527" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="308" y="569" width="123" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="304" y="523" width="124" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="366" y="536">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="366" y="551">DnsServerInfoStorage</text>
+ <line stroke="black" stroke-opacity="1" x1="304" y1="553" x2="428" y2="553" />
+ <line stroke="black" stroke-opacity="1" x1="304" y1="561" x2="428" y2="561" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="115" y1="269" x2="121" y2="263" />
+ <line stroke="black" stroke-opacity="1" x1="115" y1="269" x2="109" y2="262" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="116" y1="241" x2="115" y2="269" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="432" y1="548" x2="501" y2="548" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="501" y1="401" x2="495" y2="407" />
+ <line stroke="black" stroke-opacity="1" x1="501" y1="401" x2="507" y2="407" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="501" y1="548" x2="501" y2="401" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="368" y1="522" x2="374" y2="516" />
+ <line stroke="black" stroke-opacity="1" x1="368" y1="522" x2="362" y2="515" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="369" y1="506" x2="368" y2="522" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="295" y="316" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="209" y="358" width="89" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="205" y="312" width="90" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="250" y="325">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="250" y="340">DdnsDomainPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="205" y1="342" x2="295" y2="342" />
+ <line stroke="black" stroke-opacity="1" x1="205" y1="350" x2="295" y2="350" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="181" y1="297" x2="251" y2="297" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="181,297 187,291 193,297 187,303" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="251" y1="311" x2="257" y2="305" />
+ <line stroke="black" stroke-opacity="1" x1="251" y1="311" x2="245" y2="305" />
+ <line stroke="black" stroke-opacity="1" x1="251" y1="297" x2="251" y2="311" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="162" y1="553" x2="251" y2="553" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="251" y1="362" x2="245" y2="368" />
+ <line stroke="black" stroke-opacity="1" x1="251" y1="362" x2="257" y2="368" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="251" y1="553" x2="251" y2="362" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="414" y="318" width="3" height="109" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="334" y="424" width="83" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="330" y="314" width="84" height="110" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="372" y="327">DdnsDomain</text>
+ <line stroke="black" stroke-opacity="1" x1="330" y1="329" x2="414" y2="329" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="334" y="342">name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="334" y="355">key_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="330" y1="357" x2="414" y2="357" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="334" y="370">DdnsDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="334" y="383">~DdnsDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="334" y="396">getName()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="334" y="409">getKeyName()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="334" y="422">getServers()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="370" y1="455" x2="376" y2="449" />
+ <line stroke="black" stroke-opacity="1" x1="370" y1="455" x2="364" y2="448" />
+ <line stroke="black" stroke-opacity="1" x1="371" y1="428" x2="370" y2="455" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="371,428 376,434 370,439 364,433" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="329" y1="337" x2="323" y2="330" />
+ <line stroke="black" stroke-opacity="1" x1="329" y1="337" x2="322" y2="342" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="299" y1="336" x2="329" y2="337" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="9" y="183">reverse_mgr_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="162" y="187">forward_mgr_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="254" y="78">keys_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="229" y="285">wildcard_domain_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="380" y="452">servers_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="128" y="446">domains_</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="408" y="65" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="302" y="107" width="109" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="298" y="61" width="110" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="353" y="74">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="353" y="89">TSIGKeyInfoMapPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="298" y1="91" x2="408" y2="91" />
+ <line stroke="black" stroke-opacity="1" x1="298" y1="99" x2="408" y2="99" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="167" y="461" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="59" y="503" width="111" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="55" y="457" width="112" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="111" y="470">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="111" y="485">DdnsDomainMapPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="55" y1="487" x2="167" y2="487" />
+ <line stroke="black" stroke-opacity="1" x1="55" y1="495" x2="167" y2="495" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="297" y1="85" x2="291" y2="79" />
+ <line stroke="black" stroke-opacity="1" x1="297" y1="85" x2="291" y2="91" />
+ <line stroke="black" stroke-opacity="1" x1="167" y1="85" x2="297" y2="85" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="167,85 173,79 179,85 173,91" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="111" y1="527" x2="117" y2="521" />
+ <line stroke="black" stroke-opacity="1" x1="111" y1="527" x2="105" y2="520" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="112" y1="507" x2="111" y2="527" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="113" y1="456" x2="119" y2="450" />
+ <line stroke="black" stroke-opacity="1" x1="113" y1="456" x2="107" y2="450" />
+ <line stroke="black" stroke-opacity="1" x1="113" y1="422" x2="113" y2="456" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="113,422 119,428 113,434 107,428" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="395" y="136" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="303" y="178" width="95" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="299" y="132" width="96" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="347" y="145">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="347" y="160">TSIGKeyInfoMap</text>
+ <line stroke="black" stroke-opacity="1" x1="299" y1="162" x2="395" y2="162" />
+ <line stroke="black" stroke-opacity="1" x1="299" y1="170" x2="395" y2="170" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="399" y1="157" x2="509" y2="157" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="509" y1="110" x2="503" y2="116" />
+ <line stroke="black" stroke-opacity="1" x1="509" y1="110" x2="515" y2="116" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="509" y1="157" x2="509" y2="110" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="350" y1="131" x2="356" y2="125" />
+ <line stroke="black" stroke-opacity="1" x1="350" y1="131" x2="344" y2="124" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="352" y1="111" x2="350" y2="131" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/config_from_file_sequence.svg b/src/bin/d2/images/config_from_file_sequence.svg
new file mode 100644
index 0000000..b5f53f7
--- /dev/null
+++ b/src/bin/d2/images/config_from_file_sequence.svg
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="746" height="226" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="261" y="8" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="167" y="23" width="97" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="163" y="4" width="98" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="212" y="20">:DControllerBase</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="488" y="8" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="402" y="23" width="89" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="398" y="4" width="90" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="443" y="20">:DProcessBase</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="214" y1="45" x2="214" y2="226" />
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="445" y1="45" x2="445" y2="226" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="730" y="8" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="648" y="23" width="85" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="644" y="4" width="86" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="687" y="20">:DCfgMgrBase</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="689" y1="45" x2="689" y2="226" />
+</g>
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="440" y="175" width="10" height="28" />
+</g>
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="684" y="180" width="10" height="24" />
+</g>
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="209" y="67" width="10" height="147" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="39" y1="72" x2="209" y2="72" />
+ <polygon fill="#000000" stroke="none" points="209,72 205,68 205,76" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="451" y1="194" x2="684" y2="194" />
+ <polygon fill="#000000" stroke="none" points="684,194 680,190 680,198" />
+</g>
+<ellipse fill="black" stroke="none" cx="34.5" cy="73.5" rx="4.5" ry="4.5" />
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="215" y="99" width="10" height="24" />
+</g>
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="215" y="164" width="10" height="37" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 227 165 L 244 165 L 244 172 L 227 172" />
+ <polygon fill="#000000" stroke="none" points="227,172 231,176 231,168" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="226" y1="187" x2="440" y2="187" />
+ <polygon fill="#000000" stroke="none" points="440,187 436,183 436,191" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 227 100 L 244 100 L 244 107 L 227 107" />
+ <polygon fill="#000000" stroke="none" points="227,107 231,111 231,103" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="591" y="191">parseConfig()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="111" y="67">configFromFile()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="224" y="95">getConfigFile()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="372" y="182">configure()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="231" y="160">updateConfig()</text>
+</g>
+</svg>
diff --git a/src/bin/d2/images/config_parser_classes.svg b/src/bin/d2/images/config_parser_classes.svg
new file mode 100644
index 0000000..974bf0f
--- /dev/null
+++ b/src/bin/d2/images/config_parser_classes.svg
@@ -0,0 +1,262 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="1126" height="713" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="146" y="343" width="3" height="147" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="20" y="487" width="129" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="16" y="339" width="130" height="148" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="81" y="352">DdnsDomainListMgr</text>
+ <line stroke="black" stroke-opacity="1" x1="16" y1="354" x2="146" y2="354" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="20" y="367">wildcard_domain_name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="380">name_</text>
+ <line stroke="black" stroke-opacity="1" x1="16" y1="382" x2="146" y2="382" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="395">DdnsDomainListMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="408">~DdnsDomainListMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="421">matchDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="434">getName()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="447">size()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="460">getWildcardDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="473">getDomains()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="20" y="486">setDomains()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="750" y="474" width="3" height="213" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="622" y="684" width="131" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="618" y="470" width="132" height="214" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="684" y="483">DnsServerInfo</text>
+ <line stroke="black" stroke-opacity="1" x1="618" y1="485" x2="750" y2="485" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="622" y="498">STANDARD_DNS_PORT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="622" y="511">EMPTY_IP_STR</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="524">hostname_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="537">ip_address_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="550">port_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="563">enabled_</text>
+ <line stroke="black" stroke-opacity="1" x1="618" y1="565" x2="750" y2="565" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="578">DnsServerInfo()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="591">~DnsServerInfo()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="604">getHostname()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="617">getPort()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="630">getIpAddress()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="643">isEnabled()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="656">enable()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="669">disable()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="622" y="682">toText()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="422" y="422" width="3" height="109" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="342" y="528" width="83" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="338" y="418" width="84" height="110" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="380" y="431">DdnsDomain</text>
+ <line stroke="black" stroke-opacity="1" x1="338" y1="433" x2="422" y2="433" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="342" y="446">name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="342" y="459">key_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="338" y1="461" x2="422" y2="461" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="342" y="474">DdnsDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="342" y="487">~DdnsDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="342" y="500">getName()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="342" y="513">getKeyName()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="342" y="526">getServers()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="598" y="316" width="3" height="95" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="462" y="408" width="139" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="458" y="312" width="140" height="96" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="528" y="325">DnsServerInfoListParser</text>
+ <line stroke="black" stroke-opacity="1" x1="458" y1="327" x2="598" y2="327" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="462" y="340">list_name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="462" y="353">parsers_</text>
+ <line stroke="black" stroke-opacity="1" x1="458" y1="355" x2="598" y2="355" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="462" y="368">DnsServerInfoListParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="462" y="381">~DnsServerInfoListParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="462" y="394">build()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="462" y="407">commit()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="738" y="348" width="3" height="95" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="618" y="440" width="123" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="614" y="344" width="124" height="96" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="676" y="357">DnsServerInfoParser</text>
+ <line stroke="black" stroke-opacity="1" x1="614" y1="359" x2="738" y2="359" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="618" y="372">entry_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="614" y1="374" x2="738" y2="374" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="618" y="387">DnsServerInfoParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="618" y="400">~DnsServerInfoParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="618" y="413">build()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="618" y="426">createConfigParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="618" y="439">commit()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="160" y="205" width="3" height="95" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="10" y="297" width="153" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="6" y="201" width="154" height="96" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="83" y="214">DdnsDomainListMgrParser</text>
+ <line stroke="black" stroke-opacity="1" x1="6" y1="216" x2="160" y2="216" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="10" y="229">entry_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="6" y1="231" x2="160" y2="231" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="10" y="244">DdnsDomainListMgrParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="10" y="257">~DdnsDomainListMgrParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="10" y="270">build()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="10" y="283">createConfigParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="10" y="296">commit()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="585" y="65" width="3" height="95" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="473" y="157" width="115" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="469" y="61" width="116" height="96" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="527" y="74">TSIGKeyInfoParser</text>
+ <line stroke="black" stroke-opacity="1" x1="469" y1="76" x2="585" y2="76" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="473" y="89">entry_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="469" y1="91" x2="585" y2="91" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="473" y="104">TSIGKeyInfoParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="473" y="117">~TSIGKeyInfoParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="473" y="130">build()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="473" y="143">createConfigParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="473" y="156">commit()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="680" y1="469" x2="686" y2="463" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="469" x2="674" y2="463" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="680" y1="444" x2="680" y2="469" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="602" y1="324" x2="675" y2="324" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="83" y1="338" x2="89" y2="332" />
+ <line stroke="black" stroke-opacity="1" x1="83" y1="338" x2="77" y2="331" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="84" y1="301" x2="83" y2="338" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="675" y1="343" x2="681" y2="337" />
+ <line stroke="black" stroke-opacity="1" x1="675" y1="343" x2="669" y2="337" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="675" y1="324" x2="675" y2="343" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="434" y="33" width="3" height="95" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="304" y="125" width="133" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="300" y="29" width="134" height="96" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="367" y="42">TSIGKeyInfoListParser</text>
+ <line stroke="black" stroke-opacity="1" x1="300" y1="44" x2="434" y2="44" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="57">list_name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="70">parsers_</text>
+ <line stroke="black" stroke-opacity="1" x1="300" y1="72" x2="434" y2="72" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="85">TSIGKeyInfoListParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="98">~TSIGKeyInfoListParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="111">build()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="124">commit()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="443" y="276" width="3" height="95" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="329" y="368" width="117" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="325" y="272" width="118" height="96" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="384" y="285">DdnsDomainParser</text>
+ <line stroke="black" stroke-opacity="1" x1="325" y1="287" x2="443" y2="287" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="300">entry_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="325" y1="302" x2="443" y2="302" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="315">DdnsDomainParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="328">~DdnsDomainParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="341">build()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="354">createConfigParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="367">commit()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="311" y="241" width="3" height="95" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="181" y="333" width="133" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="177" y="237" width="134" height="96" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="244" y="250">DdnsDomainListParser</text>
+ <line stroke="black" stroke-opacity="1" x1="177" y1="252" x2="311" y2="252" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="181" y="265">list_name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="181" y="278">parsers_</text>
+ <line stroke="black" stroke-opacity="1" x1="177" y1="280" x2="311" y2="280" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="181" y="293">DdnsDomainListParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="181" y="306">~DdnsDomainListParser()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="181" y="319">build()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="181" y="332">commit()</text>
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="315" y1="253" x2="386" y2="253" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="164" y1="220" x2="246" y2="220" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="383" y1="417" x2="389" y2="411" />
+ <line stroke="black" stroke-opacity="1" x1="383" y1="417" x2="377" y2="410" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="384" y1="372" x2="383" y2="417" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="447" y1="287" x2="530" y2="287" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="438" y1="41" x2="529" y2="41" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="529" y1="60" x2="535" y2="54" />
+ <line stroke="black" stroke-opacity="1" x1="529" y1="60" x2="523" y2="54" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="529" y1="41" x2="529" y2="60" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="530" y1="311" x2="536" y2="305" />
+ <line stroke="black" stroke-opacity="1" x1="530" y1="311" x2="524" y2="305" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="530" y1="287" x2="530" y2="311" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="246" y1="236" x2="252" y2="230" />
+ <line stroke="black" stroke-opacity="1" x1="246" y1="236" x2="240" y2="230" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="246" y1="220" x2="246" y2="236" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="386" y1="271" x2="392" y2="265" />
+ <line stroke="black" stroke-opacity="1" x1="386" y1="271" x2="380" y2="265" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="386" y1="253" x2="386" y2="271" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="723" y="99" width="3" height="121" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="643" y="217" width="83" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="639" y="95" width="84" height="122" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="681" y="108">TSIGKeyInfo</text>
+ <line stroke="black" stroke-opacity="1" x1="639" y1="110" x2="723" y2="110" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="643" y="123">name_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="643" y="136">algorithm_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="643" y="149">secret_</text>
+ <line stroke="black" stroke-opacity="1" x1="639" y1="151" x2="723" y2="151" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="643" y="164">TSIGKeyInfo()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="643" y="177">~TSIGKeyInfo()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="643" y="190">getName()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="643" y="203">getAlgorithm()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="643" y="216">getSecret()</text>
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="589" y1="74" x2="683" y2="74" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="149" y="9" width="3" height="161" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="11" y="167" width="141" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="7" y="5" width="142" height="162" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="78" y="18">D2CfgMgr</text>
+ <line stroke="black" stroke-opacity="1" x1="7" y1="20" x2="149" y2="20" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="11" y="33">IPV4_REV_ZONE_SUFFIX</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="11" y="46">IPV6_REV_ZONE_SUFFIX</text>
+ <line stroke="black" stroke-opacity="1" x1="7" y1="48" x2="149" y2="48" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="61">D2CfgMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="74">~D2CfgMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="87">getD2CfgContext()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="100">matchForward()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="113">matchReverse()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="11" y="126">reverseIpAddress()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="11" y="139">reverseV4Address()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="11" y="152">reverseV6Address()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="165">createConfigParser()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="683" y1="94" x2="689" y2="88" />
+ <line stroke="black" stroke-opacity="1" x1="683" y1="94" x2="677" y2="88" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="683" y1="74" x2="683" y2="94" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="82" y1="200" x2="88" y2="194" />
+ <line stroke="black" stroke-opacity="1" x1="82" y1="200" x2="76" y2="194" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="82" y1="171" x2="82" y2="200" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/cpl_signal_classes.svg b/src/bin/d2/images/cpl_signal_classes.svg
new file mode 100644
index 0000000..f70e302
--- /dev/null
+++ b/src/bin/d2/images/cpl_signal_classes.svg
@@ -0,0 +1,393 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="804" height="836" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="344" y="10" width="3" height="495" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="186" y="502" width="161" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="182" y="6" width="162" height="496" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="263" y="20">DControllerBase</text>
+ <line stroke="black" stroke-opacity="1" x1="182" y1="22" x2="344" y2="22" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="36">app_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="50">bin_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="64">verbose_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="78">spec_file_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="182" y1="80" x2="344" y2="80" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="94">DControllerBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="108">~DControllerBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="122">launch()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="136">updateConfig()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="150">configFromFile()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="164">executeCommand()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="178">getAppName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="192">getBinName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="206">customOption()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="186" y="220">createProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="234">customControllerCommand()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="248">getUsageText()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="262">getCustomOpts()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="276">processSignal()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="290">isVerbose()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="304">setVerbose()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="318">getIOService()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="332">getSpecFileName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="346">setSpecFileName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="186" y="360">getController()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="186" y="374">setController()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="388">parseArgs()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="402">initProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="416">runProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="430">shutdownProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="444">initSignalHandling()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="458">osSignalHandler()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="472">ioSignalHandler()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="486">getProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="186" y="500">usage()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="737" y="207" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="667" y="251" width="73" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="663" y="203" width="74" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="700" y="217">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="700" y="233">IOSignalPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="663" y1="235" x2="737" y2="235" />
+ <line stroke="black" stroke-opacity="1" x1="663" y1="243" x2="737" y2="243" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="467" y="353" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="371" y="397" width="99" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="367" y="349" width="100" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="417" y="363">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="417" y="379">IOSignalHandler</text>
+ <line stroke="black" stroke-opacity="1" x1="367" y1="381" x2="467" y2="381" />
+ <line stroke="black" stroke-opacity="1" x1="367" y1="389" x2="467" y2="389" />
+</g>
+ <rect fill="#c0ffff" stroke="none" stroke-opacity="1" x="323" y="800" width="268" height="25" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" x="323" y="812">Blue class integrate signal handling into D2</text>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="348" y1="278" x2="419" y2="278" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="419" y1="348" x2="425" y2="342" />
+ <line stroke="black" stroke-opacity="1" x1="419" y1="348" x2="413" y2="342" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="419" y1="278" x2="419" y2="348" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="751" y="309" width="3" height="103" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="653" y="409" width="101" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="649" y="305" width="102" height="104" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="700" y="319">IOSignal</text>
+ <line stroke="black" stroke-opacity="1" x1="649" y1="321" x2="751" y2="321" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="335">signum_</text>
+ <line stroke="black" stroke-opacity="1" x1="649" y1="337" x2="751" y2="337" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="351">IOSignal()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="365">~IOSignal()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="653" y="379">nextSequenceId()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="393">getSequenceId()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="653" y="407">getSignum()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="698" y1="304" x2="703" y2="297" />
+ <line stroke="black" stroke-opacity="1" x1="698" y1="304" x2="691" y2="298" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="697" y1="255" x2="698" y2="304" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="538" y="197">signals_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="100" y="444">signal_set_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="566" y="314">sequence_id_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="193" y="703">onreceipt_handler_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="454" y="27">io_signal_queue_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="599" y="729">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="433" y="729">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="549" y="424">sequence_id_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="429" y="419">handler_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="724" y="539">timer_</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="669" y="10" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="563" y="54" width="109" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="559" y="6" width="110" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="614" y="20">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="614" y="36">IOSignalQueuePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="559" y1="38" x2="669" y2="38" />
+ <line stroke="black" stroke-opacity="1" x1="559" y1="46" x2="669" y2="46" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="558" y1="31" x2="552" y2="25" />
+ <line stroke="black" stroke-opacity="1" x1="558" y1="31" x2="552" y2="37" />
+ <line stroke="black" stroke-opacity="1" x1="348" y1="31" x2="558" y2="31" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="348,31 354,25 360,31 354,37" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="668" y="77" width="3" height="95" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="568" y="169" width="103" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="564" y="73" width="104" height="96" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="616" y="87">IOSignalQueue</text>
+ <line stroke="black" stroke-opacity="1" x1="564" y1="89" x2="668" y2="89" />
+ <line stroke="black" stroke-opacity="1" x1="564" y1="97" x2="668" y2="97" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="111">IOSignalQueue()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="125">~IOSignalQueue()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="139">pushSignal()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="153">popSignal()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="568" y="167">clear()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="618" y1="72" x2="624" y2="66" />
+ <line stroke="black" stroke-opacity="1" x1="618" y1="72" x2="612" y2="66" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="618" y1="58" x2="618" y2="72" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="168" y="532" width="3" height="285" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="24" y="814" width="147" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="20" y="528" width="148" height="286" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="94" y="542">SignalSet</text>
+ <line stroke="black" stroke-opacity="1" x1="20" y1="544" x2="168" y2="544" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="558">local_signals_</text>
+ <line stroke="black" stroke-opacity="1" x1="20" y1="560" x2="168" y2="560" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="574">SignalSet()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="588">SignalSet()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="602">SignalSet()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="616">~SignalSet()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="630">add()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="644">clear()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="658">getNext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="672">handleNext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="686">remove()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="24" y="700">setOnReceiptHandler()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="24" y="714">clearOnReceiptHandler()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="24" y="728">invokeOnReceiptHandler()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="742">block()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="756">erase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="770">insert()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="784">maskSignals()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="798">popNext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="24" y="812">unblock()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="566" y="206" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="490" y="250" width="79" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="486" y="202" width="80" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="526" y="216">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="526" y="232">IOSignalMap</text>
+ <line stroke="black" stroke-opacity="1" x1="486" y1="234" x2="566" y2="234" />
+ <line stroke="black" stroke-opacity="1" x1="486" y1="242" x2="566" y2="242" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="585" y="712" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="509" y="756" width="79" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="505" y="708" width="80" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="545" y="722">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="545" y="738">IOServicePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="505" y1="740" x2="585" y2="740" />
+ <line stroke="black" stroke-opacity="1" x1="505" y1="748" x2="585" y2="748" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="563" y1="123" x2="528" y2="123" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="563,123 557,129 551,123 557,117" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="528" y1="201" x2="534" y2="195" />
+ <line stroke="black" stroke-opacity="1" x1="528" y1="201" x2="522" y2="195" />
+ <line stroke="black" stroke-opacity="1" x1="528" y1="123" x2="528" y2="201" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="662" y1="228" x2="656" y2="222" />
+ <line stroke="black" stroke-opacity="1" x1="662" y1="228" x2="656" y2="234" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="570" y1="228" x2="662" y2="228" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="672" y1="120" x2="783" y2="120" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="672,120 678,114 684,120 678,126" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="783" y1="120" x2="784" y2="733" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="589" y1="733" x2="595" y2="739" />
+ <line stroke="black" stroke-opacity="1" x1="589" y1="733" x2="595" y2="727" />
+ <line stroke="black" stroke-opacity="1" x1="784" y1="733" x2="589" y2="733" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="336" y1="506" x2="336" y2="734" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="336,506 342,512 336,518 330,512" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="504" y1="734" x2="498" y2="728" />
+ <line stroke="black" stroke-opacity="1" x1="504" y1="734" x2="498" y2="740" />
+ <line stroke="black" stroke-opacity="1" x1="336" y1="734" x2="504" y2="734" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="127" y="453" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="53" y="497" width="77" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="49" y="449" width="78" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="88" y="463">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="88" y="479">SignalSetPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="49" y1="481" x2="127" y2="481" />
+ <line stroke="black" stroke-opacity="1" x1="49" y1="489" x2="127" y2="489" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="318" y="584" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="208" y="628" width="113" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="204" y="580" width="114" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="261" y="594">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="261" y="610">BoolSignalHandler</text>
+ <line stroke="black" stroke-opacity="1" x1="204" y1="612" x2="318" y2="612" />
+ <line stroke="black" stroke-opacity="1" x1="204" y1="620" x2="318" y2="620" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="574" y="358" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="504" y="402" width="73" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="500" y="354" width="74" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="537" y="368">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="537" y="384">IOSignalId</text>
+ <line stroke="black" stroke-opacity="1" x1="500" y1="386" x2="574" y2="386" />
+ <line stroke="black" stroke-opacity="1" x1="500" y1="394" x2="574" y2="394" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="90" y1="527" x2="96" y2="521" />
+ <line stroke="black" stroke-opacity="1" x1="90" y1="527" x2="84" y2="521" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="90" y1="501" x2="90" y2="527" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="263" y1="632" x2="257" y2="638" />
+ <line stroke="black" stroke-opacity="1" x1="263" y1="632" x2="269" y2="638" />
+ <line stroke="black" stroke-opacity="1" x1="263" y1="673" x2="263" y2="632" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="181" y1="401" x2="90" y2="401" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="181,401 175,407 169,401 175,395" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="172" y1="673" x2="263" y2="673" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="172,673 178,667 184,673 178,679" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="90" y1="448" x2="96" y2="442" />
+ <line stroke="black" stroke-opacity="1" x1="90" y1="448" x2="84" y2="442" />
+ <line stroke="black" stroke-opacity="1" x1="90" y1="401" x2="90" y2="448" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="648" y1="327" x2="560" y2="327" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="648,327 642,333 636,327 642,321" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="560" y1="353" x2="566" y2="347" />
+ <line stroke="black" stroke-opacity="1" x1="560" y1="353" x2="554" y2="347" />
+ <line stroke="black" stroke-opacity="1" x1="560" y1="327" x2="560" y2="353" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="499" y1="377" x2="493" y2="370" />
+ <line stroke="black" stroke-opacity="1" x1="499" y1="377" x2="492" y2="382" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="471" y1="376" x2="499" y2="377" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="267" y1="579" x2="273" y2="573" />
+ <line stroke="black" stroke-opacity="1" x1="267" y1="579" x2="261" y2="572" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="268" y1="506" x2="267" y2="579" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="531" y1="353" x2="536" y2="346" />
+ <line stroke="black" stroke-opacity="1" x1="531" y1="353" x2="524" y2="347" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="529" y1="254" x2="531" y2="353" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="588" y="551" width="3" height="123" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="500" y="671" width="91" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="496" y="547" width="92" height="124" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="542" y="561">IntervalTimer</text>
+ <line stroke="black" stroke-opacity="1" x1="496" y1="563" x2="588" y2="563" />
+ <line stroke="black" stroke-opacity="1" x1="496" y1="571" x2="588" y2="571" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="585">IntervalTimer()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="599">operator =()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="613">IntervalTimer()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="627">~IntervalTimer()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="641">setup()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="655">cancel()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="500" y="669">getInterval()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="546" y1="707" x2="551" y2="700" />
+ <line stroke="black" stroke-opacity="1" x1="546" y1="707" x2="539" y2="701" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="545" y1="675" x2="546" y2="707" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="585" y="463" width="3" height="53" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="497" y="513" width="91" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="493" y="459" width="92" height="54" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="539" y="473">TimerCallback</text>
+ <line stroke="black" stroke-opacity="1" x1="493" y1="475" x2="585" y2="475" />
+ <line stroke="black" stroke-opacity="1" x1="493" y1="483" x2="585" y2="483" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="497" y="497">TimerCallback()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="497" y="511">operator ()()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="763" y="548" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="667" y="592" width="99" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="663" y="544" width="100" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="713" y="558">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="713" y="574">IntervalTimerPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="663" y1="576" x2="763" y2="576" />
+ <line stroke="black" stroke-opacity="1" x1="663" y1="584" x2="763" y2="584" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="671" y1="488" x2="671" y2="425" />
+<ellipse fill="none" stroke="black" stroke-width="1" stroke-opacity="1" cx="671" cy="419" rx="5" ry="5" />
+ <line stroke="black" stroke-opacity="1" x1="666" y1="419" x2="676" y2="419" />
+ <line stroke="black" stroke-opacity="1" x1="671" y1="414" x2="671" y2="424" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="589" y1="488" x2="671" y2="488" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="539" y1="406" x2="533" y2="412" />
+ <line stroke="black" stroke-opacity="1" x1="539" y1="406" x2="545" y2="411" />
+ <line stroke="black" stroke-opacity="1" x1="540" y1="458" x2="539" y2="406" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="540,458 533,452 539,446 545,451" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="492" y1="488" x2="419" y2="488" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="492,488 486,494 480,488 486,482" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="419" y1="401" x2="413" y2="407" />
+ <line stroke="black" stroke-opacity="1" x1="419" y1="401" x2="425" y2="407" />
+ <line stroke="black" stroke-opacity="1" x1="419" y1="488" x2="419" y2="401" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="541" y1="517" x2="535" y2="523" />
+ <line stroke="black" stroke-opacity="1" x1="541" y1="517" x2="547" y2="523" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="541" y1="546" x2="541" y2="517" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="714" y1="596" x2="714" y2="611" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="592" y1="611" x2="598" y2="617" />
+ <line stroke="black" stroke-opacity="1" x1="592" y1="611" x2="598" y2="605" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="714" y1="611" x2="592" y2="611" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="714" y1="543" x2="719" y2="536" />
+ <line stroke="black" stroke-opacity="1" x1="714" y1="543" x2="707" y2="537" />
+ <line stroke="black" stroke-opacity="1" x1="713" y1="413" x2="714" y2="543" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="713,413 719,418 713,424 707,419" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/cpl_signal_sequence.svg b/src/bin/d2/images/cpl_signal_sequence.svg
new file mode 100644
index 0000000..b57f5b1
--- /dev/null
+++ b/src/bin/d2/images/cpl_signal_sequence.svg
@@ -0,0 +1,318 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="848" height="803" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="92" y1="45" x2="92" y2="803" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="139" y="8" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="45" y="23" width="97" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="41" y="4" width="98" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="90" y="20">:DControllerBase</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="384" y="74" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="296" y="89" width="91" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="292" y="70" width="92" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="338" y="86">:IOSignalQueue</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="340" y1="111" x2="340" y2="803" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="469" y="101" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="411" y="116" width="61" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="407" y="97" width="62" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="438" y="113">:SignalSet</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="440" y1="138" x2="440" y2="803" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="559" y="350" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="507" y="365" width="55" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="503" y="346" width="56" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="531" y="362">:IOSignal</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="533" y1="387" x2="533" y2="803" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="662" y="373" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="586" y="388" width="79" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="582" y="369" width="80" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="622" y="385">:IntervalTimer</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="624" y1="410" x2="624" y2="803" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="769" y="421" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="686" y="436" width="86" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="682" y="417" width="87" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="726" y="433">:TimerCallback</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="727" y1="458" x2="727" y2="803" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="832" y="8" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="773" y="23" width="62" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="769" y="4" width="63" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="801" y="20">:IOService</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="802" y1="45" x2="802" y2="803" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="262" y="8" width="3" height="18" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="176" y="23" width="89" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="172" y="4" width="90" height="19" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="217" y="20">:DProcessBase</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="219" y1="45" x2="219" y2="803" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="87" y="60" width="10" height="726" />
+</g>
+<g>
+ <polygon fill="#c0ffff" stroke="black" stroke-opacity="1" points="262,661 416,661 416,671 426,671 426,709 262,709 262,661" />
+ <line stroke="black" stroke-opacity="1" x1="416" y1="661" x2="426" y2="671" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="272" y="683">Details of configFromFile</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="272" y="695">omitted for clarity</text>
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="528" y="399" width="10" height="106" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="93" y="242" width="10" height="521" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="335" y="120" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="435" y="166" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="435" y="203" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="435" y="285" width="10" height="152" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="335" y="341" width="10" height="82" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="619" y="419" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="619" y="466" width="10" height="41" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="722" y="485" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="797" y="253" width="10" height="527" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="722" y="549" width="10" height="195" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="214" y="249" width="10" height="542" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="335" y="587" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="528" y="619" width="10" height="24" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="539" y1="471" x2="619" y2="471" />
+ <polygon fill="#000000" stroke="none" points="619,471 615,467 615,475" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="33" y1="66" x2="87" y2="66" />
+ <polygon fill="#000000" stroke="none" points="87,66 83,62 83,70" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="310" y1="290" x2="435" y2="290" />
+ <polygon fill="#000000" stroke="none" points="435,290 431,286 431,294" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="630" y1="492" x2="722" y2="492" />
+ <polygon fill="#000000" stroke="none" points="722,492 718,488 718,496" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="733" y1="554" x2="797" y2="554" />
+ <polygon fill="#000000" stroke="none" points="733,554 737,550 737,558" />
+</g>
+<g>
+ <polygon fill="#c0ffff" stroke="black" stroke-opacity="1" points="29,288 173,288 173,298 183,298 183,348 29,348 29,288" />
+ <line stroke="black" stroke-opacity="1" x1="173" y1="288" x2="183" y2="298" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="39" y="310">Sometime after</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="39" y="322">runProcess is called</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="39" y="334">SIGHUP is sent</text>
+</g>
+<ellipse fill="black" stroke="none" cx="28.5" cy="67.5" rx="4.5" ry="4.5" />
+<ellipse fill="black" stroke="none" cx="305.5" cy="291.5" rx="4.5" ry="4.5" />
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="183" y1="306" x2="300" y2="291" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="93" y="98" width="10" height="122" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="99" y="562" width="10" height="195" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="441" y="313" width="10" height="116" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="220" y="722" width="10" height="24" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 105 99 L 122 99 L 122 106 L 105 106" />
+ <polygon fill="#000000" stroke="none" points="105,106 109,110 109,102" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="104" y1="134" x2="335" y2="134" />
+ <polygon fill="#000000" stroke="none" points="335,134 331,130 331,138" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="104" y1="178" x2="435" y2="178" />
+ <polygon fill="#000000" stroke="none" points="435,178 431,174 431,182" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="104" y1="208" x2="435" y2="208" />
+ <polygon fill="#000000" stroke="none" points="435,208 431,204 431,212" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 453 314 L 470 314 L 470 321 L 453 321" />
+ <polygon fill="#000000" stroke="none" points="453,321 457,325 457,317" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="346" y1="351" x2="441" y2="351" />
+ <polygon fill="#000000" stroke="none" points="346,351 350,347 350,355" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="110" y1="567" x2="722" y2="567" />
+ <polygon fill="#000000" stroke="none" points="110,567 114,563 114,571" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="110" y1="592" x2="335" y2="592" />
+ <polygon fill="#000000" stroke="none" points="335,592 331,588 331,596" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="110" y1="624" x2="528" y2="624" />
+ <polygon fill="#000000" stroke="none" points="528,624 524,620 524,628" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="105" y="652" width="10" height="100" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="346" y1="404" x2="528" y2="404" />
+ <polygon fill="#000000" stroke="none" points="528,404 524,400 524,408" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="539" y1="431" x2="619" y2="431" />
+ <polygon fill="#000000" stroke="none" points="619,431 615,427 615,435" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 117 653 L 134 653 L 134 660 L 117 660" />
+ <polygon fill="#000000" stroke="none" points="117,660 121,664 121,656" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="111" y="686" width="10" height="61" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 123 687 L 140 687 L 140 694 L 123 694" />
+ <polygon fill="#000000" stroke="none" points="123,694 127,698 127,690" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="122" y1="727" x2="220" y2="727" />
+ <polygon fill="#000000" stroke="none" points="220,727 216,723 216,731" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="261" y1="687" x2="141" y2="691" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="38" y="61">launch()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="106" y="91">initSignalHandling()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="113" y="121">IOSignalQueue(io_service)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="116" y="161">setOnReceiptHandler(DControllerBase::osSignalHandler)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="140" y="202">SignalSet(SIGHUP,SIGINT,SIGTERM)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="288" y="279">internalHandler(SIGHUP)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="359" y="309">invokeOnReceiptHandler(SIGHUP)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="314" y="340">pushSignal(SIGHUP, DControllerBase::ioSignalHandler)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="353" y="399">IOSignal(io_service, SIGHUP, handler)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="548" y="418">IntervalTimer(io_service)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="431" y="459">setup(TimerCallBack(sequence_id, handler), 1, ONE_SHOT))</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="637" y="478">TimerCallback(sequence_id, handler)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="737" y="547">operator ()()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="722" y="256">run()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="118" y="240">runProcess()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="146" y="254">run()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="128" y="562">ioSignalHandler(sequence_id)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="191" y="587">popSignal()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="448" y="615">getSignum()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="117" y="647">processSignal(SIGHUP)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="126" y="682">configFromFile()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="142" y="722">configure()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="225" y1="260" x2="797" y2="260" />
+ <polygon fill="#000000" stroke="none" points="797,260 793,256 793,264" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 105 243 L 122 243 L 122 250 L 105 250" />
+ <polygon fill="#000000" stroke="none" points="105,250 109,254 109,246" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="104" y1="259" x2="214" y2="259" />
+ <polygon fill="#000000" stroke="none" points="214,259 210,255 210,263" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/d2_app_classes.svg b/src/bin/d2/images/d2_app_classes.svg
new file mode 100644
index 0000000..1befe23
--- /dev/null
+++ b/src/bin/d2/images/d2_app_classes.svg
@@ -0,0 +1,345 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="812" height="795" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="175" y="14" width="3" height="453" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="17" y="464" width="161" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="13" y="10" width="162" height="454" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="94" y="24">DControllerBase</text>
+ <line stroke="black" stroke-opacity="1" x1="13" y1="26" x2="175" y2="26" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="40">app_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="54">bin_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="68">verbose_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="82">spec_file_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="13" y1="84" x2="175" y2="84" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="98">DControllerBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="112">~DControllerBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="126">launch()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="140">updateConfig()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="154">configFromFile()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="168">executeCommand()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="182">getAppName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="196">getBinName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="210">customOption()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="17" y="224">createProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="238">customControllerCommand()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="252">getUsageText()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="266">getCustomOpts()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="280">isVerbose()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="294">setVerbose()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="308">getIOService()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="322">getSpecFileName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="336">setSpecFileName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="17" y="350">getController()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="17" y="364">setController()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="378">parseArgs()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="392">initProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="406">runProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="420">shutdownProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="434">getConfigFile()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="448">getProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="17" y="462">usage()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="629" y="261" width="3" height="193" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="515" y="451" width="117" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="511" y="257" width="118" height="194" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="570" y="271">DCfgMgrBase</text>
+ <line stroke="black" stroke-opacity="1" x1="511" y1="273" x2="629" y2="273" />
+ <line stroke="black" stroke-opacity="1" x1="511" y1="281" x2="629" y2="281" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="295">DCfgMgrBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="309">~DCfgMgrBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="323">parseConfig()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="337">addToParseOrder()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="351">getParseOrder()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="365">getContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="379">buildParams()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="515" y="393">createConfigParser()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="515" y="407">createNewContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="421">resetContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="435">setContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="515" y="449">buildAndCommit()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="796" y="361" width="3" height="243" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="682" y="601" width="117" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="678" y="357" width="118" height="244" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="737" y="371">DCfgContextBase</text>
+ <line stroke="black" stroke-opacity="1" x1="678" y1="373" x2="796" y2="373" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="682" y="387">OPTIONAL</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="682" y="401">REQUIRED</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="415">boolean_values_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="429">uint32_values_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="443">string_values_</text>
+ <line stroke="black" stroke-opacity="1" x1="678" y1="445" x2="796" y2="445" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="459">DCfgContextBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="473">~DCfgContextBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="487">getParam()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="501">getParam()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="515">getParam()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="529">getBooleanStorage()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="543">getUint32Storage()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="557">getStringStorage()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="682" y="571">clone()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="585">DCfgContextBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="682" y="599">operator =()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="134" y="554" width="3" height="103" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="46" y="654" width="91" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="42" y="550" width="92" height="104" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="88" y="564">D2Controller</text>
+ <line stroke="black" stroke-opacity="1" x1="42" y1="566" x2="134" y2="566" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="46" y="580">d2_app_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="46" y="594">d2_bin_name_</text>
+ <line stroke="black" stroke-opacity="1" x1="42" y1="596" x2="134" y2="596" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="46" y="610">instance()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="46" y="624">~D2Controller()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="46" y="638">createProcess()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="46" y="652">D2Controller()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="90" y1="549" x2="90" y2="473" />
+ <line stroke="black" stroke-opacity="1" x1="91" y1="468" x2="84" y2="473" />
+ <line stroke="black" stroke-opacity="1" x1="91" y1="468" x2="96" y2="474" />
+ <line stroke="black" stroke-opacity="1" x1="84" y1="473" x2="96" y2="474" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="274" y="165" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="198" y="209" width="79" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="194" y="161" width="80" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="234" y="175">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="234" y="191">IOServicePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="194" y1="193" x2="274" y2="193" />
+ <line stroke="black" stroke-opacity="1" x1="194" y1="201" x2="274" y2="201" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="179" y1="118" x2="236" y2="118" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="179,118 185,112 191,118 185,124" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="236" y1="160" x2="242" y2="154" />
+ <line stroke="black" stroke-opacity="1" x1="236" y1="160" x2="230" y2="154" />
+ <line stroke="black" stroke-opacity="1" x1="236" y1="118" x2="236" y2="160" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="424" y="153" width="3" height="229" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="320" y="379" width="107" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="316" y="149" width="108" height="230" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="370" y="163">DProcessBase</text>
+ <line stroke="black" stroke-opacity="1" x1="316" y1="165" x2="424" y2="165" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="179">app_name_</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="193">shut_down_flag_</text>
+ <line stroke="black" stroke-opacity="1" x1="316" y1="195" x2="424" y2="195" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="209">DProcessBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="320" y="223">init()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="320" y="237">run()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="320" y="251">shutdown()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="320" y="265">configure()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-style="italic" x="320" y="279">command()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="293">~DProcessBase()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="307">shouldShutdown()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="321">setShutdownFlag()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="335">getAppName()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="349">getIoService()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="363">stopIOService()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="320" y="377">getCfgMgr()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="315" y1="265" x2="236" y2="265" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="315,265 309,271 303,265 309,259" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="236" y1="213" x2="230" y2="219" />
+ <line stroke="black" stroke-opacity="1" x1="236" y1="213" x2="242" y2="219" />
+ <line stroke="black" stroke-opacity="1" x1="236" y1="265" x2="236" y2="213" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="424" y="72" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="320" y="116" width="107" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="316" y="68" width="108" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="370" y="82">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="370" y="98">DProcessBasePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="316" y1="100" x2="424" y2="100" />
+ <line stroke="black" stroke-opacity="1" x1="316" y1="108" x2="424" y2="108" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="448" y="468" width="3" height="313" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="276" y="778" width="175" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="272" y="464" width="176" height="314" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="360" y="478">D2Process</text>
+ <line stroke="black" stroke-opacity="1" x1="272" y1="480" x2="448" y2="480" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="276" y="494">QUEUE_RESTART_PERCENT</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="508">reconf_queue_flag_</text>
+ <line stroke="black" stroke-opacity="1" x1="272" y1="510" x2="448" y2="510" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="524">D2Process()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="538">init()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="552">run()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="566">shutdown()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="580">configure()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="594">command()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="608">~D2Process()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="622">checkQueueStatus()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="636">reconfigureQueueMgr()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="650">runIO()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="664">canShutdown()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="678">setReconfQueueFlag()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="692">setShutdownType()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="706">getD2CfgMgr()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="720">getD2QueueMgr()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="734">getD2UpdateMgr()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="748">getReconfQueueFlag()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="276" y="762">getShutdownType()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="276" y="776">getShutdownTypeStr()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="372" y1="148" x2="378" y2="142" />
+ <line stroke="black" stroke-opacity="1" x1="372" y1="148" x2="366" y2="142" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="372" y1="120" x2="372" y2="148" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="179" y1="39" x2="372" y2="39" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="179,39 185,33 191,39 185,45" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="372" y1="67" x2="378" y2="61" />
+ <line stroke="black" stroke-opacity="1" x1="372" y1="67" x2="366" y2="61" />
+ <line stroke="black" stroke-opacity="1" x1="372" y1="39" x2="372" y2="67" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="366" y1="463" x2="367" y2="388" />
+ <line stroke="black" stroke-opacity="1" x1="368" y1="383" x2="361" y2="388" />
+ <line stroke="black" stroke-opacity="1" x1="368" y1="383" x2="373" y2="389" />
+ <line stroke="black" stroke-opacity="1" x1="361" y1="388" x2="373" y2="389" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="271" y1="607" x2="265" y2="600" />
+ <line stroke="black" stroke-opacity="1" x1="271" y1="607" x2="264" y2="612" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="138" y1="606" x2="271" y2="607" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="649" y="539" width="3" height="243" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="499" y="779" width="153" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="495" y="535" width="154" height="244" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="572" y="549">D2CfgMgr</text>
+ <line stroke="black" stroke-opacity="1" x1="495" y1="551" x2="649" y2="551" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="499" y="565">IPV4_REV_ZONE_SUFFIX</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="499" y="579">IPV6_REV_ZONE_SUFFIX</text>
+ <line stroke="black" stroke-opacity="1" x1="495" y1="581" x2="649" y2="581" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="595">D2CfgMgr()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="609">~D2CfgMgr()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="623">getD2CfgContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="637">forwardUpdatesEnabled()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="651">reverseUpdatesEnabled()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="665">matchForward()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="679">matchReverse()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="499" y="693">reverseIpAddress()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="499" y="707">reverseV4Address()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-decoration="underline" x="499" y="721">reverseV6Address()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="735">getD2Params()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="749">buildParams()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="763">createConfigParser()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="499" y="777">createNewContext()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="573" y1="534" x2="572" y2="460" />
+ <line stroke="black" stroke-opacity="1" x1="572" y1="455" x2="566" y2="461" />
+ <line stroke="black" stroke-opacity="1" x1="572" y1="455" x2="578" y2="460" />
+ <line stroke="black" stroke-opacity="1" x1="566" y1="461" x2="578" y2="460" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="494" y1="636" x2="487" y2="630" />
+ <line stroke="black" stroke-opacity="1" x1="494" y1="636" x2="488" y2="642" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="452" y1="637" x2="494" y2="636" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="788" y="629" width="3" height="151" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="694" y="777" width="97" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="690" y="625" width="98" height="152" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="739" y="639">D2CfgContext</text>
+ <line stroke="black" stroke-opacity="1" x1="690" y1="641" x2="788" y2="641" />
+ <line stroke="black" stroke-opacity="1" x1="690" y1="649" x2="788" y2="649" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="663">D2CfgContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="677">~D2CfgContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="691">clone()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="705">getD2Params()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="719">getForwardMgr()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="733">getReverseMgr()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="747">getKeys()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="761">D2CfgContext()</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="694" y="775">operator =()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="740" y1="624" x2="739" y2="610" />
+ <line stroke="black" stroke-opacity="1" x1="739" y1="605" x2="733" y2="611" />
+ <line stroke="black" stroke-opacity="1" x1="739" y1="605" x2="745" y2="610" />
+ <line stroke="black" stroke-opacity="1" x1="733" y1="611" x2="745" y2="610" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="689" y1="678" x2="682" y2="672" />
+ <line stroke="black" stroke-opacity="1" x1="689" y1="678" x2="683" y2="684" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="653" y1="679" x2="689" y2="678" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="246" y="231">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="382" y="63">process_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="246" y="156">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="736" y="285">context_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" x="572" y="183">cfg_mgr_</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="788" y="294" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="666" y="338" width="125" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="662" y="290" width="126" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="725" y="304">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="725" y="320">DCfgContextBasePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="662" y1="322" x2="788" y2="322" />
+ <line stroke="black" stroke-opacity="1" x1="662" y1="330" x2="788" y2="330" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="728" y1="356" x2="734" y2="350" />
+ <line stroke="black" stroke-opacity="1" x1="728" y1="356" x2="722" y2="350" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="728" y1="342" x2="728" y2="356" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="633" y1="271" x2="726" y2="271" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="633,271 639,265 645,271 639,277" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="726" y1="289" x2="732" y2="283" />
+ <line stroke="black" stroke-opacity="1" x1="726" y1="289" x2="720" y2="283" />
+ <line stroke="black" stroke-opacity="1" x1="726" y1="271" x2="726" y2="289" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="612" y="192" width="3" height="47" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="512" y="236" width="103" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="508" y="188" width="104" height="48" />
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" text-anchor="middle" x="560" y="202">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="12" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="560" y="218">DCfgMgrBasePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="508" y1="220" x2="612" y2="220" />
+ <line stroke="black" stroke-opacity="1" x1="508" y1="228" x2="612" y2="228" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="564" y1="256" x2="569" y2="249" />
+ <line stroke="black" stroke-opacity="1" x1="564" y1="256" x2="557" y2="250" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="563" y1="240" x2="564" y2="256" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="428" y1="173" x2="562" y2="173" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="428,173 434,167 440,173 434,179" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="562" y1="187" x2="568" y2="181" />
+ <line stroke="black" stroke-opacity="1" x1="562" y1="187" x2="556" y2="181" />
+ <line stroke="black" stroke-opacity="1" x1="562" y1="173" x2="562" y2="187" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/nc_trans_sequence.svg b/src/bin/d2/images/nc_trans_sequence.svg
new file mode 100644
index 0000000..ae0daeb
--- /dev/null
+++ b/src/bin/d2/images/nc_trans_sequence.svg
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="848" height="647" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="593" y="8" width="3" height="16" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="541" y="21" width="55" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="537" y="4" width="56" height="17" />
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="565" y="18">:DNSClient</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="567" y1="45" x2="567" y2="647" />
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="143" y1="45" x2="143" y2="647" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="177" y="8" width="3" height="16" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="109" y="21" width="71" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="105" y="4" width="72" height="17" />
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="141" y="18">:D2UpdateMgr</text>
+</g>
+ <text font-family="Helvetica" font-size="20" fill="#000000" xml:space="preserve" x="603" y="71">Sequence depicting a</text>
+ <text font-family="Helvetica" font-size="20" fill="#000000" xml:space="preserve" x="603" y="91">simple state model which</text>
+ <text font-family="Helvetica" font-size="20" fill="#000000" xml:space="preserve" x="603" y="111">performs a single update.</text>
+<g>
+ <polygon fill="#c0ffff" stroke="black" stroke-opacity="1" points="6,115 120,115 120,125 130,125 130,185 6,185 6,115" />
+ <line stroke="black" stroke-opacity="1" x1="120" y1="115" x2="130" y2="125" />
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="16" y="135">As part of Update</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="16" y="145">Manager's sweep()</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="16" y="155">between events it</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="16" y="165">creates and starts the</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="16" y="175">transaction</text>
+</g>
+<g>
+ <polygon fill="#c0ffff" stroke="black" stroke-opacity="1" points="611,473 765,473 765,483 775,483 775,525 611,525 611,473" />
+ <line stroke="black" stroke-opacity="1" x1="765" y1="473" x2="775" y2="483" />
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="621" y="493">runStateModel() Iterates through</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="621" y="503">states until DONE_ST is</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="621" y="513">reached</text>
+</g>
+<g>
+ <line stroke="black" stroke-dasharray="18,6" stroke-opacity="1" x1="336" y1="45" x2="336" y2="647" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="138" y="131" width="10" height="213" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="396" y="8" width="3" height="16" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="277" y="21" width="122" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="273" y="4" width="123" height="17" />
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" text-decoration="underline" text-anchor="middle" x="335" y="18">:NameChangeTransaction</text>
+</g>
+<g>
+ <polygon fill="#c0ffff" stroke="black" stroke-opacity="1" points="611,330 769,330 769,340 779,340 779,388 611,388 611,330" />
+ <line stroke="black" stroke-opacity="1" x1="769" y1="330" x2="779" y2="340" />
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="621" y="350">At some point later, DNSClient</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="621" y="360">invokes callback when IO</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="621" y="370">completes</text>
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="331" y="77" width="10" height="25" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="331" y="406" width="10" height="210" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="562" y="283" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="138" y="77" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="331" y="128" width="10" height="219" />
+</g>
+<g>
+ <polygon fill="#c0ffff" stroke="black" stroke-opacity="1" points="596,206 766,206 766,216 776,216 776,252 596,252 596,206" />
+ <line stroke="black" stroke-opacity="1" x1="766" y1="206" x2="776" y2="216" />
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="606" y="226">runStateModel() Iterates through</text>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="606" y="236">states until an update is initiated</text>
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="562" y="409" width="10" height="214" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="670" y1="388" x2="572" y2="417" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="149" y1="142" x2="331" y2="142" />
+ <polygon fill="#000000" stroke="none" points="331,142 327,138 327,146" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="342" y1="414" x2="562" y2="414" />
+ <polygon fill="#000000" stroke="none" points="342,414 346,410 346,418" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="149" y1="82" x2="331" y2="82" />
+ <polygon fill="#000000" stroke="none" points="331,82 327,78 327,86" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="337" y="221" width="10" height="115" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="337" y="424" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="337" y="175" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="337" y="136" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="337" y="466" width="10" height="126" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 349 467 L 366 467 L 366 474 L 349 474" />
+ <polygon fill="#000000" stroke="none" points="349,474 353,478 353,470" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 349 222 L 366 222 L 366 229 L 349 229" />
+ <polygon fill="#000000" stroke="none" points="349,229 353,233 353,225" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 349 425 L 366 425 L 366 432 L 349 432" />
+ <polygon fill="#000000" stroke="none" points="349,432 353,436 353,428" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 349 137 L 366 137 L 366 144 L 349 144" />
+ <polygon fill="#000000" stroke="none" points="349,144 353,148 353,140" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 349 176 L 366 176 L 366 183 L 349 183" />
+ <polygon fill="#000000" stroke="none" points="349,183 353,187 353,179" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" stroke-dasharray="4,4" x1="342" y1="611" x2="562" y2="611" />
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 558 607 L 562 611 L 558 615" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="595" y1="228" x2="367" y2="227" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="610" y1="492" x2="367" y2="472" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="343" y="491" width="10" height="95" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="343" y="247" width="10" height="83" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 355 248 L 372 248 L 372 255 L 355 255" />
+ <polygon fill="#000000" stroke="none" points="355,255 359,259 359,251" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 355 492 L 372 492 L 372 499 L 355 499" />
+ <polygon fill="#000000" stroke="none" points="355,499 359,503 359,495" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="354" y1="288" x2="562" y2="288" />
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 558 284 L 562 288 L 558 292" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" stroke-dasharray="4,4" x1="149" y1="339" x2="331" y2="339" />
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 153 335 L 149 339 L 153 343" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="349" y="515" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="349" y="556" width="10" height="24" />
+</g>
+<g>
+ <rect fill="#ffffff" stroke="black" stroke-width="1" stroke-opacity="1" x="349" y="300" width="10" height="24" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 361 516 L 378 516 L 378 523 L 361 523" />
+ <polygon fill="#000000" stroke="none" points="361,523 365,527 365,519" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 361 557 L 378 557 L 378 564 L 361 564" />
+ <polygon fill="#000000" stroke="none" points="361,564 365,568 365,560" />
+</g>
+<g>
+ <path fill="none" stroke="black" stroke-opacity="1" d="M 361 301 L 378 301 L 378 308 L 361 308" />
+ <polygon fill="#000000" stroke="none" points="361,308 365,312 365,304" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="439" y="283">doUpdate()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="361" y="173">setState(READY_ST)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="352" y="216">runStateModel(START_TRANSACTION_EVT)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="211" y="137">startTransaction()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="383" y="250">(getStateHandler())()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="363" y="410">operator ()()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="357" y="133">initStateHandlerMap()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="371" y="433">setDnsUpdateStatus()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="350" y="465">runStateModel(IO_COMPLETED_EVT)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="183" y="77">NameChangeTransaction()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="377" y="499">(getStateHandler)()</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="374" y="579">setNextEvent(NOP_EVT)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="382" y="521">setState(DONE_ST)</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="10" fill="#000000" xml:space="preserve" x="391" y="310">setNextEvent(NOP_EVT)</text>
+</g>
+</svg>
diff --git a/src/bin/d2/images/remove_state_model.svg b/src/bin/d2/images/remove_state_model.svg
new file mode 100644
index 0000000..7dfb6da
--- /dev/null
+++ b/src/bin/d2/images/remove_state_model.svg
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="788" height="792" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="331" y="16" width="74" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="327" y="12" width="74" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="364" y="28">READY_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="280" y="138" width="180" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="276" y="134" width="180" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="366" y="150">SELECTING_FWD_SERVER_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="56" y="493" width="178" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="52" y="489" width="178" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="141" y="505">SELECTING_REV_SERVER_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="285" y="234" width="172" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="281" y="230" width="172" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="367" y="246">REMOVING_FWD_ADDRS_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="293" y="349" width="156" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="289" y="345" width="156" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="367" y="361">REMOVING_FWD_RRS_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="588" y="610" width="188" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="584" y="606" width="188" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="678" y="622">PROCESS_REMOVE_FAILED_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="277" y="687" width="166" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="273" y="683" width="166" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="356" y="699">PROCESS_REMOVE_OK_ST</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="62" y="608" width="160" height="22" rx="10" />
+ <rect fill="#ffffc0" stroke="black" stroke-opacity="1" x="58" y="604" width="160" height="22" rx="10" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="138" y="620">REMOVING_REV_PTRS_ST</text>
+</g>
+<polygon fill="white" stroke="black" stroke-opacity="1" points ="358,88 367,71 376,88 367,105" />
+<ellipse fill="black" cx="163" cy="26" rx="8.5" ry="8.5" />
+<g>
+ <ellipse fill="white" stroke="black" stroke-width="1" stroke-opacity="1" cx="531" cy="768" rx="11.5" ry="11.5" />
+ <ellipse fill="black" cx="531" cy="768" rx="8.5" ry="8.5" />
+</g>
+<polygon fill="white" stroke="black" stroke-opacity="1" points ="346,451 355,434 364,451 355,468" />
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="153" y="255" width="118" height="43" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="212" y="271">&lt;&lt;DNS IO Callback&gt;&gt;</text>
+</g>
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="11" y="666" width="118" height="43" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="70" y="682">&lt;&lt;DNS IO Callback&gt;&gt;</text>
+</g>
+<g>
+ <rect fill="none" stroke="black" stroke-width="1" stroke-opacity="1" x="154" y="368" width="118" height="43" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="213" y="384">&lt;&lt;DNS IO Callback&gt;&gt;</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="326" y1="25" x2="320" y2="19" />
+ <line stroke="black" stroke-opacity="1" x1="326" y1="25" x2="320" y2="31" />
+ <line stroke="black" stroke-opacity="1" x1="172" y1="25" x2="326" y2="25" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="366" y1="69" x2="372" y2="63" />
+ <line stroke="black" stroke-opacity="1" x1="366" y1="69" x2="360" y2="63" />
+ <line stroke="black" stroke-opacity="1" x1="366" y1="38" x2="366" y2="69" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="367" y1="133" x2="373" y2="127" />
+ <line stroke="black" stroke-opacity="1" x1="367" y1="133" x2="361" y2="127" />
+ <line stroke="black" stroke-opacity="1" x1="367" y1="106" x2="367" y2="133" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="354" y1="88" x2="143" y2="88" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="143" y1="488" x2="149" y2="482" />
+ <line stroke="black" stroke-opacity="1" x1="143" y1="488" x2="137" y2="482" />
+ <line stroke="black" stroke-opacity="1" x1="143" y1="88" x2="143" y2="488" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="368" y1="229" x2="374" y2="223" />
+ <line stroke="black" stroke-opacity="1" x1="368" y1="229" x2="362" y2="223" />
+ <line stroke="black" stroke-opacity="1" x1="368" y1="160" x2="368" y2="229" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="351" y1="229" x2="293" y2="190" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="340" y1="160" x2="331" y2="158" />
+ <line stroke="black" stroke-opacity="1" x1="340" y1="160" x2="338" y2="168" />
+ <line stroke="black" stroke-opacity="1" x1="293" y1="190" x2="340" y2="160" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="368" y1="344" x2="374" y2="338" />
+ <line stroke="black" stroke-opacity="1" x1="368" y1="344" x2="362" y2="337" />
+ <line stroke="black" stroke-opacity="1" x1="369" y1="256" x2="368" y2="344" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="460" y1="147" x2="680" y2="147" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="680" y1="605" x2="686" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="605" x2="674" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="147" x2="680" y2="605" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="457" y1="243" x2="680" y2="243" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="680" y1="605" x2="686" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="605" x2="674" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="243" x2="680" y2="605" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="449" y1="357" x2="680" y2="357" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="680" y1="605" x2="686" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="605" x2="674" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="357" x2="680" y2="605" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="429" y1="371" x2="429" y2="398" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="429" y1="398" x2="679" y2="398" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="679" y1="605" x2="685" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="679" y1="605" x2="673" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="679" y1="398" x2="679" y2="605" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="680" y1="632" x2="680" y2="767" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="543" y1="767" x2="549" y2="773" />
+ <line stroke="black" stroke-opacity="1" x1="543" y1="767" x2="549" y2="761" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="767" x2="543" y2="767" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="358" y1="709" x2="358" y2="767" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="518" y1="767" x2="512" y2="761" />
+ <line stroke="black" stroke-opacity="1" x1="518" y1="767" x2="512" y2="773" />
+ <line stroke="black" stroke-opacity="1" x1="358" y1="767" x2="518" y2="767" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="161" y1="515" x2="219" y2="561" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="156" y1="603" x2="164" y2="604" />
+ <line stroke="black" stroke-opacity="1" x1="156" y1="603" x2="157" y2="594" />
+ <line stroke="black" stroke-opacity="1" x1="219" y1="561" x2="156" y2="603" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="125" y1="603" x2="72" y2="559" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="125" y1="515" x2="116" y2="514" />
+ <line stroke="black" stroke-opacity="1" x1="125" y1="515" x2="124" y2="523" />
+ <line stroke="black" stroke-opacity="1" x1="72" y1="559" x2="125" y2="515" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="342" y1="450" x2="143" y2="450" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="143" y1="488" x2="149" y2="482" />
+ <line stroke="black" stroke-opacity="1" x1="143" y1="488" x2="137" y2="482" />
+ <line stroke="black" stroke-opacity="1" x1="143" y1="450" x2="143" y2="488" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="354" y1="432" x2="360" y2="426" />
+ <line stroke="black" stroke-opacity="1" x1="354" y1="432" x2="348" y2="426" />
+ <line stroke="black" stroke-opacity="1" x1="354" y1="371" x2="354" y2="432" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="358" y1="682" x2="364" y2="676" />
+ <line stroke="black" stroke-opacity="1" x1="358" y1="682" x2="352" y2="676" />
+ <line stroke="black" stroke-opacity="1" x1="358" y1="469" x2="358" y2="682" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="234" y1="501" x2="680" y2="501" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="680" y1="605" x2="686" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="605" x2="674" y2="599" />
+ <line stroke="black" stroke-opacity="1" x1="680" y1="501" x2="680" y2="605" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="583" y1="618" x2="577" y2="611" />
+ <line stroke="black" stroke-opacity="1" x1="583" y1="618" x2="576" y2="623" />
+ <line stroke="black" stroke-opacity="1" x1="222" y1="617" x2="583" y2="618" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="163" y1="630" x2="163" y2="696" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="272" y1="696" x2="266" y2="690" />
+ <line stroke="black" stroke-opacity="1" x1="272" y1="696" x2="266" y2="702" />
+ <line stroke="black" stroke-opacity="1" x1="163" y1="696" x2="272" y2="696" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="213" y1="367" x2="213" y2="358" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="209" y1="254" x2="209" y2="243" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="73" y1="667" x2="74" y2="667" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="74" y1="630" x2="68" y2="636" />
+ <line stroke="black" stroke-opacity="1" x1="74" y1="630" x2="80" y2="636" />
+ <line stroke="black" stroke-opacity="1" x1="74" y1="667" x2="74" y2="630" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="527" y="497">NO_MORE_SERVERS_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="655" y="695">END_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="298" y="420">UPDATE_OK_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="551" y="352">UPDATE_FAILED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="374" y="202">SERVER_SELECTED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="176" y="688">UPDATE_OK_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="171" y="350">IO_COMPLETED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="133" y="580">SERVER_SELECTED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="366" y="681">No reverse change requested</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="332" y="733">END_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="164" y="448">Reverse change requested</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="452" y="612">UPDATE_FAILED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="529" y="394">SERVER_IO_ERROR_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="525" y="140">NO_MORE_SERVERS_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="381" y="332">UPDATE_OK_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="548" y="234">UPDATE_FAILED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="213" y="192">SERVER_IO_ERROR_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="164" y="82">Only reverse change requested</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="203" y="14">START_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="384" y="115">Forward change requested</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="19" y="554">SERVER_IO_ERROR_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="156" y="237">IO_COMPLETED_EVT</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="655">IO_COMPLETED_EVT</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="288" y1="358" x2="282" y2="352" />
+ <line stroke="black" stroke-opacity="1" x1="288" y1="358" x2="282" y2="364" />
+ <line stroke="black" stroke-opacity="1" x1="213" y1="358" x2="288" y2="358" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="280" y1="243" x2="274" y2="237" />
+ <line stroke="black" stroke-opacity="1" x1="280" y1="243" x2="274" y2="249" />
+ <line stroke="black" stroke-opacity="1" x1="209" y1="243" x2="280" y2="243" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/request_mgt_classes.svg b/src/bin/d2/images/request_mgt_classes.svg
new file mode 100644
index 0000000..600c187
--- /dev/null
+++ b/src/bin/d2/images/request_mgt_classes.svg
@@ -0,0 +1,316 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="791" height="846" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="169" y="10" width="3" height="291" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="11" y="298" width="161" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="7" y="6" width="162" height="292" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="88" y="19">D2Process</text>
+ <line stroke="black" stroke-opacity="1" x1="7" y1="21" x2="169" y2="21" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="11" y="34">QUEUE_RESTART_PERCENT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="47">reconf_queue_flag_</text>
+ <line stroke="black" stroke-opacity="1" x1="7" y1="49" x2="169" y2="49" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="62">D2Process()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="75">init()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="88">run()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="101">shutdown()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="114">configure()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="127">command()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="140">~D2Process()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="153">checkQueueStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="166">reconfigureQueueMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="179">runIO()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="192">canShutdown()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="205">setReconfQueueFlag()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="218">setShutdownType()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="231">getD2CfgMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="244">getD2QueueMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="257">getD2UpdateMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="270">getReconfQueueFlag()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="11" y="283">getShutdownType()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="11" y="296">getShutdownTypeStr()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="367" y="446" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="297" y="488" width="73" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="293" y="442" width="74" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="330" y="455">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="330" y="470">IOServicePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="293" y1="472" x2="367" y2="472" />
+ <line stroke="black" stroke-opacity="1" x1="293" y1="480" x2="367" y2="480" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="587" y="319" width="3" height="199" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="463" y="515" width="127" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="459" y="315" width="128" height="200" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" font-style="italic" text-anchor="middle" x="523" y="328">NameChangeListener</text>
+ <line stroke="black" stroke-opacity="1" x1="459" y1="330" x2="587" y2="330" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="343">listening_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="356">io_pending_</text>
+ <line stroke="black" stroke-opacity="1" x1="459" y1="358" x2="587" y2="358" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="371">NameChangeListener()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="384">~NameChangeListener()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="397">startListening()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="410">stopListening()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="423">receiveNext()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="436">invokeRecvHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="463" y="449">open()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="463" y="462">close()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-style="italic" x="463" y="475">doReceive()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="488">amListening()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="501">isIoPending()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="463" y="514">setListening()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="371" y1="464" x2="377" y2="470" />
+ <line stroke="black" stroke-opacity="1" x1="371" y1="464" x2="377" y2="458" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="458" y1="464" x2="371" y2="464" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="775" y="218" width="3" height="615" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="653" y="830" width="125" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="649" y="214" width="126" height="616" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="712" y="227">NameChangeRequest</text>
+ <line stroke="black" stroke-opacity="1" x1="649" y1="229" x2="775" y2="229" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="242">forward_change_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="255">reverse_change_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="268">fqdn_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="281">lease_expires_on_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="294">lease_length_</text>
+ <line stroke="black" stroke-opacity="1" x1="649" y1="296" x2="775" y2="296" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="309">NameChangeRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="322">NameChangeRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="653" y="335">fromFormat()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="348">toFormat()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="653" y="361">fromJSON()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="374">toJSON()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="387">validateContent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="400">getChangeType()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="413">setChangeType()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="426">setChangeType()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="439">isForwardChange()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="452">setForwardChange()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="465">setForwardChange()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="478">isReverseChange()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="491">setReverseChange()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="504">setReverseChange()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="517">getFqdn()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="530">setFqdn()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="543">setFqdn()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="556">getIpAddress()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="569">getIpIoAddress()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="582">isV4()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="595">isV6()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="608">setIpAddress()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="621">setIpAddress()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="634">getDhcid()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="647">setDhcid()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="660">setDhcid()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="673">getLeaseExpiresOn()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="686">getLeaseExpiresOnStr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="699">setLeaseExpiresOn()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="712">setLeaseExpiresOn()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="725">getLeaseLength()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="738">setLeaseLength()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="751">setLeaseLength()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="764">getStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="777">setStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="790">getElement()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="803">toText()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="816">operator ==()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="653" y="829">operator !=()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="377" y="113" width="3" height="277" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="249" y="387" width="131" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="245" y="109" width="132" height="278" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="311" y="122">D2QueueMgr</text>
+ <line stroke="black" stroke-opacity="1" x1="245" y1="124" x2="377" y2="124" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="137">MAX_QUEUE_DEFAULT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="150">max_queue_size_</text>
+ <line stroke="black" stroke-opacity="1" x1="245" y1="152" x2="377" y2="152" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="165">D2QueueMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="178">~D2QueueMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="191">initUDPListener()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="204">startListening()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="217">operator ()()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="230">stopListening()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="243">removeListener()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="256">getQueueSize()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="269">getMaxQueueSize()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="282">setMaxQueueSize()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="295">getMgrState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="308">peek()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="321">peekAt()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="334">dequeueAt()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="347">dequeue()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="360">enqueue()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="373">clearQueue()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="386">updateStopState()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="590" y="227" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="458" y="269" width="135" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="454" y="223" width="136" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="522" y="236">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="522" y="251">NameChangeListenerPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="454" y1="253" x2="590" y2="253" />
+ <line stroke="black" stroke-opacity="1" x1="454" y1="261" x2="590" y2="261" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="604" y="547" width="3" height="173" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="456" y="717" width="151" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="452" y="543" width="152" height="174" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="528" y="556">NameChangeUDPListener</text>
+ <line stroke="black" stroke-opacity="1" x1="452" y1="558" x2="604" y2="558" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="456" y="571">RECV_BUF_MAX</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="584">port_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="597">asio_socket_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="610">reuse_address_</text>
+ <line stroke="black" stroke-opacity="1" x1="452" y1="612" x2="604" y2="612" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="625">NameChangeUDPListener()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="638">~NameChangeUDPListener()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="651">open()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="664">close()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="677">doReceive()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="690">receiveCompletionHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="703">NameChangeUDPListener()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="456" y="716">operator =()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="331" y1="441" x2="337" y2="435" />
+ <line stroke="black" stroke-opacity="1" x1="331" y1="441" x2="325" y2="435" />
+ <line stroke="black" stroke-opacity="1" x1="331" y1="391" x2="331" y2="441" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="331,391 337,397 331,403 325,397" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="524" y1="314" x2="530" y2="308" />
+ <line stroke="black" stroke-opacity="1" x1="524" y1="314" x2="518" y2="308" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="524" y1="273" x2="524" y2="314" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="453" y1="245" x2="447" y2="239" />
+ <line stroke="black" stroke-opacity="1" x1="453" y1="245" x2="447" y2="251" />
+ <line stroke="black" stroke-opacity="1" x1="381" y1="245" x2="453" y2="245" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="381,245 387,239 393,245 387,251" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="527" y1="542" x2="526" y2="524" />
+ <line stroke="black" stroke-opacity="1" x1="526" y1="519" x2="520" y2="525" />
+ <line stroke="black" stroke-opacity="1" x1="526" y1="519" x2="532" y2="524" />
+ <line stroke="black" stroke-opacity="1" x1="520" y1="525" x2="532" y2="524" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="554" y="129" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="472" y="171" width="85" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="468" y="125" width="86" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="511" y="138">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="511" y="153">RequestQueue</text>
+ <line stroke="black" stroke-opacity="1" x1="468" y1="155" x2="554" y2="155" />
+ <line stroke="black" stroke-opacity="1" x1="468" y1="163" x2="554" y2="163" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="173" y="353" width="3" height="129" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="35" y="479" width="141" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="31" y="349" width="142" height="130" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="102" y="362">&lt;&lt;enum&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="102" y="377">State</text>
+ <line stroke="black" stroke-opacity="1" x1="31" y1="379" x2="173" y2="379" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="35" y="392">NOT_INITTED</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="35" y="405">INITTED</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="35" y="418">RUNNING</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="35" y="431">STOPPING</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="35" y="444">STOPPED_QUEUE_FULL</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="35" y="457">STOPPED_RECV_ERROR</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="35" y="470">STOPPED</text>
+ <line stroke="black" stroke-opacity="1" x1="31" y1="472" x2="173" y2="472" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="467" y1="150" x2="461" y2="144" />
+ <line stroke="black" stroke-opacity="1" x1="467" y1="150" x2="461" y2="156" />
+ <line stroke="black" stroke-opacity="1" x1="381" y1="150" x2="467" y2="150" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="381,150 387,144 393,150 387,156" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="244" y1="319" x2="102" y2="319" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="244,319 238,325 232,319 238,313" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="102" y1="348" x2="108" y2="342" />
+ <line stroke="black" stroke-opacity="1" x1="102" y1="348" x2="96" y2="342" />
+ <line stroke="black" stroke-opacity="1" x1="102" y1="319" x2="102" y2="348" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="285" y1="391" x2="285" y2="416" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="285,391 291,397 285,403 279,397" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="177" y1="416" x2="183" y2="422" />
+ <line stroke="black" stroke-opacity="1" x1="177" y1="416" x2="183" y2="410" />
+ <line stroke="black" stroke-opacity="1" x1="285" y1="416" x2="177" y2="416" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="177" y1="367" x2="232" y2="366" />
+<ellipse fill="none" stroke="black" stroke-width="1" stroke-opacity="1" cx="238" cy="366" rx="5" ry="5" />
+ <line stroke="black" stroke-opacity="1" x1="233" y1="366" x2="243" y2="366" />
+ <line stroke="black" stroke-opacity="1" x1="238" y1="361" x2="238" y2="371" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="774" y="129" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="642" y="171" width="135" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="638" y="125" width="136" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="706" y="138">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="706" y="153">NameChangeRequestPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="638" y1="155" x2="774" y2="155" />
+ <line stroke="black" stroke-opacity="1" x1="638" y1="163" x2="774" y2="163" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="708" y1="213" x2="714" y2="207" />
+ <line stroke="black" stroke-opacity="1" x1="708" y1="213" x2="702" y2="207" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="708" y1="175" x2="708" y2="213" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="637" y1="150" x2="631" y2="144" />
+ <line stroke="black" stroke-opacity="1" x1="637" y1="150" x2="631" y2="156" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="558" y1="150" x2="637" y2="150" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="187" y="54">queue_mgr_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="341" y="438">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="187" y="413">target_stop_state_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="401" y="242">listener_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="112" y="345">mgr_state_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="399" y="147">ncr_queue_</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="352" y="36" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="264" y="78" width="91" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="260" y="32" width="92" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="306" y="45">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="306" y="60">D2QueueMgrPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="260" y1="62" x2="352" y2="62" />
+ <line stroke="black" stroke-opacity="1" x1="260" y1="70" x2="352" y2="70" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="259" y1="57" x2="252" y2="51" />
+ <line stroke="black" stroke-opacity="1" x1="259" y1="57" x2="253" y2="63" />
+ <line stroke="black" stroke-opacity="1" x1="173" y1="58" x2="259" y2="57" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="173,58 178,51 184,57 179,63" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="308" y1="108" x2="314" y2="102" />
+ <line stroke="black" stroke-opacity="1" x1="308" y1="108" x2="302" y2="102" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="308" y1="82" x2="308" y2="108" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/state_model_classes.svg b/src/bin/d2/images/state_model_classes.svg
new file mode 100644
index 0000000..d212694
--- /dev/null
+++ b/src/bin/d2/images/state_model_classes.svg
@@ -0,0 +1,246 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="645" height="712" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="212" y="431" width="3" height="63" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="170" y="491" width="45" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="166" y="427" width="46" height="64" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="189" y="440">State</text>
+ <line stroke="black" stroke-opacity="1" x1="166" y1="442" x2="212" y2="442" />
+ <line stroke="black" stroke-opacity="1" x1="166" y1="450" x2="212" y2="450" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="170" y="463">State()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="170" y="476">~State()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="170" y="489">run()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="227" y="531" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="161" y="573" width="69" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="157" y="527" width="70" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="192" y="540">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="192" y="555">StatePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="157" y1="557" x2="227" y2="557" />
+ <line stroke="black" stroke-opacity="1" x1="157" y1="565" x2="227" y2="565" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="191" y1="495" x2="185" y2="501" />
+ <line stroke="black" stroke-opacity="1" x1="191" y1="495" x2="197" y2="500" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="192" y1="526" x2="191" y2="495" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="403" y="271">states_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="255" y="125">map_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="108" y="458">handler_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="426" y="86">events_</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="94" y="441" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="22" y="483" width="75" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="18" y="437" width="76" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="56" y="450">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="56" y="465">StateHandler</text>
+ <line stroke="black" stroke-opacity="1" x1="18" y1="467" x2="94" y2="467" />
+ <line stroke="black" stroke-opacity="1" x1="18" y1="475" x2="94" y2="475" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="98" y1="461" x2="104" y2="467" />
+ <line stroke="black" stroke-opacity="1" x1="98" y1="461" x2="104" y2="455" />
+ <line stroke="black" stroke-opacity="1" x1="165" y1="461" x2="98" y2="461" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="165,461 159,467 153,461 159,455" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="84" y="302" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="18" y="344" width="69" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="14" y="298" width="70" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="49" y="311">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="49" y="326">Event</text>
+ <line stroke="black" stroke-opacity="1" x1="14" y1="328" x2="84" y2="328" />
+ <line stroke="black" stroke-opacity="1" x1="14" y1="336" x2="84" y2="336" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="84" y="183" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="18" y="225" width="69" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="14" y="179" width="70" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="49" y="192">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="49" y="207">EventPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="14" y1="209" x2="84" y2="209" />
+ <line stroke="black" stroke-opacity="1" x1="14" y1="217" x2="84" y2="217" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="629" y="32" width="3" height="667" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="485" y="696" width="147" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="481" y="28" width="148" height="668" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="555" y="41">StateModel</text>
+ <line stroke="black" stroke-opacity="1" x1="481" y1="43" x2="629" y2="43" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="485" y="56">NEW_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="485" y="69">END_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="485" y="82">SM_DERIVED_STATE_MIN</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="485" y="95">NOP_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="485" y="108">START_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="485" y="121">END_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="485" y="134">FAIL_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="485" y="147">SM_DERIVED_EVENT_MIN</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="160">dictionaries_initted_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="173">curr_state_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="186">prev_state_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="199">last_event_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="212">next_event_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="225">on_entry_flag_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="238">on_exit_flag_</text>
+ <line stroke="black" stroke-opacity="1" x1="481" y1="240" x2="629" y2="240" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="253">StateModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="266">~StateModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="279">startModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="292">runModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="305">endModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="318">nopStateHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="331">initDictionaries()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="344">defineEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="357">defineEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="370">getEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="383">verifyEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="396">defineStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="409">defineState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="422">getState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="435">verifyStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="448">onModelFailure()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="461">transition()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="474">abortModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="487">setState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="500">postNextEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="513">doOnEntry()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="526">doOnExit()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="539">getCurrState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="552">getPrevState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="565">getLastEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="578">getNextEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="591">isModelNew()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="604">isModelRunning()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="617">isModelWaiting()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="630">isModelDone()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="643">didModelFail()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="656">getEventLabel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="669">getStateLabel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="682">getContextStr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="485" y="695">getPrevContextStr()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="412" y="69" width="3" height="121" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="304" y="187" width="111" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="300" y="65" width="112" height="122" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="356" y="78">LabeledValueSet</text>
+ <line stroke="black" stroke-opacity="1" x1="300" y1="80" x2="412" y2="80" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="304" y="93">UNDEFINED_LABEL</text>
+ <line stroke="black" stroke-opacity="1" x1="300" y1="95" x2="412" y2="95" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="108">LabeledValueSet()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="121">~LabeledValueSet()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="134">add()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="147">add()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="160">get()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="173">isDefined()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="304" y="186">getLabel()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="416" y1="89" x2="422" y2="95" />
+ <line stroke="black" stroke-opacity="1" x1="416" y1="89" x2="422" y2="83" />
+ <line stroke="black" stroke-opacity="1" x1="480" y1="89" x2="416" y2="89" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="480,89 474,95 468,89 474,83" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="241" y="107" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="145" y="149" width="99" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="141" y="103" width="100" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="191" y="116">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="191" y="131">LabeledValueMap</text>
+ <line stroke="black" stroke-opacity="1" x1="141" y1="133" x2="241" y2="133" />
+ <line stroke="black" stroke-opacity="1" x1="141" y1="141" x2="241" y2="141" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="389" y="239" width="3" height="75" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="329" y="311" width="63" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="325" y="235" width="64" height="76" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="357" y="248">StateSet</text>
+ <line stroke="black" stroke-opacity="1" x1="325" y1="250" x2="389" y2="250" />
+ <line stroke="black" stroke-opacity="1" x1="325" y1="258" x2="389" y2="258" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="271">StateSet()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="284">~StateSet()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="297">add()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="329" y="310">getState()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="245" y1="128" x2="251" y2="134" />
+ <line stroke="black" stroke-opacity="1" x1="245" y1="128" x2="251" y2="122" />
+ <line stroke="black" stroke-opacity="1" x1="299" y1="128" x2="245" y2="128" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="299,128 293,134 287,128 293,122" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="358" y1="234" x2="358" y2="197" />
+ <line stroke="black" stroke-opacity="1" x1="358" y1="191" x2="352" y2="197" />
+ <line stroke="black" stroke-opacity="1" x1="358" y1="191" x2="364" y2="197" />
+ <line stroke="black" stroke-opacity="1" x1="352" y1="197" x2="364" y2="197" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="393" y1="274" x2="399" y2="280" />
+ <line stroke="black" stroke-opacity="1" x1="393" y1="274" x2="399" y2="268" />
+ <line stroke="black" stroke-opacity="1" x1="480" y1="274" x2="393" y2="274" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="480,274 474,280 468,274 474,268" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="240" y="185" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="150" y="227" width="93" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="146" y="181" width="94" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="193" y="194">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="193" y="209">LabeledValuePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="146" y1="211" x2="240" y2="211" />
+ <line stroke="black" stroke-opacity="1" x1="146" y1="219" x2="240" y2="219" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="145" y1="205" x2="139" y2="198" />
+ <line stroke="black" stroke-opacity="1" x1="145" y1="205" x2="138" y2="210" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="88" y1="204" x2="145" y2="205" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="194" y1="180" x2="199" y2="173" />
+ <line stroke="black" stroke-opacity="1" x1="194" y1="180" x2="187" y2="174" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="193" y1="153" x2="194" y2="180" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="234" y="260" width="3" height="135" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="150" y="392" width="87" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="146" y="256" width="88" height="136" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="190" y="269">LabeledValue</text>
+ <line stroke="black" stroke-opacity="1" x1="146" y1="271" x2="234" y2="271" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="284">value_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="297">label_</text>
+ <line stroke="black" stroke-opacity="1" x1="146" y1="299" x2="234" y2="299" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="312">LabeledValue()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="325">~LabeledValue()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="338">getValue()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="351">getLabel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="364">operator ==()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="377">operator !=()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="150" y="390">operator &lt;()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="191" y1="426" x2="191" y2="402" />
+ <line stroke="black" stroke-opacity="1" x1="191" y1="396" x2="185" y2="402" />
+ <line stroke="black" stroke-opacity="1" x1="191" y1="396" x2="197" y2="402" />
+ <line stroke="black" stroke-opacity="1" x1="185" y1="402" x2="197" y2="402" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="145" y1="324" x2="139" y2="317" />
+ <line stroke="black" stroke-opacity="1" x1="145" y1="324" x2="138" y2="329" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="88" y1="323" x2="145" y2="324" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="193" y1="255" x2="199" y2="249" />
+ <line stroke="black" stroke-opacity="1" x1="193" y1="255" x2="187" y2="248" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="194" y1="231" x2="193" y2="255" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/trans_classes.svg b/src/bin/d2/images/trans_classes.svg
new file mode 100644
index 0000000..5023c02
--- /dev/null
+++ b/src/bin/d2/images/trans_classes.svg
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="733" height="836" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="717" y="431" width="3" height="303" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="541" y="731" width="179" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="537" y="427" width="180" height="304" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="627" y="440">NameAddTransaction</text>
+ <line stroke="black" stroke-opacity="1" x1="537" y1="442" x2="717" y2="442" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="541" y="455">ADDING_FWD_ADDRS_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="541" y="468">REPLACING_FWD_ADDRS_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="541" y="481">REPLACING_REV_PTRS_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="541" y="494">FQDN_IN_USE_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="541" y="507">FQDN_NOT_IN_USE_EVT</text>
+ <line stroke="black" stroke-opacity="1" x1="537" y1="509" x2="717" y2="509" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="522">NameAddTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="535">~NameAddTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="548">defineEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="561">verifyEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="574">defineStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="587">verifyStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="600">readyHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="613">selectingFwdServerHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="626">selectingRevServerHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="639">addingFwdAddrsHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="652">replacingFwdAddrsHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="665">replacingRevPtrsHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="678">processAddOkHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="691">processAddFailedHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="704">buildAddFwdAddressRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="717">buildReplaceFwdAddressRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="541" y="730">buildReplaceRevPtrsRequest()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="445" y="12" width="3" height="811" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="249" y="820" width="199" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="245" y="8" width="200" height="812" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="345" y="21">NameChangeTransaction</text>
+ <line stroke="black" stroke-opacity="1" x1="245" y1="23" x2="445" y2="23" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="36">READY_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="49">SELECTING_FWD_SERVER_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="62">SELECTING_REV_SERVER_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="75">PROCESS_TRANS_OK_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="88">PROCESS_TRANS_FAILED_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="101">NCT_DERIVED_STATE_MIN</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="114">SELECT_SERVER_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="127">SERVER_SELECTED_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="140">SERVER_IO_ERROR_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="153">NO_MORE_SERVERS_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="166">IO_COMPLETED_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="179">UPDATE_OK_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="192">UPDATE_FAILED_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="205">NCT_DERIVED_EVENT_MIN</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="218">DNS_UPDATE_DEFAULT_TIMEOUT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="249" y="231">MAX_UPDATE_TRIES_PER_SERVER</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="244">forward_change_completed_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="257">reverse_change_completed_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="270">next_server_pos_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="283">update_attempts_</text>
+ <line stroke="black" stroke-opacity="1" x1="245" y1="285" x2="445" y2="285" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="298">NameChangeTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="311">~NameChangeTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="324">startTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="337">operator ()()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="350">sendUpdate()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="363">defineEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="376">verifyEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="389">defineStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="402">verifyStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="415">onModelFailure()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="428">retryTransition()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="441">setDnsUpdateRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="454">clearDnsUpdateRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="467">setDnsUpdateStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="480">setDnsUpdateResponse()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="493">clearDnsUpdateResponse()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="506">setForwardChangeCompleted()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="519">setReverseChangeCompleted()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="532">setNcrStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="545">initServerSelection()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="558">selectNextServer()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="571">setUpdateAttempts()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="584">getIOService()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="597">prepNewRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="610">addLeaseAddressRdata()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="623">addDhcidRdata()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="636">addPtrRdata()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="649">getNcr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="662">getTransactionKey()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="675">getNcrStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="688">getForwardDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="701">getReverseDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="714">getCurrentServer()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="727">getDNSClient()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="740">getDnsUpdateRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="753">getDnsUpdateStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="766">getDnsUpdateResponse()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="779">getForwardChangeCompleted()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="792">getReverseChangeCompleted()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="805">getUpdateAttempts()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="249" y="818">getAddressRRType()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="536" y1="596" x2="455" y2="596" />
+ <line stroke="black" stroke-opacity="1" x1="449" y1="596" x2="455" y2="602" />
+ <line stroke="black" stroke-opacity="1" x1="449" y1="596" x2="455" y2="590" />
+ <line stroke="black" stroke-opacity="1" x1="455" y1="602" x2="455" y2="590" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="716" y="87" width="3" height="277" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="540" y="361" width="179" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="536" y="83" width="180" height="278" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="626" y="96">NameRemoveTransaction</text>
+ <line stroke="black" stroke-opacity="1" x1="536" y1="98" x2="716" y2="98" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="540" y="111">REMOVING_FWD_ADDRS_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="540" y="124">REMOVING_FWD_RRS_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="540" y="137">REMOVING_REV_PTRS_ST</text>
+ <line stroke="black" stroke-opacity="1" x1="536" y1="139" x2="716" y2="139" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="152">NameRemoveTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="165">~NameRemoveTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="178">defineEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="191">verifyEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="204">defineStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="217">verifyStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="230">readyHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="243">selectingFwdServerHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="256">selectingRevServerHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="269">removingFwdAddrsHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="282">removingFwdRRsHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="295">removingRevPtrsHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="308">processRemoveOkHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="321">processRemoveFailedHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="334">buildRemoveFwdAddressRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="347">buildRemoveFwdRRsRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="540" y="360">buildRemoveRevPtrsRequest()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="177" y="87" width="3" height="667" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="33" y="751" width="147" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="29" y="83" width="148" height="668" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="103" y="96">StateModel</text>
+ <line stroke="black" stroke-opacity="1" x1="29" y1="98" x2="177" y2="98" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="33" y="111">NEW_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="33" y="124">END_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="33" y="137">SM_DERIVED_STATE_MIN</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="33" y="150">NOP_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="33" y="163">START_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="33" y="176">END_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="33" y="189">FAIL_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="33" y="202">SM_DERIVED_EVENT_MIN</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="215">dictionaries_initted_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="228">curr_state_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="241">prev_state_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="254">last_event_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="267">next_event_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="280">on_entry_flag_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="293">on_exit_flag_</text>
+ <line stroke="black" stroke-opacity="1" x1="29" y1="295" x2="177" y2="295" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="308">StateModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="321">~StateModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="334">startModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="347">runModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="360">endModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="373">nopStateHandler()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="386">initDictionaries()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="399">defineEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="412">defineEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="425">getEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="438">verifyEvents()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="451">defineStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="464">defineState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="477">getState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="490">verifyStates()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="503">onModelFailure()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="516">transition()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="529">abortModel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="542">setState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="555">postNextEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="568">doOnEntry()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="581">doOnExit()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="594">getCurrState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="607">getPrevState()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="620">getLastEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="633">getNextEvent()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="646">isModelNew()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="659">isModelRunning()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="672">isModelWaiting()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="685">isModelDone()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="698">didModelFail()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="711">getEventLabel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="724">getStateLabel()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="737">getContextStr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="33" y="750">getPrevContextStr()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="535" y1="228" x2="454" y2="227" />
+ <line stroke="black" stroke-opacity="1" x1="449" y1="227" x2="454" y2="233" />
+ <line stroke="black" stroke-opacity="1" x1="449" y1="227" x2="455" y2="221" />
+ <line stroke="black" stroke-opacity="1" x1="454" y1="233" x2="455" y2="221" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="244" y1="401" x2="187" y2="401" />
+ <line stroke="black" stroke-opacity="1" x1="181" y1="401" x2="187" y2="407" />
+ <line stroke="black" stroke-opacity="1" x1="181" y1="401" x2="187" y2="395" />
+ <line stroke="black" stroke-opacity="1" x1="187" y1="407" x2="187" y2="395" />
+</g>
+</svg>
diff --git a/src/bin/d2/images/update_exec_classes.svg b/src/bin/d2/images/update_exec_classes.svg
new file mode 100644
index 0000000..d815a09
--- /dev/null
+++ b/src/bin/d2/images/update_exec_classes.svg
@@ -0,0 +1,387 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Bouml (http://bouml.free.fr/) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="847" height="932" version="1.1" xmlns="http://www.w3.org/2000/svg">
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="175" y="5" width="3" height="291" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="17" y="293" width="161" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="13" y="1" width="162" height="292" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="94" y="14">D2Process</text>
+ <line stroke="black" stroke-opacity="1" x1="13" y1="16" x2="175" y2="16" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="17" y="29">QUEUE_RESTART_PERCENT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="42">reconf_queue_flag_</text>
+ <line stroke="black" stroke-opacity="1" x1="13" y1="44" x2="175" y2="44" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="57">D2Process()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="70">init()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="83">run()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="96">shutdown()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="109">configure()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="122">command()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="135">~D2Process()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="148">checkQueueStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="161">reconfigureQueueMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="174">runIO()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="187">canShutdown()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="200">setReconfQueueFlag()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="213">setShutdownType()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="226">getD2CfgMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="239">getD2QueueMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="252">getD2UpdateMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="265">getReconfQueueFlag()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="17" y="278">getShutdownType()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="17" y="291">getShutdownTypeStr()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="332" y="412" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="262" y="454" width="73" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="258" y="408" width="74" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="295" y="421">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="295" y="436">IOServicePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="258" y1="438" x2="332" y2="438" />
+ <line stroke="black" stroke-opacity="1" x1="258" y1="446" x2="332" y2="446" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="617" y="285" width="3" height="511" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="421" y="793" width="199" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="417" y="281" width="200" height="512" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="517" y="294">NameChangeTransaction</text>
+ <line stroke="black" stroke-opacity="1" x1="417" y1="296" x2="617" y2="296" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="309">READY_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="322">SELECTING_FWD_SERVER_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="335">SELECTING_REV_SERVER_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="348">PROCESS_TRANS_OK_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="361">PROCESS_TRANS_FAILED_ST</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="374">NCT_DERIVED_STATE_MIN</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="387">SELECT_SERVER_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="400">SERVER_SELECTED_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="413">SERVER_IO_ERROR_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="426">NO_MORE_SERVERS_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="439">IO_COMPLETED_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="452">UPDATE_OK_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="465">UPDATE_FAILED_EVT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="478">NCT_DERIVED_EVENT_MIN</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="491">DNS_UPDATE_DEFAULT_TIMEOUT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="421" y="504">MAX_UPDATE_TRIES_PER_SERVER</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="517">forward_change_completed_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="530">reverse_change_completed_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="543">next_server_pos_</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="556">update_attempts_</text>
+ <line stroke="black" stroke-opacity="1" x1="417" y1="558" x2="617" y2="558" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="571">NameChangeTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="584">~NameChangeTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="597">startTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="610">operator ()()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="623">getNcr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="636">getTransactionKey()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="649">getNcrStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="662">getForwardDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="675">getReverseDomain()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="688">getCurrentServer()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="701">getDNSClient()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="714">getDnsUpdateRequest()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="727">getDnsUpdateStatus()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="740">getDnsUpdateResponse()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="753">getForwardChangeCompleted()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="766">getReverseChangeCompleted()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="779">getUpdateAttempts()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="421" y="792">getAddressRRType()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="336" y1="432" x2="342" y2="438" />
+ <line stroke="black" stroke-opacity="1" x1="336" y1="432" x2="342" y2="426" />
+ <line stroke="black" stroke-opacity="1" x1="416" y1="432" x2="336" y2="432" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="416,432 410,438 404,432 410,426" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="586" y="211" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="436" y="253" width="153" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="432" y="207" width="154" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="509" y="220">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="509" y="235">NameChangeTransactionPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="432" y1="237" x2="586" y2="237" />
+ <line stroke="black" stroke-opacity="1" x1="432" y1="245" x2="586" y2="245" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="511" y1="280" x2="517" y2="274" />
+ <line stroke="black" stroke-opacity="1" x1="511" y1="280" x2="505" y2="274" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="511" y1="257" x2="511" y2="280" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="361" y="487" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="229" y="529" width="135" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="225" y="483" width="136" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="293" y="496">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="293" y="511">NameChangeRequestPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="225" y1="513" x2="361" y2="513" />
+ <line stroke="black" stroke-opacity="1" x1="225" y1="521" x2="361" y2="521" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="365" y1="507" x2="371" y2="513" />
+ <line stroke="black" stroke-opacity="1" x1="365" y1="507" x2="371" y2="501" />
+ <line stroke="black" stroke-opacity="1" x1="416" y1="507" x2="365" y2="507" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="416,507 410,513 404,507 410,501" />
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="506" y="138">transaction_list_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="650" y="529">dns_update_request_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="312" y="650">reverse_domain_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="626" y="637">dns_update_response_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="312" y="580">forward_domain_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="714" y="313">dns_client_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="134" y="354">queue_mgr_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="311" y="35">update_mgr_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="375" y="504">ncr_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="303" y="404">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="346" y="429">io_service_</text>
+</g>
+<g>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="85" y="329">queue_mgr_</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="346" y="588" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="260" y="630" width="89" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="256" y="584" width="90" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="301" y="597">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="301" y="612">DdnsDomainPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="256" y1="614" x2="346" y2="614" />
+ <line stroke="black" stroke-opacity="1" x1="256" y1="622" x2="346" y2="622" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="120" y="337" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="32" y="379" width="91" height="3" />
+ <rect fill="#c0ffff" stroke="black" stroke-width="1" stroke-opacity="1" x="28" y="333" width="92" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="74" y="346">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="74" y="361">D2QueueMgrPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="28" y1="363" x2="120" y2="363" />
+ <line stroke="black" stroke-opacity="1" x1="28" y1="371" x2="120" y2="371" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="740" y="321" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="668" y="363" width="75" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="664" y="317" width="76" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="702" y="330">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="702" y="345">DNSClientPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="664" y1="347" x2="740" y2="347" />
+ <line stroke="black" stroke-opacity="1" x1="664" y1="355" x2="740" y2="355" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="760" y="553" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="644" y="595" width="119" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="640" y="549" width="120" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="700" y="562">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="700" y="577">D2UpdateMessagePtr</text>
+ <line stroke="black" stroke-opacity="1" x1="640" y1="579" x2="760" y2="579" />
+ <line stroke="black" stroke-opacity="1" x1="640" y1="587" x2="760" y2="587" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="416" y1="563" x2="302" y2="563" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="416,563 410,569 404,563 410,557" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="416" y1="656" x2="302" y2="656" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="416,656 410,662 404,656 410,650" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="75" y1="332" x2="80" y2="325" />
+ <line stroke="black" stroke-opacity="1" x1="75" y1="332" x2="68" y2="326" />
+ <line stroke="black" stroke-opacity="1" x1="74" y1="297" x2="75" y2="332" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="74,297 80,302 74,308 68,303" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="621" y1="295" x2="704" y2="295" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="621,295 627,289 633,295 627,301" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="621" y1="534" x2="682" y2="534" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="621,534 627,528 633,534 627,540" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="621" y1="618" x2="688" y2="618" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="621,618 627,612 633,618 627,624" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="688" y1="599" x2="682" y2="605" />
+ <line stroke="black" stroke-opacity="1" x1="688" y1="599" x2="694" y2="605" />
+ <line stroke="black" stroke-opacity="1" x1="688" y1="618" x2="688" y2="599" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="302" y1="634" x2="296" y2="640" />
+ <line stroke="black" stroke-opacity="1" x1="302" y1="634" x2="308" y2="640" />
+ <line stroke="black" stroke-opacity="1" x1="302" y1="656" x2="302" y2="634" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="302" y1="583" x2="308" y2="577" />
+ <line stroke="black" stroke-opacity="1" x1="302" y1="583" x2="296" y2="577" />
+ <line stroke="black" stroke-opacity="1" x1="302" y1="563" x2="302" y2="583" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="704" y1="316" x2="710" y2="310" />
+ <line stroke="black" stroke-opacity="1" x1="704" y1="316" x2="698" y2="310" />
+ <line stroke="black" stroke-opacity="1" x1="704" y1="295" x2="704" y2="316" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="682" y1="548" x2="688" y2="542" />
+ <line stroke="black" stroke-opacity="1" x1="682" y1="548" x2="676" y2="542" />
+ <line stroke="black" stroke-opacity="1" x1="682" y1="534" x2="682" y2="548" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="383" y="112" width="3" height="265" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="209" y="374" width="177" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="205" y="108" width="178" height="266" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="294" y="121">D2UpdateMgr</text>
+ <line stroke="black" stroke-opacity="1" x1="205" y1="123" x2="383" y2="123" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="209" y="136">MAX_TRANSACTIONS_DEFAULT</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="149">max_transactions_</text>
+ <line stroke="black" stroke-opacity="1" x1="205" y1="151" x2="383" y2="151" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="164">D2UpdateMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="177">~D2UpdateMgr()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="190">sweep()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="203">checkFinishedTransactions()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="216">pickNextJob()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="229">makeTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="242">getIOService()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="255">getMaxTransactions()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="268">setMaxTransactions()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="281">findTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="294">transactionListEnd()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="307">transactionListBegin()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="320">hasTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="333">removeTransaction()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="346">clearTransactionList()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="359">getQueueCount()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="209" y="372">getTransactionCount()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="756" y="385" width="3" height="115" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="670" y="497" width="89" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="666" y="381" width="90" height="116" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="711" y="394">DNSClient</text>
+ <line stroke="black" stroke-opacity="1" x1="666" y1="396" x2="756" y2="396" />
+ <line stroke="black" stroke-opacity="1" x1="666" y1="404" x2="756" y2="404" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="670" y="417">DNSClient()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="670" y="430">~DNSClient()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="670" y="443">DNSClient()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="670" y="456">operator =()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="670" y="469">getMaxTimeout()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="670" y="482">doUpdate()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="670" y="495">doUpdate()</text>
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="124" y1="357" x2="130" y2="362" />
+ <line stroke="black" stroke-opacity="1" x1="124" y1="357" x2="129" y2="350" />
+ <line stroke="black" stroke-opacity="1" x1="204" y1="356" x2="124" y2="357" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="204,356 198,362 192,356 197,350" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="706" y1="380" x2="712" y2="374" />
+ <line stroke="black" stroke-opacity="1" x1="706" y1="380" x2="700" y2="374" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="706" y1="367" x2="706" y2="380" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="293" y1="407" x2="298" y2="400" />
+ <line stroke="black" stroke-opacity="1" x1="293" y1="407" x2="286" y2="401" />
+ <line stroke="black" stroke-opacity="1" x1="292" y1="378" x2="293" y2="407" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="292,378 298,383 292,389 286,384" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="831" y="654" width="3" height="265" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="727" y="916" width="107" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="723" y="650" width="108" height="266" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="777" y="663">D2UpdateMessage</text>
+ <line stroke="black" stroke-opacity="1" x1="723" y1="665" x2="831" y2="665" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="678">message_</text>
+ <line stroke="black" stroke-opacity="1" x1="723" y1="680" x2="831" y2="680" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="693">D2UpdateMessage()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="706">D2UpdateMessage()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="719">operator =()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="732">getQRFlag()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="745">getId()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="758">setId()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="771">getRcode()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="784">setRcode()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="797">getRRCount()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="810">beginSection()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="823">endSection()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="836">setZone()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="849">getZone()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="862">addRRset()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="875">toWire()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="888">fromWire()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-decoration="underline" x="727" y="901">ddnsToDnsSection()</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" x="727" y="914">validateResponse()</text>
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="349" y="43" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="259" y="85" width="93" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="255" y="39" width="94" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="302" y="52">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="302" y="67">D2UpdateMgrPtr</text>
+ <line stroke="black" stroke-opacity="1" x1="255" y1="69" x2="349" y2="69" />
+ <line stroke="black" stroke-opacity="1" x1="255" y1="77" x2="349" y2="77" />
+</g>
+<g>
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="764" y1="574" x2="776" y2="574" />
+</g>
+<g>
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="539" y="146" width="3" height="45" />
+ <rect fill="#cbcbcb" stroke="none" stroke-opacity="1" x="455" y="188" width="87" height="3" />
+ <rect fill="#ffffc0" stroke="black" stroke-width="1" stroke-opacity="1" x="451" y="142" width="88" height="46" />
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" text-anchor="middle" x="495" y="155">&lt;&lt;typedef&gt;&gt;</text>
+ <text font-family="Helvetica" font-size="11" fill="#000000" xml:space="preserve" font-weight="bold" text-anchor="middle" x="495" y="170">TransactionList</text>
+ <line stroke="black" stroke-opacity="1" x1="451" y1="172" x2="539" y2="172" />
+ <line stroke="black" stroke-opacity="1" x1="451" y1="180" x2="539" y2="180" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="179" y1="17" x2="301" y2="17" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="179,17 185,11 191,17 185,23" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="504" y1="206" x2="509" y2="199" />
+ <line stroke="black" stroke-opacity="1" x1="504" y1="206" x2="497" y2="200" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="502" y1="192" x2="504" y2="206" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="301" y1="107" x2="307" y2="101" />
+ <line stroke="black" stroke-opacity="1" x1="301" y1="107" x2="295" y2="100" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="302" y1="89" x2="301" y2="107" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="387" y1="120" x2="497" y2="120" />
+ <polygon fill="#000000" stroke="black" stroke-opacity="1" points="387,120 393,114 399,120 393,126" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="776" y1="649" x2="782" y2="643" />
+ <line stroke="black" stroke-opacity="1" x1="776" y1="649" x2="770" y2="643" />
+ <line stroke-dasharray="4,4" stroke="black" stroke-opacity="1" x1="776" y1="574" x2="776" y2="649" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="497" y1="141" x2="503" y2="135" />
+ <line stroke="black" stroke-opacity="1" x1="497" y1="141" x2="491" y2="135" />
+ <line stroke="black" stroke-opacity="1" x1="497" y1="120" x2="497" y2="141" />
+</g>
+<g>
+ <line stroke="black" stroke-opacity="1" x1="301" y1="38" x2="307" y2="32" />
+ <line stroke="black" stroke-opacity="1" x1="301" y1="38" x2="295" y2="32" />
+ <line stroke="black" stroke-opacity="1" x1="301" y1="17" x2="301" y2="38" />
+</g>
+</svg>
diff --git a/src/bin/d2/location.hh b/src/bin/d2/location.hh
new file mode 100644
index 0000000..4145366
--- /dev/null
+++ b/src/bin/d2/location.hh
@@ -0,0 +1,306 @@
+// A Bison parser, made by GNU Bison 3.8.2.
+
+// Locations for Bison parsers in C++
+
+// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton. Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file location.hh
+ ** Define the isc::d2::location class.
+ */
+
+#ifndef YY_D2_PARSER_LOCATION_HH_INCLUDED
+# define YY_D2_PARSER_LOCATION_HH_INCLUDED
+
+# include <iostream>
+# include <string>
+
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+#line 14 "d2_parser.yy"
+namespace isc { namespace d2 {
+#line 59 "location.hh"
+
+ /// A point in a source file.
+ class position
+ {
+ public:
+ /// Type for file name.
+ typedef const std::string filename_type;
+ /// Type for line and column numbers.
+ typedef int counter_type;
+
+ /// Construct a position.
+ explicit position (filename_type* f = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ : filename (f)
+ , line (l)
+ , column (c)
+ {}
+
+
+ /// Initialization.
+ void initialize (filename_type* fn = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ {
+ filename = fn;
+ line = l;
+ column = c;
+ }
+
+ /** \name Line and Column related manipulators
+ ** \{ */
+ /// (line related) Advance to the COUNT next lines.
+ void lines (counter_type count = 1)
+ {
+ if (count)
+ {
+ column = 1;
+ line = add_ (line, count, 1);
+ }
+ }
+
+ /// (column related) Advance to the COUNT next columns.
+ void columns (counter_type count = 1)
+ {
+ column = add_ (column, count, 1);
+ }
+ /** \} */
+
+ /// File name to which this position refers.
+ filename_type* filename;
+ /// Current line number.
+ counter_type line;
+ /// Current column number.
+ counter_type column;
+
+ private:
+ /// Compute max (min, lhs+rhs).
+ static counter_type add_ (counter_type lhs, counter_type rhs, counter_type min)
+ {
+ return lhs + rhs < min ? min : lhs + rhs;
+ }
+ };
+
+ /// Add \a width columns, in place.
+ inline position&
+ operator+= (position& res, position::counter_type width)
+ {
+ res.columns (width);
+ return res;
+ }
+
+ /// Add \a width columns.
+ inline position
+ operator+ (position res, position::counter_type width)
+ {
+ return res += width;
+ }
+
+ /// Subtract \a width columns, in place.
+ inline position&
+ operator-= (position& res, position::counter_type width)
+ {
+ return res += -width;
+ }
+
+ /// Subtract \a width columns.
+ inline position
+ operator- (position res, position::counter_type width)
+ {
+ return res -= width;
+ }
+
+ /** \brief Intercept output stream redirection.
+ ** \param ostr the destination output stream
+ ** \param pos a reference to the position to redirect
+ */
+ template <typename YYChar>
+ std::basic_ostream<YYChar>&
+ operator<< (std::basic_ostream<YYChar>& ostr, const position& pos)
+ {
+ if (pos.filename)
+ ostr << *pos.filename << ':';
+ return ostr << pos.line << '.' << pos.column;
+ }
+
+ /// Two points in a source file.
+ class location
+ {
+ public:
+ /// Type for file name.
+ typedef position::filename_type filename_type;
+ /// Type for line and column numbers.
+ typedef position::counter_type counter_type;
+
+ /// Construct a location from \a b to \a e.
+ location (const position& b, const position& e)
+ : begin (b)
+ , end (e)
+ {}
+
+ /// Construct a 0-width location in \a p.
+ explicit location (const position& p = position ())
+ : begin (p)
+ , end (p)
+ {}
+
+ /// Construct a 0-width location in \a f, \a l, \a c.
+ explicit location (filename_type* f,
+ counter_type l = 1,
+ counter_type c = 1)
+ : begin (f, l, c)
+ , end (f, l, c)
+ {}
+
+
+ /// Initialization.
+ void initialize (filename_type* f = YY_NULLPTR,
+ counter_type l = 1,
+ counter_type c = 1)
+ {
+ begin.initialize (f, l, c);
+ end = begin;
+ }
+
+ /** \name Line and Column related manipulators
+ ** \{ */
+ public:
+ /// Reset initial location to final location.
+ void step ()
+ {
+ begin = end;
+ }
+
+ /// Extend the current location to the COUNT next columns.
+ void columns (counter_type count = 1)
+ {
+ end += count;
+ }
+
+ /// Extend the current location to the COUNT next lines.
+ void lines (counter_type count = 1)
+ {
+ end.lines (count);
+ }
+ /** \} */
+
+
+ public:
+ /// Beginning of the located region.
+ position begin;
+ /// End of the located region.
+ position end;
+ };
+
+ /// Join two locations, in place.
+ inline location&
+ operator+= (location& res, const location& end)
+ {
+ res.end = end.end;
+ return res;
+ }
+
+ /// Join two locations.
+ inline location
+ operator+ (location res, const location& end)
+ {
+ return res += end;
+ }
+
+ /// Add \a width columns to the end position, in place.
+ inline location&
+ operator+= (location& res, location::counter_type width)
+ {
+ res.columns (width);
+ return res;
+ }
+
+ /// Add \a width columns to the end position.
+ inline location
+ operator+ (location res, location::counter_type width)
+ {
+ return res += width;
+ }
+
+ /// Subtract \a width columns to the end position, in place.
+ inline location&
+ operator-= (location& res, location::counter_type width)
+ {
+ return res += -width;
+ }
+
+ /// Subtract \a width columns to the end position.
+ inline location
+ operator- (location res, location::counter_type width)
+ {
+ return res -= width;
+ }
+
+ /** \brief Intercept output stream redirection.
+ ** \param ostr the destination output stream
+ ** \param loc a reference to the location to redirect
+ **
+ ** Avoid duplicate information.
+ */
+ template <typename YYChar>
+ std::basic_ostream<YYChar>&
+ operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
+ {
+ location::counter_type end_col
+ = 0 < loc.end.column ? loc.end.column - 1 : 0;
+ ostr << loc.begin;
+ if (loc.end.filename
+ && (!loc.begin.filename
+ || *loc.begin.filename != *loc.end.filename))
+ ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col;
+ else if (loc.begin.line < loc.end.line)
+ ostr << '-' << loc.end.line << '.' << end_col;
+ else if (loc.begin.column < end_col)
+ ostr << '-' << end_col;
+ return ostr;
+ }
+
+#line 14 "d2_parser.yy"
+} } // isc::d2
+#line 305 "location.hh"
+
+#endif // !YY_D2_PARSER_LOCATION_HH_INCLUDED
diff --git a/src/bin/d2/main.cc b/src/bin/d2/main.cc
new file mode 100644
index 0000000..25975a7
--- /dev/null
+++ b/src/bin/d2/main.cc
@@ -0,0 +1,58 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <d2/d2_controller.h>
+#include <d2srv/d2_log.h>
+#include <exceptions/exceptions.h>
+#include <log/logger_manager.h>
+#include <log/logger_support.h>
+
+#include <iostream>
+
+using namespace isc::d2;
+using namespace isc::process;
+using namespace std;
+
+/// This file contains entry point (main() function) for standard DHCP-DDNS
+/// process, kea-dhcp-ddns, component of Kea software suite. It fetches
+/// the D2Controller singleton instance and invokes its launch method.
+/// The exit value of the program will be EXIT_SUCCESS if there were no
+/// errors, EXIT_FAILURE otherwise.
+int main(int argc, char* argv[]) {
+ int ret = EXIT_SUCCESS;
+
+ // Launch the controller passing in command line arguments.
+ // Exit program with the controller's return code.
+ try {
+ // Instantiate/fetch the DHCP-DDNS application controller singleton.
+ DControllerBasePtr& controller = D2Controller::instance();
+
+ // 'false' value disables test mode.
+ ret = controller->launch(argc, argv, false);
+ } catch (const VersionMessage& ex) {
+ std::string msg(ex.what());
+ if (!msg.empty()) {
+ std::cout << msg << std::endl;
+ }
+ } catch (const InvalidUsage& ex) {
+ std::string msg(ex.what());
+ if (!msg.empty()) {
+ std::cerr << msg << std::endl;
+ }
+ ret = EXIT_FAILURE;
+ } catch (const std::exception& ex) {
+ std::cerr << "Service failed: " << ex.what() << std::endl;
+ ret = EXIT_FAILURE;
+ } catch (...) {
+ std::cerr << "Service failed" << std::endl;
+ ret = EXIT_FAILURE;
+ }
+
+ D2Controller::instance().reset();
+
+ return (ret);
+}
diff --git a/src/bin/d2/nc_add.cc b/src/bin/d2/nc_add.cc
new file mode 100644
index 0000000..79f4647
--- /dev/null
+++ b/src/bin/d2/nc_add.cc
@@ -0,0 +1,707 @@
+// Copyright (C) 2013-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 <d2/nc_add.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_log.h>
+
+#include <util/buffer.h>
+#include <dns/rdataclass.h>
+
+#include <functional>
+
+namespace isc {
+namespace d2 {
+
+// NameAddTransaction states
+const int NameAddTransaction::ADDING_FWD_ADDRS_ST;
+const int NameAddTransaction::REPLACING_FWD_ADDRS_ST;
+const int NameAddTransaction::REPLACING_REV_PTRS_ST;
+
+// NameAddTransaction events
+const int NameAddTransaction::FQDN_IN_USE_EVT;
+const int NameAddTransaction::FQDN_NOT_IN_USE_EVT;
+
+NameAddTransaction::
+NameAddTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
+ cfg_mgr) {
+ if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) {
+ isc_throw (NameAddTransactionError,
+ "NameAddTransaction, request type must be CHG_ADD");
+ }
+}
+
+NameAddTransaction::~NameAddTransaction(){
+}
+
+void
+NameAddTransaction::defineEvents() {
+ // Call superclass impl first.
+ NameChangeTransaction::defineEvents();
+
+ // Define NameAddTransaction events.
+ defineEvent(FQDN_IN_USE_EVT, "FQDN_IN_USE_EVT");
+ defineEvent(FQDN_NOT_IN_USE_EVT, "FQDN_NOT_IN_USE_EVT");
+}
+
+void
+NameAddTransaction::verifyEvents() {
+ // Call superclass implementation first to verify its events. These are
+ // events common to all transactions, and they must be defined.
+ // SELECT_SERVER_EVT
+ // SERVER_SELECTED_EVT
+ // SERVER_IO_ERROR_EVT
+ // NO_MORE_SERVERS_EVT
+ // IO_COMPLETED_EVT
+ // UPDATE_OK_EVT
+ // UPDATE_FAILED_EVT
+ NameChangeTransaction::verifyEvents();
+
+ // Verify NameAddTransaction events by attempting to fetch them.
+ getEvent(FQDN_IN_USE_EVT);
+ getEvent(FQDN_NOT_IN_USE_EVT);
+}
+
+void
+NameAddTransaction::defineStates() {
+ // Call superclass impl first.
+ NameChangeTransaction::defineStates();
+
+ // Define NameAddTransaction states.
+ defineState(READY_ST, "READY_ST",
+ std::bind(&NameAddTransaction::readyHandler, this));
+
+ defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+ std::bind(&NameAddTransaction::selectingFwdServerHandler, this));
+
+ defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+ std::bind(&NameAddTransaction::selectingRevServerHandler, this));
+
+ defineState(ADDING_FWD_ADDRS_ST, "ADDING_FWD_ADDRS_ST",
+ std::bind(&NameAddTransaction::addingFwdAddrsHandler, this));
+
+ defineState(REPLACING_FWD_ADDRS_ST, "REPLACING_FWD_ADDRS_ST",
+ std::bind(&NameAddTransaction::replacingFwdAddrsHandler, this));
+
+ defineState(REPLACING_REV_PTRS_ST, "REPLACING_REV_PTRS_ST",
+ std::bind(&NameAddTransaction::replacingRevPtrsHandler, this));
+
+ defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+ std::bind(&NameAddTransaction::processAddOkHandler, this));
+
+ defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+ std::bind(&NameAddTransaction::processAddFailedHandler, this));
+}
+
+void
+NameAddTransaction::verifyStates() {
+ // Call superclass implementation first to verify its states. These are
+ // states common to all transactions, and they must be defined.
+ // READY_ST
+ // SELECTING_FWD_SERVER_ST
+ // SELECTING_REV_SERVER_ST
+ // PROCESS_TRANS_OK_ST
+ // PROCESS_TRANS_FAILED_ST
+ NameChangeTransaction::verifyStates();
+
+ // Verify NameAddTransaction states by attempting to fetch them.
+ getStateInternal(ADDING_FWD_ADDRS_ST);
+ getStateInternal(REPLACING_FWD_ADDRS_ST);
+ getStateInternal(REPLACING_REV_PTRS_ST);
+}
+
+void
+NameAddTransaction::readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ if (getForwardDomain()) {
+ // Request includes a forward change, do that first.
+ transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ // Reverse change only, transition accordingly.
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ }
+
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameAddTransaction::selectingFwdServerHandler() {
+ switch(getNextEvent()) {
+ case SELECT_SERVER_EVT:
+ // First time through for this transaction, so initialize server
+ // selection.
+ initServerSelection(getForwardDomain());
+ break;
+ case SERVER_IO_ERROR_EVT:
+ // We failed to communicate with current server. Attempt to select
+ // another one below.
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+
+ // Select the next server from the list of forward servers.
+ if (selectNextServer()) {
+ // We have a server to try.
+ transition(ADDING_FWD_ADDRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Server list is exhausted, so fail the transaction.
+ transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+ }
+}
+
+void
+NameAddTransaction::addingFwdAddrsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildAddFwdAddressRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Forward Add");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if (rcode == dns::Rcode::NOERROR()) {
+ // We were able to add it. Mark it as done.
+ setForwardChangeCompleted(true);
+
+ // If request calls for reverse update then do that next,
+ // otherwise we can process ok.
+ if (getReverseDomain()) {
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ }
+ } else if (rcode == dns::Rcode::YXDOMAIN()) {
+ // FQDN is in use so we need to attempt to replace
+ // forward address.
+ transition(REPLACING_FWD_ADDRS_ST, FQDN_IN_USE_EVT);
+ } else {
+ // Per RFC4703 any other value means cease.
+ // If we get not authorized should we try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(NameAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameAddTransaction::replacingFwdAddrsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case FQDN_IN_USE_EVT:
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildReplaceFwdAddressRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Forward Replace");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if (rcode == dns::Rcode::NOERROR()) {
+ // We were able to replace the forward mapping. Mark it as done.
+ setForwardChangeCompleted(true);
+
+ // If request calls for reverse update then do that next,
+ // otherwise we can process ok.
+ if (getReverseDomain()) {
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ }
+ } else if (rcode == dns::Rcode::NXDOMAIN()) {
+ // FQDN is NOT in use so go back and do the forward add address.
+ // Covers the case that it was there when we tried to add it,
+ // but has since been removed per RFC 4703.
+ transition(ADDING_FWD_ADDRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Per RFC4703 any other value means cease.
+ // If we get not authorized should try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REPLACE_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REPLACE_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REPLACE_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_FORWARD_REPLACE_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(NameAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameAddTransaction::selectingRevServerHandler() {
+ switch(getNextEvent()) {
+ case SELECT_SERVER_EVT:
+ // First time through for this transaction, so initialize server
+ // selection.
+ initServerSelection(getReverseDomain());
+ break;
+ case SERVER_IO_ERROR_EVT:
+ // We failed to communicate with current server. Attempt to select
+ // another one below.
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+
+ // Select the next server from the list of forward servers.
+ if (selectNextServer()) {
+ // We have a server to try.
+ transition(REPLACING_REV_PTRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Server list is exhausted, so fail the transaction.
+ transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+ }
+}
+
+
+void
+NameAddTransaction::replacingRevPtrsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildReplaceRevPtrsRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Reverse Replace");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if (rcode == dns::Rcode::NOERROR()) {
+ // We were able to update the reverse mapping. Mark it as done.
+ setReverseChangeCompleted(true);
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ } else {
+ // Per RFC4703 any other value means cease.
+ // If we get not authorized should try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_REV_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_REV_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(NameAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameAddTransaction::processAddOkHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_OK_EVT:
+ LOG_INFO(d2_to_dns_logger, DHCP_DDNS_ADD_SUCCEEDED)
+ .arg(getRequestId())
+ .arg(getNcr()->toText());
+ setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ endModel();
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameAddTransaction::processAddFailedHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_FAILED_EVT:
+ case NO_MORE_SERVERS_EVT:
+ setNcrStatus(dhcp_ddns::ST_FAILED);
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_ADD_FAILED)
+ .arg(getRequestId())
+ .arg(transactionOutcomeString());
+ endModel();
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameAddTransaction::buildAddFwdAddressRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+ // Construct dns::Name from NCR fqdn.
+ dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+
+ // Content on this request is based on RFC 4703, section 5.3.1
+ // First build the Prerequisite Section.
+
+ // Create 'FQDN Is Not In Use' prerequisite and add it to the
+ // prerequisite section.
+ // Based on RFC 2136, section 2.4.5
+ dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::NONE(),
+ dns::RRType::ANY(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+ // Next build the Update Section.
+
+ // Create the TTL based on lease length.
+ dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
+ // Create the FQDN/IP 'add' RR and add it to the to update section.
+ // Based on RFC 2136, section 2.5.1
+ dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::IN(),
+ getAddressRRType(), lease_ttl));
+
+ addLeaseAddressRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Now create the FQDN/DHCID 'add' RR and add it to update section.
+ // Based on RFC 2136, section 2.5.1
+ update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
+ dns::RRType::DHCID(), lease_ttl));
+ addDhcidRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+void
+NameAddTransaction::buildReplaceFwdAddressRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+ // Construct dns::Name from NCR fqdn.
+ dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+
+ // Content on this request is based on RFC 4703, section 5.3.2
+ // First build the Prerequisite Section.
+
+ // Create an 'FQDN Is In Use' prerequisite and add it to the
+ // pre-requisite section.
+ // Based on RFC 2136, section 2.4.4
+ dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::ANY(),
+ dns::RRType::ANY(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+ // Create an DHCID matches prerequisite RR and add it to the
+ // pre-requisite section.
+ // Based on RFC 2136, section 2.4.2.
+ prereq.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ addDhcidRdata(prereq);
+ request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+ // Next build the Update Section.
+
+ // Create the TTL based on lease length.
+ dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
+ // Create the FQDN/IP 'delete' RR and add it to the update section.
+ // Based on RFC 2136, section 2.5.2
+ dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
+ getAddressRRType(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the FQDN/IP 'add' RR and add it to the update section.
+ // Based on RFC 2136, section 2.5.1
+ update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
+ getAddressRRType(), lease_ttl));
+ addLeaseAddressRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+void
+NameAddTransaction::buildReplaceRevPtrsRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getReverseDomain());
+
+ // Create the reverse IP address "FQDN".
+ std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
+ dns::Name rev_ip(rev_addr);
+
+ // Create the TTL based on lease length.
+ dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
+ // Content on this request is based on RFC 4703, section 5.4
+ // Reverse replacement has no prerequisites so straight on to
+ // building the Update section.
+
+ // Create the PTR 'delete' RR and add it to update section.
+ dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+ dns::RRType::PTR(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the DHCID 'delete' RR and add it to the update section.
+ update.reset(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the FQDN/IP PTR 'add' RR, add the FQDN as the PTR Rdata
+ // then add it to update section.
+ update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
+ dns::RRType::PTR(), lease_ttl));
+ addPtrRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the FQDN/IP PTR 'add' RR, add the DHCID Rdata
+ // then add it to update section.
+ update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
+ dns::RRType::DHCID(), lease_ttl));
+ addDhcidRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/nc_add.h b/src/bin/d2/nc_add.h
new file mode 100644
index 0000000..6bb8ce3
--- /dev/null
+++ b/src/bin/d2/nc_add.h
@@ -0,0 +1,445 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NC_ADD_H
+#define NC_ADD_H
+
+/// @file nc_add.h This file defines the class NameAddTransaction.
+
+#include <d2srv/nc_trans.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the NameAddTransaction encounters a general error.
+class NameAddTransactionError : public isc::Exception {
+public:
+ NameAddTransactionError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS Add update.
+///
+/// NameAddTransaction implements a state machine for adding (or replacing) a
+/// forward and/or reverse DNS mapping. This state machine is based upon the
+/// processing logic described in RFC 4703, Sections 5.3 and 5.4. That logic
+/// may be paraphrased as follows:
+///
+/// @code
+///
+/// If the request includes a forward change:
+/// Select a forward server
+/// Send the server a request to add the forward entry
+/// If the server responds with already in use:
+/// Send a server a request to delete and then add forward entry
+///
+/// If the forward update is unsuccessful:
+/// abandon the update
+///
+/// If the request includes a reverse change:
+/// Select a reverse server
+/// Send a server a request to delete and then add reverse entry
+///
+/// @endcode
+///
+/// This class derives from NameChangeTransaction from which it inherits
+/// states, events, and methods common to NameChangeRequest processing.
+class NameAddTransaction : public NameChangeTransaction {
+public:
+
+ //@{ Additional states needed for NameAdd state model.
+ /// @brief State that attempts to add forward address records.
+ static const int ADDING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 1;
+
+ /// @brief State that attempts to replace forward address records.
+ static const int REPLACING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 2;
+
+ /// @brief State that attempts to replace reverse PTR records
+ static const int REPLACING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
+ //@}
+
+ //@{ Additional events needed for NameAdd state model.
+ /// @brief Event sent when an add attempt fails with address in use.
+ static const int FQDN_IN_USE_EVT = NCT_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Event sent when replace attempt to fails with address not in use.
+ static const int FQDN_NOT_IN_USE_EVT = NCT_DERIVED_EVENT_MIN + 2;
+ //@}
+
+ /// @brief Constructor
+ ///
+ /// Instantiates an Add transaction that is ready to be started.
+ ///
+ /// @param io_service IO service to be used for IO processing
+ /// @param ncr is the NameChangeRequest to fulfill
+ /// @param forward_domain is the domain to use for forward DNS updates
+ /// @param reverse_domain is the domain to use for reverse DNS updates
+ /// @param cfg_mgr pointer to the configuration manager
+ ///
+ /// @throw NameAddTransaction error if given request is not a CHG_ADD,
+ /// NameChangeTransaction error for base class construction errors.
+ NameAddTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr);
+
+ /// @brief Destructor
+ virtual ~NameAddTransaction();
+
+protected:
+ /// @brief Adds events defined by NameAddTransaction to the event set.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then defines the
+ /// events unique to NCR Add transaction processing.
+ ///
+ /// @throw StateModelError if an event definition is invalid or a duplicate.
+ virtual void defineEvents();
+
+ /// @brief Validates the contents of the set of events.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then verifies the
+ /// Add transaction's. This tests that the needed events are in the event
+ /// dictionary.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyEvents();
+
+ /// @brief Adds states defined by NameAddTransaction to the state set.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then defines the
+ /// states unique to NCR Add transaction processing.
+ ///
+ /// @throw StateModelError if an state definition is invalid or a duplicate.
+ virtual void defineStates();
+
+ /// @brief Validates the contents of the set of states.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then verifies the
+ /// Add transaction's states. This tests that the needed states are in the
+ /// state dictionary.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyStates();
+
+ /// @brief State handler for READY_ST.
+ ///
+ /// Entered from:
+ /// - INIT_ST with next event of START_EVT
+ ///
+ /// The READY_ST is the state the model transitions into when the inherited
+ /// method, startTransaction() is invoked. This handler, therefore, is the
+ /// entry point into the state model execution.h Its primary task is to
+ /// determine whether to start with a forward DNS change or a reverse DNS
+ /// change.
+ ///
+ /// Transitions to:
+ /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
+ /// includes a forward change.
+ ///
+ /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
+ /// includes only a reverse change.
+ ///
+ /// @throw NameAddTransactionError if upon entry next event is not
+ /// START_EVT.
+ void readyHandler();
+
+ /// @brief State handler for SELECTING_FWD_SERVER_ST.
+ ///
+ /// Entered from:
+ /// - READY_ST with next event of SELECT_SERVER_EVT
+ /// - ADDING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
+ /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
+ ///
+ /// Selects the server to be used from the forward domain for the forward
+ /// DNS update. If next event is SELECT_SERVER_EVT the handler initializes
+ /// the forward domain's server selection mechanism and then attempts to
+ /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+ /// handler simply attempts to select the next server.
+ ///
+ /// Transitions to:
+ /// - ADDING_FWD_ADDRS_ST with next event of SERVER_SELECTED upon successful
+ /// server selection
+ ///
+ /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+ /// failure to select a server
+ ///
+ /// @throw NameAddTransactionError if upon entry next event is not
+ /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+ void selectingFwdServerHandler();
+
+ /// @brief State handler for SELECTING_REV_SERVER_ST.
+ ///
+ /// Entered from:
+ /// - READY_ST with next event of SELECT_SERVER_EVT
+ /// - ADDING_FWD_ADDRS_ST with next event of SELECT_SERVER_EVT
+ /// - REPLACING_FWD_ADDRS_ST with next event of SELECT_SERVER_EVT
+ /// - REPLACING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
+ ///
+ /// Selects the server to be used from the reverse domain for the reverse
+ /// DNS update. If next event is SELECT_SERVER_EVT the handler initializes
+ /// the reverse domain's server selection mechanism and then attempts to
+ /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+ /// handler simply attempts to select the next server.
+ ///
+ /// Transitions to:
+ /// - ADDING_REV_PTRS_ST with next event of SERVER_SELECTED upon successful
+ /// server selection
+ ///
+ /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+ /// failure to select a server
+ ///
+ /// @throw NameAddTransactionError if upon entry next event is not
+ /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+ void selectingRevServerHandler();
+
+ /// @brief State handler for ADD_FWD_ADDRS_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_FWD_SERVER with next event of SERVER_SELECTED_EVT
+ /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_SELECTED_EVT
+ ///
+ /// Attempts to add a forward DNS entry for a given FQDN. If this is
+ /// first invocation of the handler after transitioning into this state,
+ /// any previous update request context is deleted. If next event
+ /// is SERVER_SELECTED_EVT, the handler builds the forward add request,
+ /// schedules an asynchronous send via sendUpdate(), and returns. Note
+ /// that sendUpdate will post NOP_EVT as next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - SELECTING_REV_SERVER_ST with next event of SELECT_SERVER_EVT upon
+ /// successful addition and the request includes a reverse DNS update.
+ ///
+ /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+ /// addition and no reverse DNS update is required.
+ ///
+ /// - REPLACING_FWD_ADDRS_ST with next event of FQDN_IN_USE_EVT if the DNS
+ /// server response indicates that an entry for the given FQDN already
+ /// exists.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with next event of UPDATE_FAILED_EVT if the
+ /// DNS server rejected the update for any other reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this states with next event of SERVER_SELECTED_EVT_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has been exhausted.
+ ///
+ /// @throw NameAddTransactionError if upon entry next event is not
+ /// SERVER_SELECTED_EVT or IO_COMPLETE_EVT.
+ void addingFwdAddrsHandler();
+
+ /// @brief State handler for REPLACING_FWD_ADDRS_ST.
+ ///
+ /// Entered from:
+ /// - ADDING_FWD_ADDRS_ST with next event of FQDN_IN_USE_EVT
+ ///
+ /// Attempts to delete and then add a forward DNS entry for a given
+ /// FQDN. If this is first invocation of the handler after transitioning
+ /// into this state, any previous update request context is deleted. If
+ /// next event is FDQN_IN_USE_EVT or SERVER_SELECTED_EVT, the handler
+ /// builds the forward replacement request, schedules an asynchronous send
+ /// via sendUpdate(), and returns. Note that sendUpdate will post NOP_EVT
+ /// as the next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - SELECTING_REV_SERVER_ST with a next event of SELECT_SERVER_EVT upon
+ /// successful replacement and the request includes a reverse DNS update.
+ ///
+ /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+ /// replacement and the request does not include a reverse DNS update.
+ ///
+ /// - ADDING_FWD_ADDR_STR with a next event of SERVER_SELECTED_EVT if the
+ /// DNS server response indicates that the FQDN is not in use. This could
+ /// occur if a previous add attempt indicated the FQDN was in use, but
+ /// that entry has since been removed by another entity prior to this
+ /// replacement attempt.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT if the
+ /// DNS server rejected the update for any other reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has been exhausted.
+ ///
+ /// @throw NameAddTransactionError if upon entry next event is not:
+ /// FQDN_IN_USE_EVT, SERVER_SELECTED_EVT or IO_COMPLETE_EVT.
+ void replacingFwdAddrsHandler();
+
+ /// @brief State handler for REPLACING_REV_PTRS_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
+ ///
+ /// Attempts to delete and then add a reverse DNS entry for a given FQDN.
+ /// If this is first invocation of the handler after transitioning into
+ /// this state, any previous update request context is deleted. If next
+ /// event is SERVER_SELECTED_EVT, the handler builds the reverse replacement
+ /// add request, schedules an asynchronous send via sendUpdate(), and
+ /// returns. Note that sendUpdate will post NOP_EVT as next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
+ /// successful replacement.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
+ /// DNS server rejected the update for any reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has been exhausted.
+ ///
+ /// @throw NameAddTransactionError if upon entry next event is not:
+ /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
+ void replacingRevPtrsHandler();
+
+ /// @brief State handler for PROCESS_TRANS_OK_ST.
+ ///
+ /// Entered from:
+ /// - ADDING_FWD_ADDRS_ST with a next event of UPDATE_OK_EVT
+ /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_OK_EVT
+ /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
+ ///
+ /// Sets the transaction action status to indicate success and ends
+ /// model execution.
+ ///
+ /// Transitions to:
+ /// - END_ST with a next event of END_EVT.
+ ///
+ /// @throw NameAddTransactionError if upon entry next event is not:
+ /// UPDATE_OK_EVT
+ void processAddOkHandler();
+
+ /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
+ /// - ADDING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+ /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+ /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
+ /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
+ ///
+ /// Sets the transaction status to indicate failure and ends
+ /// model execution.
+ ///
+ /// Transitions to:
+ /// - END_ST with a next event of FAIL_EVT.
+ ///
+ /// @throw NameAddTransactionError if upon entry next event is not:
+ /// UPDATE_FAILED_EVT
+ void processAddFailedHandler();
+
+ /// @brief Builds a DNS request to add an forward DNS entry for an FQDN
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for adding a
+ /// forward DNS mapping. Once constructed, the request is stored as
+ /// the transaction's DNS update request.
+ ///
+ /// The request content is adherent to RFC 4703 section 5.3.1:
+ ///
+ /// Prerequisite RRsets:
+ /// 1. An assertion that the FQDN does not exist
+ ///
+ /// Updates RRsets:
+ /// 1. An FQDN/IP RR addition (type A for IPv4, AAAA for IPv6)
+ /// 2. An FQDN/DHCID RR addition (type DHCID)
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildAddFwdAddressRequest();
+
+ /// @brief Builds a DNS request to replace forward DNS entry for an FQDN
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for replacing a
+ /// forward DNS mapping. Once constructed, the request is stored as
+ /// the transaction's DNS update request.
+ ///
+ /// The request content is adherent to RFC 4703 section 5.3.2:
+ ///
+ /// Prerequisite RRsets:
+ /// 1. An assertion that the FQDN is in use
+ /// 2. An assertion that the FQDN/DHCID RR exists for the lease client's
+ /// DHCID.
+ ///
+ /// Updates RRsets:
+ /// 1. A deletion of any existing FQDN RRs (type A for IPv4, AAAA for IPv6)
+ /// 2. A FQDN/IP RR addition (type A for IPv4, AAAA for IPv6)
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildReplaceFwdAddressRequest();
+
+ /// @brief Builds a DNS request to replace a reverse DNS entry for an FQDN
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for replacing a
+ /// reverse DNS mapping. Once constructed, the request is stored as
+ /// the transaction's DNS update request.
+ ///
+ /// The request content is adherent to RFC 4703 section 5.4:
+ ///
+ /// Prerequisite RRsets:
+ /// - There are not prerequisites.
+ ///
+ /// Updates RRsets:
+ /// 1. A delete of any existing PTR RRs for the lease address
+ /// 2. A delete of any existing DHCID RRs for the lease address
+ /// 3. A PTR RR addition for the lease address and FQDN
+ /// 4. A DHCID RR addition for the lease address and lease client DHCID
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildReplaceRevPtrsRequest();
+};
+
+/// @brief Defines a pointer to a NameAddTransaction.
+typedef boost::shared_ptr<NameAddTransaction> NameAddTransactionPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/bin/d2/nc_remove.cc b/src/bin/d2/nc_remove.cc
new file mode 100644
index 0000000..98785c9
--- /dev/null
+++ b/src/bin/d2/nc_remove.cc
@@ -0,0 +1,701 @@
+// Copyright (C) 2013-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 <d2/nc_remove.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_log.h>
+
+#include <functional>
+
+namespace isc {
+namespace d2 {
+
+
+// NameRemoveTransaction states
+const int NameRemoveTransaction::REMOVING_FWD_ADDRS_ST;
+const int NameRemoveTransaction::REMOVING_FWD_RRS_ST;
+const int NameRemoveTransaction::REMOVING_REV_PTRS_ST;
+
+// NameRemoveTransaction events
+// Currently NameRemoveTransaction does not define any events.
+
+NameRemoveTransaction::
+NameRemoveTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
+ cfg_mgr) {
+ if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) {
+ isc_throw (NameRemoveTransactionError,
+ "NameRemoveTransaction, request type must be CHG_REMOVE");
+ }
+}
+
+NameRemoveTransaction::~NameRemoveTransaction(){
+}
+
+void
+NameRemoveTransaction::defineEvents() {
+ // Call superclass impl first.
+ NameChangeTransaction::defineEvents();
+
+ // Define NameRemoveTransaction events.
+ // Currently NameRemoveTransaction does not define any events.
+ // defineEvent(TBD_EVENT, "TBD_EVT");
+}
+
+void
+NameRemoveTransaction::verifyEvents() {
+ // Call superclass implementation first to verify its events. These are
+ // events common to all transactions, and they must be defined.
+ // SELECT_SERVER_EVT
+ // SERVER_SELECTED_EVT
+ // SERVER_IO_ERROR_EVT
+ // NO_MORE_SERVERS_EVT
+ // IO_COMPLETED_EVT
+ // UPDATE_OK_EVT
+ // UPDATE_FAILED_EVT
+ NameChangeTransaction::verifyEvents();
+
+ // Verify NameRemoveTransaction events by attempting to fetch them.
+ // Currently NameRemoveTransaction does not define any events.
+ // getEvent(TBD_EVENT);
+}
+
+void
+NameRemoveTransaction::defineStates() {
+ // Call superclass impl first.
+ NameChangeTransaction::defineStates();
+
+ // Define NameRemoveTransaction states.
+ defineState(READY_ST, "READY_ST",
+ std::bind(&NameRemoveTransaction::readyHandler, this));
+
+ defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+ std::bind(&NameRemoveTransaction::selectingFwdServerHandler,
+ this));
+
+ defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+ std::bind(&NameRemoveTransaction::selectingRevServerHandler,
+ this));
+
+ defineState(REMOVING_FWD_ADDRS_ST, "REMOVING_FWD_ADDRS_ST",
+ std::bind(&NameRemoveTransaction::removingFwdAddrsHandler,
+ this));
+
+ defineState(REMOVING_FWD_RRS_ST, "REMOVING_FWD_RRS_ST",
+ std::bind(&NameRemoveTransaction::removingFwdRRsHandler,
+ this));
+
+ defineState(REMOVING_REV_PTRS_ST, "REMOVING_REV_PTRS_ST",
+ std::bind(&NameRemoveTransaction::removingRevPtrsHandler,
+ this));
+
+ defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+ std::bind(&NameRemoveTransaction::processRemoveOkHandler,
+ this));
+
+ defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+ std::bind(&NameRemoveTransaction::processRemoveFailedHandler,
+ this));
+}
+
+void
+NameRemoveTransaction::verifyStates() {
+ // Call superclass implementation first to verify its states. These are
+ // states common to all transactions, and they must be defined.
+ // READY_ST
+ // SELECTING_FWD_SERVER_ST
+ // SELECTING_REV_SERVER_ST
+ // PROCESS_TRANS_OK_ST
+ // PROCESS_TRANS_FAILED_ST
+ NameChangeTransaction::verifyStates();
+
+ // Verify NameRemoveTransaction states by attempting to fetch them.
+ getStateInternal(REMOVING_FWD_ADDRS_ST);
+ getStateInternal(REMOVING_FWD_RRS_ST);
+ getStateInternal(REMOVING_REV_PTRS_ST);
+}
+
+void
+NameRemoveTransaction::readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ if (getForwardDomain()) {
+ // Request includes a forward change, do that first.
+ transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ // Reverse change only, transition accordingly.
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ }
+
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameRemoveTransaction::selectingFwdServerHandler() {
+ switch(getNextEvent()) {
+ case SELECT_SERVER_EVT:
+ // First time through for this transaction, so initialize server
+ // selection.
+ initServerSelection(getForwardDomain());
+ break;
+ case SERVER_IO_ERROR_EVT:
+ // We failed to communicate with current server. Attempt to select
+ // another one below.
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+
+ // Select the next server from the list of forward servers.
+ if (selectNextServer()) {
+ // We have a server to try.
+ transition(REMOVING_FWD_ADDRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Server list is exhausted, so fail the transaction.
+ transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+ }
+}
+
+void
+NameRemoveTransaction::removingFwdAddrsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildRemoveFwdAddressRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Forward A/AAAA Remove");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ // The RCODE will be based on a value-dependent RRset search,
+ // see RFC 2136 section 3.2.3/3.2.4.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if ((rcode == dns::Rcode::NOERROR()) ||
+ (rcode == dns::Rcode::NXRRSET())) {
+ // We were able to remove it or it wasn't there, now we
+ // need to remove any other RRs for this FQDN.
+ transition(REMOVING_FWD_RRS_ST, UPDATE_OK_EVT);
+ } else {
+ // Per RFC4703 any other value means cease.
+ // If we get not authorized should we try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(NameRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+
+void
+NameRemoveTransaction::removingFwdRRsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case UPDATE_OK_EVT:
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildRemoveFwdRRsRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Forward RR Remove");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ // The RCODE will be based on a value-dependent RRset search,
+ // see RFC 2136 section 3.2.3/3.2.4.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if ((rcode == dns::Rcode::NOERROR()) ||
+ (rcode == dns::Rcode::NXRRSET())) {
+ // We were able to remove them or they were not there (
+ // Rcode of NXRRSET means there are no matching RRsets).
+ // In either case, we consider it success and mark it as done.
+ setForwardChangeCompleted(true);
+
+ // If request calls for reverse update then do that next,
+ // otherwise we can process ok.
+ if (getReverseDomain()) {
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ }
+ } else {
+ // Per RFC4703 any other value means cease.
+ // If we get not authorized should try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ // @note If we exhaust the IO retries for the current server
+ // due to IO failures, we will abort the remaining updates.
+ // The rational is that we are only in this state, if the remove
+ // of the forward address RR succeeded (removingFwdAddrsHandler)
+ // on the current server. Therefore we should not attempt another
+ // removal on a different server. This is perhaps a point
+ // for discussion.
+ // @todo Should we go ahead with the reverse remove?
+ retryTransition(PROCESS_TRANS_FAILED_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ // If we are out of retries on this server abandon the transaction.
+ // (Same logic as the case for TIMEOUT above).
+ retryTransition(PROCESS_TRANS_FAILED_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(NameRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+
+void
+NameRemoveTransaction::selectingRevServerHandler() {
+ switch(getNextEvent()) {
+ case SELECT_SERVER_EVT:
+ // First time through for this transaction, so initialize server
+ // selection.
+ initServerSelection(getReverseDomain());
+ break;
+ case SERVER_IO_ERROR_EVT:
+ // We failed to communicate with current server. Attempt to select
+ // another one below.
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+
+ // Select the next server from the list of forward servers.
+ if (selectNextServer()) {
+ // We have a server to try.
+ transition(REMOVING_REV_PTRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Server list is exhausted, so fail the transaction.
+ transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+ }
+}
+
+
+void
+NameRemoveTransaction::removingRevPtrsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildRemoveRevPtrsRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Reverse Remove");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ // The RCODE will be based on a value-dependent RRset search,
+ // see RFC 2136 section 3.2.3/3.2.4.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if ((rcode == dns::Rcode::NOERROR()) ||
+ (rcode == dns::Rcode::NXRRSET())) {
+ // We were able to remove the reverse mapping or they were
+ // not there (Rcode of NXRRSET means there are no matching
+ // RRsets). In either case, mark it as done.
+ setReverseChangeCompleted(true);
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ } else {
+ // Per RFC4703 any other value means cease.
+ // If we get not authorized should try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_REV_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_REV_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(NameRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+
+void
+NameRemoveTransaction::processRemoveOkHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_OK_EVT:
+ LOG_INFO(d2_to_dns_logger, DHCP_DDNS_REMOVE_SUCCEEDED)
+ .arg(getRequestId())
+ .arg(getNcr()->toText());
+ setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ endModel();
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameRemoveTransaction::processRemoveFailedHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_FAILED_EVT:
+ case NO_MORE_SERVERS_EVT:
+ case SERVER_IO_ERROR_EVT:
+ setNcrStatus(dhcp_ddns::ST_FAILED);
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REMOVE_FAILED)
+ .arg(getRequestId())
+ .arg(transactionOutcomeString());
+ endModel();
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(NameRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+NameRemoveTransaction::buildRemoveFwdAddressRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+ // Content on this request is based on RFC 4703, section 5.5, paragraph 4.
+ // Construct dns::Name from NCR fqdn.
+ dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+ // First build the Prerequisite Section
+
+ // Create an DHCID matches prerequisite RR and add it to the
+ // pre-requisite section
+ // Based on RFC 2136, section 2.4.2.
+ dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::IN(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ addDhcidRdata(prereq);
+ request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+ // Next build the Update Section
+
+ // Create the FQDN/IP 'delete' RR and add it to the update section.
+ // Add the RR to update section.
+ // Based on 2136 section 2.5.4
+ dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::NONE(),
+ getAddressRRType(), dns::RRTTL(0)));
+ addLeaseAddressRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+void
+NameRemoveTransaction::buildRemoveFwdRRsRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+ // Construct dns::Name from NCR fqdn.
+ dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+
+ // Content on this request is based on RFC 4703, section 5.5, paragraph 5.
+ // First build the Prerequisite Section.
+
+ // Now create an DHCID matches prerequisite RR.
+ // Set the RR's RData to DHCID.
+ // Add it to the pre-requisite section.
+ // Based on RFC 2136, section 2.4.2.
+ dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::IN(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ addDhcidRdata(prereq);
+ request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+ // Create an assertion that there are no A RRs for the FQDN.
+ // Add it to the pre-reqs.
+ // Based on RFC 2136, section 2.4.3.
+ prereq.reset(new dns::RRset(fqdn, dns::RRClass::NONE(),
+ dns::RRType::A(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+ // Create an assertion that there are no A RRs for the FQDN.
+ // Add it to the pre-reqs.
+ // Based on RFC 2136, section 2.4.3.
+ prereq.reset(new dns::RRset(fqdn, dns::RRClass::NONE(),
+ dns::RRType::AAAA(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+ // Next build the Update Section.
+
+ // Create the 'delete' of all RRs for FQDN.
+ // Set the message RData to lease address.
+ // Add the RR to update section.
+ // Based on RFC 2136, section 2.5.3.
+ dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
+ dns::RRType::ANY(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+void
+NameRemoveTransaction::buildRemoveRevPtrsRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getReverseDomain());
+
+ // Create the reverse IP address "FQDN".
+ std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
+ dns::Name rev_ip(rev_addr);
+
+ // Content on this request is based on RFC 4703, section 5.5, paragraph 2.
+ // First build the Prerequisite Section.
+ // (Note that per RFC 4703, section 5.4, there is no need to validate
+ // DHCID RR for PTR entries.)
+
+ // Create an assertion that the PTRDNAME in the PTR record matches the
+ // client's FQDN for the address that was released.
+ // Based on RFC 2136, section 3.2.3
+ dns::RRsetPtr prereq(new dns::RRset(rev_ip, dns::RRClass::IN(),
+ dns::RRType::PTR(), dns::RRTTL(0)));
+ addPtrRdata(prereq);
+ request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+ // Now, build the Update section.
+
+ // Create a delete of any RRs for the FQDN and add it to update section.
+ // Based on RFC 2136, section 3.4.2.3
+ dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+ dns::RRType::ANY(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/nc_remove.h b/src/bin/d2/nc_remove.h
new file mode 100644
index 0000000..672a3d9
--- /dev/null
+++ b/src/bin/d2/nc_remove.h
@@ -0,0 +1,429 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef NC_REMOVE_H
+#define NC_REMOVE_H
+
+/// @file nc_remove.h This file defines the class NameRemoveTransaction.
+
+#include <d2srv/nc_trans.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the NameRemoveTransaction encounters a general error.
+class NameRemoveTransactionError : public isc::Exception {
+public:
+ NameRemoveTransactionError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS Remove update.
+///
+/// NameRemoveTransaction implements a state machine for removing a forward
+/// and/or reverse DNS mappings. This state machine is based upon the processing
+/// logic described in RFC 4703, Section 5.5. That logic may be paraphrased as
+/// follows:
+///
+/// @code
+///
+/// If the request includes a forward change:
+/// Select a forward server
+/// Send the server a request to remove client's specific forward address RR
+/// If it succeeds or the server responds with name no longer in use
+/// Send a server a request to delete any other RRs for that FQDN, such
+/// as the DHCID RR.
+/// otherwise
+/// abandon the update
+///
+/// If the request includes a reverse change:
+/// Select a reverse server
+/// Send a server a request to delete reverse entry (PTR RR)
+///
+/// @endcode
+///
+/// This class derives from NameChangeTransaction from which it inherits
+/// states, events, and methods common to NameChangeRequest processing.
+class NameRemoveTransaction : public NameChangeTransaction {
+public:
+
+ //@{ Additional states needed for NameRemove state model.
+ /// @brief State that attempts to remove specific forward address record.
+ static const int REMOVING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 1;
+
+ /// @brief State that attempts to remove any other forward RRs for the DHCID
+ static const int REMOVING_FWD_RRS_ST = NCT_DERIVED_STATE_MIN + 2;
+
+ /// @brief State that attempts to remove reverse PTR records
+ static const int REMOVING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
+ //@}
+
+ //@{ Additional events needed for NameRemove state model.
+ /// @brief Event sent when replace attempt to fails with address not in use.
+ /// @todo Currently none have been identified.
+ //@}
+
+ /// @brief Constructor
+ ///
+ /// Instantiates an Remove transaction that is ready to be started.
+ ///
+ /// @param io_service IO service to be used for IO processing
+ /// @param ncr is the NameChangeRequest to fulfill
+ /// @param forward_domain is the domain to use for forward DNS updates
+ /// @param reverse_domain is the domain to use for reverse DNS updates
+ /// @param cfg_mgr pointer to the configuration manager
+ ///
+ /// @throw NameRemoveTransaction error if given request is not a CHG_REMOVE,
+ /// NameChangeTransaction error for base class construction errors.
+ NameRemoveTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr);
+
+ /// @brief Destructor
+ virtual ~NameRemoveTransaction();
+
+protected:
+ /// @brief Adds events defined by NameRemoveTransaction to the event set.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then defines the
+ /// events unique to NCR Remove transaction processing.
+ ///
+ /// @throw StateModelError if an event definition is invalid or a duplicate.
+ virtual void defineEvents();
+
+ /// @brief Validates the contents of the set of events.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then verifies the
+ /// Remove transaction's events. This tests that the needed events are in
+ /// the event dictionary.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyEvents();
+
+ /// @brief Adds states defined by NameRemoveTransaction to the state set.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then defines the
+ /// states unique to NCR Remove transaction processing.
+ ///
+ /// @throw StateModelError if an state definition is invalid or a duplicate.
+ virtual void defineStates();
+
+ /// @brief Validates the contents of the set of states.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then verifies the
+ /// Remove transaction's states. This tests that the needed states are in
+ /// the state dictionary.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyStates();
+
+ /// @brief State handler for READY_ST.
+ ///
+ /// Entered from:
+ /// - INIT_ST with next event of START_EVT
+ ///
+ /// The READY_ST is the state the model transitions into when the inherited
+ /// method, startTransaction() is invoked. This handler, therefore, is the
+ /// entry point into the state model execution. Its primary task is to
+ /// determine whether to start with a forward DNS change or a reverse DNS
+ /// change.
+ ///
+ /// Transitions to:
+ /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
+ /// includes a forward change.
+ ///
+ /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
+ /// includes only a reverse change.
+ ///
+ /// @throw NameRemoveTransactionError if upon entry next event is not
+ /// START_EVT.
+ void readyHandler();
+
+ /// @brief State handler for SELECTING_FWD_SERVER_ST.
+ ///
+ /// Entered from:
+ /// - READY_ST with next event of SELECT_SERVER_EVT
+ /// - REMOVING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
+ ///
+ /// Selects the server to be used from the forward domain for the forward
+ /// DNS update. If next event is SELECT_SERVER_EVT the handler initializes
+ /// the forward domain's server selection mechanism and then attempts to
+ /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+ /// handler simply attempts to select the next server.
+ ///
+ /// Transitions to:
+ /// - REMOVING_FWD_ADDRS_ST with next event of SERVER_SELECTED upon
+ /// successful server selection
+ ///
+ /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+ /// failure to select a server
+ ///
+ /// @throw NameRemoveTransactionError if upon entry next event is not
+ /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+ void selectingFwdServerHandler();
+
+ /// @brief State handler for SELECTING_REV_SERVER_ST.
+ ///
+ /// Entered from:
+ /// - READY_ST with next event of SELECT_SERVER_EVT
+ /// - REMOVING_FWD_RRS_ST with next event of SELECT_SERVER_EVT
+ /// - REMOVING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
+ ///
+ /// Selects the server to be used from the reverse domain for the reverse
+ /// DNS update. If next event is SELECT_SERVER_EVT the handler initializes
+ /// the reverse domain's server selection mechanism and then attempts to
+ /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+ /// handler simply attempts to select the next server.
+ ///
+ /// Transitions to:
+ /// - REMOVING_REV_PTRS_ST with next event of SERVER_SELECTED upon
+ /// successful server selection
+ ///
+ /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+ /// failure to select a server
+ ///
+ /// @throw NameRemoveTransactionError if upon entry next event is not
+ /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+ void selectingRevServerHandler();
+
+ /// @brief State handler for REMOVING_FWD_ADDRS_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_FWD_SERVER with next event of SERVER_SELECTED_EVT
+ ///
+ /// Attempts to remove the forward DNS entry for a given FQDN, provided
+ /// a DHCID RR exists which matches the requesting DHCID. If this is
+ /// first invocation of the handler after transitioning into this state,
+ /// any previous update request context is deleted. If next event
+ /// is SERVER_SELECTED_EVT, the handler builds the forward remove request,
+ /// schedules an asynchronous send via sendUpdate(), and returns. Note
+ /// that sendUpdate will post NOP_EVT as next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - REMOVING_FWD_RRS_ST with next event of UPDATE_OK_EVT upon successful
+ /// removal or RCODE of indication FQDN is no longer in use (NXDOMAIN).
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with next event of UPDATE_FAILED_EVT if the
+ /// DNS server rejected the update for any reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this state with next event of SERVER_SELECTED_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has been exhausted.
+ ///
+ /// @throw NameRemoveTransactionError if upon entry next event is not
+ /// SERVER_SELECTED_EVT or IO_COMPLETE_EVT
+ void removingFwdAddrsHandler();
+
+ /// @brief State handler for REMOVING_FWD_RRS_ST.
+ ///
+ /// Entered from:
+ /// - REMOVING_FWD_ADDRS_ST with next event of UPDATE_OK_EVT
+ ///
+ /// Attempts to delete any remaining RRs associated with the given FQDN
+ /// such as the DHCID RR. If this is first invocation of the handler after
+ /// transitioning into this state, any previous update request context is
+ /// deleted and the handler builds the forward remove request. It then
+ /// schedules an asynchronous send via sendUpdate(),
+ /// and returns. Note that sendUpdate will post NOP_EVT as the next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - SELECTING_REV_SERVER_ST with a next event of SELECT_SERVER_EVT upon
+ /// successful completion and the request includes a reverse DNS update.
+ ///
+ /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+ /// completion and the request does not include a reverse DNS update.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT if the
+ /// DNS server rejected the update for any other reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of SERVER_IO_ERROR_EVT if
+ /// there we have reached maximum number of retries without success on the
+ /// current server.
+ ///
+ /// @note If we exhaust the IO retries for the current server due to IO
+ /// failures, we will abort the remaining updates. The rational is that
+ /// we are only in this state, if the remove of the forward address RR
+ /// succeeded (removingFwdAddrsHandler) on the current server so we should
+ /// not attempt another removal on a different server. This is perhaps a
+ /// point for discussion. @todo Should we go ahead with the reverse remove?
+ ///
+ /// @throw NameRemoveTransactionError if upon entry next event is not:
+ /// UPDATE_OK_EVT or IO_COMPLETE_EVT
+ void removingFwdRRsHandler();
+
+ /// @brief State handler for REMOVING_REV_PTRS_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
+ ///
+ /// Attempts to delete a reverse DNS entry for a given FQDN. If this is
+ /// first invocation of the handler after transitioning into this state,
+ /// any previous update request context is deleted. If next event is
+ /// SERVER_SELECTED_EVT, the handler builds the reverse remove request,
+ /// schedules an asynchronous send via sendUpdate(), and then returns.
+ /// Note that sendUpdate will post NOP_EVT as next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
+ /// successful completion.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
+ /// DNS server rejected the update for any reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has been exhausted.
+ ///
+ /// @throw NameRemoveTransactionError if upon entry next event is not:
+ /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
+ void removingRevPtrsHandler();
+
+ /// @brief State handler for PROCESS_TRANS_OK_ST.
+ ///
+ /// Entered from:
+ /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_OK_EVT
+ /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
+ ///
+ /// Sets the transaction action status to indicate success and ends
+ /// model execution.
+ ///
+ /// Transitions to:
+ /// - END_ST with a next event of END_EVT.
+ ///
+ /// @throw NameRemoveTransactionError if upon entry next event is not:
+ /// UPDATE_OK_EVT
+ void processRemoveOkHandler();
+
+ /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
+ /// - REMOVING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+ /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_FAILED_EVT
+ /// - REMOVING_FWD_RRS_ST with a next event of SERVER_IO_ERROR_EVT
+ /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
+ /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
+ ///
+ /// Sets the transaction status to indicate failure and ends
+ /// model execution.
+ ///
+ /// Transitions to:
+ /// - END_ST with a next event of FAIL_EVT.
+ ///
+ /// @throw NameRemoveTransactionError if upon entry next event is not:
+ /// UPDATE_FAILED_EVT
+ void processRemoveFailedHandler();
+
+ /// @brief Builds a DNS request to remove a forward DNS address for a FQDN.
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for removing a
+ /// forward DNS address mapping. Once constructed, the request is stored as
+ /// the transaction's DNS update request.
+ ///
+ /// The request content is adherent to RFC 4703 section 5.5, paragraph 4.
+ ///
+ /// Prerequisite RRsets:
+ /// 1. An assertion that a matching DHCID RR exists
+ ///
+ /// Updates RRsets:
+ /// 1. A delete of the FQDN/IP RR (type A for IPv4, AAAA for IPv6)
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildRemoveFwdAddressRequest();
+
+ /// @brief Builds a DNS request to remove all forward DNS RRs for a FQDN.
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for removing any
+ /// remaining forward DNS RRs, once all A or AAAA entries for the FQDN
+ /// have been removed. Once constructed, the request is stored as the
+ /// transaction's DNS update request.
+ ///
+ /// The request content is adherent to RFC 4703 section 5.5, paragraph 5.
+ ///
+ /// Prerequisite RRsets:
+ /// 1. An assertion that a matching DHCID RR exists
+ /// 2. An assertion that no A RRs for the FQDN exist
+ /// 3. An assertion that no AAAA RRs for the FQDN exist
+ ///
+ /// Updates RRsets:
+ /// 1. A delete of all RRs for the FQDN
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildRemoveFwdRRsRequest();
+
+ /// @brief Builds a DNS request to remove a reverse DNS entry for a FQDN
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for removing a
+ /// reverse DNS mapping. Once constructed, the request is stored as
+ /// the transaction's DNS update request.
+ ///
+ /// The request content is adherent to RFC 4703 section 5.5, paragraph 2:
+ ///
+ /// Prerequisite RRsets:
+ /// 1. An assertion that a PTR record matching the client's FQDN exists.
+ ///
+ /// Updates RRsets:
+ /// 1. A delete of all RRs for the FQDN
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildRemoveRevPtrsRequest();
+};
+
+/// @brief Defines a pointer to a NameRemoveTransaction.
+typedef boost::shared_ptr<NameRemoveTransaction> NameRemoveTransactionPtr;
+
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/bin/d2/parser_context.cc b/src/bin/d2/parser_context.cc
new file mode 100644
index 0000000..f27a628
--- /dev/null
+++ b/src/bin/d2/parser_context.cc
@@ -0,0 +1,221 @@
+// 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/.
+
+#include <config.h>
+
+#include <d2/d2_parser.h>
+#include <d2/parser_context.h>
+#include <d2srv/d2_log.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <sstream>
+#include <limits>
+
+namespace isc {
+namespace d2 {
+
+D2ParserContext::D2ParserContext()
+ : sfile_(0), ctx_(NO_KEYWORD), trace_scanning_(false), trace_parsing_(false)
+{
+}
+
+D2ParserContext::~D2ParserContext()
+{
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseString(const std::string& str, ParserType parser_type)
+{
+ scanStringBegin(str, parser_type);
+ return (parseCommon());
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseFile(const std::string& filename, ParserType parser_type) {
+ FILE* f = fopen(filename.c_str(), "r");
+ if (!f) {
+ isc_throw(D2ParseError, "Unable to open file " << filename);
+ }
+ scanFileBegin(f, filename, parser_type);
+ return (parseCommon());
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseCommon() {
+ isc::d2::D2Parser parser(*this);
+ // Uncomment this to get detailed parser logs.
+ // trace_parsing_ = true;
+ parser.set_debug_level(trace_parsing_);
+ try {
+ int res = parser.parse();
+ if (res != 0) {
+ isc_throw(D2ParseError, "Parser abort");
+ }
+ scanEnd();
+ }
+ catch (...) {
+ scanEnd();
+ throw;
+ }
+ if (stack_.size() == 1) {
+ return (stack_[0]);
+ } else {
+ isc_throw(D2ParseError, "Expected exactly one terminal Element expected, found "
+ << stack_.size());
+ }
+}
+
+void
+D2ParserContext::error(const isc::d2::location& loc,
+ const std::string& what,
+ size_t pos)
+{
+ if (pos == 0) {
+ isc_throw(D2ParseError, loc << ": " << what);
+ } else {
+ isc_throw(D2ParseError, loc << " (near " << pos << "): " << what);
+ }
+}
+
+void
+D2ParserContext::error (const std::string& what)
+{
+ isc_throw(D2ParseError, what);
+}
+
+void
+D2ParserContext::fatal (const std::string& what)
+{
+ isc_throw(D2ParseError, what);
+}
+
+isc::data::Element::Position
+D2ParserContext::loc2pos(isc::d2::location& loc)
+{
+ const std::string& file = *loc.begin.filename;
+ const uint32_t line = loc.begin.line;
+ const uint32_t pos = loc.begin.column;
+ return (isc::data::Element::Position(file, line, pos));
+}
+
+void
+D2ParserContext::require(const std::string& name,
+ isc::data::Element::Position open_loc,
+ isc::data::Element::Position close_loc)
+{
+ ConstElementPtr value = stack_.back()->get(name);
+ if (!value) {
+ isc_throw(D2ParseError,
+ "missing parameter '" << name << "' ("
+ << stack_.back()->getPosition() << ") ["
+ << contextName() << " map between "
+ << open_loc << " and " << close_loc << "]");
+ }
+}
+
+void
+D2ParserContext::unique(const std::string& name,
+ isc::data::Element::Position loc)
+{
+ ConstElementPtr value = stack_.back()->get(name);
+ if (value) {
+ if (ctx_ != NO_KEYWORD) {
+ isc_throw(D2ParseError, loc << ": duplicate " << name
+ << " entries in " << contextName()
+ << " map (previous at " << value->getPosition() << ")");
+ } else {
+ isc_throw(D2ParseError, loc << ": duplicate " << name
+ << " entries in JSON"
+ << " map (previous at " << value->getPosition() << ")");
+ }
+ }
+}
+
+void
+D2ParserContext::enter(const ParserContext& ctx)
+{
+ cstack_.push_back(ctx_);
+ ctx_ = ctx;
+}
+
+void
+D2ParserContext::leave()
+{
+ if (cstack_.empty()) {
+ fatal("unbalanced syntactic context");
+ }
+
+ ctx_ = cstack_.back();
+ cstack_.pop_back();
+}
+
+const std::string
+D2ParserContext::contextName()
+{
+ switch (ctx_) {
+ case NO_KEYWORD:
+ return ("__no keyword__");
+ case CONFIG:
+ return ("toplevel");
+ case DHCPDDNS:
+ return ("DhcpDdns");
+ case TSIG_KEY:
+ return ("tsig-key");
+ case TSIG_KEYS:
+ return ("tsig-keys");
+ case ALGORITHM:
+ return("algorithm");
+ case DIGEST_BITS:
+ return("digest-bits");
+ case SECRET:
+ return("secret");
+ case FORWARD_DDNS:
+ return("forward-ddns");
+ case REVERSE_DDNS:
+ return("reverse-ddns");
+ case DDNS_DOMAIN:
+ return("ddns-domain");
+ case DDNS_DOMAINS:
+ return("ddns-domains");
+ case DNS_SERVER:
+ return("dns-server");
+ case DNS_SERVERS:
+ return("dns-servers");
+ case CONTROL_SOCKET:
+ return("control-socket");
+ case LOGGERS:
+ return ("loggers");
+ case OUTPUT_OPTIONS:
+ return ("output-options");
+ case NCR_PROTOCOL:
+ return ("ncr-protocol");
+ case NCR_FORMAT:
+ return ("ncr-format");
+ case HOOKS_LIBRARIES:
+ return ("hooks-libraries");
+ default:
+ return ("__unknown__");
+ }
+}
+
+void
+D2ParserContext::warning(const isc::d2::location& loc,
+ const std::string& what) {
+ std::ostringstream msg;
+ msg << loc << ": " << what;
+ LOG_WARN(d2_to_dns_logger, DHCP_DDNS_CONFIG_SYNTAX_WARNING)
+ .arg(msg.str());
+}
+
+void
+D2ParserContext::warnAboutExtraCommas(const isc::d2::location& loc) {
+ warning(loc, "Extraneous comma. A piece of configuration may have been omitted.");
+}
+
+} // namespace d2
+} // namespace isc
diff --git a/src/bin/d2/parser_context.h b/src/bin/d2/parser_context.h
new file mode 100644
index 0000000..e838ffd
--- /dev/null
+++ b/src/bin/d2/parser_context.h
@@ -0,0 +1,351 @@
+// 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 PARSER_CONTEXT_H
+#define PARSER_CONTEXT_H
+#include <string>
+#include <map>
+#include <vector>
+#include <d2/d2_parser.h>
+#include <d2/parser_context_decl.h>
+#include <exceptions/exceptions.h>
+
+// Tell Flex the lexer's prototype ...
+#define YY_DECL isc::d2::D2Parser::symbol_type d2_parser_lex (D2ParserContext& driver)
+
+// ... and declare it for the parser's sake.
+YY_DECL;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Evaluation error exception raised when trying to parse.
+///
+/// @todo: This probably should be common for Dhcp4 and Dhcp6.
+class D2ParseError : public isc::Exception {
+public:
+ D2ParseError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Evaluation context, an interface to the expression evaluation.
+class D2ParserContext
+{
+public:
+
+ /// @brief Defines currently supported scopes
+ ///
+ /// D2Parser may eventually support multiple levels of parsing scope.
+ /// Currently it supports only the D2 module scope which expects the data
+ /// to be parsed to be a map containing the DhcpDdns element and its
+ /// constituents.
+ ///
+ typedef enum {
+ /// This parser will parse the content as generic JSON.
+ PARSER_JSON,
+
+ ///< Used for parsing top level (contains DhcpDdns)
+ PARSER_DHCPDDNS,
+
+ ///< Used for parsing content of DhcpDdns.
+ PARSER_SUB_DHCPDDNS,
+
+ ///< Used for parsing content of a TSIG key.
+ PARSER_TSIG_KEY,
+
+ ///< Used for parsing a list of TSIG Keys.
+ PARSER_TSIG_KEYS,
+
+ ///< Used for parsing content of a DDNS Domain.
+ PARSER_DDNS_DOMAIN,
+
+ ///< Used for parsing a list a DDNS Domains.
+ PARSER_DDNS_DOMAINS,
+
+ ///< Used for parsing content of a DNS Server.
+ PARSER_DNS_SERVER,
+
+ ///< Used for parsing a list of DNS servers.
+ PARSER_DNS_SERVERS,
+
+ ///< Used for parsing content of hooks libraries.
+ PARSER_HOOKS_LIBRARY
+ } ParserType;
+
+ /// @brief Default constructor.
+ D2ParserContext();
+
+ /// @brief destructor.
+ virtual ~D2ParserContext();
+
+ /// @brief JSON elements being parsed.
+ std::vector<isc::data::ElementPtr> stack_;
+
+ /// @brief Method called before scanning starts on a string.
+ ///
+ /// @param str string to be parsed
+ /// @param type specifies expected content
+ void scanStringBegin(const std::string& str, ParserType type);
+
+ /// @brief Method called before scanning starts on a file.
+ ///
+ /// @param f stdio FILE pointer
+ /// @param filename file to be parsed
+ /// @param type specifies expected content
+ void scanFileBegin(FILE* f, const std::string& filename, ParserType type);
+
+ /// @brief Method called after the last tokens are scanned.
+ void scanEnd();
+
+ /// @brief Divert input to an include file.
+ ///
+ /// @param filename file to be included
+ void includeFile(const std::string& filename);
+
+ /// @brief Run the parser on the string specified.
+ ///
+ /// This method parses specified string. Depending on the value of
+ /// parser_type, parser may either check only that the input is valid
+ /// JSON, or may do more specific syntax checking. See @ref ParserType
+ /// for supported syntax checkers.
+ ///
+ /// @param str string to be parsed
+ /// @param parser_type specifies expected content
+ /// @return Element structure representing parsed text.
+ isc::data::ElementPtr parseString(const std::string& str,
+ ParserType parser_type);
+
+ /// @brief Run the parser on the file specified.
+ ///
+ /// This method parses specified file. Depending on the value of
+ /// parser_type, parser may either check only that the input is valid
+ /// JSON, or may do more specific syntax checking. See @ref ParserType
+ /// for supported syntax checkers.
+ ///
+ /// @param filename file to be parsed
+ /// @param parser_type specifies expected content
+ /// @return Element structure representing parsed text.
+ isc::data::ElementPtr parseFile(const std::string& filename,
+ ParserType parser_type);
+
+ /// @brief Error handler
+ ///
+ /// @note The optional position for an error in a string begins by 1
+ /// so the caller should add 1 to the position of the C++ string.
+ ///
+ /// @param loc location within the parsed file where the problem was experienced.
+ /// @param what string explaining the nature of the error.
+ /// @param pos optional position for in string errors.
+ /// @throw D2ParseError
+ void error(const isc::d2::location& loc,
+ const std::string& what,
+ size_t pos = 0);
+
+ /// @brief Error handler
+ ///
+ /// This is a simplified error reporting tool for reporting
+ /// parsing errors.
+ ///
+ /// @param what string explaining the nature of the error.
+ /// @throw D2ParseError
+ void error(const std::string& what);
+
+ /// @brief Fatal error handler
+ ///
+ /// This is for should not happen but fatal errors.
+ /// Used by YY_FATAL_ERROR macro so required to be static.
+ ///
+ /// @param what string explaining the nature of the error.
+ /// @throw D2ParseError
+ static void fatal(const std::string& what);
+
+ /// @brief Converts bison's position to one understood by isc::data::Element
+ ///
+ /// Convert a bison location into an element position
+ /// (take the begin, the end is lost)
+ ///
+ /// @param loc location in bison format
+ /// @return Position in format accepted by Element
+ isc::data::Element::Position loc2pos(isc::d2::location& loc);
+
+ /// @brief Check if a required parameter is present
+ ///
+ /// Check if a required parameter is present in the map at the top
+ /// of the stack and raise an error when it is not.
+ ///
+ /// @param name name of the parameter to check
+ /// @param open_loc location of the opening curly bracket
+ /// @param close_loc location of the closing curly bracket
+ /// @throw D2ParseError
+ void require(const std::string& name,
+ isc::data::Element::Position open_loc,
+ isc::data::Element::Position close_loc);
+
+ /// @brief Check if a parameter is already present
+ ///
+ /// Check if a parameter is already present in the map at the top
+ /// of the stack and raise an error when it is.
+ ///
+ /// @param name name of the parameter to check
+ /// @param loc location of the current parameter
+ /// @throw D2ParseError
+ void unique(const std::string& name,
+ isc::data::Element::Position loc);
+
+ /// @brief Warning handler
+ ///
+ /// @param loc location within the parsed file where the problem was experienced
+ /// @param what string explaining the nature of the error
+ ///
+ /// @throw ParseError
+ void warning(const isc::d2::location& loc, const std::string& what);
+
+ /// @brief Warning for extra commas
+ ///
+ /// @param loc location within the parsed file of the extra comma
+ ///
+ /// @throw ParseError
+ void warnAboutExtraCommas(const isc::d2::location& loc);
+
+ /// @brief Defines syntactic contexts for lexical tie-ins
+ typedef enum {
+ ///< This one is used in pure JSON mode.
+ NO_KEYWORD,
+
+ ///< Used while parsing top level (contains DhcpDdns).
+ CONFIG,
+
+ ///< Used while parsing content of DhcpDdns.
+ DHCPDDNS,
+
+ ///< Used while parsing content of a tsig-key
+ TSIG_KEY,
+
+ ///< Used while parsing a list of tsig-keys
+ TSIG_KEYS,
+
+ ///< Used while parsing content of DhcpDdns/tsig-keys/algorithm
+ ALGORITHM,
+
+ ///< Used while parsing content of DhcpDdns/tsig-keys/digest-bits
+ DIGEST_BITS,
+
+ ///< Used while parsing content of DhcpDdns/tsig-keys/secret
+ SECRET,
+
+ ///< Used while parsing content of DhcpDdns/forward-ddns
+ FORWARD_DDNS,
+
+ ///< Used while parsing content of DhcpDdns/reverse-ddns
+ REVERSE_DDNS,
+
+ ///< Used while parsing content of a ddns-domain
+ DDNS_DOMAIN,
+
+ ///< Used while parsing a list of ddns-domains
+ DDNS_DOMAINS,
+
+ ///< Used while parsing content of a dns-server
+ DNS_SERVER,
+
+ ///< Used while parsing content of list of dns-servers
+ DNS_SERVERS,
+
+ ///< Used while parsing content of a control-socket
+ CONTROL_SOCKET,
+
+ /// Used while parsing DhcpDdns/loggers structures.
+ LOGGERS,
+
+ /// Used while parsing DhcpDdns/loggers/output_options structures.
+ OUTPUT_OPTIONS,
+
+ /// Used while parsing DhcpDdns/ncr-protocol
+ NCR_PROTOCOL,
+
+ /// Used while parsing DhcpDdns/ncr-format
+ NCR_FORMAT,
+
+ /// Used while parsing DhcpDdns/hooks-libraries.
+ HOOKS_LIBRARIES
+
+ } ParserContext;
+
+ /// @brief File name
+ std::string file_;
+
+ /// @brief File name stack
+ std::vector<std::string> files_;
+
+ /// @brief Location of the current token
+ ///
+ /// The lexer will keep updating it. This variable will be useful
+ /// for logging errors.
+ isc::d2::location loc_;
+
+ /// @brief Location stack
+ std::vector<isc::d2::location> locs_;
+
+ /// @brief Lexer state stack
+ std::vector<struct yy_buffer_state*> states_;
+
+ /// @brief sFile (aka FILE)
+ FILE* sfile_;
+
+ /// @brief sFile (aka FILE) stack
+ ///
+ /// This is a stack of files. Typically there's only one file (the
+ /// one being currently parsed), but there may be more if one
+ /// file includes another.
+ std::vector<FILE*> sfiles_;
+
+ /// @brief Current syntactic context
+ ParserContext ctx_;
+
+ /// @brief Enter a new syntactic context
+ ///
+ /// Entering a new syntactic context is useful in several ways.
+ /// First, it allows the parser to avoid conflicts. Second, it
+ /// allows the lexer to return different tokens depending on
+ /// context (e.g. if "name" string is detected, the lexer
+ /// will return STRING token if in JSON mode or NAME if
+ /// in TSIG_KEY mode. Finally, the syntactic context allows the
+ /// error message to be more descriptive if the input string
+ /// does not parse properly.
+ ///
+ /// @param ctx the syntactic context to enter into
+ void enter(const ParserContext& ctx);
+
+ /// @brief Leave a syntactic context
+ ///
+ /// @throw isc::Unexpected if unbalanced
+ void leave();
+
+ /// @brief Get the syntax context name
+ ///
+ /// @return printable name of the context.
+ const std::string contextName();
+
+ private:
+ /// @brief Flag determining scanner debugging.
+ bool trace_scanning_;
+
+ /// @brief Flag determining parser debugging.
+ bool trace_parsing_;
+
+ /// @brief Syntactic context stack
+ std::vector<ParserContext> cstack_;
+
+ /// @brief Common part of parseXXX
+ ///
+ /// @return Element structure representing parsed text.
+ isc::data::ElementPtr parseCommon();
+};
+
+} // end of isc::eval namespace
+} // end of isc namespace
+
+#endif
diff --git a/src/bin/d2/parser_context_decl.h b/src/bin/d2/parser_context_decl.h
new file mode 100644
index 0000000..a927ac1
--- /dev/null
+++ b/src/bin/d2/parser_context_decl.h
@@ -0,0 +1,20 @@
+// 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 D2_PARSER_CONTEXT_DECL_H
+#define D2_PARSER_CONTEXT_DECL_H
+
+/// @file d2/parser_context_decl.h Forward declaration of the ParserContext class
+
+namespace isc {
+namespace d2 {
+
+class D2ParserContext;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif
diff --git a/src/bin/d2/simple_add.cc b/src/bin/d2/simple_add.cc
new file mode 100644
index 0000000..129c5ed
--- /dev/null
+++ b/src/bin/d2/simple_add.cc
@@ -0,0 +1,536 @@
+// Copyright (C) 2020-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2/simple_add.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_log.h>
+
+#include <util/buffer.h>
+#include <dns/rdataclass.h>
+
+#include <functional>
+
+namespace isc {
+namespace d2 {
+
+// SimpleAddTransaction states
+const int SimpleAddTransaction::REPLACING_FWD_ADDRS_ST;
+const int SimpleAddTransaction::REPLACING_REV_PTRS_ST;
+
+// SimpleAddTransaction events
+const int SimpleAddTransaction::FQDN_IN_USE_EVT;
+const int SimpleAddTransaction::FQDN_NOT_IN_USE_EVT;
+
+SimpleAddTransaction::
+SimpleAddTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
+ cfg_mgr) {
+ if (ncr->getChangeType() != isc::dhcp_ddns::CHG_ADD) {
+ isc_throw (SimpleAddTransactionError,
+ "SimpleAddTransaction, request type must be CHG_ADD");
+ }
+}
+
+SimpleAddTransaction::~SimpleAddTransaction(){
+}
+
+void
+SimpleAddTransaction::defineEvents() {
+ // Call superclass impl first.
+ NameChangeTransaction::defineEvents();
+
+ // Define SimpleAddTransaction events.
+ defineEvent(FQDN_IN_USE_EVT, "FQDN_IN_USE_EVT");
+ defineEvent(FQDN_NOT_IN_USE_EVT, "FQDN_NOT_IN_USE_EVT");
+}
+
+void
+SimpleAddTransaction::verifyEvents() {
+ // Call superclass implementation first to verify its events. These are
+ // events common to all transactions, and they must be defined.
+ // SELECT_SERVER_EVT
+ // SERVER_SELECTED_EVT
+ // SERVER_IO_ERROR_EVT
+ // NO_MORE_SERVERS_EVT
+ // IO_COMPLETED_EVT
+ // UPDATE_OK_EVT
+ // UPDATE_FAILED_EVT
+ NameChangeTransaction::verifyEvents();
+
+ // Verify SimpleAddTransaction events by attempting to fetch them.
+ getEvent(FQDN_IN_USE_EVT);
+ getEvent(FQDN_NOT_IN_USE_EVT);
+}
+
+void
+SimpleAddTransaction::defineStates() {
+ // Call superclass impl first.
+ NameChangeTransaction::defineStates();
+
+ // Define SimpleAddTransaction states.
+ defineState(READY_ST, "READY_ST",
+ std::bind(&SimpleAddTransaction::readyHandler, this));
+
+ defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+ std::bind(&SimpleAddTransaction::selectingFwdServerHandler, this));
+
+ defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+ std::bind(&SimpleAddTransaction::selectingRevServerHandler, this));
+
+ defineState(REPLACING_FWD_ADDRS_ST, "REPLACING_FWD_ADDRS_ST",
+ std::bind(&SimpleAddTransaction::replacingFwdAddrsHandler, this));
+
+ defineState(REPLACING_REV_PTRS_ST, "REPLACING_REV_PTRS_ST",
+ std::bind(&SimpleAddTransaction::replacingRevPtrsHandler, this));
+
+ defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+ std::bind(&SimpleAddTransaction::processAddOkHandler, this));
+
+ defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+ std::bind(&SimpleAddTransaction::processAddFailedHandler, this));
+}
+
+void
+SimpleAddTransaction::verifyStates() {
+ // Call superclass implementation first to verify its states. These are
+ // states common to all transactions, and they must be defined.
+ // READY_ST
+ // SELECTING_FWD_SERVER_ST
+ // SELECTING_REV_SERVER_ST
+ // PROCESS_TRANS_OK_ST
+ // PROCESS_TRANS_FAILED_ST
+ NameChangeTransaction::verifyStates();
+
+ // Verify SimpleAddTransaction states by attempting to fetch them.
+ getStateInternal(REPLACING_FWD_ADDRS_ST);
+ getStateInternal(REPLACING_REV_PTRS_ST);
+}
+
+void
+SimpleAddTransaction::readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ if (getForwardDomain()) {
+ // Request includes a forward change, do that first.
+ transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ // Reverse change only, transition accordingly.
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ }
+
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleAddTransaction::selectingFwdServerHandler() {
+ switch(getNextEvent()) {
+ case SELECT_SERVER_EVT:
+ // First time through for this transaction, so initialize server
+ // selection.
+ initServerSelection(getForwardDomain());
+ break;
+ case SERVER_IO_ERROR_EVT:
+ // We failed to communicate with current server. Attempt to select
+ // another one below.
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+
+ // Select the next server from the list of forward servers.
+ if (selectNextServer()) {
+ // We have a server to try.
+ transition(REPLACING_FWD_ADDRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Server list is exhausted, so fail the transaction.
+ transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+ }
+}
+
+void
+SimpleAddTransaction::replacingFwdAddrsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildReplaceFwdAddressRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Forward Add");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if (rcode == dns::Rcode::NOERROR()) {
+ // We were able to add it. Mark it as done.
+ setForwardChangeCompleted(true);
+
+ // If request calls for reverse update then do that next,
+ // otherwise we can process ok.
+ if (getReverseDomain()) {
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ }
+ } else {
+ // Any other value means cease. Really shouldn't happen.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_ADD_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(SimpleAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleAddTransaction::selectingRevServerHandler() {
+ switch(getNextEvent()) {
+ case SELECT_SERVER_EVT:
+ // First time through for this transaction, so initialize server
+ // selection.
+ initServerSelection(getReverseDomain());
+ break;
+ case SERVER_IO_ERROR_EVT:
+ // We failed to communicate with current server. Attempt to select
+ // another one below.
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+
+ // Select the next server from the list of forward servers.
+ if (selectNextServer()) {
+ // We have a server to try.
+ transition(REPLACING_REV_PTRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Server list is exhausted, so fail the transaction.
+ transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+ }
+}
+
+
+void
+SimpleAddTransaction::replacingRevPtrsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildReplaceRevPtrsRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Reverse Replace");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if (rcode == dns::Rcode::NOERROR()) {
+ // We were able to update the reverse mapping. Mark it as done.
+ setReverseChangeCompleted(true);
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ } else {
+ // Per RFC4703 any other value means cease.
+ // If we get not authorized should try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_REV_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REPLACE_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_REV_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_REVERSE_REPLACE_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(SimpleAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleAddTransaction::processAddOkHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_OK_EVT:
+ LOG_INFO(d2_to_dns_logger, DHCP_DDNS_ADD_SUCCEEDED)
+ .arg(getRequestId())
+ .arg(getNcr()->toText());
+ setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ endModel();
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleAddTransaction::processAddFailedHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_FAILED_EVT:
+ case NO_MORE_SERVERS_EVT:
+ setNcrStatus(dhcp_ddns::ST_FAILED);
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_ADD_FAILED)
+ .arg(getRequestId())
+ .arg(transactionOutcomeString());
+ endModel();
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleAddTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleAddTransaction::buildReplaceFwdAddressRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+ // Construct dns::Name from NCR fqdn.
+ dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+
+ // There are no prerequisites.
+
+ // Build the Update Section. First we delete any pre-existing
+ // FQDN/IP and DHCID RRs. Then we add new ones.
+
+ // Create the FQDN/IP 'delete' RR and add it to update section.
+ dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
+ getAddressRRType(), dns::RRTTL(0)));
+
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the DHCID 'delete' RR and add it to the update section.
+ update.reset(new dns::RRset(fqdn, dns::RRClass::ANY(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Now make the new RRs.
+ // Create the TTL based on lease length.
+ dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
+ // Create the FQDN/IP 'add' RR and add it to the to update section.
+ // Based on RFC 2136, section 2.5.1
+ update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
+ getAddressRRType(), lease_ttl));
+
+ addLeaseAddressRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Now create the FQDN/DHCID 'add' RR and add it to update section.
+ // Based on RFC 2136, section 2.5.1
+ update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
+ dns::RRType::DHCID(), lease_ttl));
+
+ // We add the DHCID for auditing purposes and in the event
+ // conflict resolution is later enabled.
+ addDhcidRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+void
+SimpleAddTransaction::buildReplaceRevPtrsRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getReverseDomain());
+
+ // Create the reverse IP address "FQDN".
+ std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
+ dns::Name rev_ip(rev_addr);
+
+ // Create the TTL based on lease length.
+ dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
+ // There are no prerequisites.
+
+ // Create the FQDN/IP PTR 'delete' RR for this IP and add it to
+ // the update section.
+ dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+ dns::RRType::PTR(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the DHCID 'delete' RR and add it to the update section.
+ update.reset(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the FQDN/IP PTR 'add' RR, add the FQDN as the PTR Rdata
+ // then add it to update section.
+ update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
+ dns::RRType::PTR(), lease_ttl));
+ addPtrRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the FQDN/IP PTR 'add' RR, add the DHCID Rdata
+ // then add it to update section.
+ update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
+ dns::RRType::DHCID(), lease_ttl));
+ addDhcidRdata(update);
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/simple_add.h b/src/bin/d2/simple_add.h
new file mode 100644
index 0000000..bf617a5
--- /dev/null
+++ b/src/bin/d2/simple_add.h
@@ -0,0 +1,355 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SIMPLE_ADD_H
+#define SIMPLE_ADD_H
+
+/// @file nc_add.h This file defines the class SimpleAddTransaction.
+
+#include <d2srv/nc_trans.h>
+#include <dns/rdata.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the SimpleAddTransaction encounters a general error.
+class SimpleAddTransactionError : public isc::Exception {
+public:
+ SimpleAddTransactionError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS Add update.
+///
+/// SimpleAddTransaction implements a state machine for adding (or replacing) a
+/// forward and/or reverse DNS mapping. This state machine follows a basic
+/// remove and replace scheme, that does not attempt to avoid conflicts
+/// between updating clients. The logic may be paraphrased as follows:
+///
+/// @code
+///
+/// If the request includes a forward change:
+/// Select a forward server
+/// Send the server a request to delete and then add forward entry
+///
+/// If the request includes a reverse change:
+/// Select a reverse server
+/// Send a server a request to delete and then add reverse entry
+///
+/// @endcode
+///
+/// This class derives from NameChangeTransaction from which it inherits
+/// states, events, and methods common to NameChangeRequest processing.
+class SimpleAddTransaction : public NameChangeTransaction {
+public:
+
+ //@{ Additional states needed for SimpleAdd state model.
+ /// @brief State that attempts to add forward address records.
+ static const int REPLACING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 1;
+
+ /// @brief State that attempts to replace reverse PTR records
+ static const int REPLACING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
+ //@}
+
+ //@{ Additional events needed for SimpleAdd state model.
+ /// @brief Event sent when an add attempt fails with address in use.
+ static const int FQDN_IN_USE_EVT = NCT_DERIVED_EVENT_MIN + 1;
+
+ /// @brief Event sent when replace attempt to fails with address not in use.
+ static const int FQDN_NOT_IN_USE_EVT = NCT_DERIVED_EVENT_MIN + 2;
+ //@}
+
+ /// @brief Constructor
+ ///
+ /// Instantiates an Add transaction that is ready to be started.
+ ///
+ /// @param io_service IO service to be used for IO processing
+ /// @param ncr is the NameChangeRequest to fulfill
+ /// @param forward_domain is the domain to use for forward DNS updates
+ /// @param reverse_domain is the domain to use for reverse DNS updates
+ /// @param cfg_mgr pointer to the configuration manager
+ ///
+ /// @throw SimpleAddTransaction error if given request is not a CHG_ADD,
+ /// NameChangeTransaction error for base class construction errors.
+ SimpleAddTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr);
+
+ /// @brief Destructor
+ virtual ~SimpleAddTransaction();
+
+protected:
+ /// @brief Adds events defined by SimpleAddTransaction to the event set.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then defines the
+ /// events unique to NCR Add transaction processing.
+ ///
+ /// @throw StateModelError if an event definition is invalid or a duplicate.
+ virtual void defineEvents();
+
+ /// @brief Validates the contents of the set of events.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then verifies the
+ /// Add transaction's. This tests that the needed events are in the event
+ /// dictionary.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyEvents();
+
+ /// @brief Adds states defined by SimpleAddTransaction to the state set.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then defines the
+ /// states unique to NCR Add transaction processing.
+ ///
+ /// @throw StateModelError if an state definition is invalid or a duplicate.
+ virtual void defineStates();
+
+ /// @brief Validates the contents of the set of states.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then verifies the
+ /// Add transaction's states. This tests that the needed states are in the
+ /// state dictionary.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyStates();
+
+ /// @brief State handler for READY_ST.
+ ///
+ /// Entered from:
+ /// - INIT_ST with next event of START_EVT
+ ///
+ /// The READY_ST is the state the model transitions into when the inherited
+ /// method, startTransaction() is invoked. This handler, therefore, is the
+ /// entry point into the state model execution.h Its primary task is to
+ /// determine whether to start with a forward DNS change or a reverse DNS
+ /// change.
+ ///
+ /// Transitions to:
+ /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
+ /// includes a forward change.
+ ///
+ /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
+ /// includes only a reverse change.
+ ///
+ /// @throw SimpleAddTransactionError if upon entry next event is not
+ /// START_EVT.
+ void readyHandler();
+
+ /// @brief State handler for SELECTING_FWD_SERVER_ST.
+ ///
+ /// Entered from:
+ /// - READY_ST with next event of SELECT_SERVER_EVT
+ /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
+ ///
+ /// Selects the server to be used from the forward domain for the forward
+ /// DNS update. If next event is SELECT_SERVER_EVT the handler initializes
+ /// the forward domain's server selection mechanism and then attempts to
+ /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+ /// handler simply attempts to select the next server.
+ ///
+ /// Transitions to:
+ /// - REPLACING_FWD_ADDRS_ST with next event of SERVER_SELECTED upon
+ /// successful server selection
+ ///
+ /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+ /// failure to select a server
+ ///
+ /// @throw SimpleAddTransactionError if upon entry next event is not
+ /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+ void selectingFwdServerHandler();
+
+ /// @brief State handler for SELECTING_REV_SERVER_ST.
+ ///
+ /// Entered from:
+ /// - READY_ST with next event of SELECT_SERVER_EVT
+ /// - REPLACING_FWD_ADDRS_ST with next event of SELECT_SERVER_EVT
+ /// - REPLACING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
+ ///
+ /// Selects the server to be used from the reverse domain for the reverse
+ /// DNS update. If next event is SELECT_SERVER_EVT the handler initializes
+ /// the reverse domain's server selection mechanism and then attempts to
+ /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+ /// handler simply attempts to select the next server.
+ ///
+ /// Transitions to:
+ /// - REPLACING_REV_PTRS_ST with next event of SERVER_SELECTED upon
+ /// successful server selection
+ ///
+ /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+ /// failure to select a server
+ ///
+ /// @throw SimpleAddTransactionError if upon entry next event is not
+ /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+ void selectingRevServerHandler();
+
+ /// @brief State handler for REPLACING_FWD_ADDRS_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_FWD_SERVER with next event of SERVER_SELECTED_EVT
+ ///
+ /// Attempts to replace a forward DNS entry for a given FQDN. If this is
+ /// first invocation of the handler after transitioning into this state,
+ /// any previous update request context is deleted. If next event
+ /// is SERVER_SELECTED_EVT, the handler builds the forward add request,
+ /// schedules an asynchronous send via sendUpdate(), and returns. Note
+ /// that sendUpdate will post NOP_EVT as next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - SELECTING_REV_SERVER_ST with next event of SELECT_SERVER_EVT upon
+ /// successful addition and the request includes a reverse DNS update.
+ ///
+ /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+ /// addition and no reverse DNS update is required.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with next event of UPDATE_FAILED_EVT if the
+ /// DNS server rejected the update for any other reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this states with next event of SERVER_SELECTED_EVT_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has been exhausted.
+ ///
+ /// @throw SimpleAddTransactionError if upon entry next event is not
+ /// SERVER_SELECTED_EVT or IO_COMPLETE_EVT.
+ void replacingFwdAddrsHandler();
+
+ /// @brief State handler for REPLACING_REV_PTRS_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
+ ///
+ /// Attempts to delete and then add a reverse DNS entry for a given FQDN.
+ /// If this is first invocation of the handler after transitioning into
+ /// this state, any previous update request context is deleted. If next
+ /// event is SERVER_SELECTED_EVT, the handler builds the reverse replacement
+ /// add request, schedules an asynchronous send via sendUpdate(), and
+ /// returns. Note that sendUpdate will post NOP_EVT as next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
+ /// successful replacement.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
+ /// DNS server rejected the update for any reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has been exhausted.
+ ///
+ /// @throw SimpleAddTransactionError if upon entry next event is not:
+ /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
+ void replacingRevPtrsHandler();
+
+ /// @brief State handler for PROCESS_TRANS_OK_ST.
+ ///
+ /// Entered from:
+ /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_OK_EVT
+ /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
+ ///
+ /// Sets the transaction action status to indicate success and ends
+ /// model execution.
+ ///
+ /// Transitions to:
+ /// - END_ST with a next event of END_EVT.
+ ///
+ /// @throw SimpleAddTransactionError if upon entry next event is not:
+ /// UPDATE_OK_EVT
+ void processAddOkHandler();
+
+ /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
+ /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+ /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
+ /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
+ ///
+ /// Sets the transaction status to indicate failure and ends
+ /// model execution.
+ ///
+ /// Transitions to:
+ /// - END_ST with a next event of FAIL_EVT.
+ ///
+ /// @throw SimpleAddTransactionError if upon entry next event is not:
+ /// UPDATE_FAILED_EVT
+ void processAddFailedHandler();
+
+ /// @brief Builds a DNS request to add/replace a forward DNS entry for an
+ /// FQDN
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for adding a
+ /// forward DNS mapping. Once constructed, the request is stored as
+ /// the transaction's DNS update request.
+ ///
+ /// Prerequisite RRsets:
+ /// - There are no prerequisites.
+ ///
+ /// Updates RRsets:
+ /// -# A delete of any existing PTR RRs for the lease address
+ /// -# A delete of any existing DHCID RRs for the lease address
+ /// -# An FQDN/IP RR addition (type A for IPv4, AAAA for IPv6)
+ /// -# An FQDN/DHCID RR addition (type DHCID)
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildReplaceFwdAddressRequest();
+
+ /// @brief Builds a DNS request to replace a reverse DNS entry for an FQDN
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for replacing a
+ /// reverse DNS mapping. Once constructed, the request is stored as
+ /// the transaction's DNS update request.
+ ///
+ /// Prerequisite RRsets:
+ /// - There are no prerequisites.
+ ///
+ /// Updates RRsets:
+ /// -# A delete of any existing PTR RRs for the lease address
+ /// -# A delete of any existing DHCID RRs for the lease address
+ /// -# A PTR RR addition for the lease address and FQDN
+ /// -# A DHCID RR addition for the lease address and lease client DHCID
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildReplaceRevPtrsRequest();
+};
+
+/// @brief Defines a pointer to a SimpleAddTransaction.
+typedef boost::shared_ptr<SimpleAddTransaction> SimpleAddTransactionPtr;
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/bin/d2/simple_remove.cc b/src/bin/d2/simple_remove.cc
new file mode 100644
index 0000000..48a446e
--- /dev/null
+++ b/src/bin/d2/simple_remove.cc
@@ -0,0 +1,519 @@
+// Copyright (C) 2013-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 <d2/simple_remove.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_log.h>
+
+#include <functional>
+
+namespace isc {
+namespace d2 {
+
+
+// SimpleRemoveTransaction states
+const int SimpleRemoveTransaction::REMOVING_FWD_RRS_ST;
+const int SimpleRemoveTransaction::REMOVING_REV_PTRS_ST;
+
+// SimpleRemoveTransaction events
+// Currently SimpleRemoveTransaction does not define any events.
+
+SimpleRemoveTransaction::
+SimpleRemoveTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain,
+ cfg_mgr) {
+ if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) {
+ isc_throw (SimpleRemoveTransactionError,
+ "SimpleRemoveTransaction, request type must be CHG_REMOVE");
+ }
+}
+
+SimpleRemoveTransaction::~SimpleRemoveTransaction(){
+}
+
+void
+SimpleRemoveTransaction::defineEvents() {
+ // Call superclass impl first.
+ NameChangeTransaction::defineEvents();
+
+ // Define SimpleRemoveTransaction events.
+ // Currently SimpleRemoveTransaction does not define any events.
+ // defineEvent(TBD_EVENT, "TBD_EVT");
+}
+
+void
+SimpleRemoveTransaction::verifyEvents() {
+ // Call superclass implementation first to verify its events. These are
+ // events common to all transactions, and they must be defined.
+ // SELECT_SERVER_EVT
+ // SERVER_SELECTED_EVT
+ // SERVER_IO_ERROR_EVT
+ // NO_MORE_SERVERS_EVT
+ // IO_COMPLETED_EVT
+ // UPDATE_OK_EVT
+ // UPDATE_FAILED_EVT
+ NameChangeTransaction::verifyEvents();
+
+ // Verify SimpleRemoveTransaction events by attempting to fetch them.
+ // Currently SimpleRemoveTransaction does not define any events.
+ // getEvent(TBD_EVENT);
+}
+
+void
+SimpleRemoveTransaction::defineStates() {
+ // Call superclass impl first.
+ NameChangeTransaction::defineStates();
+
+ // Define SimpleRemoveTransaction states.
+ defineState(READY_ST, "READY_ST",
+ std::bind(&SimpleRemoveTransaction::readyHandler, this));
+
+ defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+ std::bind(&SimpleRemoveTransaction::selectingFwdServerHandler,
+ this));
+
+ defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+ std::bind(&SimpleRemoveTransaction::selectingRevServerHandler,
+ this));
+
+ defineState(REMOVING_FWD_RRS_ST, "REMOVING_FWD_RRS_ST",
+ std::bind(&SimpleRemoveTransaction::removingFwdRRsHandler,
+ this));
+
+ defineState(REMOVING_REV_PTRS_ST, "REMOVING_REV_PTRS_ST",
+ std::bind(&SimpleRemoveTransaction::removingRevPtrsHandler,
+ this));
+
+ defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+ std::bind(&SimpleRemoveTransaction::processRemoveOkHandler,
+ this));
+
+ defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+ std::bind(&SimpleRemoveTransaction::processRemoveFailedHandler,
+ this));
+}
+
+void
+SimpleRemoveTransaction::verifyStates() {
+ // Call superclass implementation first to verify its states. These are
+ // states common to all transactions, and they must be defined.
+ // READY_ST
+ // SELECTING_FWD_SERVER_ST
+ // SELECTING_REV_SERVER_ST
+ // PROCESS_TRANS_OK_ST
+ // PROCESS_TRANS_FAILED_ST
+ NameChangeTransaction::verifyStates();
+
+ // Verify SimpleRemoveTransaction states by attempting to fetch them.
+ getStateInternal(REMOVING_FWD_RRS_ST);
+ getStateInternal(REMOVING_REV_PTRS_ST);
+}
+
+void
+SimpleRemoveTransaction::readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ if (getForwardDomain()) {
+ // Request includes a forward change, do that first.
+ transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ // Reverse change only, transition accordingly.
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ }
+
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleRemoveTransaction::selectingFwdServerHandler() {
+ switch(getNextEvent()) {
+ case SELECT_SERVER_EVT:
+ // First time through for this transaction, so initialize server
+ // selection.
+ initServerSelection(getForwardDomain());
+ break;
+ case SERVER_IO_ERROR_EVT:
+ // We failed to communicate with current server. Attempt to select
+ // another one below.
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+
+ // Select the next server from the list of forward servers.
+ if (selectNextServer()) {
+ // We have a server to try.
+ transition(REMOVING_FWD_RRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Server list is exhausted, so fail the transaction.
+ transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+ }
+}
+
+void
+SimpleRemoveTransaction::removingFwdRRsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case UPDATE_OK_EVT:
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildRemoveFwdRRsRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Forward RR Remove");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ // The RCODE will be based on a value-dependent RRset search,
+ // see RFC 2136 section 3.2.3/3.2.4.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if ((rcode == dns::Rcode::NOERROR()) ||
+ (rcode == dns::Rcode::NXRRSET())) {
+ // We were able to remove them or they were not there (
+ // Rcode of NXRRSET means there are no matching RRsets).
+ // In either case, we consider it success and mark it as done.
+ setForwardChangeCompleted(true);
+
+ // If request calls for reverse update then do that next,
+ // otherwise we can process ok.
+ if (getReverseDomain()) {
+ transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+ } else {
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ }
+ } else {
+ // Any other value means cease.
+ // If we get not authorized should try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ retryTransition(SELECTING_FWD_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(SimpleRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleRemoveTransaction::selectingRevServerHandler() {
+ switch(getNextEvent()) {
+ case SELECT_SERVER_EVT:
+ // First time through for this transaction, so initialize server
+ // selection.
+ initServerSelection(getReverseDomain());
+ break;
+ case SERVER_IO_ERROR_EVT:
+ // We failed to communicate with current server. Attempt to select
+ // another one below.
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+
+ // Select the next server from the list of forward servers.
+ if (selectNextServer()) {
+ // We have a server to try.
+ transition(REMOVING_REV_PTRS_ST, SERVER_SELECTED_EVT);
+ } else {
+ // Server list is exhausted, so fail the transaction.
+ transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+ }
+}
+
+
+void
+SimpleRemoveTransaction::removingRevPtrsHandler() {
+ if (doOnEntry()) {
+ // Clear the update attempts count on initial transition.
+ clearUpdateAttempts();
+ }
+
+ switch(getNextEvent()) {
+ case SERVER_SELECTED_EVT:
+ try {
+ clearDnsUpdateRequest();
+ buildRemoveRevPtrsRequest();
+ } catch (const std::exception& ex) {
+ // While unlikely, the build might fail if we have invalid
+ // data. Should that be the case, we need to fail the
+ // transaction.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE)
+ .arg(getRequestId())
+ .arg(getNcr()->toText())
+ .arg(ex.what());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ }
+
+ // Call sendUpdate() to initiate the async send. Note it also sets
+ // next event to NOP_EVT.
+ sendUpdate("Reverse Remove");
+ break;
+
+ case IO_COMPLETED_EVT: {
+ switch (getDnsUpdateStatus()) {
+ case DNSClient::SUCCESS: {
+ // We successfully received a response packet from the server.
+ // The RCODE will be based on a value-dependent RRset search,
+ // see RFC 2136 section 3.2.3/3.2.4.
+ const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+ if ((rcode == dns::Rcode::NOERROR()) ||
+ (rcode == dns::Rcode::NXRRSET())) {
+ // We were able to remove the reverse mapping or they were
+ // not there (Rcode of NXRRSET means there are no matching
+ // RRsets). In either case, mark it as done.
+ setReverseChangeCompleted(true);
+ transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+ } else {
+ // Per RFC4703 any other value means cease.
+ // If we get not authorized should try the next server in
+ // the list? @todo This needs some discussion perhaps.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_REJECTED)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn())
+ .arg(rcode.getCode());
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ }
+
+ break;
+ }
+
+ case DNSClient::TIMEOUT:
+ case DNSClient::OTHER:
+ // We couldn't send to the current server, log it and set up
+ // to select the next server for a retry.
+ // @note For now we treat OTHER as an IO error like TIMEOUT. It
+ // is not entirely clear if this is accurate.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_IO_ERROR)
+ .arg(getRequestId())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_REV_SERVER_ST);
+ break;
+
+ case DNSClient::INVALID_RESPONSE:
+ // A response was received but was corrupt. Retry it like an IO
+ // error.
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT)
+ .arg(getRequestId())
+ .arg(getCurrentServer()->toText())
+ .arg(getNcr()->getFqdn());
+
+ // If we are out of retries on this server, we go back and start
+ // all over on a new server.
+ retryTransition(SELECTING_REV_SERVER_ST);
+ break;
+
+ default:
+ // Any other value and we will fail this transaction, something
+ // bigger is wrong.
+ LOG_ERROR(d2_to_dns_logger,
+ DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS)
+ .arg(getRequestId())
+ .arg(getDnsUpdateStatus())
+ .arg(getNcr()->getFqdn())
+ .arg(getCurrentServer()->toText());
+
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ break;
+ } // end switch on dns_status
+
+ break;
+ } // end case IO_COMPLETE_EVT
+
+ default:
+ // Event is invalid.
+ isc_throw(SimpleRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+
+void
+SimpleRemoveTransaction::processRemoveOkHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_OK_EVT:
+ LOG_INFO(d2_to_dns_logger, DHCP_DDNS_REMOVE_SUCCEEDED)
+ .arg(getRequestId())
+ .arg(getNcr()->toText());
+ setNcrStatus(dhcp_ddns::ST_COMPLETED);
+ endModel();
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleRemoveTransaction::processRemoveFailedHandler() {
+ switch(getNextEvent()) {
+ case UPDATE_FAILED_EVT:
+ case NO_MORE_SERVERS_EVT:
+ case SERVER_IO_ERROR_EVT:
+ setNcrStatus(dhcp_ddns::ST_FAILED);
+ LOG_ERROR(d2_to_dns_logger, DHCP_DDNS_REMOVE_FAILED)
+ .arg(getRequestId())
+ .arg(transactionOutcomeString());
+ endModel();
+ break;
+ default:
+ // Event is invalid.
+ isc_throw(SimpleRemoveTransactionError,
+ "Wrong event for context: " << getContextStr());
+ }
+}
+
+void
+SimpleRemoveTransaction::buildRemoveFwdRRsRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+ // There are no pre-requisites.
+
+ // Build the Update Section
+ // Construct dns::Name from NCR fqdn.
+ dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+
+ // Build the Update Section.
+
+ // Create the FQDN/IP 'delete' RR and add it to update section.
+ dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
+ getAddressRRType(), dns::RRTTL(0)));
+
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the DHCID 'delete' RR and add it to the update section.
+ update.reset(new dns::RRset(fqdn, dns::RRClass::ANY(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+void
+SimpleRemoveTransaction::buildRemoveRevPtrsRequest() {
+ // Construct an empty request.
+ D2UpdateMessagePtr request = prepNewRequest(getReverseDomain());
+
+ // Create the reverse IP address "FQDN".
+ std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
+ dns::Name rev_ip(rev_addr);
+
+ // There are no pre-requisites.
+
+ // Build the Update section.
+
+ // Create the FQDN/IP PTR 'delete' RR for this IP and add it to
+ // the update section.
+ dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+ dns::RRType::PTR(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Create the DHCID 'delete' RR and add it to the update section.
+ update.reset(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+ dns::RRType::DHCID(), dns::RRTTL(0)));
+ request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+ // Set the transaction's update request to the new request.
+ setDnsUpdateRequest(request);
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/simple_remove.h b/src/bin/d2/simple_remove.h
new file mode 100644
index 0000000..957cb13
--- /dev/null
+++ b/src/bin/d2/simple_remove.h
@@ -0,0 +1,351 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef SIMPLE_REMOVE_H
+#define SIMPLE_REMOVE_H
+
+/// @file nc_remove.h This file defines the class SimpleRemoveTransaction.
+
+#include <d2srv/nc_trans.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the SimpleRemoveTransaction encounters a general error.
+class SimpleRemoveTransactionError : public isc::Exception {
+public:
+ SimpleRemoveTransactionError(const char* file, size_t line,
+ const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS Remove update.
+///
+/// SimpleRemoveTransaction implements a state machine for removing a forward
+/// and/or reverse DNS mappings. This state machine follows a basic
+/// removal scheme, that does not attempt to avoid conflicts between updating
+/// clients. The logic may be paraphrased as follows:
+///
+/// @code
+///
+/// If the request includes a forward change:
+/// Select a forward server
+/// Send the server a request to remove client's specific forward address
+/// RR and DHCID RR.
+/// If it does not succeed
+/// abandon the update
+///
+/// If the request includes a reverse change:
+/// Select a reverse server
+/// Send a server a request to delete the matching PTR and DHCID RRs.
+///
+/// @endcode
+///
+/// This class derives from NameChangeTransaction from which it inherits
+/// states, events, and methods common to NameChangeRequest processing.
+class SimpleRemoveTransaction : public NameChangeTransaction {
+public:
+
+ //@{ Additional states needed for SimpleRemove state model.
+ /// @brief State that attempts to remove FQDN/IP and DHCID RRs for an FQDN
+ static const int REMOVING_FWD_RRS_ST = NCT_DERIVED_STATE_MIN + 2;
+
+ /// @brief State that attempts to remove reverse PTR records
+ static const int REMOVING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
+ //@}
+
+ //@{ Additional events needed for SimpleRemove state model.
+ /// @brief Event sent when replace attempt to fails with address not in use.
+ /// @todo Currently none have been identified.
+ //@}
+
+ /// @brief Constructor
+ ///
+ /// Instantiates an Remove transaction that is ready to be started.
+ ///
+ /// @param io_service IO service to be used for IO processing
+ /// @param ncr is the NameChangeRequest to fulfill
+ /// @param forward_domain is the domain to use for forward DNS updates
+ /// @param reverse_domain is the domain to use for reverse DNS updates
+ /// @param cfg_mgr pointer to the configuration manager
+ ///
+ /// @throw SimpleRemoveTransaction error if given request is not a CHG_REMOVE,
+ /// NameChangeTransaction error for base class construction errors.
+ SimpleRemoveTransaction(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr);
+
+ /// @brief Destructor
+ virtual ~SimpleRemoveTransaction();
+
+protected:
+ /// @brief Adds events defined by SimpleRemoveTransaction to the event set.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then defines the
+ /// events unique to NCR Remove transaction processing.
+ ///
+ /// @throw StateModelError if an event definition is invalid or a duplicate.
+ virtual void defineEvents();
+
+ /// @brief Validates the contents of the set of events.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then verifies the
+ /// Remove transaction's events. This tests that the needed events are in
+ /// the event dictionary.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyEvents();
+
+ /// @brief Adds states defined by SimpleRemoveTransaction to the state set.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then defines the
+ /// states unique to NCR Remove transaction processing.
+ ///
+ /// @throw StateModelError if an state definition is invalid or a duplicate.
+ virtual void defineStates();
+
+ /// @brief Validates the contents of the set of states.
+ ///
+ /// Invokes NameChangeTransaction's implementation and then verifies the
+ /// Remove transaction's states. This tests that the needed states are in
+ /// the state dictionary.
+ ///
+ /// @throw StateModelError if an event value is undefined.
+ virtual void verifyStates();
+
+ /// @brief State handler for READY_ST.
+ ///
+ /// Entered from:
+ /// - INIT_ST with next event of START_EVT
+ ///
+ /// The READY_ST is the state the model transitions into when the inherited
+ /// method, startTransaction() is invoked. This handler, therefore, is the
+ /// entry point into the state model execution. Its primary task is to
+ /// determine whether to start with a forward DNS change or a reverse DNS
+ /// change.
+ ///
+ /// Transitions to:
+ /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
+ /// includes a forward change.
+ ///
+ /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
+ /// includes only a reverse change.
+ ///
+ /// @throw SimpleRemoveTransactionError if upon entry next event is not
+ /// START_EVT.
+ void readyHandler();
+
+ /// @brief State handler for SELECTING_FWD_SERVER_ST.
+ ///
+ /// Entered from:
+ /// - READY_ST with next event of SELECT_SERVER_EVT
+ /// - REMOVING_FWD_RRS_ST with next event of SERVER_IO_ERROR_EVT
+ ///
+ /// Selects the server to be used from the forward domain for the forward
+ /// DNS update. If next event is SELECT_SERVER_EVT the handler initializes
+ /// the forward domain's server selection mechanism and then attempts to
+ /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+ /// handler simply attempts to select the next server.
+ ///
+ /// Transitions to:
+ /// - REMOVING_FWD_RRS_ST with next event of SERVER_SELECTED upon
+ /// successful server selection
+ ///
+ /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+ /// failure to select a server
+ ///
+ /// @throw SimpleRemoveTransactionError if upon entry next event is not
+ /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+ void selectingFwdServerHandler();
+
+ /// @brief State handler for SELECTING_REV_SERVER_ST.
+ ///
+ /// Entered from:
+ /// - READY_ST with next event of SELECT_SERVER_EVT
+ /// - REMOVING_FWD_RRS_ST with next event of SELECT_SERVER_EVT
+ /// - REMOVING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
+ ///
+ /// Selects the server to be used from the reverse domain for the reverse
+ /// DNS update. If next event is SELECT_SERVER_EVT the handler initializes
+ /// the reverse domain's server selection mechanism and then attempts to
+ /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+ /// handler simply attempts to select the next server.
+ ///
+ /// Transitions to:
+ /// - REMOVING_REV_PTRS_ST with next event of SERVER_SELECTED upon
+ /// successful server selection
+ ///
+ /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+ /// failure to select a server
+ ///
+ /// @throw SimpleRemoveTransactionError if upon entry next event is not
+ /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+ void selectingRevServerHandler();
+
+ /// @brief State handler for REMOVING_FWD_RRS_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_FWD_SERVER_ST with next event SEVER_SELECTED_EVT
+ ///
+ /// Attempts to delete any remaining RRs associated with the given FQDN
+ /// such as the DHCID RR. If this is first invocation of the handler after
+ /// transitioning into this state, any previous update request context is
+ /// deleted and the handler builds the forward remove request. It then
+ /// schedules an asynchronous send via sendUpdate(),
+ /// and returns. Note that sendUpdate will post NOP_EVT as the next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - SELECTING_REV_SERVER_ST with a next event of SELECT_SERVER_EVT upon
+ /// successful completion and the request includes a reverse DNS update.
+ ///
+ /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+ /// completion and the request does not include a reverse DNS update.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT if the
+ /// DNS server rejected the update for any other reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of SERVER_IO_ERROR_EVT if
+ /// there we have reached maximum number of retries without success on the
+ /// current server.
+ ///
+ /// @throw SimpleRemoveTransactionError if upon entry next event is not:
+ /// UPDATE_OK_EVT or IO_COMPLETE_EVT
+ void removingFwdRRsHandler();
+
+ /// @brief State handler for REMOVING_REV_PTRS_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
+ ///
+ /// Attempts to delete a reverse DNS entry for a given FQDN. If this is
+ /// first invocation of the handler after transitioning into this state,
+ /// any previous update request context is deleted. If next event is
+ /// SERVER_SELECTED_EVT, the handler builds the reverse remove request,
+ /// schedules an asynchronous send via sendUpdate(), and then returns.
+ /// Note that sendUpdate will post NOP_EVT as next event.
+ ///
+ /// Posting the NOP_EVT will cause runModel() to suspend execution of
+ /// the state model thus affecting a "wait" for the update IO to complete.
+ /// Update completion occurs via the DNSClient callback operator() method
+ /// inherited from NameChangeTransaction. When invoked this callback will
+ /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+ /// resumes execution of the state model.
+ ///
+ /// When the handler is invoked with a next event of IO_COMPLETED_EVT,
+ /// the DNS update status is checked and acted upon accordingly:
+ ///
+ /// Transitions to:
+ /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
+ /// successful completion.
+ ///
+ /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
+ /// DNS server rejected the update for any reason or the IO completed
+ /// with an unrecognized status.
+ ///
+ /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has not been exhausted.
+ ///
+ /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+ /// there was an IO error communicating with the server and the number of
+ /// per server retries has been exhausted.
+ ///
+ /// @throw SimpleRemoveTransactionError if upon entry next event is not:
+ /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
+ void removingRevPtrsHandler();
+
+ /// @brief State handler for PROCESS_TRANS_OK_ST.
+ ///
+ /// Entered from:
+ /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_OK_EVT
+ /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
+ ///
+ /// Sets the transaction action status to indicate success and ends
+ /// model execution.
+ ///
+ /// Transitions to:
+ /// - END_ST with a next event of END_EVT.
+ ///
+ /// @throw SimpleRemoveTransactionError if upon entry next event is not:
+ /// UPDATE_OK_EVT
+ void processRemoveOkHandler();
+
+ /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+ ///
+ /// Entered from:
+ /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
+ /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_FAILED_EVT
+ /// - REMOVING_FWD_RRS_ST with a next event of SERVER_IO_ERROR_EVT
+ /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
+ /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
+ ///
+ /// Sets the transaction status to indicate failure and ends
+ /// model execution.
+ ///
+ /// Transitions to:
+ /// - END_ST with a next event of FAIL_EVT.
+ ///
+ /// @throw SimpleRemoveTransactionError if upon entry next event is not:
+ /// UPDATE_FAILED_EVT
+ void processRemoveFailedHandler();
+
+ /// @brief Builds a DNS request to remove all forward DNS RRs for a FQDN.
+ ///
+ /// Constructs a DNS update request, based upon the NCR, to remove the
+ /// forward DNS (A or AAAA) RR and DHCID RR. Once constructed, the request
+ /// is stored as the transaction's DNS update request.
+ ///
+ /// Prerequisite RRsets:
+ /// - None
+ ///
+ /// Updates RRsets:
+ /// -# A delete of FQDN/IP RR for the FQDN
+ /// -# A delete of DHCID RR for the FQDN
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildRemoveFwdRRsRequest();
+
+ /// @brief Builds a DNS request to remove a reverse DNS entry for a FQDN
+ ///
+ /// Constructs a DNS update request, based upon the NCR, for removing a
+ /// reverse DNS mapping. Once constructed, the request is stored as
+ /// the transaction's DNS update request.
+ ///
+ /// Prerequisite RRsets:
+ /// - None
+ ///
+ /// Updates RRsets:
+ /// -# A delete of PTR RR for the IP
+ /// -# A delete of DHCID RR for the IP
+ ///
+ /// @throw This method does not throw but underlying methods may.
+ void buildRemoveRevPtrsRequest();
+};
+
+/// @brief Defines a pointer to a SimpleRemoveTransaction.
+typedef boost::shared_ptr<SimpleRemoveTransaction> SimpleRemoveTransactionPtr;
+
+
+} // namespace isc::d2
+} // namespace isc
+#endif
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
new file mode 100644
index 0000000..0da992b
--- /dev/null
+++ b/src/bin/d2/tests/Makefile.am
@@ -0,0 +1,133 @@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST =
+EXTRA_DIST += testdata/d2_cfg_tests.json
+EXTRA_DIST += testdata/get_config.json
+
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+# Shell tests
+SHTESTS = d2_process_tests.sh
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+TESTS = $(SHTESTS)
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS)
+DISTCLEANFILES += d2_process_tests.sh
+DISTCLEANFILES += test_data_files_config.h
+DISTCLEANFILES += test_callout_libraries.h
+DISTCLEANFILES += test_configured_libraries.h
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+if HAVE_GTEST
+
+# C++ tests
+PROGRAM_TESTS = d2_unittests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/d2/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/ddns\"
+AM_CPPFLAGS += -DSYNTAX_FILE=\"$(abs_srcdir)/../d2_parser.yy\"
+
+AM_CXXFLAGS = $(KEA_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+d2_unittests_SOURCES = d2_unittests.cc
+d2_unittests_SOURCES += d2_process_unittests.cc
+d2_unittests_SOURCES += d2_cfg_mgr_unittests.cc
+d2_unittests_SOURCES += d2_queue_mgr_unittests.cc
+d2_unittests_SOURCES += d2_update_mgr_unittests.cc
+d2_unittests_SOURCES += nc_add_unittests.cc
+d2_unittests_SOURCES += nc_remove_unittests.cc
+d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d2_simple_parser_unittest.cc
+d2_unittests_SOURCES += parser_unittest.cc parser_unittest.h
+d2_unittests_SOURCES += get_config_unittest.cc
+d2_unittests_SOURCES += d2_command_unittest.cc
+d2_unittests_SOURCES += simple_add_unittests.cc
+d2_unittests_SOURCES += simple_remove_unittests.cc
+
+d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
+if HAVE_MYSQL
+d2_unittests_LDFLAGS += $(MYSQL_LIBS)
+endif
+if HAVE_PGSQL
+d2_unittests_LDFLAGS += $(PGSQL_LIBS)
+endif
+d2_unittests_LDFLAGS += $(GTEST_LDFLAGS)
+
+d2_unittests_LDADD = $(top_builddir)/src/bin/d2/libd2.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/d2srv/libkea-d2srv.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/process/testutils/libprocesstest.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/http/libkea-http.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/database/libkea-database.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+d2_unittests_LDADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+d2_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD)
+
+# The basic callout library - contains standard callouts
+libcallout_la_SOURCES = callout_library.cc
+libcallout_la_CXXFLAGS = $(AM_CXXFLAGS)
+libcallout_la_CPPFLAGS = $(AM_CPPFLAGS)
+libcallout_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libcallout_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libcallout_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libcallout_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The d2_srv_configured callout library
+libconfigured_la_SOURCES = configured_library.cc
+libconfigured_la_CXXFLAGS = $(AM_CXXFLAGS)
+libconfigured_la_CPPFLAGS = $(AM_CPPFLAGS)
+libconfigured_la_LIBADD = $(top_builddir)/src/lib/hooks/libkea-hooks.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
+libconfigured_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+libconfigured_la_LIBADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS)
+libconfigured_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+noinst_LTLIBRARIES = libcallout.la libconfigured.la
+
+nodist_d2_unittests_SOURCES =
+nodist_d2_unittests_SOURCES += test_data_files_config.h
+nodist_d2_unittests_SOURCES += test_callout_libraries.h
+nodist_d2_unittests_SOURCES += test_configured_libraries.h
+
+# Run C++ tests on "make check".
+TESTS += $(PROGRAM_TESTS)
+
+# Don't install C++ tests.
+noinst_PROGRAMS = $(PROGRAM_TESTS)
+
+endif
diff --git a/src/bin/d2/tests/Makefile.in b/src/bin/d2/tests/Makefile.in
new file mode 100644
index 0000000..74bc717
--- /dev/null
+++ b/src/bin/d2/tests/Makefile.in
@@ -0,0 +1,1427 @@
+# 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 = $(SHTESTS) $(am__EXEEXT_2)
+@HAVE_GTEST_TRUE@@HAVE_MYSQL_TRUE@am__append_1 = $(MYSQL_LIBS)
+@HAVE_GTEST_TRUE@@HAVE_PGSQL_TRUE@am__append_2 = $(PGSQL_LIBS)
+
+# Run C++ tests on "make check".
+@HAVE_GTEST_TRUE@am__append_3 = $(PROGRAM_TESTS)
+@HAVE_GTEST_TRUE@noinst_PROGRAMS = $(am__EXEEXT_1)
+subdir = src/bin/d2/tests
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \
+ $(top_srcdir)/m4macros/ax_cpp11.m4 \
+ $(top_srcdir)/m4macros/ax_crypto.m4 \
+ $(top_srcdir)/m4macros/ax_find_library.m4 \
+ $(top_srcdir)/m4macros/ax_gssapi.m4 \
+ $(top_srcdir)/m4macros/ax_gtest.m4 \
+ $(top_srcdir)/m4macros/ax_isc_rpath.m4 \
+ $(top_srcdir)/m4macros/ax_sysrepo.m4 \
+ $(top_srcdir)/m4macros/libtool.m4 \
+ $(top_srcdir)/m4macros/ltoptions.m4 \
+ $(top_srcdir)/m4macros/ltsugar.m4 \
+ $(top_srcdir)/m4macros/ltversion.m4 \
+ $(top_srcdir)/m4macros/lt~obsolete.m4 \
+ $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES = d2_process_tests.sh test_callout_libraries.h \
+ test_configured_libraries.h test_data_files_config.h
+CONFIG_CLEAN_VPATH_FILES =
+@HAVE_GTEST_TRUE@am__EXEEXT_1 = d2_unittests$(EXEEXT)
+PROGRAMS = $(noinst_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+@HAVE_GTEST_TRUE@libcallout_la_DEPENDENCIES = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+am__libcallout_la_SOURCES_DIST = callout_library.cc
+@HAVE_GTEST_TRUE@am_libcallout_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libcallout_la-callout_library.lo
+libcallout_la_OBJECTS = $(am_libcallout_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 =
+libcallout_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libcallout_la_CXXFLAGS) $(CXXFLAGS) $(libcallout_la_LDFLAGS) \
+ $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libcallout_la_rpath =
+am__DEPENDENCIES_1 =
+@HAVE_GTEST_TRUE@libconfigured_la_DEPENDENCIES = $(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/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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)
+am__libconfigured_la_SOURCES_DIST = configured_library.cc
+@HAVE_GTEST_TRUE@am_libconfigured_la_OBJECTS = \
+@HAVE_GTEST_TRUE@ libconfigured_la-configured_library.lo
+libconfigured_la_OBJECTS = $(am_libconfigured_la_OBJECTS)
+libconfigured_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \
+ $(libconfigured_la_CXXFLAGS) $(CXXFLAGS) \
+ $(libconfigured_la_LDFLAGS) $(LDFLAGS) -o $@
+@HAVE_GTEST_TRUE@am_libconfigured_la_rpath =
+am__d2_unittests_SOURCES_DIST = d2_unittests.cc \
+ d2_process_unittests.cc d2_cfg_mgr_unittests.cc \
+ d2_queue_mgr_unittests.cc d2_update_mgr_unittests.cc \
+ nc_add_unittests.cc nc_remove_unittests.cc \
+ d2_controller_unittests.cc d2_simple_parser_unittest.cc \
+ parser_unittest.cc parser_unittest.h get_config_unittest.cc \
+ d2_command_unittest.cc simple_add_unittests.cc \
+ simple_remove_unittests.cc
+@HAVE_GTEST_TRUE@am_d2_unittests_OBJECTS = \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_process_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_cfg_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_queue_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_update_mgr_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-nc_add_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-nc_remove_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_controller_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_simple_parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-parser_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-get_config_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-d2_command_unittest.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-simple_add_unittests.$(OBJEXT) \
+@HAVE_GTEST_TRUE@ d2_unittests-simple_remove_unittests.$(OBJEXT)
+nodist_d2_unittests_OBJECTS =
+d2_unittests_OBJECTS = $(am_d2_unittests_OBJECTS) \
+ $(nodist_d2_unittests_OBJECTS)
+@HAVE_GTEST_TRUE@d2_unittests_DEPENDENCIES = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/d2/libd2.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/testutils/libprocesstest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(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/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.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/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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)
+d2_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \
+ $(CXXFLAGS) $(d2_unittests_LDFLAGS) $(LDFLAGS) -o $@
+SCRIPTS = $(noinst_SCRIPTS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = \
+ ./$(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_command_unittest.Po \
+ ./$(DEPDIR)/d2_unittests-d2_controller_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_process_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po \
+ ./$(DEPDIR)/d2_unittests-d2_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-get_config_unittest.Po \
+ ./$(DEPDIR)/d2_unittests-nc_add_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-nc_remove_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-parser_unittest.Po \
+ ./$(DEPDIR)/d2_unittests-simple_add_unittests.Po \
+ ./$(DEPDIR)/d2_unittests-simple_remove_unittests.Po \
+ ./$(DEPDIR)/libcallout_la-callout_library.Plo \
+ ./$(DEPDIR)/libconfigured_la-configured_library.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 = $(libcallout_la_SOURCES) $(libconfigured_la_SOURCES) \
+ $(d2_unittests_SOURCES) $(nodist_d2_unittests_SOURCES)
+DIST_SOURCES = $(am__libcallout_la_SOURCES_DIST) \
+ $(am__libconfigured_la_SOURCES_DIST) \
+ $(am__d2_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; \
+}
+@HAVE_GTEST_TRUE@am__EXEEXT_2 = $(am__EXEEXT_1)
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(srcdir)/d2_process_tests.sh.in \
+ $(srcdir)/test_callout_libraries.h.in \
+ $(srcdir)/test_configured_libraries.h.in \
+ $(srcdir)/test_data_files_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_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@
+DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@
+DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@
+DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@
+DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GENHTML = @GENHTML@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+GTEST_CONFIG = @GTEST_CONFIG@
+GTEST_INCLUDES = @GTEST_INCLUDES@
+GTEST_LDADD = @GTEST_LDADD@
+GTEST_LDFLAGS = @GTEST_LDFLAGS@
+GTEST_SOURCE = @GTEST_SOURCE@
+HAVE_SYSREPO = @HAVE_SYSREPO@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+KEA_CXXFLAGS = @KEA_CXXFLAGS@
+KEA_SRCID = @KEA_SRCID@
+KRB5_CONFIG = @KRB5_CONFIG@
+LCOV = @LCOV@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@
+LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PDFLATEX = @PDFLATEX@
+PERL = @PERL@
+PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PKGPYTHONDIR = @PKGPYTHONDIR@
+PKG_CONFIG = @PKG_CONFIG@
+PLANTUML = @PLANTUML@
+PREMIUM_DIR = @PREMIUM_DIR@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+SED = @SED@
+SEP = @SEP@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINXBUILD = @SPHINXBUILD@
+SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@
+SR_REPO_PATH = @SR_REPO_PATH@
+STRIP = @STRIP@
+SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@
+SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@
+SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@
+SYSREPO_LIBS = @SYSREPO_LIBS@
+SYSREPO_VERSION = @SYSREPO_VERSION@
+USE_LCOV = @USE_LCOV@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@
+YACC = @YACC@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CXX = @ac_ct_CXX@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+SUBDIRS = .
+
+# Add to the tarball:
+EXTRA_DIST = testdata/d2_cfg_tests.json testdata/get_config.json
+TESTS_ENVIRONMENT = \
+ $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+
+# Shell tests
+SHTESTS = d2_process_tests.sh
+
+# Run shell tests on "make check".
+check_SCRIPTS = $(SHTESTS)
+
+# As with every file generated by ./configure, clean them up when running
+# "make distclean", but not on "make clean".
+DISTCLEANFILES = $(SHTESTS) d2_process_tests.sh \
+ test_data_files_config.h test_callout_libraries.h \
+ test_configured_libraries.h
+
+# Don't install shell tests.
+noinst_SCRIPTS = $(SHTESTS)
+
+# C++ tests
+@HAVE_GTEST_TRUE@PROGRAM_TESTS = d2_unittests
+@HAVE_GTEST_TRUE@AM_CPPFLAGS = -I$(top_srcdir)/src/lib \
+@HAVE_GTEST_TRUE@ -I$(top_builddir)/src/lib \
+@HAVE_GTEST_TRUE@ -I$(top_srcdir)/src/bin \
+@HAVE_GTEST_TRUE@ -I$(top_builddir)/src/bin $(BOOST_INCLUDES) \
+@HAVE_GTEST_TRUE@ -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/d2/tests\" \
+@HAVE_GTEST_TRUE@ -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\" \
+@HAVE_GTEST_TRUE@ -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/ddns\" \
+@HAVE_GTEST_TRUE@ -DSYNTAX_FILE=\"$(abs_srcdir)/../d2_parser.yy\"
+@HAVE_GTEST_TRUE@AM_CXXFLAGS = $(KEA_CXXFLAGS)
+@HAVE_GTEST_TRUE@@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static
+@HAVE_GTEST_TRUE@d2_unittests_SOURCES = d2_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_process_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_cfg_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_queue_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_update_mgr_unittests.cc \
+@HAVE_GTEST_TRUE@ nc_add_unittests.cc nc_remove_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_controller_unittests.cc \
+@HAVE_GTEST_TRUE@ d2_simple_parser_unittest.cc \
+@HAVE_GTEST_TRUE@ parser_unittest.cc parser_unittest.h \
+@HAVE_GTEST_TRUE@ get_config_unittest.cc d2_command_unittest.cc \
+@HAVE_GTEST_TRUE@ simple_add_unittests.cc \
+@HAVE_GTEST_TRUE@ simple_remove_unittests.cc
+@HAVE_GTEST_TRUE@d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+@HAVE_GTEST_TRUE@d2_unittests_LDFLAGS = $(AM_LDFLAGS) \
+@HAVE_GTEST_TRUE@ $(CRYPTO_LDFLAGS) $(am__append_1) \
+@HAVE_GTEST_TRUE@ $(am__append_2) $(GTEST_LDFLAGS)
+@HAVE_GTEST_TRUE@d2_unittests_LDADD = \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/bin/d2/libd2.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/testutils/libd2srvtest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/d2srv/libkea-d2srv.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/testutils/libprocesstest.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cfgrpt/libcfgrpt.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/process/libkea-process.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiodns/libkea-asiodns.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/stats/libkea-stats.la \
+@HAVE_GTEST_TRUE@ $(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/database/libkea-database.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.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/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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)
+
+# The basic callout library - contains standard callouts
+@HAVE_GTEST_TRUE@libcallout_la_SOURCES = callout_library.cc
+@HAVE_GTEST_TRUE@libcallout_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libcallout_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libcallout_la_LIBADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la
+@HAVE_GTEST_TRUE@libcallout_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+
+# The d2_srv_configured callout library
+@HAVE_GTEST_TRUE@libconfigured_la_SOURCES = configured_library.cc
+@HAVE_GTEST_TRUE@libconfigured_la_CXXFLAGS = $(AM_CXXFLAGS)
+@HAVE_GTEST_TRUE@libconfigured_la_CPPFLAGS = $(AM_CPPFLAGS)
+@HAVE_GTEST_TRUE@libconfigured_la_LIBADD = $(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/libkea-asiolink.la \
+@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.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) $(BOOST_LIBS)
+@HAVE_GTEST_TRUE@libconfigured_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
+@HAVE_GTEST_TRUE@noinst_LTLIBRARIES = libcallout.la libconfigured.la
+@HAVE_GTEST_TRUE@nodist_d2_unittests_SOURCES = \
+@HAVE_GTEST_TRUE@ test_data_files_config.h \
+@HAVE_GTEST_TRUE@ test_callout_libraries.h \
+@HAVE_GTEST_TRUE@ test_configured_libraries.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/bin/d2/tests/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/bin/d2/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):
+d2_process_tests.sh: $(top_builddir)/config.status $(srcdir)/d2_process_tests.sh.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_callout_libraries.h: $(top_builddir)/config.status $(srcdir)/test_callout_libraries.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_configured_libraries.h: $(top_builddir)/config.status $(srcdir)/test_configured_libraries.h.in
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@
+test_data_files_config.h: $(top_builddir)/config.status $(srcdir)/test_data_files_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
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libcallout.la: $(libcallout_la_OBJECTS) $(libcallout_la_DEPENDENCIES) $(EXTRA_libcallout_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libcallout_la_LINK) $(am_libcallout_la_rpath) $(libcallout_la_OBJECTS) $(libcallout_la_LIBADD) $(LIBS)
+
+libconfigured.la: $(libconfigured_la_OBJECTS) $(libconfigured_la_DEPENDENCIES) $(EXTRA_libconfigured_la_DEPENDENCIES)
+ $(AM_V_CXXLD)$(libconfigured_la_LINK) $(am_libconfigured_la_rpath) $(libconfigured_la_OBJECTS) $(libconfigured_la_LIBADD) $(LIBS)
+
+d2_unittests$(EXEEXT): $(d2_unittests_OBJECTS) $(d2_unittests_DEPENDENCIES) $(EXTRA_d2_unittests_DEPENDENCIES)
+ @rm -f d2_unittests$(EXEEXT)
+ $(AM_V_CXXLD)$(d2_unittests_LINK) $(d2_unittests_OBJECTS) $(d2_unittests_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_command_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_controller_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_process_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-get_config_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-nc_add_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-nc_remove_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-parser_unittest.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-simple_add_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/d2_unittests-simple_remove_unittests.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libcallout_la-callout_library.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libconfigured_la-configured_library.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 $@ $<
+
+libcallout_la-callout_library.lo: callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcallout_la_CPPFLAGS) $(CPPFLAGS) $(libcallout_la_CXXFLAGS) $(CXXFLAGS) -MT libcallout_la-callout_library.lo -MD -MP -MF $(DEPDIR)/libcallout_la-callout_library.Tpo -c -o libcallout_la-callout_library.lo `test -f 'callout_library.cc' || echo '$(srcdir)/'`callout_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libcallout_la-callout_library.Tpo $(DEPDIR)/libcallout_la-callout_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='callout_library.cc' object='libcallout_la-callout_library.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libcallout_la_CPPFLAGS) $(CPPFLAGS) $(libcallout_la_CXXFLAGS) $(CXXFLAGS) -c -o libcallout_la-callout_library.lo `test -f 'callout_library.cc' || echo '$(srcdir)/'`callout_library.cc
+
+libconfigured_la-configured_library.lo: configured_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libconfigured_la_CPPFLAGS) $(CPPFLAGS) $(libconfigured_la_CXXFLAGS) $(CXXFLAGS) -MT libconfigured_la-configured_library.lo -MD -MP -MF $(DEPDIR)/libconfigured_la-configured_library.Tpo -c -o libconfigured_la-configured_library.lo `test -f 'configured_library.cc' || echo '$(srcdir)/'`configured_library.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libconfigured_la-configured_library.Tpo $(DEPDIR)/libconfigured_la-configured_library.Plo
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='configured_library.cc' object='libconfigured_la-configured_library.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libconfigured_la_CPPFLAGS) $(CPPFLAGS) $(libconfigured_la_CXXFLAGS) $(CXXFLAGS) -c -o libconfigured_la-configured_library.lo `test -f 'configured_library.cc' || echo '$(srcdir)/'`configured_library.cc
+
+d2_unittests-d2_unittests.o: d2_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_unittests.Tpo -c -o d2_unittests-d2_unittests.o `test -f 'd2_unittests.cc' || echo '$(srcdir)/'`d2_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_unittests.Tpo $(DEPDIR)/d2_unittests-d2_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_unittests.cc' object='d2_unittests-d2_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_unittests.o `test -f 'd2_unittests.cc' || echo '$(srcdir)/'`d2_unittests.cc
+
+d2_unittests-d2_unittests.obj: d2_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_unittests.Tpo -c -o d2_unittests-d2_unittests.obj `if test -f 'd2_unittests.cc'; then $(CYGPATH_W) 'd2_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_unittests.Tpo $(DEPDIR)/d2_unittests-d2_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_unittests.cc' object='d2_unittests-d2_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_unittests.obj `if test -f 'd2_unittests.cc'; then $(CYGPATH_W) 'd2_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_unittests.cc'; fi`
+
+d2_unittests-d2_process_unittests.o: d2_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_process_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_process_unittests.Tpo -c -o d2_unittests-d2_process_unittests.o `test -f 'd2_process_unittests.cc' || echo '$(srcdir)/'`d2_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_process_unittests.Tpo $(DEPDIR)/d2_unittests-d2_process_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_process_unittests.cc' object='d2_unittests-d2_process_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_process_unittests.o `test -f 'd2_process_unittests.cc' || echo '$(srcdir)/'`d2_process_unittests.cc
+
+d2_unittests-d2_process_unittests.obj: d2_process_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_process_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_process_unittests.Tpo -c -o d2_unittests-d2_process_unittests.obj `if test -f 'd2_process_unittests.cc'; then $(CYGPATH_W) 'd2_process_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_process_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_process_unittests.Tpo $(DEPDIR)/d2_unittests-d2_process_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_process_unittests.cc' object='d2_unittests-d2_process_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_process_unittests.obj `if test -f 'd2_process_unittests.cc'; then $(CYGPATH_W) 'd2_process_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_process_unittests.cc'; fi`
+
+d2_unittests-d2_cfg_mgr_unittests.o: d2_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_cfg_mgr_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Tpo -c -o d2_unittests-d2_cfg_mgr_unittests.o `test -f 'd2_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`d2_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_cfg_mgr_unittests.cc' object='d2_unittests-d2_cfg_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_cfg_mgr_unittests.o `test -f 'd2_cfg_mgr_unittests.cc' || echo '$(srcdir)/'`d2_cfg_mgr_unittests.cc
+
+d2_unittests-d2_cfg_mgr_unittests.obj: d2_cfg_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_cfg_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Tpo -c -o d2_unittests-d2_cfg_mgr_unittests.obj `if test -f 'd2_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_cfg_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_cfg_mgr_unittests.cc' object='d2_unittests-d2_cfg_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_cfg_mgr_unittests.obj `if test -f 'd2_cfg_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_cfg_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_cfg_mgr_unittests.cc'; fi`
+
+d2_unittests-d2_queue_mgr_unittests.o: d2_queue_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_queue_mgr_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Tpo -c -o d2_unittests-d2_queue_mgr_unittests.o `test -f 'd2_queue_mgr_unittests.cc' || echo '$(srcdir)/'`d2_queue_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_queue_mgr_unittests.cc' object='d2_unittests-d2_queue_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_queue_mgr_unittests.o `test -f 'd2_queue_mgr_unittests.cc' || echo '$(srcdir)/'`d2_queue_mgr_unittests.cc
+
+d2_unittests-d2_queue_mgr_unittests.obj: d2_queue_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_queue_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Tpo -c -o d2_unittests-d2_queue_mgr_unittests.obj `if test -f 'd2_queue_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_queue_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_queue_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_queue_mgr_unittests.cc' object='d2_unittests-d2_queue_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_queue_mgr_unittests.obj `if test -f 'd2_queue_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_queue_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_queue_mgr_unittests.cc'; fi`
+
+d2_unittests-d2_update_mgr_unittests.o: d2_update_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_update_mgr_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Tpo -c -o d2_unittests-d2_update_mgr_unittests.o `test -f 'd2_update_mgr_unittests.cc' || echo '$(srcdir)/'`d2_update_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_mgr_unittests.cc' object='d2_unittests-d2_update_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_update_mgr_unittests.o `test -f 'd2_update_mgr_unittests.cc' || echo '$(srcdir)/'`d2_update_mgr_unittests.cc
+
+d2_unittests-d2_update_mgr_unittests.obj: d2_update_mgr_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_update_mgr_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Tpo -c -o d2_unittests-d2_update_mgr_unittests.obj `if test -f 'd2_update_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_update_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_update_mgr_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Tpo $(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_update_mgr_unittests.cc' object='d2_unittests-d2_update_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_update_mgr_unittests.obj `if test -f 'd2_update_mgr_unittests.cc'; then $(CYGPATH_W) 'd2_update_mgr_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_update_mgr_unittests.cc'; fi`
+
+d2_unittests-nc_add_unittests.o: nc_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-nc_add_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-nc_add_unittests.Tpo -c -o d2_unittests-nc_add_unittests.o `test -f 'nc_add_unittests.cc' || echo '$(srcdir)/'`nc_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-nc_add_unittests.Tpo $(DEPDIR)/d2_unittests-nc_add_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_add_unittests.cc' object='d2_unittests-nc_add_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-nc_add_unittests.o `test -f 'nc_add_unittests.cc' || echo '$(srcdir)/'`nc_add_unittests.cc
+
+d2_unittests-nc_add_unittests.obj: nc_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-nc_add_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-nc_add_unittests.Tpo -c -o d2_unittests-nc_add_unittests.obj `if test -f 'nc_add_unittests.cc'; then $(CYGPATH_W) 'nc_add_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_add_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-nc_add_unittests.Tpo $(DEPDIR)/d2_unittests-nc_add_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_add_unittests.cc' object='d2_unittests-nc_add_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-nc_add_unittests.obj `if test -f 'nc_add_unittests.cc'; then $(CYGPATH_W) 'nc_add_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_add_unittests.cc'; fi`
+
+d2_unittests-nc_remove_unittests.o: nc_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-nc_remove_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-nc_remove_unittests.Tpo -c -o d2_unittests-nc_remove_unittests.o `test -f 'nc_remove_unittests.cc' || echo '$(srcdir)/'`nc_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-nc_remove_unittests.Tpo $(DEPDIR)/d2_unittests-nc_remove_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_remove_unittests.cc' object='d2_unittests-nc_remove_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-nc_remove_unittests.o `test -f 'nc_remove_unittests.cc' || echo '$(srcdir)/'`nc_remove_unittests.cc
+
+d2_unittests-nc_remove_unittests.obj: nc_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-nc_remove_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-nc_remove_unittests.Tpo -c -o d2_unittests-nc_remove_unittests.obj `if test -f 'nc_remove_unittests.cc'; then $(CYGPATH_W) 'nc_remove_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_remove_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-nc_remove_unittests.Tpo $(DEPDIR)/d2_unittests-nc_remove_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='nc_remove_unittests.cc' object='d2_unittests-nc_remove_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-nc_remove_unittests.obj `if test -f 'nc_remove_unittests.cc'; then $(CYGPATH_W) 'nc_remove_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/nc_remove_unittests.cc'; fi`
+
+d2_unittests-d2_controller_unittests.o: d2_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_controller_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_controller_unittests.Tpo -c -o d2_unittests-d2_controller_unittests.o `test -f 'd2_controller_unittests.cc' || echo '$(srcdir)/'`d2_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_controller_unittests.Tpo $(DEPDIR)/d2_unittests-d2_controller_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_controller_unittests.cc' object='d2_unittests-d2_controller_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_controller_unittests.o `test -f 'd2_controller_unittests.cc' || echo '$(srcdir)/'`d2_controller_unittests.cc
+
+d2_unittests-d2_controller_unittests.obj: d2_controller_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_controller_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_controller_unittests.Tpo -c -o d2_unittests-d2_controller_unittests.obj `if test -f 'd2_controller_unittests.cc'; then $(CYGPATH_W) 'd2_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_controller_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_controller_unittests.Tpo $(DEPDIR)/d2_unittests-d2_controller_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_controller_unittests.cc' object='d2_unittests-d2_controller_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_controller_unittests.obj `if test -f 'd2_controller_unittests.cc'; then $(CYGPATH_W) 'd2_controller_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/d2_controller_unittests.cc'; fi`
+
+d2_unittests-d2_simple_parser_unittest.o: d2_simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_simple_parser_unittest.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Tpo -c -o d2_unittests-d2_simple_parser_unittest.o `test -f 'd2_simple_parser_unittest.cc' || echo '$(srcdir)/'`d2_simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Tpo $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_simple_parser_unittest.cc' object='d2_unittests-d2_simple_parser_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_simple_parser_unittest.o `test -f 'd2_simple_parser_unittest.cc' || echo '$(srcdir)/'`d2_simple_parser_unittest.cc
+
+d2_unittests-d2_simple_parser_unittest.obj: d2_simple_parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_simple_parser_unittest.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Tpo -c -o d2_unittests-d2_simple_parser_unittest.obj `if test -f 'd2_simple_parser_unittest.cc'; then $(CYGPATH_W) 'd2_simple_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_simple_parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Tpo $(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_simple_parser_unittest.cc' object='d2_unittests-d2_simple_parser_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_simple_parser_unittest.obj `if test -f 'd2_simple_parser_unittest.cc'; then $(CYGPATH_W) 'd2_simple_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_simple_parser_unittest.cc'; fi`
+
+d2_unittests-parser_unittest.o: parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-parser_unittest.o -MD -MP -MF $(DEPDIR)/d2_unittests-parser_unittest.Tpo -c -o d2_unittests-parser_unittest.o `test -f 'parser_unittest.cc' || echo '$(srcdir)/'`parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-parser_unittest.Tpo $(DEPDIR)/d2_unittests-parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittest.cc' object='d2_unittests-parser_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-parser_unittest.o `test -f 'parser_unittest.cc' || echo '$(srcdir)/'`parser_unittest.cc
+
+d2_unittests-parser_unittest.obj: parser_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-parser_unittest.obj -MD -MP -MF $(DEPDIR)/d2_unittests-parser_unittest.Tpo -c -o d2_unittests-parser_unittest.obj `if test -f 'parser_unittest.cc'; then $(CYGPATH_W) 'parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-parser_unittest.Tpo $(DEPDIR)/d2_unittests-parser_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser_unittest.cc' object='d2_unittests-parser_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-parser_unittest.obj `if test -f 'parser_unittest.cc'; then $(CYGPATH_W) 'parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/parser_unittest.cc'; fi`
+
+d2_unittests-get_config_unittest.o: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-get_config_unittest.o -MD -MP -MF $(DEPDIR)/d2_unittests-get_config_unittest.Tpo -c -o d2_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-get_config_unittest.Tpo $(DEPDIR)/d2_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='d2_unittests-get_config_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-get_config_unittest.o `test -f 'get_config_unittest.cc' || echo '$(srcdir)/'`get_config_unittest.cc
+
+d2_unittests-get_config_unittest.obj: get_config_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-get_config_unittest.obj -MD -MP -MF $(DEPDIR)/d2_unittests-get_config_unittest.Tpo -c -o d2_unittests-get_config_unittest.obj `if test -f 'get_config_unittest.cc'; then $(CYGPATH_W) 'get_config_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/get_config_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-get_config_unittest.Tpo $(DEPDIR)/d2_unittests-get_config_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='get_config_unittest.cc' object='d2_unittests-get_config_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-get_config_unittest.obj `if test -f 'get_config_unittest.cc'; then $(CYGPATH_W) 'get_config_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/get_config_unittest.cc'; fi`
+
+d2_unittests-d2_command_unittest.o: d2_command_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_command_unittest.o -MD -MP -MF $(DEPDIR)/d2_unittests-d2_command_unittest.Tpo -c -o d2_unittests-d2_command_unittest.o `test -f 'd2_command_unittest.cc' || echo '$(srcdir)/'`d2_command_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_command_unittest.Tpo $(DEPDIR)/d2_unittests-d2_command_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_command_unittest.cc' object='d2_unittests-d2_command_unittest.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_command_unittest.o `test -f 'd2_command_unittest.cc' || echo '$(srcdir)/'`d2_command_unittest.cc
+
+d2_unittests-d2_command_unittest.obj: d2_command_unittest.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-d2_command_unittest.obj -MD -MP -MF $(DEPDIR)/d2_unittests-d2_command_unittest.Tpo -c -o d2_unittests-d2_command_unittest.obj `if test -f 'd2_command_unittest.cc'; then $(CYGPATH_W) 'd2_command_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_command_unittest.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-d2_command_unittest.Tpo $(DEPDIR)/d2_unittests-d2_command_unittest.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='d2_command_unittest.cc' object='d2_unittests-d2_command_unittest.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-d2_command_unittest.obj `if test -f 'd2_command_unittest.cc'; then $(CYGPATH_W) 'd2_command_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/d2_command_unittest.cc'; fi`
+
+d2_unittests-simple_add_unittests.o: simple_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-simple_add_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-simple_add_unittests.Tpo -c -o d2_unittests-simple_add_unittests.o `test -f 'simple_add_unittests.cc' || echo '$(srcdir)/'`simple_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-simple_add_unittests.Tpo $(DEPDIR)/d2_unittests-simple_add_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_add_unittests.cc' object='d2_unittests-simple_add_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-simple_add_unittests.o `test -f 'simple_add_unittests.cc' || echo '$(srcdir)/'`simple_add_unittests.cc
+
+d2_unittests-simple_add_unittests.obj: simple_add_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-simple_add_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-simple_add_unittests.Tpo -c -o d2_unittests-simple_add_unittests.obj `if test -f 'simple_add_unittests.cc'; then $(CYGPATH_W) 'simple_add_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/simple_add_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-simple_add_unittests.Tpo $(DEPDIR)/d2_unittests-simple_add_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_add_unittests.cc' object='d2_unittests-simple_add_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-simple_add_unittests.obj `if test -f 'simple_add_unittests.cc'; then $(CYGPATH_W) 'simple_add_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/simple_add_unittests.cc'; fi`
+
+d2_unittests-simple_remove_unittests.o: simple_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-simple_remove_unittests.o -MD -MP -MF $(DEPDIR)/d2_unittests-simple_remove_unittests.Tpo -c -o d2_unittests-simple_remove_unittests.o `test -f 'simple_remove_unittests.cc' || echo '$(srcdir)/'`simple_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-simple_remove_unittests.Tpo $(DEPDIR)/d2_unittests-simple_remove_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_remove_unittests.cc' object='d2_unittests-simple_remove_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-simple_remove_unittests.o `test -f 'simple_remove_unittests.cc' || echo '$(srcdir)/'`simple_remove_unittests.cc
+
+d2_unittests-simple_remove_unittests.obj: simple_remove_unittests.cc
+@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT d2_unittests-simple_remove_unittests.obj -MD -MP -MF $(DEPDIR)/d2_unittests-simple_remove_unittests.Tpo -c -o d2_unittests-simple_remove_unittests.obj `if test -f 'simple_remove_unittests.cc'; then $(CYGPATH_W) 'simple_remove_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/simple_remove_unittests.cc'; fi`
+@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/d2_unittests-simple_remove_unittests.Tpo $(DEPDIR)/d2_unittests-simple_remove_unittests.Po
+@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_remove_unittests.cc' object='d2_unittests-simple_remove_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) $(d2_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o d2_unittests-simple_remove_unittests.obj `if test -f 'simple_remove_unittests.cc'; then $(CYGPATH_W) 'simple_remove_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/simple_remove_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_SCRIPTS)
+ $(MAKE) $(AM_MAKEFLAGS) check-TESTS
+check: check-recursive
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES) $(SCRIPTS)
+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:
+
+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-noinstLTLIBRARIES \
+ clean-noinstPROGRAMS mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_cfg_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_command_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_controller_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_process_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-nc_add_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-nc_remove_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-parser_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-simple_add_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-simple_remove_unittests.Po
+ -rm -f ./$(DEPDIR)/libcallout_la-callout_library.Plo
+ -rm -f ./$(DEPDIR)/libconfigured_la-configured_library.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-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)/d2_unittests-d2_cfg_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_command_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_controller_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_process_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_queue_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_simple_parser_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-d2_update_mgr_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-get_config_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-nc_add_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-nc_remove_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-parser_unittest.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-simple_add_unittests.Po
+ -rm -f ./$(DEPDIR)/d2_unittests-simple_remove_unittests.Po
+ -rm -f ./$(DEPDIR)/libcallout_la-callout_library.Plo
+ -rm -f ./$(DEPDIR)/libconfigured_la-configured_library.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+uninstall-am:
+
+.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-noinstLTLIBRARIES 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/bin/d2/tests/callout_library.cc b/src/bin/d2/tests/callout_library.cc
new file mode 100644
index 0000000..8587eba
--- /dev/null
+++ b/src/bin/d2/tests/callout_library.cc
@@ -0,0 +1,72 @@
+// 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/.
+
+/// @file
+/// @brief Basic callout library
+///
+/// This is source of a test library for Control Agent.
+///
+/// - Only the "version" framework function is supplied.
+///
+/// - hookpt_one callout is supplied.
+
+#include <config.h>
+#include <hooks/hooks.h>
+
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+extern "C" {
+
+// Callouts. All return their result through the "result" argument.
+
+int
+context_create(CalloutHandle& handle) {
+ handle.setContext("result", static_cast<int>(10));
+ handle.setArgument("result", static_cast<int>(10));
+ return (0);
+}
+
+// First callout adds the passed "integer" argument to the initialized context
+// value of 10. (Note that the value set by context_create is accessed through
+// context and not the argument, so checking that context is correctly passed
+// between callouts in the same library.)
+
+int
+hookpt_one(CalloutHandle& handle) {
+ int data;
+ handle.getArgument("integer", data);
+
+ int result;
+ handle.getArgument("result", result);
+
+ result += data;
+ handle.setArgument("result", result);
+
+ return (0);
+}
+
+// Framework functions.
+
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+// load() initializes the user library if the main image was statically linked.
+int
+load(isc::hooks::LibraryHandle&) {
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ return (0);
+}
+
+}
+}
+
diff --git a/src/bin/d2/tests/configured_library.cc b/src/bin/d2/tests/configured_library.cc
new file mode 100644
index 0000000..3aeb94d
--- /dev/null
+++ b/src/bin/d2/tests/configured_library.cc
@@ -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/.
+
+/// @brief d2_srv_configured callout testing library
+
+#include <config.h>
+#include <cc/data.h>
+#include <hooks/hooks.h>
+
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace std;
+
+namespace {
+
+extern "C" {
+
+// d2_srv_configured callout.
+int
+d2_srv_configured(CalloutHandle& handle) {
+ // Get the parameters.
+ ConstElementPtr cfg;
+ string error;
+ handle.getArgument("json_config", cfg);
+ handle.getArgument("error", error);
+
+ if (cfg) {
+ ConstElementPtr uc = cfg->get("user-context");
+ if (uc) {
+ ConstElementPtr msg = uc->get("error");
+ if (msg && (msg->getType() == Element::string)) {
+ error += msg->stringValue();
+ }
+ }
+ }
+
+ if (!error.empty()) {
+ handle.setArgument("error", error);
+ handle.setStatus(CalloutHandle::NEXT_STEP_DROP);
+ }
+ return (0);
+}
+
+// Framework functions.
+int
+version() {
+ return (KEA_HOOKS_VERSION);
+}
+
+// load() initializes the user library if the main image was statically linked.
+int
+load(isc::hooks::LibraryHandle&) {
+#ifdef USE_STATIC_LINK
+ hooksStaticLinkInit();
+#endif
+ return (0);
+}
+
+}
+}
diff --git a/src/bin/d2/tests/d2_cfg_mgr_unittests.cc b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
new file mode 100644
index 0000000..5e27ff8
--- /dev/null
+++ b/src/bin/d2/tests/d2_cfg_mgr_unittests.cc
@@ -0,0 +1,1080 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2/parser_context.h>
+#include <d2/tests/parser_unittest.h>
+#include <d2/tests/test_callout_libraries.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_config.h>
+#include <d2srv/d2_simple_parser.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <process/testutils/d_test_stubs.h>
+#include <test_data_files_config.h>
+#include <util/encode/base64.h>
+
+#include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::hooks;
+using namespace isc::process;
+
+namespace {
+
+/// @brief Function to create full path to test data file
+///
+/// The full path is dependent upon the value of D2_TEST_DATA_DIR which
+/// whose value is generated from test_data_files_config.h.in
+///
+/// @param name file name to which the path should be prepended
+std::string testDataFile(const std::string& name) {
+ return (std::string(D2_TEST_DATA_DIR) + "/" + name);
+}
+
+/// @brief Test fixture class for testing D2CfgMgr class.
+/// It maintains an member instance of D2CfgMgr and provides methods for
+/// converting JSON strings to configuration element sets, checking parse
+/// results, and accessing the configuration context.
+class D2CfgMgrTest : public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ D2CfgMgrTest():cfg_mgr_(new D2CfgMgr()), d2_params_() {
+ }
+
+ /// @brief Destructor
+ ~D2CfgMgrTest() {
+ }
+
+ /// @brief Configuration manager instance.
+ D2CfgMgrPtr cfg_mgr_;
+
+ /// @brief Build JSON configuration string for a D2Params element
+ ///
+ /// Constructs a JSON string for "params" element using replaceable
+ /// parameters.
+ ///
+ /// @param ip_address string to insert as ip_address value
+ /// @param port integer to insert as port value
+ /// @param dns_server_timeout integer to insert as dns_server_timeout value
+ /// @param ncr_protocol string to insert as ncr_protocol value
+ /// @param ncr_format string to insert as ncr_format value
+ ///
+ /// @return std::string containing the JSON configuration text
+ std::string makeParamsConfigString(const std::string& ip_address,
+ const int port,
+ const int dns_server_timeout,
+ const std::string& ncr_protocol,
+ const std::string& ncr_format) {
+ std::ostringstream config;
+ config <<
+ "{"
+ " \"ip-address\": \"" << ip_address << "\" , "
+ " \"port\": " << port << " , "
+ " \"dns-server-timeout\": " << dns_server_timeout << " , "
+ " \"ncr-protocol\": \"" << ncr_protocol << "\" , "
+ " \"ncr-format\": \"" << ncr_format << "\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ return (config.str());
+ }
+
+ /// @brief Enumeration to select between expected configuration outcomes
+ enum RunConfigMode {
+ NO_ERROR,
+ SYNTAX_ERROR,
+ LOGIC_ERROR
+ };
+
+ /// @brief Parses a configuration string and tests against a given outcome
+ ///
+ /// Convenience method which accepts JSON text and an expected pass or fail
+ /// outcome. It uses the D2ParserContext to parse the text under the
+ /// PARSE_SUB_DHCPDDNS context, then adds the D2 defaults to the resultant
+ /// element tree. Assuming that's successful the element tree is passed
+ /// to D2CfgMgr::parseConfig() method.
+ ///
+ /// @param config_str the JSON configuration text to parse
+ /// @param error_type indicates the type error expected, NONE, SYNTAX,
+ /// or LOGIC. SYNTAX errors are emitted by JSON parser, logic errors
+ /// are emitted by element parser(s).
+ /// @param exp_error exact text of the error message expected
+ /// defaults to SHOULD_PASS.
+ ///
+ /// @return AssertionSuccess if test passes, AssertionFailure otherwise
+ ::testing::AssertionResult runConfigOrFail(const std::string& json,
+ const RunConfigMode mode,
+ const std::string& exp_error) {
+
+ try {
+ // Invoke the JSON parser, casting the returned element tree
+ // into mutable form.
+ D2ParserContext parser_context;
+ data::ElementPtr elem =
+ boost::const_pointer_cast<Element>
+ (parser_context.parseString(json, D2ParserContext::
+ PARSER_SUB_DHCPDDNS));
+
+ // If parsing succeeded when we expected a syntax error, then fail.
+ if (mode == SYNTAX_ERROR) {
+ return ::testing::AssertionFailure()
+ << "Unexpected JSON parsing success"
+ << "\njson: [" << json << " ]";
+ }
+
+ // JSON parsed ok, so add the defaults to the element tree it produced.
+ D2SimpleParser::setAllDefaults(elem);
+ config_set_ = elem;
+ } catch (const std::exception& ex) {
+ // JSON Parsing failed
+ if (exp_error.empty()) {
+ // We did not expect an error, so fail.
+ return ::testing::AssertionFailure()
+ << "Unexpected syntax error:" << ex.what()
+ << "\njson: [" << json << " ]";
+ }
+
+ if (ex.what() != exp_error) {
+ // Expected an error not the one we got, so fail
+ return ::testing::AssertionFailure()
+ << "Wrong syntax error detected, expected: "
+ << exp_error << ", got: " << ex.what()
+ << "\njson: [" << json << " ]";
+ }
+
+ // We go the syntax error we expected, so return success
+ return ::testing::AssertionSuccess();
+ }
+
+ // The JSON parsed ok and we've added the defaults, pass the config
+ // into the Element parser and check for the expected outcome.
+ data::ConstElementPtr answer;
+ answer = cfg_mgr_->simpleParseConfig(config_set_, false);
+
+ // Extract the result and error text from the answer.
+ int rcode = 0;
+ isc::data::ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+
+ if (rcode != 0) {
+ // Element Parsing failed.
+ if (exp_error.empty()) {
+ // We didn't expect it to, fail the test.
+ return ::testing::AssertionFailure()
+ << "Unexpected logic error: " << *comment
+ << "\njson: [" << json << " ]";
+ }
+
+ if (comment->stringValue() != exp_error) {
+ // We 't expect a different error, fail the test.
+ return ::testing::AssertionFailure()
+ << "Wrong logic error detected, expected: "
+ << exp_error << ", got: " << *comment
+ << "\njson: [" << json << " ]";
+ }
+ } else {
+ // Element parsing succeeded.
+ if (!exp_error.empty()) {
+ // It was supposed to fail, so fail the test.
+ return ::testing::AssertionFailure()
+ << "Unexpected logic success, expected error:"
+ << exp_error
+ << "\njson: [" << json << " ]";
+ }
+ }
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ context = cfg_mgr_->getD2CfgContext();
+ if (!context) {
+ return ::testing::AssertionFailure() << "D2CfgContext is null";
+ }
+
+ // Verify that the global scalar container has been created.
+ d2_params_ = context->getD2Params();
+ if (!d2_params_) {
+ return ::testing::AssertionFailure() << "D2Params is null";
+ }
+
+ return ::testing::AssertionSuccess();
+ }
+
+ /// @brief Replaces %LIBRARY% with specified library name
+ ///
+ /// @param config input config text (should contain "%LIBRARY%" string)
+ /// @param lib_name %LIBRARY% will be replaced with that name
+ /// @return configuration text with library name replaced
+ std::string pathReplacer(const char* config, const char* lib_name) {
+ string txt(config);
+ txt.replace(txt.find("%LIBRARY%"), strlen("%LIBRARY%"), string(lib_name));
+ return (txt);
+ }
+
+ /// @brief Pointer the D2Params most recently parsed.
+ D2ParamsPtr d2_params_;
+};
+
+/// @brief Convenience macros for invoking runOrConfig()
+#define RUN_CONFIG_OK(a) (runConfigOrFail(a, NO_ERROR, ""))
+#define SYNTAX_ERROR(a,b) ASSERT_TRUE(runConfigOrFail(a,SYNTAX_ERROR,b))
+#define LOGIC_ERROR(a,b) ASSERT_TRUE(runConfigOrFail(a,LOGIC_ERROR,b))
+
+/// @brief Tests a basic valid configuration for D2Param.
+TEST_F(D2CfgMgrTest, validParamsEntry) {
+ // Verify that ip_address can be valid v4 address.
+ std::string config = makeParamsConfigString ("192.0.0.1", 777, 333,
+ "UDP", "JSON");
+ RUN_CONFIG_OK(config);
+
+ EXPECT_EQ(isc::asiolink::IOAddress("192.0.0.1"),
+ d2_params_->getIpAddress());
+ EXPECT_EQ(777, d2_params_->getPort());
+ EXPECT_EQ(333, d2_params_->getDnsServerTimeout());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_params_->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_params_->getNcrFormat());
+
+ // Verify that ip_address can be valid v6 address.
+ config = makeParamsConfigString ("3001::5", 777, 333, "UDP", "JSON");
+ RUN_CONFIG_OK(config);
+
+ // Verify that the global scalars have the proper values.
+ EXPECT_EQ(isc::asiolink::IOAddress("3001::5"),
+ d2_params_->getIpAddress());
+
+ // Verify the configuration summary.
+ EXPECT_EQ("listening on 3001::5, port 777, using UDP",
+ d2_params_->getConfigSummary());
+}
+
+/// @brief Tests default values for D2Params.
+/// It verifies that D2Params is populated with default value for optional
+/// parameter if not supplied in the configuration.
+/// Currently they are all optional.
+TEST_F(D2CfgMgrTest, defaultValues) {
+
+ ElementPtr defaults = isc::d2::test::parseJSON("{ }");
+ ASSERT_NO_THROW(D2SimpleParser::setAllDefaults(defaults));
+
+ // Check that omitting ip_address gets you its default
+ std::string config =
+ "{"
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ConstElementPtr deflt;
+ ASSERT_NO_THROW(deflt = defaults->get("ip-address"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(deflt->stringValue(), d2_params_->getIpAddress().toText());
+
+ // Check that omitting port gets you its default
+ config =
+ "{"
+ " \"ip-address\": \"192.0.0.1\" , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ASSERT_NO_THROW(deflt = defaults->get("port"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(deflt->intValue(), d2_params_->getPort());
+
+ // Check that omitting timeout gets you its default
+ config =
+ "{"
+ " \"ip-address\": \"192.0.0.1\" , "
+ " \"port\": 777 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ASSERT_NO_THROW(deflt = defaults->get("dns-server-timeout"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(deflt->intValue(), d2_params_->getDnsServerTimeout());
+
+ // Check that omitting protocol gets you its default
+ config =
+ "{"
+ " \"ip-address\": \"192.0.0.1\" , "
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ASSERT_NO_THROW(deflt = defaults->get("ncr-protocol"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(dhcp_ddns::stringToNcrProtocol(deflt->stringValue()),
+ d2_params_->getNcrProtocol());
+
+ // Check that omitting format gets you its default
+ config =
+ "{"
+ " \"ip-address\": \"192.0.0.1\" , "
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {} "
+ "}";
+
+ RUN_CONFIG_OK(config);
+ ASSERT_NO_THROW(deflt = defaults->get("ncr-format"));
+ ASSERT_TRUE(deflt);
+ EXPECT_EQ(dhcp_ddns::stringToNcrFormat(deflt->stringValue()),
+ d2_params_->getNcrFormat());
+}
+
+/// @brief Tests the unsupported scalar parameters and objects are detected.
+TEST_F(D2CfgMgrTest, unsupportedTopLevelItems) {
+ // Check that an unsupported top level parameter fails.
+ std::string config =
+ "{"
+ " \"ip-address\": \"127.0.0.1\", "
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {}, "
+ " \"bogus-param\" : true "
+ "}";
+
+ SYNTAX_ERROR(config, "<string>:1.185-197: got unexpected "
+ "keyword \"bogus-param\" in DhcpDdns map.");
+
+ // Check that unsupported top level objects fails. For
+ // D2 these fail as they are not in the parse order.
+ config =
+ "{"
+ " \"ip-address\": \"127.0.0.1\", "
+ " \"port\": 777 , "
+ " \"dns-server-timeout\": 333 , "
+ " \"ncr-protocol\": \"UDP\" , "
+ " \"ncr-format\": \"JSON\", "
+ " \"tsig-keys\": [], "
+ " \"bogus-object-one\" : {}, "
+ " \"forward-ddns\" : {}, "
+ " \"reverse-ddns\" : {}, "
+ " \"bogus-object-two\" : {} "
+ "}";
+
+ SYNTAX_ERROR(config, "<string>:1.141-158: got unexpected"
+ " keyword \"bogus-object-one\" in DhcpDdns map.");
+}
+
+
+/// @brief Tests the enforcement of data validation when parsing D2Params.
+/// It verifies that:
+/// -# ip_address cannot be "0.0.0.0"
+/// -# ip_address cannot be "::"
+/// -# port cannot be 0
+/// -# dns_server_timeout cannot be 0
+/// -# ncr_protocol must be valid
+/// -# ncr_format must be valid
+TEST_F(D2CfgMgrTest, invalidEntry) {
+ // Cannot use IPv4 ANY address
+ std::string config = makeParamsConfigString ("0.0.0.0", 777, 333,
+ "UDP", "JSON");
+ LOGIC_ERROR(config, "IP address cannot be \"0.0.0.0\" (<string>:1:17)");
+
+ // Cannot use IPv6 ANY address
+ config = makeParamsConfigString ("::", 777, 333, "UDP", "JSON");
+ LOGIC_ERROR(config, "IP address cannot be \"::\" (<string>:1:17)");
+
+ // Cannot use port 0
+ config = makeParamsConfigString ("127.0.0.1", 0, 333, "UDP", "JSON");
+ SYNTAX_ERROR(config, "<string>:1.40: port must be greater than zero but less than 65536");
+
+ // Cannot use dns server timeout of 0
+ config = makeParamsConfigString ("127.0.0.1", 777, 0, "UDP", "JSON");
+ SYNTAX_ERROR(config, "<string>:1.69: dns-server-timeout"
+ " must be greater than zero");
+
+ // Invalid protocol
+ config = makeParamsConfigString ("127.0.0.1", 777, 333, "BOGUS", "JSON");
+ SYNTAX_ERROR(config, "<string>:1.92-98: syntax error,"
+ " unexpected constant string, expecting UDP or TCP");
+
+ // Unsupported protocol
+ config = makeParamsConfigString ("127.0.0.1", 777, 333, "TCP", "JSON");
+ LOGIC_ERROR(config, "ncr-protocol : TCP is not yet supported"
+ " (<string>:1:92)");
+
+ // Invalid format
+ config = makeParamsConfigString ("127.0.0.1", 777, 333, "UDP", "BOGUS");
+ SYNTAX_ERROR(config, "<string>:1.115-121: syntax error,"
+ " unexpected constant string, expecting JSON");
+}
+
+// Control socket tests in d2_process_unittests.cc
+
+// DdnsDomainList and TSIGKey tests moved to d2_simple_parser_unittest.cc
+
+/// @brief Tests construction of D2CfgMgr
+/// This test verifies that a D2CfgMgr constructs properly.
+TEST(D2CfgMgr, construction) {
+ boost::scoped_ptr<D2CfgMgr> cfg_mgr;
+
+ // Verify that configuration manager constructions without error.
+ ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+
+ // Verify that the context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr->getD2CfgContext());
+ EXPECT_TRUE(context);
+
+ // Verify that the forward manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getForwardMgr());
+
+ // Verify that the reverse manager can be retrieved and is not null.
+ EXPECT_TRUE(context->getReverseMgr());
+
+ // Verify that the manager can be destructed without error.
+ EXPECT_NO_THROW(cfg_mgr.reset());
+}
+
+/// @brief Tests the parsing of a complete, valid DHCP-DDNS configuration.
+/// This tests passes the configuration into an instance of D2CfgMgr just
+/// as it would be done by d2_process in response to a configuration update
+/// event.
+TEST_F(D2CfgMgrTest, fullConfig) {
+ // Create a configuration with all of application level parameters, plus
+ // both the forward and reverse ddns managers. Both managers have two
+ // domains with three servers per domain.
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"dns-server-timeout\": 333 , "
+ "\"ncr-protocol\": \"UDP\" , "
+ "\"ncr-format\": \"JSON\", "
+ "\"control-socket\" : {"
+ " \"socket-type\" : \"unix\" ,"
+ " \"socket-name\" : \"/tmp/d2-ctrl-channel\" "
+ "},"
+ "\"hooks-libraries\": ["
+ "{"
+ " \"library\": \"%LIBRARY%\" , "
+ " \"parameters\": "
+ " { \"param1\": \"foo\" } "
+ "}"
+ "],"
+ "\"tsig-keys\": ["
+ "{"
+ " \"name\": \"d2_key.example.com\" , "
+ " \"algorithm\": \"hmac-md5\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "},"
+ "{"
+ " \"name\": \"d2_key.billcat.net\" , "
+ " \"algorithm\": \"hmac-md5\" , "
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}"
+ "],"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } , "
+ " { \"ip-address\": \"127.0.0.2\" } , "
+ " { \"ip-address\": \"127.0.0.3\"} "
+ " ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key-name\": \"d2_key.billcat.net\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.4\" } , "
+ " { \"ip-address\": \"127.0.0.5\" } , "
+ " { \"ip-address\": \"127.0.0.6\" } "
+ " ] } "
+ "] },"
+ "\"reverse-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.1.1\" } , "
+ " { \"ip-address\": \"127.0.2.1\" } , "
+ " { \"ip-address\": \"127.0.3.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \" 0.247.106.in.addr.arpa.\" , "
+ " \"key-name\": \"d2_key.billcat.net\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.4.1\" }, "
+ " { \"ip-address\": \"127.0.5.1\" } , "
+ " { \"ip-address\": \"127.0.6.1\" } "
+ " ] } "
+ "] } }";
+
+ // Replace the library path.
+ std::string pr_config = pathReplacer(config.c_str(), CALLOUT_LIBRARY);
+ // Should parse without error.
+ RUN_CONFIG_OK(pr_config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that the global scalars have the proper values.
+ D2ParamsPtr& d2_params = context->getD2Params();
+ ASSERT_TRUE(d2_params);
+
+ EXPECT_EQ(isc::asiolink::IOAddress("192.168.1.33"),
+ d2_params->getIpAddress());
+ EXPECT_EQ(88, d2_params->getPort());
+ EXPECT_EQ(333, d2_params->getDnsServerTimeout());
+ EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_params->getNcrProtocol());
+ EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_params->getNcrFormat());
+
+ // Verify that the control socket can be retrieved.
+ ConstElementPtr ctrl_sock = context->getControlSocketInfo();
+ ASSERT_TRUE(ctrl_sock);
+ ASSERT_EQ(Element::map, ctrl_sock->getType());
+ EXPECT_EQ(2, ctrl_sock->size());
+ ASSERT_TRUE(ctrl_sock->get("socket-type"));
+ EXPECT_EQ("\"unix\"", ctrl_sock->get("socket-type")->str());
+ ASSERT_TRUE(ctrl_sock->get("socket-name"));
+ EXPECT_EQ("\"/tmp/d2-ctrl-channel\"", ctrl_sock->get("socket-name")->str());
+
+ // Verify that the hooks libraries can be retrieved.
+ const HookLibsCollection libs = context->getHooksConfig().get();
+ ASSERT_EQ(1, libs.size());
+ EXPECT_EQ(string(CALLOUT_LIBRARY), libs[0].first);
+ ASSERT_TRUE(libs[0].second);
+ EXPECT_EQ("{ \"param1\": \"foo\" }", libs[0].second->str());
+
+ // Verify that the forward manager can be retrieved.
+ DdnsDomainListMgrPtr mgr = context->getForwardMgr();
+ ASSERT_TRUE(mgr);
+ EXPECT_EQ("forward-ddns", mgr->getName());
+
+ // Verify that the forward manager has the correct number of domains.
+ DdnsDomainMapPtr domains = mgr->getDomains();
+ ASSERT_TRUE(domains);
+ int count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the forward manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ DdnsDomainMapPair domain_pair;
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // Verify that the reverse manager can be retrieved.
+ mgr = context->getReverseMgr();
+ ASSERT_TRUE(mgr);
+ EXPECT_EQ("reverse-ddns", mgr->getName());
+
+ // Verify that the reverse manager has the correct number of domains.
+ domains = mgr->getDomains();
+ count = domains->size();
+ EXPECT_EQ(2, count);
+
+ // Verify that the server count in each of the reverse manager domains.
+ // NOTE that since prior tests have validated server parsing, we are are
+ // assuming that the servers did in fact parse correctly if the correct
+ // number of them are there.
+ BOOST_FOREACH(domain_pair, (*domains)) {
+ DdnsDomainPtr domain = domain_pair.second;
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ count = servers->size();
+ EXPECT_TRUE(servers);
+ EXPECT_EQ(3, count);
+ }
+
+ // Test directional update flags.
+ EXPECT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+ EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Verify that parsing the exact same configuration a second time
+ // does not cause a duplicate value errors.
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_, false);
+ ASSERT_TRUE(checkAnswer(0));
+}
+
+/// @brief Tests the basics of the D2CfgMgr FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr
+/// forward FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, forwardMatch) {
+ // Create configuration with one domain, one sub domain, and the wild
+ // card.
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.2\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"*\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" } "
+ " ] } "
+ "] }, "
+ "\"reverse-ddns\" : {} "
+ "}";
+
+ // Verify that we can parse the configuration.
+ RUN_CONFIG_OK(config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Test directional update flags.
+ EXPECT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+ EXPECT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
+ DdnsDomainPtr match;
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ // Verify that search is case insensitive.
+ EXPECT_TRUE(cfg_mgr_->matchForward("EXAMPLE.COM", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ // Verify that an exact match works.
+ EXPECT_TRUE(cfg_mgr_->matchForward("one.example.com", match));
+ EXPECT_EQ("one.example.com", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.example.com", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ // Verify that a FQDN for sub-domain matches.
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.example.com", match));
+ EXPECT_EQ("one.example.com", match->getName());
+
+ // Verify that an FQDN with no match, returns the wild card domain.
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN throws.
+ ASSERT_THROW(cfg_mgr_->matchForward("", match), D2CfgError);
+}
+
+/// @brief Tests domain matching when there is no wild card domain.
+/// This test verifies that matches are found only for FQDNs that match
+/// some or all of a domain name. FQDNs without matches should not return
+/// a match.
+TEST_F(D2CfgMgrTest, matchNoWildcard) {
+ // Create a configuration with one domain, one sub-domain, and NO wild card.
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ ", "
+ "{ \"name\": \"one.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.2\" } "
+ " ] } "
+ "] }, "
+ "\"reverse-ddns\" : {} "
+ " }";
+
+ // Verify that we can parse the configuration.
+ RUN_CONFIG_OK(config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ DdnsDomainPtr match;
+ // Verify that full or partial matches, still match.
+ EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("blue.example.com", match));
+ EXPECT_EQ("example.com", match->getName());
+
+ EXPECT_TRUE(cfg_mgr_->matchForward("red.one.example.com", match));
+ EXPECT_EQ("one.example.com", match->getName());
+
+ // Verify that a FQDN with no match, fails to match.
+ EXPECT_FALSE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+}
+
+/// @brief Tests domain matching when there is ONLY a wild card domain.
+/// This test verifies that any FQDN matches the wild card.
+TEST_F(D2CfgMgrTest, matchAll) {
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"*\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ "] }, "
+ "\"reverse-ddns\" : {} "
+ "}";
+
+ // Verify that we can parse the configuration.
+ RUN_CONFIG_OK(config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Verify that wild card domain is returned for any FQDN.
+ DdnsDomainPtr match;
+ EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
+ EXPECT_EQ("*", match->getName());
+ EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an empty FQDN still throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+
+}
+
+/// @brief Tests the basics of the D2CfgMgr reverse FQDN-domain matching
+/// This test uses a valid configuration to exercise the D2CfgMgr's
+/// reverse FQDN-to-domain matching.
+/// It verifies that:
+/// 1. Given an FQDN which exactly matches a domain's name, that domain is
+/// returned as match.
+/// 2. Given a FQDN for sub-domain in the list, returns the proper match.
+/// 3. Given a FQDN that matches no domain name, returns the wild card domain
+/// as a match.
+TEST_F(D2CfgMgrTest, matchReverse) {
+ std::string config = "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {}, "
+ "\"reverse-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"5.100.168.192.in-addr.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"100.200.192.in-addr.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] }, "
+ "{ \"name\": \"170.192.in-addr.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] }, "
+ // Note mixed case to test case insensitivity.
+ "{ \"name\": \"2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] },"
+ "{ \"name\": \"*\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ "] } }";
+
+ // Verify that we can parse the configuration.
+ RUN_CONFIG_OK(config);
+
+ // Verify that the D2 context can be retrieved and is not null.
+ D2CfgContextPtr context;
+ ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+
+ // Test directional update flags.
+ EXPECT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+ EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+ DdnsDomainPtr match;
+
+ // Verify an exact match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.168.100.5", match));
+ EXPECT_EQ("5.100.168.192.in-addr.arpa.", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.200.100.27", match));
+ EXPECT_EQ("100.200.192.in-addr.arpa.", match->getName());
+
+ // Verify a sub-domain match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("192.170.50.30", match));
+ EXPECT_EQ("170.192.in-addr.arpa.", match->getName());
+
+ // Verify a wild card match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("1.1.1.1", match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify a IPv6 match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:302:99::",match));
+ EXPECT_EQ("2.0.3.0.8.b.d.0.1.0.0.2.IP6.ARPA.", match->getName());
+
+ // Verify a IPv6 wild card match.
+ EXPECT_TRUE(cfg_mgr_->matchReverse("2001:db8:99:302::",match));
+ EXPECT_EQ("*", match->getName());
+
+ // Verify that an attempt to match an invalid IP address throws.
+ ASSERT_THROW(cfg_mgr_->matchReverse("", match), D2CfgError);
+}
+
+/// @brief Tests D2 config parsing against a wide range of config permutations.
+///
+/// It tests for both syntax errors that the JSON parsing (D2ParserContext)
+/// should detect as well as post-JSON parsing logic errors generated by
+/// the Element parsers (i.e...SimpleParser/DhcpParser derivations)
+///
+/// It iterates over all of the test configurations described in given file.
+/// The file content is JSON specialized to this test. The format of the file
+/// is:
+///
+/// @code
+/// # The file must open with a list. It's name is arbitrary.
+///
+/// { "test_list" :
+/// [
+///
+/// # Test one starts here:
+/// {
+///
+/// # Each test has:
+/// # 1. description - optional text description
+/// # 2. syntax-error - error JSON parser should emit (omit if none)
+/// # 3. logic-error - error element parser(s) should emit (omit if none)
+/// # 4. data - configuration text to parse
+/// #
+/// "description" : "<text describing test>",
+/// "syntax-error" : "<exact text from JSON parser including position>" ,
+/// "logic-error" : "<exact text from element parser including position>" ,
+/// "data" :
+/// {
+/// # configuration elements here
+/// "bool_val" : false,
+/// "some_map" : {}
+/// # :
+/// }
+/// }
+///
+/// # Next test would start here
+/// ,
+/// {
+/// }
+///
+/// ]}
+///
+/// @endcode
+///
+/// (The file supports comments per Element::fromJSONFile())
+///
+TEST_F(D2CfgMgrTest, configPermutations) {
+ std::string test_file = testDataFile("d2_cfg_tests.json");
+ isc::data::ConstElementPtr tests;
+
+ // Read contents of the file and parse it as JSON. Note it must contain
+ // all valid JSON, we aren't testing JSON parsing.
+ try {
+ tests = isc::data::Element::fromJSONFile(test_file, true);
+ } catch (const std::exception& ex) {
+ FAIL() << "ERROR parsing file : " << test_file << " : " << ex.what();
+ }
+
+ // Read in each test For each test, read:
+ //
+ // 1. description - optional text description
+ // 2. syntax-error or logic-error or neither
+ // 3. data - configuration text to parse
+ // 4. convert data into JSON text
+ // 5. submit JSON for parsing
+ isc::data::ConstElementPtr test;
+ ASSERT_TRUE(tests->get("test-list"));
+ BOOST_FOREACH(test, tests->get("test-list")->listValue()) {
+ // Grab the description.
+ std::string description = "<no desc>";
+ isc::data::ConstElementPtr elem = test->get("description");
+ if (elem) {
+ elem->getValue(description);
+ }
+
+ // Grab the expected error message, if there is one.
+ std::string expected_error = "";
+ RunConfigMode mode = NO_ERROR;
+ elem = test->get("syntax-error");
+ if (elem) {
+ elem->getValue(expected_error);
+ mode = SYNTAX_ERROR;
+ } else {
+ elem = test->get("logic-error");
+ if (elem) {
+ elem->getValue(expected_error);
+ mode = LOGIC_ERROR;
+ }
+ }
+
+ // Grab the test's configuration data.
+ isc::data::ConstElementPtr data = test->get("data");
+ ASSERT_TRUE(data) << "No data for test: " << test->getPosition();
+
+ // Convert the test data back to JSON text, then submit it for parsing.
+ stringstream os;
+ data->toJSON(os);
+ EXPECT_TRUE(runConfigOrFail(os.str(), mode, expected_error))
+ << " failed for test: " << test->getPosition() << std::endl;
+ }
+}
+
+/// @brief Tests comments.
+TEST_F(D2CfgMgrTest, comments) {
+ std::string config = "{ "
+ "\"comment\": \"D2 config\" , "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"control-socket\": {"
+ " \"comment\": \"Control channel\" , "
+ " \"socket-type\": \"unix\" ,"
+ " \"socket-name\": \"/tmp/d2-ctrl-channel\" "
+ "},"
+ "\"tsig-keys\": ["
+ "{"
+ " \"user-context\": { "
+ " \"comment\": \"Indirect comment\" } , "
+ " \"name\": \"d2_key.example.com\" , "
+ " \"algorithm\": \"hmac-md5\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}"
+ "],"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"comment\": \"A DDNS domain\" , "
+ " \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" , "
+ " \"user-context\": { \"version\": 1 } } "
+ " ] } "
+ "] } }";
+
+ // Should parse without error.
+ RUN_CONFIG_OK(config);
+
+ // Check the D2 context.
+ D2CfgContextPtr d2_context;
+ ASSERT_NO_THROW(d2_context = cfg_mgr_->getD2CfgContext());
+ ASSERT_TRUE(d2_context);
+
+ // Check global user context.
+ ConstElementPtr ctx = d2_context->getContext();
+ ASSERT_TRUE(ctx);
+ ASSERT_EQ(1, ctx->size());
+ ASSERT_TRUE(ctx->get("comment"));
+ EXPECT_EQ("\"D2 config\"", ctx->get("comment")->str());
+
+ // Check control socket.
+ ConstElementPtr ctrl_sock = d2_context->getControlSocketInfo();
+ ASSERT_TRUE(ctrl_sock);
+ ASSERT_TRUE(ctrl_sock->get("user-context"));
+ EXPECT_EQ("{ \"comment\": \"Control channel\" }",
+ ctrl_sock->get("user-context")->str());
+
+ // Check TSIG keys.
+ TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ ASSERT_EQ(1, keys->size());
+
+ // Check the TSIG key.
+ TSIGKeyInfoMap::iterator gotkey = keys->find("d2_key.example.com");
+ ASSERT_TRUE(gotkey != keys->end());
+ TSIGKeyInfoPtr key = gotkey->second;
+ ASSERT_TRUE(key);
+
+ // Check the TSIG key user context.
+ ConstElementPtr key_ctx = key->getContext();
+ ASSERT_TRUE(key_ctx);
+ ASSERT_EQ(1, key_ctx->size());
+ ASSERT_TRUE(key_ctx->get("comment"));
+ EXPECT_EQ("\"Indirect comment\"", key_ctx->get("comment")->str());
+
+ // Check the forward manager.
+ DdnsDomainListMgrPtr mgr = d2_context->getForwardMgr();
+ ASSERT_TRUE(mgr);
+ EXPECT_EQ("forward-ddns", mgr->getName());
+ DdnsDomainMapPtr domains = mgr->getDomains();
+ ASSERT_TRUE(domains);
+ ASSERT_EQ(1, domains->size());
+
+ // Check the DDNS domain.
+ DdnsDomainMap::iterator gotdns = domains->find("example.com");
+ ASSERT_TRUE(gotdns != domains->end());
+ DdnsDomainPtr domain = gotdns->second;
+ ASSERT_TRUE(domain);
+
+ // Check the DNS server.
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ ASSERT_TRUE(servers);
+ ASSERT_EQ(1, servers->size());
+ DnsServerInfoPtr server = (*servers)[0];
+ ASSERT_TRUE(server);
+
+ // Check the DNS server user context.
+ ConstElementPtr srv_ctx = server->getContext();
+ ASSERT_TRUE(srv_ctx);
+ ASSERT_EQ(1, srv_ctx->size());
+ ASSERT_TRUE(srv_ctx->get("version"));
+ EXPECT_EQ("1", srv_ctx->get("version")->str());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_command_unittest.cc b/src/bin/d2/tests/d2_command_unittest.cc
new file mode 100644
index 0000000..8f956e7
--- /dev/null
+++ b/src/bin/d2/tests/d2_command_unittest.cc
@@ -0,0 +1,1391 @@
+// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
+#include <config/timeouts.h>
+#include <testutils/io_utils.h>
+#include <testutils/unix_control_client.h>
+#include <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <d2/parser_context.h>
+#include <gtest/gtest.h>
+#include <testutils/sandbox.h>
+#include <boost/pointer_cast.hpp>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <thread>
+#include <unistd.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::dhcp::test;
+using namespace isc::process;
+using namespace boost::asio;
+namespace ph = std::placeholders;
+
+namespace isc {
+namespace d2 {
+
+class NakedD2Controller;
+typedef boost::shared_ptr<NakedD2Controller> NakedD2ControllerPtr;
+
+class NakedD2Controller : public D2Controller {
+ // "Naked" D2 controller, exposes internal methods.
+public:
+ static DControllerBasePtr& instance() {
+ static DControllerBasePtr controller_ptr;
+ if (!controller_ptr) {
+ controller_ptr.reset(new NakedD2Controller());
+ }
+
+ return (controller_ptr);
+ }
+
+ virtual ~NakedD2Controller() { deregisterCommands(); }
+
+ using DControllerBase::getIOService;
+ using DControllerBase::initProcess;
+
+ D2ProcessPtr getProcess() {
+ return (boost::dynamic_pointer_cast<D2Process>(DControllerBase::getProcess()));
+ }
+
+private:
+ NakedD2Controller() { }
+};
+
+}; // namespace isc::d2
+}; // namespace isc
+
+namespace {
+
+/// @brief Simple RAII class which stops IO service upon destruction
+/// of the object.
+class IOServiceWork {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param io_service Pointer to the IO service to be stopped.
+ explicit IOServiceWork(const IOServicePtr& io_service)
+ : io_service_(io_service) {
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Stops IO service.
+ ~IOServiceWork() {
+ io_service_->stop();
+ }
+
+private:
+
+ /// @brief Pointer to the IO service to be stopped upon destruction.
+ IOServicePtr io_service_;
+
+};
+
+/// @brief Fixture class intended for testing control channel in D2.
+class CtrlChannelD2Test : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// @brief Path to the UNIX socket being used to communicate with the server.
+ string socket_path_;
+
+ /// @brief Reference to the base controller object.
+ DControllerBasePtr& server_;
+
+ /// @brief Cast controller object.
+ NakedD2Controller* d2Controller() {
+ return (dynamic_cast<NakedD2Controller*>(server_.get()));
+ }
+
+ /// @brief Configuration file.
+ static const char* CFG_TEST_FILE;
+
+ /// @brief Default constructor.
+ ///
+ /// Sets socket path to its default value.
+ CtrlChannelD2Test()
+ : server_(NakedD2Controller::instance()) {
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path_ = string(env) + "/d2.sock";
+ } else {
+ socket_path_ = sandbox.join("d2.sock");
+ }
+ ::remove(socket_path_.c_str());
+ }
+
+ /// @brief Destructor.
+ ~CtrlChannelD2Test() {
+ // Deregister & co.
+ server_.reset();
+
+ // Remove files.
+ ::remove(CFG_TEST_FILE);
+ ::remove(socket_path_.c_str());
+
+ // Reset command manager.
+ CommandMgr::instance().deregisterAll();
+ CommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
+ }
+
+ /// @brief Returns pointer to the server's IO service.
+ ///
+ /// @return Pointer to the server's IO service or null pointer if the
+ /// hasn't been created server.
+ IOServicePtr getIOService() {
+ return (server_ ? d2Controller()->getIOService() : IOServicePtr());
+ }
+
+ /// @brief Runs parser in DHCPDDNS mode
+ ///
+ /// @param config input configuration
+ /// @param verbose display errors
+ /// @return element pointer representing the configuration
+ ElementPtr parseDHCPDDNS(const string& config, bool verbose = false) {
+ try {
+ D2ParserContext ctx;
+ return (ctx.parseString(config,
+ D2ParserContext::PARSER_SUB_DHCPDDNS));
+ } catch (const std::exception& ex) {
+ if (verbose) {
+ cout << "EXCEPTION: " << ex.what() << endl;
+ }
+ throw;
+ }
+ }
+
+ /// @brief Create a server with a command channel.
+ void createUnixChannelServer() {
+ ::remove(socket_path_.c_str());
+
+ // Just a simple config. The important part here is the socket
+ // location information.
+ string header =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"";
+
+ string footer =
+ "\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+
+ // Fill in the socket-name value with socket_path_ to make
+ // the actual configuration text.
+ string config_txt = header + socket_path_ + footer;
+
+ ASSERT_TRUE(server_);
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCPDDNS(config_txt, true));
+ ASSERT_NO_THROW(d2Controller()->initProcess());
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+ ConstElementPtr answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+ ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+ int status = 0;
+ ConstElementPtr txt = parseAnswer(status, answer);
+ // This should succeed. If not, print the error message.
+ ASSERT_EQ(0, status) << txt->str();
+
+ // Now check that the socket was indeed open.
+ ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+ }
+
+ /// @brief Conducts a command/response exchange via UnixCommandSocket.
+ ///
+ /// This method connects to the given server over the given socket path.
+ /// If successful, it then sends the given command and retrieves the
+ /// server's response. Note that it polls the server's I/O service
+ /// where needed to cause the server to process IO events on
+ /// the control channel sockets
+ ///
+ /// @param command the command text to execute in JSON form
+ /// @param response variable into which the received response should be
+ /// placed.
+ void sendUnixCommand(const string& command, string& response) {
+ response = "";
+ boost::scoped_ptr<UnixControlClient> client;
+ client.reset(new UnixControlClient());
+ ASSERT_TRUE(client);
+
+ // Connect to the server. This is expected to trigger server's acceptor
+ // handler when IOService::poll() is run.
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Send the command. This will trigger server's handler which receives
+ // data over the unix domain socket. The server will start sending
+ // response to the client.
+ ASSERT_TRUE(client->sendCommand(command));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Read the response generated by the server. Note that getResponse
+ // only fails if there an IO error or no response data was present.
+ // It is not based on the response content.
+ ASSERT_TRUE(client->getResponse(response));
+
+ // Now disconnect and process the close event.
+ client->disconnectFromServer();
+
+ ASSERT_NO_THROW(getIOService()->poll());
+ }
+
+ /// @brief Checks response for list-commands.
+ ///
+ /// This method checks if the list-commands response is generally sane
+ /// and whether specified command is mentioned in the response.
+ ///
+ /// @param rsp response sent back by the server.
+ /// @param command command expected to be on the list.
+ void checkListCommands(const ConstElementPtr& rsp, const string command) {
+ ConstElementPtr params;
+ int status_code = -1;
+ EXPECT_NO_THROW(params = parseAnswer(status_code, rsp));
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status_code);
+ ASSERT_TRUE(params);
+ ASSERT_EQ(Element::list, params->getType());
+
+ int cnt = 0;
+ for (size_t i = 0; i < params->size(); ++i) {
+ string tmp = params->get(i)->stringValue();
+ if (tmp == command) {
+ // Command found, but that's not enough.
+ // Need to continue working through the list to see
+ // if there are no duplicates.
+ cnt++;
+ }
+ }
+
+ // Exactly one command on the list is expected.
+ EXPECT_EQ(1, cnt) << "Command " << command << " not found";
+ }
+
+ /// @brief Check if the answer for config-write command is correct.
+ ///
+ /// @param response_txt response in text form.
+ /// (as read from the control socket)
+ /// @param exp_status expected status.
+ /// (0 success, 1 failure)
+ /// @param exp_txt for success cases this defines the expected filename,
+ /// for failure cases this defines the expected error message.
+ void checkConfigWrite(const string& response_txt, int exp_status,
+ const string& exp_txt = "") {
+
+ ConstElementPtr rsp;
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response_txt));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr params = parseAnswer(status, rsp);
+ EXPECT_EQ(exp_status, status);
+
+ if (exp_status == CONTROL_RESULT_SUCCESS) {
+ // Let's check couple things...
+
+ // The parameters must include filename.
+ ASSERT_TRUE(params);
+ ASSERT_TRUE(params->get("filename"));
+ ASSERT_EQ(Element::string, params->get("filename")->getType());
+ EXPECT_EQ(exp_txt, params->get("filename")->stringValue());
+
+ // The parameters must include size. And the size
+ // must indicate some content.
+ ASSERT_TRUE(params->get("size"));
+ ASSERT_EQ(Element::integer, params->get("size")->getType());
+ int64_t size = params->get("size")->intValue();
+ EXPECT_LE(1, size);
+
+ // Now check if the file is really there and suitable for
+ // opening.
+ ifstream f(exp_txt, ios::binary | ios::ate);
+ ASSERT_TRUE(f.good());
+
+ // Now check that it is the correct size as reported.
+ EXPECT_EQ(size, static_cast<int64_t>(f.tellg()));
+
+ // Finally, check that it's really a JSON.
+ ElementPtr from_file = Element::fromJSONFile(exp_txt);
+ ASSERT_TRUE(from_file);
+ } else if (exp_status == CONTROL_RESULT_ERROR) {
+
+ // Let's check if the reason for failure was given.
+ ConstElementPtr text = rsp->get("text");
+ ASSERT_TRUE(text);
+ ASSERT_EQ(Element::string, text->getType());
+ EXPECT_EQ(exp_txt, text->stringValue());
+ } else {
+ ADD_FAILURE() << "Invalid expected status: " << exp_status;
+ }
+ }
+
+ /// @brief Handler for long command.
+ ///
+ /// It checks whether the received command is equal to the one specified
+ /// as an argument.
+ ///
+ /// @param expected_command String representing an expected command.
+ /// @param command_name Command name received by the handler.
+ /// @param arguments Command arguments received by the handler.
+ ///
+ /// @returns Success answer.
+ static ConstElementPtr
+ longCommandHandler(const string& expected_command,
+ const string& command_name,
+ const ConstElementPtr& arguments) {
+ // The handler is called with a command name and the structure holding
+ // command arguments. We have to rebuild the command from those
+ // two arguments so as it can be compared against expected_command.
+ ElementPtr entire_command = Element::createMap();
+ entire_command->set("command", Element::create(command_name));
+ entire_command->set("arguments", (arguments));
+
+ // The rebuilt command will have a different order of parameters so
+ // let's parse expected_command back to JSON to guarantee that
+ // both structures are built using the same order.
+ EXPECT_EQ(Element::fromJSON(expected_command)->str(),
+ entire_command->str());
+ return (createAnswer(0, "long command received ok"));
+ }
+
+ /// @brief Command handler which generates long response.
+ ///
+ /// This handler generates a large response (over 400kB). It includes
+ /// a list of randomly generated strings to make sure that the test
+ /// can catch out of order delivery.
+ static ConstElementPtr
+ longResponseHandler(const string&, const ConstElementPtr&) {
+ ElementPtr arguments = Element::createList();
+ for (unsigned i = 0; i < 80000; ++i) {
+ std::ostringstream s;
+ s << std::setw(5) << i;
+ arguments->add(Element::create(s.str()));
+ }
+ return (createAnswer(0, arguments));
+ }
+};
+
+const char* CtrlChannelD2Test::CFG_TEST_FILE = "d2-test-config.json";
+
+// Test bad syntax rejected by the parser.
+TEST_F(CtrlChannelD2Test, parser) {
+ // no empty map.
+ string bad1 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": { },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ASSERT_THROW(parseDHCPDDNS(bad1), D2ParseError);
+
+ // unknown keyword.
+ string bad2 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"/tmp/d2.sock\","
+ " \"bogus\": \"unknown...\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ASSERT_THROW(parseDHCPDDNS(bad2), D2ParseError);
+}
+
+// Test bad syntax rejected by the process.
+TEST_F(CtrlChannelD2Test, configure) {
+ ASSERT_TRUE(server_);
+ ASSERT_NO_THROW(d2Controller()->initProcess());
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+
+ // no type.
+ string bad1 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-name\": \"/tmp/d2.sock\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCPDDNS(bad1, true));
+
+ ConstElementPtr answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+
+ int status = 0;
+ ConstElementPtr txt = parseAnswer(status, answer);
+ EXPECT_EQ(1, status);
+ ASSERT_TRUE(txt);
+ ASSERT_EQ(Element::string, txt->getType());
+ EXPECT_EQ("Mandatory 'socket-type' parameter missing", txt->stringValue());
+ EXPECT_EQ(-1, CommandMgr::instance().getControlSocketFD());
+
+ // bad type.
+ string bad2 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"bogus\","
+ " \"socket-name\": \"/tmp/d2.sock\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ASSERT_NO_THROW(config = parseDHCPDDNS(bad2, true));
+
+ answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+
+ status = 0;
+ txt = parseAnswer(status, answer);
+ EXPECT_EQ(1, status);
+ ASSERT_TRUE(txt);
+ ASSERT_EQ(Element::string, txt->getType());
+ EXPECT_EQ("Invalid 'socket-type' parameter value bogus",
+ txt->stringValue());
+ EXPECT_EQ(-1, CommandMgr::instance().getControlSocketFD());
+
+ // no name.
+ string bad3 =
+ "{"
+ " \"ip-address\": \"192.168.77.1\","
+ " \"port\": 777,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\""
+ " },"
+ " \"tsig-keys\": [],"
+ " \"forward-ddns\" : {},"
+ " \"reverse-ddns\" : {}"
+ "}";
+ ASSERT_NO_THROW(config = parseDHCPDDNS(bad3, true));
+
+ answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+
+ status = 0;
+ txt = parseAnswer(status, answer);
+ EXPECT_EQ(1, status);
+ ASSERT_TRUE(txt);
+ ASSERT_EQ(Element::string, txt->getType());
+ EXPECT_EQ("Mandatory 'socket-name' parameter missing",
+ txt->stringValue());
+ EXPECT_EQ(-1, CommandMgr::instance().getControlSocketFD());
+}
+
+// This test checks which commands are registered by the D2 server.
+TEST_F(CtrlChannelD2Test, commandsRegistration) {
+
+ ConstElementPtr list_cmds = createCommand("list-commands");
+ ConstElementPtr answer;
+
+ // By default the list should be empty (except the standard list-commands
+ // supported by the CommandMgr itself).
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+
+ // Created server should register several additional commands.
+ EXPECT_NO_THROW(createUnixChannelServer());
+
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ string command_list = answer->get("arguments")->str();
+
+ EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"build-report\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-reload\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-set\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-test\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"config-write\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"shutdown\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"status-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-reset\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"statistic-reset-all\"") != string::npos);
+ EXPECT_TRUE(command_list.find("\"version-get\"") != string::npos);
+
+ // Ok, and now delete the server. It should deregister its commands.
+ server_.reset();
+
+ // The list should be (almost) empty again.
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+}
+
+// Tests that the server properly responds to invalid commands.
+TEST_F(CtrlChannelD2Test, invalid) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"bogus\" }", response);
+ EXPECT_EQ("{ \"result\": 2, \"text\": \"'bogus' command not supported.\" }",
+ response);
+
+ sendUnixCommand("utter nonsense", response);
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"invalid first character u\" }",
+ response);
+}
+
+// Tests that the server properly responds to shutdown command.
+TEST_F(CtrlChannelD2Test, shutdown) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"shutdown\" }", response);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutdown initiated, type is: normal\" }",
+ response);
+ EXPECT_EQ(EXIT_SUCCESS, server_->getExitValue());
+}
+
+// Tests that the server sets exit value supplied as argument
+// to shutdown command.
+TEST_F(CtrlChannelD2Test, shutdownExitValue) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"shutdown\", "
+ "\"arguments\": { \"exit-value\": 77 }}",
+ response);
+
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutdown initiated, type is: normal\" }",
+ response);
+
+ EXPECT_EQ(77, server_->getExitValue());
+}
+
+// This test verifies that the DHCP server handles version-get commands.
+TEST_F(CtrlChannelD2Test, getversion) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // Send the version-get command.
+ sendUnixCommand("{ \"command\": \"version-get\" }", response);
+ EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
+ EXPECT_TRUE(response.find("log4cplus") != string::npos);
+ EXPECT_FALSE(response.find("GTEST_VERSION") != string::npos);
+
+ // Send the build-report command.
+ sendUnixCommand("{ \"command\": \"build-report\" }", response);
+ EXPECT_TRUE(response.find("\"result\": 0") != string::npos);
+ EXPECT_TRUE(response.find("GTEST_VERSION") != string::npos);
+}
+
+// Tests that the server properly responds to list-commands command.
+TEST_F(CtrlChannelD2Test, listCommands) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"list-commands\" }", response);
+
+ ConstElementPtr rsp;
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+
+ // We expect the server to report at least the following commands:
+ checkListCommands(rsp, "build-report");
+ checkListCommands(rsp, "config-get");
+ checkListCommands(rsp, "config-reload");
+ checkListCommands(rsp, "config-set");
+ checkListCommands(rsp, "config-test");
+ checkListCommands(rsp, "config-write");
+ checkListCommands(rsp, "list-commands");
+ checkListCommands(rsp, "statistic-get");
+ checkListCommands(rsp, "statistic-get-all");
+ checkListCommands(rsp, "statistic-reset");
+ checkListCommands(rsp, "statistic-reset-all");
+ checkListCommands(rsp, "status-get");
+ checkListCommands(rsp, "shutdown");
+ checkListCommands(rsp, "version-get");
+}
+
+// This test verifies that the D2 server handles status-get commands
+TEST_F(CtrlChannelD2Test, statusGet) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+
+ std::string response_txt;
+
+ // Send the version-get command
+ sendUnixCommand("{ \"command\": \"status-get\" }", response_txt);
+ ConstElementPtr response;
+ ASSERT_NO_THROW(response = Element::fromJSON(response_txt));
+ ASSERT_TRUE(response);
+ ASSERT_EQ(Element::map, response->getType());
+ EXPECT_EQ(2, response->size());
+ ConstElementPtr result = response->get("result");
+ ASSERT_TRUE(result);
+ ASSERT_EQ(Element::integer, result->getType());
+ EXPECT_EQ(0, result->intValue());
+ ConstElementPtr arguments = response->get("arguments");
+ ASSERT_EQ(Element::map, arguments->getType());
+
+ // The returned pid should be the pid of our process.
+ auto found_pid = arguments->get("pid");
+ ASSERT_TRUE(found_pid);
+ EXPECT_EQ(static_cast<int64_t>(getpid()), found_pid->intValue());
+
+ // It is hard to check the actual reload time as it is based
+ // on current time. Let's just make sure it is within a reasonable
+ // range.
+ auto found_reload = arguments->get("reload");
+ ASSERT_TRUE(found_reload);
+ EXPECT_LE(found_reload->intValue(), 5);
+ EXPECT_GE(found_reload->intValue(), 0);
+
+ /// @todo uptime is not available in this test, because the launch()
+ /// function is not called. This is not critical to test here,
+ /// because the same logic is tested for CA and in that case the
+ /// uptime is tested.
+}
+
+// Tests if the server returns its configuration using config-get.
+// Note there are separate tests that verify if toElement() called by the
+// config-get handler are actually converting the configuration correctly.
+TEST_F(CtrlChannelD2Test, configGet) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"config-get\" }", response);
+ ConstElementPtr rsp;
+
+ // The response should be a valid JSON.
+ EXPECT_NO_THROW(rsp = Element::fromJSON(response));
+ ASSERT_TRUE(rsp);
+
+ int status;
+ ConstElementPtr cfg = parseAnswer(status, rsp);
+ EXPECT_EQ(CONTROL_RESULT_SUCCESS, status);
+
+ // Ok, now roughly check if the response seems legit.
+ ASSERT_TRUE(cfg);
+ ASSERT_EQ(Element::map, cfg->getType());
+ EXPECT_TRUE(cfg->get("DhcpDdns"));
+}
+
+// Verify that the "config-test" command will do what we expect.
+TEST_F(CtrlChannelD2Test, configTest) {
+
+ // Define strings to permutate the config arguments.
+ // (Note the line feeds makes errors easy to find)
+ string config_test_txt = "{ \"command\": \"config-test\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string d2_header =
+ " \"DhcpDdns\": \n";
+ string d2_cfg_txt =
+ " { \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, \n"
+ " \"forward-ddns\" : {}, \n"
+ " \"reverse-ddns\" : {}, \n"
+ " \"tsig-keys\": [ \n";
+ string key1 =
+ " {\"name\": \"d2_key.example.com\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string key2 =
+ " {\"name\": \"d2_key.billcat.net\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"digest-bits\": 120, \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string bad_key =
+ " {\"BOGUS\": \"d2_key.example.com\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string key_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+
+ ostringstream os;
+ // Create a valid config with all the parts should parse.
+ os << d2_cfg_txt
+ << key1
+ << key_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n";
+
+ ASSERT_TRUE(server_);
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCPDDNS(os.str(), true));
+ ASSERT_NO_THROW(d2Controller()->initProcess());
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+ ConstElementPtr answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
+ answer->str());
+ ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+ // Check that the config was indeed applied.
+ D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
+ ASSERT_TRUE(cfg_mgr);
+ D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
+ ASSERT_TRUE(d2_context);
+ TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+
+ ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << d2_header
+ << d2_cfg_txt
+ << bad_key
+ << key_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close DhcpDdns.
+ << "}}";
+
+ // Send the config-test command.
+ string response;
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"missing parameter 'name' (<wire>:9:14)\" }",
+ response);
+
+ // Check that the config was not lost (fix: reacquire the context).
+ d2_context = cfg_mgr->getD2CfgContext();
+ keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+
+ // Create a valid config with two keys and no command channel.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << d2_header
+ << d2_cfg_txt
+ << key1
+ << ",\n"
+ << key2
+ << key_footer
+ << "}\n" // close DhcpDdns.
+ << "}}";
+
+ // Verify the control channel socket exists.
+ ASSERT_TRUE(test::fileExists(socket_path_));
+
+ // Send the config-test command.
+ sendUnixCommand(os.str(), response);
+
+ // Verify the control channel socket still exists.
+ EXPECT_TRUE(test::fileExists(socket_path_));
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration check successful\" }",
+ response);
+
+ // Check that the config was not applied.
+ d2_context = cfg_mgr->getD2CfgContext();
+ keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+}
+
+// Verify that the "config-set" command will do what we expect.
+TEST_F(CtrlChannelD2Test, configSet) {
+
+ // Define strings to permutate the config arguments.
+ // (Note the line feeds makes errors easy to find)
+ string config_set_txt = "{ \"command\": \"config-set\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string d2_header =
+ " \"DhcpDdns\": \n";
+ string d2_cfg_txt =
+ " { \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, \n"
+ " \"forward-ddns\" : {}, \n"
+ " \"reverse-ddns\" : {}, \n"
+ " \"tsig-keys\": [ \n";
+ string key1 =
+ " {\"name\": \"d2_key.example.com\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string key2 =
+ " {\"name\": \"d2_key.billcat.net\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"digest-bits\": 120, \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string bad_key =
+ " {\"BOGUS\": \"d2_key.example.com\", \n"
+ " \"algorithm\": \"hmac-md5\", \n"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\"} \n";
+ string key_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+
+ ostringstream os;
+ // Create a valid config with all the parts should parse.
+ os << d2_cfg_txt
+ << key1
+ << key_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n";
+
+ ASSERT_TRUE(server_);
+
+ ConstElementPtr config;
+ ASSERT_NO_THROW(config = parseDHCPDDNS(os.str(), true));
+ ASSERT_NO_THROW(d2Controller()->initProcess());
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+ ConstElementPtr answer = proc->configure(config, false);
+ ASSERT_TRUE(answer);
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
+ answer->str());
+ ASSERT_NO_THROW(d2Controller()->registerCommands());
+
+ // Check that the config was indeed applied.
+ D2CfgMgrPtr cfg_mgr = proc->getD2CfgMgr();
+ ASSERT_TRUE(cfg_mgr);
+ D2CfgContextPtr d2_context = cfg_mgr->getD2CfgContext();
+ ASSERT_TRUE(d2_context);
+ TSIGKeyInfoMapPtr keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+
+ ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << config_set_txt << ","
+ << args_txt
+ << d2_header
+ << d2_cfg_txt
+ << bad_key
+ << key_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close DhcpDdns.
+ << "}}";
+
+ // Send the config-set command.
+ string response;
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"missing parameter 'name' (<wire>:9:14)\" }",
+ response);
+
+ // Check that the config was not lost (fix: reacquire the context).
+ d2_context = cfg_mgr->getD2CfgContext();
+ keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(1, keys->size());
+
+ // Create a valid config with two keys and no command channel.
+ os.str("");
+ os << config_set_txt << ","
+ << args_txt
+ << d2_header
+ << d2_cfg_txt
+ << key1
+ << ",\n"
+ << key2
+ << key_footer
+ << "}\n" // close DhcpDdns.
+ << "}}";
+
+ // Verify the control channel socket exists.
+ ASSERT_TRUE(test::fileExists(socket_path_));
+
+ // Send the config-set command.
+ sendUnixCommand(os.str(), response);
+
+ // Verify the control channel socket no longer exists.
+ EXPECT_FALSE(test::fileExists(socket_path_));
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration applied successfully.\" }",
+ response);
+
+ // Check that the config was applied.
+ d2_context = cfg_mgr->getD2CfgContext();
+ keys = d2_context->getKeys();
+ ASSERT_TRUE(keys);
+ EXPECT_EQ(2, keys->size());
+}
+
+// Tests if config-write can be called without any parameters.
+TEST_F(CtrlChannelD2Test, writeConfigNoFilename) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // This is normally set by the command line -c parameter.
+ server_->setConfigFile("test1.json");
+
+ // If the filename is not explicitly specified, the name used
+ // in -c command line switch is used.
+ sendUnixCommand("{ \"command\": \"config-write\" }", response);
+
+ checkConfigWrite(response, CONTROL_RESULT_SUCCESS, "test1.json");
+ ::remove("test1.json");
+}
+
+// Tests if config-write can be called with a valid filename as parameter.
+TEST_F(CtrlChannelD2Test, writeConfigFilename) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ sendUnixCommand("{ \"command\": \"config-write\", "
+ "\"arguments\": { \"filename\": \"test2.json\" } }",
+ response);
+ checkConfigWrite(response, CONTROL_RESULT_SUCCESS, "test2.json");
+ ::remove("test2.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is missing.
+TEST_F(CtrlChannelD2Test, configReloadMissingFile) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("does-not-exist.json");
+
+ // Tell the server to reload its configuration. It should attempt to load
+ // does-not-exist.json (and fail, because the file is not there).
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the reload was rejected.
+ string expected = "{ \"result\": 1, \"text\": "
+ "\"Configuration parsing failed: "
+ "Unable to open file does-not-exist.json\" }";
+ EXPECT_EQ(expected, response);
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is not a valid JSON.
+TEST_F(CtrlChannelD2Test, configReloadBrokenFile) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("testbad.json");
+
+ // Although Kea is smart, its AI routines are not smart enough to handle
+ // this one... at least not yet.
+ ofstream f("testbad.json", ios::trunc);
+ f << "bla bla bla...";
+ f.close();
+
+ // Tell the server to reload its configuration. It should attempt to load
+ // testbad.json (and fail, because the file is not valid JSON).
+ // does-not-exist.json (and fail, because the file is not there).
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the reload was rejected.
+ string expected = "{ \"result\": 1, \"text\": "
+ "\"Configuration parsing failed: "
+ "testbad.json:1.1: Invalid character: b\" }";
+ EXPECT_EQ(expected, response);
+
+ // Remove the file.
+ ::remove("testbad.json");
+}
+
+// Tests if config-reload attempts to reload a file and reports that the
+// file is missing.
+TEST_F(CtrlChannelD2Test, configReloadFileValid) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+ string response;
+
+ // This is normally set to whatever value is passed to -c when the server is
+ // started, but we're not starting it that way, so need to set it by hand.
+ server_->setConfigFile("testvalid.json");
+
+ // Ok, enough fooling around. Let's create a valid config.
+ ofstream f("testvalid.json", ios::trunc);
+ f << "{ \"DhcpDdns\": "
+ << "{"
+ << " \"ip-address\": \"192.168.77.1\" , "
+ << " \"port\": 777 , "
+ << "\"tsig-keys\": [], "
+ << "\"forward-ddns\" : {}, "
+ << "\"reverse-ddns\" : {} "
+ << "}"
+ << " }" << endl;
+ f.close();
+
+ // Tell the server to reload its configuration. It should attempt to load
+ // testvalid.json (and succeed).
+ sendUnixCommand("{ \"command\": \"config-reload\" }", response);
+
+ // Verify the reload was successful.
+ string expected = "{ \"result\": 0, \"text\": "
+ "\"Configuration applied successfully.\" }";
+ EXPECT_EQ(expected, response);
+
+ // Check that the config was indeed applied.
+ D2ProcessPtr proc = d2Controller()->getProcess();
+ ASSERT_TRUE(proc);
+ D2CfgMgrPtr d2_cfg_mgr = proc->getD2CfgMgr();
+ ASSERT_TRUE(d2_cfg_mgr);
+ D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
+ ASSERT_TRUE(d2_params);
+
+ EXPECT_EQ("192.168.77.1", d2_params->getIpAddress().toText());
+ EXPECT_EQ(777, d2_params->getPort());
+ EXPECT_FALSE(d2_cfg_mgr->forwardUpdatesEnabled());
+ EXPECT_FALSE(d2_cfg_mgr->reverseUpdatesEnabled());
+
+ // Remove the file.
+ ::remove("testvalid.json");
+}
+
+/// Verify that concurrent connections over the control channel can be
+/// established. (@todo change when response will be sent in multiple chunks)
+TEST_F(CtrlChannelD2Test, concurrentConnections) {
+ EXPECT_NO_THROW(createUnixChannelServer());
+
+ boost::scoped_ptr<UnixControlClient> client1(new UnixControlClient());
+ ASSERT_TRUE(client1);
+
+ boost::scoped_ptr<UnixControlClient> client2(new UnixControlClient());
+ ASSERT_TRUE(client2);
+
+ // Client 1 connects.
+ ASSERT_TRUE(client1->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Client 2 connects.
+ ASSERT_TRUE(client2->connectToServer(socket_path_));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ // Send the command while another client is connected.
+ ASSERT_TRUE(client2->sendCommand("{ \"command\": \"list-commands\" }"));
+ ASSERT_NO_THROW(getIOService()->poll());
+
+ string response;
+ // The server should respond ok.
+ ASSERT_TRUE(client2->getResponse(response));
+ EXPECT_TRUE(response.find("\"result\": 0") != std::string::npos);
+
+ // Disconnect the servers.
+ client1->disconnectFromServer();
+ client2->disconnectFromServer();
+ ASSERT_NO_THROW(getIOService()->poll());
+}
+
+// This test verifies that the server can receive and process a large command.
+TEST_F(CtrlChannelD2Test, longCommand) {
+
+ ostringstream command;
+
+ // This is the desired size of the command sent to the server (1MB).
+ // The actual size sent will be slightly greater than that.
+ const size_t command_size = 1024 * 1000;
+
+ while (command.tellp() < command_size) {
+
+ // We're sending command 'foo' with arguments being a list of
+ // strings. If this is the first transmission, send command name
+ // and open the arguments list. Also insert the first argument
+ // so as all subsequent arguments can be prefixed with a comma.
+ if (command.tellp() == 0) {
+ command << "{ \"command\": \"foo\", \"arguments\": [ \"begin\"";
+
+ } else {
+ // Generate a random number and insert it into the stream as
+ // 10 digits long string.
+ ostringstream arg;
+ arg << setw(10) << std::rand();
+ // Append the argument in the command.
+ command << ", \"" << arg.str() << "\"\n";
+
+ // If we have hit the limit of the command size, close braces to
+ // get appropriate JSON.
+ if (command.tellp() > command_size) {
+ command << "] }";
+ }
+ }
+ }
+
+ ASSERT_NO_THROW(
+ CommandMgr::instance().registerCommand("foo",
+ std::bind(&CtrlChannelD2Test::longCommandHandler,
+ command.str(), ph::_1, ph::_2));
+ );
+
+ createUnixChannelServer();
+
+ string response;
+ std::thread th([this, &response, &command]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create client which we will use to send command to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+
+ // Connect to the server. This will trigger acceptor handler on the
+ // server side and create a new connection.
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Initially the remaining_string holds the entire command and we
+ // will be erasing the portions that we have sent.
+ string remaining_data = command.str();
+ while (!remaining_data.empty()) {
+ // Send the command in chunks of 1024 bytes.
+ const size_t l = remaining_data.size() < 1024 ? remaining_data.size() : 1024;
+ ASSERT_TRUE(client->sendCommand(remaining_data.substr(0, l)));
+ remaining_data.erase(0, l);
+ }
+
+ // Set timeout to 5 seconds to allow the time for the server to send
+ // a response.
+ const unsigned int timeout = 5;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // We're done. Close the connection to the server.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until the command has been processed and response
+ // received.
+ getIOService()->run();
+
+ // Wait for the thread to complete.
+ th.join();
+
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"long command received ok\" }",
+ response);
+}
+
+// This test verifies that the server can send long response to the client.
+TEST_F(CtrlChannelD2Test, longResponse) {
+ // We need to generate large response. The simplest way is to create
+ // a command and a handler which will generate some static response
+ // of a desired size
+ ASSERT_NO_THROW(
+ CommandMgr::instance().registerCommand("foo",
+ std::bind(&CtrlChannelD2Test::longResponseHandler, ph::_1, ph::_2));
+ );
+
+ createUnixChannelServer();
+
+ // The UnixControlClient doesn't have any means to check that the entire
+ // response has been received. What we want to do is to generate a
+ // reference response using our command handler and then compare
+ // what we have received over the unix domain socket with this reference
+ // response to figure out when to stop receiving.
+ string reference_response = longResponseHandler("foo", ConstElementPtr())->str();
+
+ // In this stream we're going to collect out partial responses.
+ ostringstream response;
+
+ // The client is synchronous so it is useful to run it in a thread.
+ std::thread th([this, &response, reference_response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Remember the response size so as we know when we should stop
+ // receiving.
+ const size_t long_response_size = reference_response.size();
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Send the stub command.
+ std::string command = "{ \"command\": \"foo\", \"arguments\": { } }";
+ ASSERT_TRUE(client->sendCommand(command));
+
+ // Keep receiving response data until we have received the full answer.
+ while (response.tellp() < long_response_size) {
+ std::string partial;
+ const unsigned int timeout = 5;
+ ASSERT_TRUE(client->getResponse(partial, timeout));
+ response << partial;
+ }
+
+ // We have received the entire response, so close the connection and
+ // stop the IO service.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until the entire response has been received.
+ getIOService()->run();
+
+ // Wait for the thread to complete.
+ th.join();
+
+ // Make sure we have received correct response.
+ EXPECT_EQ(reference_response, response.str());
+}
+
+// This test verifies that the server signals timeout if the transmission
+// takes too long, after receiving a partial command
+TEST_F(CtrlChannelD2Test, connectionTimeoutPartialCommand) {
+ createUnixChannelServer();
+
+ // Set connection timeout to 2s to prevent long waiting time for the
+ // timeout during this test.
+ const unsigned short timeout = 2000;
+ CommandMgr::instance().setConnectionTimeout(timeout);
+
+ // Server's response will be assigned to this variable.
+ string response;
+
+ // It is useful to create a thread and run the server and the client
+ // at the same time and independently.
+ std::thread th([this, &response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Send partial command. The server will be waiting for the remaining
+ // part to be sent and will eventually signal a timeout.
+ string command = "{ \"command\": \"foo\" ";
+ ASSERT_TRUE(client->sendCommand(command));
+
+ // Let's wait up to 15s for the server's response. The response
+ // should arrive sooner assuming that the timeout mechanism for
+ // the server is working properly.
+ const unsigned int timeout = 15;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // Explicitly close the client's connection.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until stopped.
+ getIOService()->run();
+
+ // Wait for the thread to return.
+ th.join();
+
+ // Check that the server has signalled a timeout.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"Connection over control channel timed out, discarded partial command of 19 bytes\" }" ,
+ response);
+}
+
+// This test verifies that the server signals timeout if the transmission
+// takes too long, having received no data from the client.
+TEST_F(CtrlChannelD2Test, connectionTimeoutNoData) {
+ createUnixChannelServer();
+
+ // Set connection timeout to 2s to prevent long waiting time for the
+ // timeout during this test.
+ const unsigned short timeout = 2000;
+ CommandMgr::instance().setConnectionTimeout(timeout);
+
+ // Server's response will be assigned to this variable.
+ string response;
+
+ // It is useful to create a thread and run the server and the client
+ // at the same time and independently.
+ std::thread th([this, &response]() {
+
+ // IO service will be stopped automatically when this object goes
+ // out of scope and is destroyed. This is useful because we use
+ // asserts which may break the thread in various exit points.
+ IOServiceWork work(getIOService());
+
+ // Create the client and connect it to the server.
+ boost::scoped_ptr<UnixControlClient> client(new UnixControlClient());
+ ASSERT_TRUE(client);
+ ASSERT_TRUE(client->connectToServer(socket_path_));
+
+ // Let's wait up to 15s for the server's response. The response
+ // should arrive sooner assuming that the timeout mechanism for
+ // the server is working properly.
+ const unsigned int timeout = 15;
+ ASSERT_TRUE(client->getResponse(response, timeout));
+
+ // Explicitly close the client's connection.
+ client->disconnectFromServer();
+ });
+
+ // Run the server until stopped.
+ getIOService()->run();
+
+ // Wait for the thread to return.
+ th.join();
+
+ // Check that the server has signalled a timeout.
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"Connection over control channel timed out\" }",
+ response);
+}
+
+} // End of anonymous namespace
diff --git a/src/bin/d2/tests/d2_controller_unittests.cc b/src/bin/d2/tests/d2_controller_unittests.cc
new file mode 100644
index 0000000..0c6e5ec
--- /dev/null
+++ b/src/bin/d2/tests/d2_controller_unittests.cc
@@ -0,0 +1,303 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/testutils/timed_signal.h>
+#include <cc/command_interpreter.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <d2/d2_controller.h>
+#include <d2/d2_process.h>
+#include <process/testutils/d_test_stubs.h>
+
+#include <boost/pointer_cast.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <sstream>
+
+using namespace isc::asiolink::test;
+using namespace isc::process;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Test fixture class for testing D2Controller class.
+///
+/// This class derives from DControllerTest and wraps a D2Controller. Much of
+/// the underlying functionality is in the DControllerBase class which has an
+/// extensive set of unit tests that are independent of DHCP-DDNS.
+/// @TODO Currently These tests are relatively light and duplicate some of
+/// the testing done on the base class. These tests are sufficient to ensure
+/// that D2Controller properly derives from its base class and to test the
+/// logic that is unique to D2Controller. These tests will be augmented and
+/// or new tests added as additional functionality evolves.
+/// Unlike the stub testing, there is no use of SimFailure to induce error
+/// conditions as this is production code.
+class D2ControllerTest : public DControllerTest {
+public:
+ /// @brief Constructor
+ /// Note the constructor passes in the static D2Controller instance
+ /// method.
+ D2ControllerTest() : DControllerTest(D2Controller::instance) {
+ }
+
+ /// @brief Fetches the D2Controller's D2Process
+ ///
+ /// @return A pointer to the process which may be null if it has not yet
+ /// been instantiated.
+ D2ProcessPtr getD2Process() {
+ return (boost::dynamic_pointer_cast<D2Process>(getProcess()));
+ }
+
+ /// @brief Fetches the D2Process's D2Configuration manager
+ ///
+ /// @return A pointer to the manager which may be null if it has not yet
+ /// been instantiated.
+ D2CfgMgrPtr getD2CfgMgr() {
+ D2CfgMgrPtr p;
+ if (getD2Process()) {
+ p = getD2Process()->getD2CfgMgr();
+ }
+
+ return (p);
+ }
+
+ /// @brief Fetches the D2Configuration manager's D2CfgContext
+ ///
+ /// @return A pointer to the context which may be null if it has not yet
+ /// been instantiated.
+ D2CfgContextPtr getD2CfgContext() {
+ D2CfgContextPtr p;
+ if (getD2CfgMgr()) {
+ p = getD2CfgMgr()->getD2CfgContext();
+ }
+
+ return (p);
+ }
+};
+
+/// @brief Basic Controller instantiation testing.
+/// Verifies that the controller singleton gets created and that the
+/// basic derivation from the base class is intact.
+TEST_F(D2ControllerTest, basicInstanceTesting) {
+ // Verify the singleton instance can be fetched and that
+ // it has the correct type.
+ DControllerBasePtr& controller = DControllerTest::getController();
+ ASSERT_TRUE(controller);
+ ASSERT_NO_THROW(boost::dynamic_pointer_cast<D2Controller>(controller));
+
+ // Verify that controller's app name is correct.
+ EXPECT_TRUE(checkAppName(D2Controller::d2_app_name_));
+
+ // Verify that controller's bin name is correct.
+ EXPECT_TRUE(checkBinName(D2Controller::d2_bin_name_));
+
+ // Verify that controller's IOService exists.
+ EXPECT_TRUE(checkIOService());
+
+ // Verify that the Process does NOT exist.
+ EXPECT_FALSE(checkProcess());
+}
+
+/// @brief Tests basic command line processing.
+/// Verifies that:
+/// 1. Standard command line options are supported.
+/// 2. Invalid options are detected.
+TEST_F(D2ControllerTest, commandLineArgs) {
+ char* argv[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-c"),
+ const_cast<char*>(DControllerTest::CFG_TEST_FILE),
+ const_cast<char*>("-d") };
+ int argc = 4;
+
+ // Verify that verbose flag is false initially.
+ EXPECT_TRUE(checkVerbose(false));
+
+ // Verify that standard options can be parsed without error.
+ EXPECT_NO_THROW(parseArgs(argc, argv));
+
+ // Verify that verbose flag is true.
+ EXPECT_TRUE(checkVerbose(true));
+
+ // Verify configuration file name is correct.
+ EXPECT_TRUE(checkConfigFileName(DControllerTest::CFG_TEST_FILE));
+
+ // Verify that an unknown option is detected.
+ char* argv2[] = { const_cast<char*>("progName"),
+ const_cast<char*>("-x") };
+ argc = 2;
+ EXPECT_THROW(parseArgs(argc, argv2), InvalidUsage);
+}
+
+/// @brief Tests application process creation and initialization.
+/// Verifies that the process can be successfully created and initialized.
+TEST_F(D2ControllerTest, initProcessTesting) {
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+}
+
+/// @brief Tests launch and normal shutdown (stand alone mode).
+/// This creates an interval timer to generate a normal shutdown and then
+/// launches with a valid, stand-alone command line and no simulated errors.
+TEST_F(D2ControllerTest, launchNormalShutdown) {
+ // Write valid_d2_config and then run launch() for 1000 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 1000, elapsed_time);
+
+ // Give a generous margin to accommodate slower test environs.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() >= 800 &&
+ elapsed_time.total_milliseconds() <= 1300);
+}
+
+/// @brief Configuration update event testing.
+/// This really tests just the ability of the handlers to invoke the necessary
+/// chain of methods and handle error conditions. Configuration parsing and
+/// retrieval should be tested as part of the d2 configuration management
+/// implementation.
+/// This test verifies that:
+/// 1. A valid configuration yields a successful parse result.
+/// 2. That an application process error in configuration updating is handled
+/// properly.
+TEST_F(D2ControllerTest, configUpdateTests) {
+ int rcode = -1;
+ isc::data::ConstElementPtr answer;
+
+ // Initialize the application process.
+ ASSERT_NO_THROW(initProcess());
+ EXPECT_TRUE(checkProcess());
+
+ // Create a configuration set using a small, valid D2 configuration.
+ isc::data::ElementPtr config_set =
+ isc::data::Element::fromJSON(valid_d2_config);
+
+ // Verify that given a valid config we get a successful update result.
+ answer = updateConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Verify that given a valid config we get a successful check result.
+ answer = checkConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode);
+
+ // Use an invalid configuration to verify parsing error return.
+ std::string config = "{ \"ip-address\": 1000 } ";
+ config_set = isc::data::Element::fromJSON(config);
+ answer = updateConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+
+ // Use an invalid configuration to verify checking error return.
+ answer = checkConfig(config_set);
+ isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+}
+
+// Tests that the original configuration is retained after a SIGHUP triggered
+// reconfiguration fails due to invalid config content.
+TEST_F(D2ControllerTest, invalidConfigReload) {
+ // Schedule to replace the configuration file after launch. This way the
+ // file is updated after we have done the initial configuration.
+ scheduleTimedWrite("{ \"string_test\": BOGUS JSON }", 100);
+
+ // Setup to raise SIGHUP in 200 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+ // Write valid_d2_config and then run launch() for a maximum of 500 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 500, elapsed_time);
+
+ // Context is still available post launch.
+ // Check to see that our configuration matches the original per
+ // valid_d2_config (see src/lib/process/testutils/d_test_stubs.cc)
+ D2CfgMgrPtr d2_cfg_mgr = getD2CfgMgr();
+ D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
+ ASSERT_TRUE(d2_params);
+
+ EXPECT_EQ("127.0.0.1", d2_params->getIpAddress().toText());
+ EXPECT_EQ(5031, d2_params->getPort());
+ EXPECT_TRUE(d2_cfg_mgr->forwardUpdatesEnabled());
+ EXPECT_TRUE(d2_cfg_mgr->reverseUpdatesEnabled());
+
+ /// @todo add a way to trap log file and search it
+}
+
+// Tests that the original configuration is replaced after a SIGHUP triggered
+// reconfiguration succeeds.
+TEST_F(D2ControllerTest, validConfigReload) {
+ // Define a replacement config.
+ const char* second_cfg =
+ "{"
+ " \"ip-address\": \"192.168.77.1\" , "
+ " \"port\": 777 , "
+ "\"tsig-keys\": [], "
+ "\"forward-ddns\" : {}, "
+ "\"reverse-ddns\" : {} "
+ "}";
+
+ // Schedule to replace the configuration file after launch. This way the
+ // file is updated after we have done the initial configuration.
+ scheduleTimedWrite(second_cfg, 100);
+
+ // Setup to raise SIGHUP in 200 ms.
+ TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+ // Write valid_d2_config and then run launch() for a maximum of 500ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 500, elapsed_time);
+
+ // Context is still available post launch.
+ // Check to see that our configuration matches the replacement config.
+ D2CfgMgrPtr d2_cfg_mgr = getD2CfgMgr();
+ D2ParamsPtr d2_params = d2_cfg_mgr->getD2Params();
+ ASSERT_TRUE(d2_params);
+
+ EXPECT_EQ("192.168.77.1", d2_params->getIpAddress().toText());
+ EXPECT_EQ(777, d2_params->getPort());
+ EXPECT_FALSE(d2_cfg_mgr->forwardUpdatesEnabled());
+ EXPECT_FALSE(d2_cfg_mgr->reverseUpdatesEnabled());
+
+ /// @todo add a way to trap log file and search it
+}
+
+// Tests that the SIGINT triggers a normal shutdown.
+TEST_F(D2ControllerTest, sigintShutdown) {
+ // Setup to raise SIGINT in 1 ms.
+ TimedSignal sighup(*getIOService(), SIGINT, 1);
+
+ // Write valid_d2_config and then run launch() for a maximum of 1000 ms.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 1000, elapsed_time);
+
+ // Signaled shutdown should make our elapsed time much smaller than
+ // the maximum run time. Give generous margin to accommodate slow
+ // test environs.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+
+ /// @todo add a way to trap log file and search it
+}
+
+// Tests that the SIGTERM triggers a normal shutdown.
+TEST_F(D2ControllerTest, sigtermShutdown) {
+ // Setup to raise SIGTERM in 1 ms.
+ TimedSignal sighup(*getIOService(), SIGTERM, 1);
+
+ // Write valid_d2_config and then run launch() for a maximum of 1 s.
+ time_duration elapsed_time;
+ runWithConfig(valid_d2_config, 1000, elapsed_time);
+
+ // Signaled shutdown should make our elapsed time much smaller than
+ // the maximum run time. Give generous margin to accommodate slow
+ // test environs.
+ EXPECT_TRUE(elapsed_time.total_milliseconds() < 300);
+
+ /// @todo add a way to trap log file and search it
+}
+
+}; // end of isc::d2 namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/tests/d2_process_tests.sh.in b/src/bin/d2/tests/d2_process_tests.sh.in
new file mode 100644
index 0000000..895b9a2
--- /dev/null
+++ b/src/bin/d2/tests/d2_process_tests.sh.in
@@ -0,0 +1,332 @@
+#!/bin/sh
+
+# Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# shellcheck disable=SC1091
+# SC1091: Not following: ... was not specified as input (see shellcheck -x).
+
+# shellcheck disable=SC2039
+# SC2039: In POSIX sh, 'local' is undefined.
+
+# Exit with error if commands exit with non-zero and if undefined variables are
+# used.
+set -eu
+
+# Path to the temporary configuration file.
+CFG_FILE="@abs_top_builddir@/src/bin/d2/tests/test_config.json"
+# Path to the D2 log file.
+LOG_FILE="@abs_top_builddir@/src/bin/d2/tests/test.log"
+# D2 configuration to be stored in the configuration file.
+CONFIG="{
+ \"DhcpDdns\":
+ {
+ \"ip-address\": \"127.0.0.1\",
+ \"port\": 53001,
+ \"tsig-keys\": [],
+ \"forward-ddns\" : {},
+ \"reverse-ddns\" : {},
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp-ddns\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"DEBUG\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (syntax error) to check that Kea can check syntax.
+CONFIG_BAD_SYNTAX="{
+ \"DhcpDdns\":
+ {
+ \"ip-address\": \"127.0.0.1\",
+ \"port\": BOGUS,
+ \"tsig-keys\": [],
+ \"forward-ddns\" : {},
+ \"reverse-ddns\" : {},
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp-ddns\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid configuration (out of range port) to check that Kea can check syntax.
+CONFIG_BAD_VALUE="{
+ \"DhcpDdns\":
+ {
+ \"ip-address\": \"127.0.0.1\",
+ \"port\": 80000,
+ \"tsig-keys\": [],
+ \"forward-ddns\" : {},
+ \"reverse-ddns\" : {},
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp-ddns\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+# Invalid value configuration (invalid port) to check that D2
+# gracefully handles reconfiguration errors.
+CONFIG_INVALID="{
+ \"DhcpDdns\":
+ {
+ \"ip-address\": \"127.0.0.1\",
+ \"port\": BOGUS,
+ \"tsig-keys\": [],
+ \"forward-ddns\" : {},
+ \"reverse-ddns\" : {},
+ \"loggers\": [
+ {
+ \"name\": \"kea-dhcp-ddns\",
+ \"output_options\": [
+ {
+ \"output\": \"$LOG_FILE\"
+ }
+ ],
+ \"severity\": \"INFO\"
+ }
+ ]
+ }
+}"
+
+CONFIG_WITH_SECRET='
+{
+ "DhcpDdns": {
+ "tsig-keys": [
+ {
+ "algorithm": "HMAC-MD5",
+ "name": "d2.md5.key",
+ "secret": "sensitivejdPJI5QxlpnfQ=="
+ }
+ ],
+ "user-context": {
+ "password": "superadmin",
+ "secret": "superadmin",
+ "shared-info": {
+ "password": "superadmin",
+ "secret": "superadmin"
+ }
+ }
+ }
+}
+'
+
+# Set the location of the executable.
+bin="kea-dhcp-ddns"
+bin_path="@abs_top_builddir@/src/bin/d2"
+
+# Import common test library.
+. "@abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh"
+
+# This test verifies that syntax checking works properly. This function
+# requires 3 parameters:
+# test_name
+# config - string with a content of the config (will be written to a file)
+# expected_code - expected exit code returned by kea (0 - success, 1 - failure)
+syntax_check_test() {
+ local test_name="${1}"
+ local config="${2}"
+ local expected_code="${3}"
+
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+ # Create correct configuration file.
+ create_config "${config}"
+ # Check it
+ printf "Running command %s.\n" "\"${bin_path}/${bin} -t ${CFG_FILE}\""
+ run_command \
+ "${bin_path}/${bin}" -t "${CFG_FILE}"
+ if [ "${EXIT_CODE}" -ne "${expected_code}" ]; then
+ printf 'ERROR: expected exit code %s, got %s\n' "${expected_code}" "${EXIT_CODE}"
+ clean_exit 1
+ fi
+ test_finish 0
+}
+
+# This test verifies that D2 can be reconfigured with a SIGHUP signal.
+dynamic_reconfiguration_test() {
+ # Log the start of the test and print test name.
+ test_start "dhcp_ddns.dynamic_reconfiguration"
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct D2 to log to the specific file.
+ set_logger
+ # Start D2.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for D2 to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf "ERROR: timeout waiting for D2 to start.\n"
+ clean_exit 1
+ fi
+
+ # Check if it is still running. It could have terminated (e.g. as a result
+ # of configuration failure).
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one D2 process to be started. Found %d processes\
+ started.\n" "${_GET_PIDS_NUM}"
+ clean_exit 1
+ fi
+
+ # Check in the log file, how many times server has been configured.
+ # It should be just once on startup.
+ get_reconfigs
+ if [ "${_GET_RECONFIGS}" -ne 1 ]; then
+ printf "ERROR: D2 hasn't been configured.\n"
+ clean_exit 1
+ else
+ printf "D2 successfully configured.\n"
+ fi
+
+ # Now use invalid configuration.
+ create_config "${CONFIG_INVALID}"
+
+ # Try to reconfigure by sending SIGHUP
+ send_signal 1 ${bin}
+
+ # Wait up to 10s for the D2Controller to log reload signal received.
+ wait_for_message 10 "DCTL_CFG_FILE_RELOAD_SIGNAL_RECVD" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: D2 did report the reload signal receipt.\n"
+ clean_exit 1
+ fi
+
+ # After receiving SIGHUP the server should try to reconfigure itself.
+ # The configuration provided is invalid so it should result in
+ # reconfiguration failure but the server should still be running.
+ wait_for_message 10 "DCTL_CFG_FILE_RELOAD_ERROR" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: D2 did not report reload error.\n"
+ clean_exit 1
+ fi
+
+ # Make sure the server is still operational.
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: D2 was killed when attempting reconfiguration.\n"
+ clean_exit 1
+ fi
+
+ # Restore the good configuration.
+ create_config "${CONFIG}"
+
+ # Reconfigure the server with SIGHUP.
+ send_signal 1 ${bin}
+
+ # There should be two occurrences of the DHCP4_CONFIG_COMPLETE messages.
+ # Wait for it up to 10s.
+ wait_for_message 10 "DCTL_CONFIG_COMPLETE" 2
+
+ # After receiving SIGHUP the server should get reconfigured and the
+ # reconfiguration should be noted in the log file. We should now
+ # have two configurations logged in the log file.
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: D2 hasn't been reconfigured.\n"
+ clean_exit 1
+ else
+ printf "D2 successfully reconfigured.\n"
+ fi
+
+ # Make sure the server is still operational.
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: D2 was killed when attempting reconfiguration.\n"
+ clean_exit 1
+ fi
+
+ # All ok. Shut down D2 and exit.
+ test_finish 0
+}
+
+# This test verifies that DHCPv4 server is shut down gracefully when it
+# receives a SIGINT or SIGTERM signal.
+shutdown_test() {
+ local test_name="${1}" # Test name
+ local signum="${2}" # Signal number
+ # Log the start of the test and print test name.
+ test_start "${test_name}"
+ # Create new configuration file.
+ create_config "${CONFIG}"
+ # Instruct D2 to log to the specific file.
+ set_logger
+ # Start D2.
+ start_kea ${bin_path}/${bin}
+ # Wait up to 20s for D2 to start.
+ wait_for_kea 20
+ if [ "${_WAIT_FOR_KEA}" -eq 0 ]; then
+ printf "ERROR: timeout waiting for D2 to start.\n"
+ clean_exit 1
+ fi
+
+ # Check if it is still running. It could have terminated (e.g. as a result
+ # of configuration failure).
+ get_pid ${bin}
+ if [ "${_GET_PIDS_NUM}" -ne 1 ]; then
+ printf "ERROR: expected one D2 process to be started. Found %d processes\
+ started.\n" "${_GET_PIDS_NUM}"
+ clean_exit 1
+ fi
+
+ # Check in the log file, how many times server has been configured.
+ # It should be just once on startup.
+ get_reconfigs
+ if [ "${_GET_RECONFIGS}" -ne 1 ]; then
+ printf "ERROR: server hasn't been configured.\n"
+ clean_exit 1
+ else
+ printf "Server successfully configured.\n"
+ fi
+
+ # Send signal to D2 (SIGTERM, SIGINT etc.)
+ send_signal "${signum}" "${bin}"
+
+ # Now wait for process to log that it is exiting.
+ wait_for_message 10 "DCTL_SHUTDOWN" 1
+ if [ "${_WAIT_FOR_MESSAGE}" -eq 0 ]; then
+ printf "ERROR: DHCP-DDNS did not log shutdown.\n"
+ clean_exit 1
+ fi
+
+ # Make sure the server is down.
+ wait_for_server_down 5 ${bin}
+ assert_eq 1 "${_WAIT_FOR_SERVER_DOWN}" \
+ "Expected wait_for_server_down return %d, returned %d"
+
+ test_finish 0
+}
+
+server_pid_file_test "${CONFIG}" DCTL_ALREADY_RUNNING
+dynamic_reconfiguration_test
+shutdown_test "dhcp-ddns.sigterm_test" 15
+shutdown_test "dhcp-ddns.sigint_test" 2
+version_test "dhcp-ddns.version"
+logger_vars_test "dhcp-ddns.variables"
+syntax_check_test "dhcp-ddns.syntax_check_success" "${CONFIG}" 0
+syntax_check_test "dhcp-ddns.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
+syntax_check_test "dhcp-ddns.syntax_check_bad_values" "${CONFIG_BAD_VALUE}" 1
+password_redact_test "dhcp-ddns.password_redact_test" "${CONFIG_WITH_SECRET}" 0
diff --git a/src/bin/d2/tests/d2_process_unittests.cc b/src/bin/d2/tests/d2_process_unittests.cc
new file mode 100644
index 0000000..4dda275
--- /dev/null
+++ b/src/bin/d2/tests/d2_process_unittests.cc
@@ -0,0 +1,693 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <cc/command_interpreter.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <d2/d2_process.h>
+#include <d2/tests/test_configured_libraries.h>
+#include <dhcp_ddns/ncr_io.h>
+#include <process/testutils/d_test_stubs.h>
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <gtest/gtest.h>
+
+#include <functional>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::config;
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::process;
+using namespace boost::posix_time;
+
+namespace {
+
+/// @brief Valid configuration containing an unavailable IP address.
+const char* bad_ip_d2_config = "{ "
+ "\"ip-address\" : \"1.1.1.1\" , "
+ "\"port\" : 5031, "
+ "\"tsig-keys\": ["
+ "{ \"name\": \"d2_key.example.com\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "} ],"
+ "\"forward-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.101\" } "
+ "] } ] }, "
+ "\"reverse-ddns\" : {"
+ "\"ddns-domains\": [ "
+ "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.101\" , "
+ " \"port\": 100 } ] } "
+ "] } }";
+
+/// @brief D2Process test fixture class
+//class D2ProcessTest : public D2Process, public ::testing::Test {
+class D2ProcessTest : public D2Process, public ConfigParseTest {
+public:
+
+ /// @brief Constructor
+ D2ProcessTest() :
+ D2Process("d2test",
+ asiolink::IOServicePtr(new isc::asiolink::IOService())) {
+ }
+
+ /// @brief Destructor
+ virtual ~D2ProcessTest() {
+ }
+
+ /// @brief Callback that will invoke shutdown method.
+ void genShutdownCallback() {
+ shutdown(ConstElementPtr());
+ }
+
+ /// @brief Callback that throws an exception.
+ void genFatalErrorCallback() {
+ isc_throw (DProcessBaseError, "simulated fatal error");
+ }
+
+ /// @brief Reconfigures and starts the queue manager given a configuration.
+ ///
+ /// This method emulates the reception of a new configuration and should
+ /// conclude with the Queue manager placed in the RUNNING state.
+ ///
+ /// @param config is the configuration to use
+ ///
+ /// @return Returns AssertionSuccess if the queue manager was successfully
+ /// reconfigured, AssertionFailure otherwise.
+ ::testing::AssertionResult runWithConfig(const char* config) {
+ int rcode = -1;
+ // Convert the string configuration into an Element set.
+ ::testing::AssertionResult res = fromJSON(config);
+ if (res != ::testing::AssertionSuccess()) {
+ return res;
+ }
+
+ ConstElementPtr answer = configure(config_set_, false);
+ ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+
+ if (rcode) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "configure() failed: "
+ << comment));
+ }
+
+ // Must call checkQueueStatus, to cause queue manager to reconfigure
+ // and start.
+ checkQueueStatus();
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // If queue manager isn't in the RUNNING state, return failure.
+ if (D2QueueMgr::RUNNING != queue_mgr->getMgrState()) {
+ return (::testing::AssertionFailure(::testing::Message() <<
+ "queue manager did not start"));
+ }
+
+ // Good to go.
+ return (::testing::AssertionSuccess());
+ }
+
+ /// @brief Checks if shutdown criteria would be met given a shutdown type.
+ ///
+ /// This method sets the D2Process shutdown type to the given value, and
+ /// calls the canShutdown() method, returning its return value.
+ ///
+ /// @return Returns the boolean result canShutdown.
+ bool checkCanShutdown(ShutdownType shutdown_type) {
+ setShutdownType(shutdown_type);
+ return (canShutdown());
+ }
+
+ /// @brief Replaces %LIBRARY% with specified library name.
+ ///
+ /// @param config input config text (should contain "%LIBRARY%" string).
+ /// @param lib_name %LIBRARY% will be replaced with that name.
+ /// @return configuration text with library name replaced.
+ string pathReplacer(const char* config, const char* lib_name) {
+ string txt(config);
+ txt.replace(txt.find("%LIBRARY%"), strlen("%LIBRARY%"), string(lib_name));
+ return (txt);
+ }
+};
+
+/// @brief Verifies D2Process construction behavior.
+/// 1. Verifies that constructor fails with an invalid IOService
+/// 2. Verifies that constructor succeeds with a valid IOService
+/// 3. Verifies that all managers are accessible
+TEST(D2Process, construction) {
+ // Verify that the constructor will fail if given an empty
+ // io service.
+ asiolink::IOServicePtr lcl_io_service;
+ EXPECT_THROW (D2Process("TestProcess", lcl_io_service), DProcessBaseError);
+
+ // Verify that the constructor succeeds with a valid io_service
+ lcl_io_service.reset(new isc::asiolink::IOService());
+ ASSERT_NO_THROW (D2Process("TestProcess", lcl_io_service));
+
+ // Verify that the configuration, queue, and update managers
+ // are all accessible after construction.
+ D2Process d2process("TestProcess", lcl_io_service);
+
+ D2CfgMgrPtr cfg_mgr = d2process.getD2CfgMgr();
+ ASSERT_TRUE(cfg_mgr);
+
+ D2QueueMgrPtr queue_mgr = d2process.getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+
+ const D2UpdateMgrPtr& update_mgr = d2process.getD2UpdateMgr();
+ ASSERT_TRUE(update_mgr);
+}
+
+/// @brief Verifies basic configure method behavior.
+/// This test primarily verifies that upon receipt of a new configuration,
+/// D2Process will reconfigure the queue manager if the configuration is valid,
+/// or leave queue manager unaffected if not. Currently, the queue manager is
+/// only D2 component that must adapt to new configurations. Other components,
+/// such as Transactions will be unaffected as they are transient and use
+/// whatever configuration was in play at the time they were created.
+/// If other components need to provide "dynamic" configuration responses,
+/// those tests would need to be added.
+TEST_F(D2ProcessTest, configure) {
+ // Verify the queue manager is not yet initialized.
+ D2QueueMgrPtr queue_mgr = getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+ ASSERT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr->getMgrState());
+
+ // Verify that reconfigure queue manager flag is false.
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Create a valid configuration set from text config.
+ ASSERT_TRUE(fromJSON(valid_d2_config));
+
+ // Invoke configure() with a valid D2 configuration.
+ ConstElementPtr answer = configure(config_set_, false);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus, to cause queue manager to reconfigure and start.
+ checkQueueStatus();
+
+ // Verify that queue manager is now in the RUNNING state, and flag is false.
+ ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Create an invalid configuration set from text config.
+ ASSERT_TRUE(fromJSON("{ \"bogus\": 1000 } "));
+
+ // Invoke configure() with the invalid configuration.
+ answer = configure(config_set_, false);
+
+ // Verify that configure result is a success, as extra parameters are
+ // ignored. the reconfigure flag is false, and that the queue manager is
+ // still running.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_TRUE(getReconfQueueFlag());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+
+ // Finally, try with an invalid configuration.
+ // Create an invalid configuration set from text config.
+ ASSERT_TRUE(fromJSON("{ \"ip-address\": \"950 Charter St.\" } "));
+ answer = configure(config_set_, false);
+ ASSERT_TRUE(checkAnswer(answer, 1));
+ EXPECT_FALSE(getReconfQueueFlag());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for stopping the queue on shutdown
+/// This test manually sets shutdown flag and verifies that queue manager
+/// stop is initiated.
+TEST_F(D2ProcessTest, queueStopOnShutdown) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ setShutdownFlag(true);
+
+ // Calling checkQueueStatus restart queue manager
+ checkQueueStatus();
+
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Verify that a subsequent call with no events occurring in between,
+ // results in no change to queue manager
+ checkQueueStatus();
+
+ // Verify that the queue manager is still stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for stopping the queue on reconfigure.
+/// This test manually sets queue reconfiguration flag and verifies that queue
+/// manager stop is initiated.
+TEST_F(D2ProcessTest, queueStopOnReconf) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Manually set the reconfigure indicator.
+ setReconfQueueFlag(true);
+
+ // Calling checkQueueStatus should initiate stopping the queue manager.
+ checkQueueStatus();
+
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for recovering from queue full
+/// This test manually creates a receive queue full condition and then
+/// "drains" the queue until the queue manager resumes listening. This
+/// verifies D2Process's ability to recover from a queue full condition.
+TEST_F(D2ProcessTest, queueFullRecovery) {
+ // Valid test message, contents are unimportant.
+ const char* test_msg =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ // Start queue manager with known good config.
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Set the maximum queue size to manageable number.
+ size_t max_queue_size = 5;
+ queue_mgr->setMaxQueueSize(max_queue_size);
+
+ // Manually enqueue max requests.
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(test_msg));
+ for (int i = 0; i < max_queue_size; i++) {
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ ASSERT_NO_THROW(queue_mgr->enqueue(ncr));
+ ASSERT_EQ(i+1, queue_mgr->getQueueSize());
+ }
+
+ // Since we are not really receiving, we will simulate QUEUE FULL
+ // detection.
+ queue_mgr->stopListening(D2QueueMgr::STOPPED_QUEUE_FULL);
+ ASSERT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr->getMgrState());
+
+ // Dequeue requests one at a time, calling checkQueueStatus after each
+ // dequeue, until we reach the resume threshold. This simulates update
+ // manager consuming jobs. Queue manager should remain stopped during
+ // this loop.
+ int resume_threshold = (max_queue_size * QUEUE_RESTART_PERCENT);
+ while (queue_mgr->getQueueSize() > resume_threshold) {
+ checkQueueStatus();
+ ASSERT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr->getMgrState());
+ ASSERT_NO_THROW(queue_mgr->dequeue());
+ }
+
+ // Dequeue one more, which brings us under the threshold and call
+ // checkQueueStatus.
+ // Verify that the queue manager is again running.
+ ASSERT_NO_THROW(queue_mgr->dequeue());
+ checkQueueStatus();
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Tests checkQueueStatus() logic for queue receive error recovery
+/// This test manually creates a queue receive error condition and tests
+/// verifies that checkQueueStatus reacts properly to recover.
+TEST_F(D2ProcessTest, queueErrorRecovery) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Since we are not really receiving, we have to stage an error.
+ queue_mgr->stopListening(D2QueueMgr::STOPPED_RECV_ERROR);
+ ASSERT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Call runIO so the IO cancel event occurs and verify that queue manager
+ // has stopped.
+ runIO();
+ ASSERT_EQ(D2QueueMgr::STOPPED_RECV_ERROR, queue_mgr->getMgrState());
+
+ // Calling checkQueueStatus should restart queue manager
+ checkQueueStatus();
+
+ // Verify that queue manager is again running.
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+}
+
+/// @brief Verifies queue manager recovery from unusable configuration
+/// This test checks D2Process's gracefully handle a configuration which
+/// while valid is not operationally usable (i.e. IP address is unavailable),
+/// and to subsequently recover given a usable configuration.
+TEST_F(D2ProcessTest, badConfigureRecovery) {
+ D2QueueMgrPtr queue_mgr = getD2QueueMgr();
+ ASSERT_TRUE(queue_mgr);
+
+ // Verify the queue manager is not initialized.
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr->getMgrState());
+
+ // Invoke configure() with a valid config that contains an unusable IP
+ ASSERT_TRUE(fromJSON(bad_ip_d2_config));
+ ConstElementPtr answer = configure(config_set_, false);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus to cause queue manager to attempt to reconfigure.
+ checkQueueStatus();
+
+ // Verify that queue manager failed to start, (i.e. is in INITTED state),
+ // and the reconfigure flag is false.
+ ASSERT_EQ(D2QueueMgr::INITTED, queue_mgr->getMgrState());
+ ASSERT_FALSE(getReconfQueueFlag());
+
+ // Verify we can recover given a valid config with an usable IP address.
+ ASSERT_TRUE(fromJSON(valid_d2_config));
+ answer = configure(config_set_, false);
+
+ // Verify that configure result is success and reconfigure queue manager
+ // flag is true.
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ ASSERT_TRUE(getReconfQueueFlag());
+
+ // Call checkQueueStatus to cause queue manager to reconfigure and start.
+ checkQueueStatus();
+
+ // Verify that queue manager is now in the RUNNING state, and reconfigure
+ // flag is false.
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr->getMgrState());
+ EXPECT_FALSE(getReconfQueueFlag());
+}
+
+/// @brief Tests shutdown command argument parsing
+/// The shutdown command supports an optional "type" argument. This test
+/// checks that for valid values, the shutdown() method: sets the shutdown
+/// type to correct value, set the shutdown flag to true, and returns a
+/// success response; and for invalid values: sets the shutdown flag to false
+/// and returns a failure response.
+TEST_F(D2ProcessTest, shutdownArgs) {
+ ElementPtr args;
+ ConstElementPtr answer;
+ const char* default_args = "{}";
+ const char* normal_args = "{ \"type\" : \"normal\" }";
+ const char* drain_args = "{ \"type\" : \"drain_first\" }";
+ const char* now_args = "{ \"type\" : \"now\" }";
+ const char* bogus_args = "{ \"type\" : \"bogus\" }";
+
+ // Verify defaulting to SD_NORMAL if no argument is given.
+ ASSERT_NO_THROW(args = Element::fromJSON(default_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NORMAL, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "normal".
+ ASSERT_NO_THROW(args = Element::fromJSON(normal_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NORMAL, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "drain_first".
+ ASSERT_NO_THROW(args = Element::fromJSON(drain_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_DRAIN_FIRST, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify argument value "now".
+ ASSERT_NO_THROW(args = Element::fromJSON(now_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 0));
+ EXPECT_EQ(SD_NOW, getShutdownType());
+ EXPECT_TRUE(shouldShutdown());
+
+ // Verify correct handling of an invalid value.
+ ASSERT_NO_THROW(args = Element::fromJSON(bogus_args));
+ EXPECT_NO_THROW(answer = shutdown(args));
+ ASSERT_TRUE(checkAnswer(answer, 1));
+ EXPECT_FALSE(shouldShutdown());
+}
+
+/// @brief Tests shutdown criteria logic
+/// D2Process using the method canShutdown() to determine if a shutdown
+/// can be performed given the value of the shutdown flag and the type of
+/// shutdown requested. For each shutdown type certain criteria must be met
+/// before the shutdown is permitted. This method is invoked once each pass
+/// through the main event loop. This test checks the operation of the
+/// canShutdown method. It uses a convenience method, checkCanShutdown(),
+/// which sets the shutdown type to the given value and invokes canShutdown(),
+/// returning its result.
+TEST_F(D2ProcessTest, canShutdown) {
+ ASSERT_TRUE(runWithConfig(valid_d2_config));
+ const D2QueueMgrPtr& queue_mgr = getD2QueueMgr();
+
+ // Shutdown flag is false. Method should return false for all types.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_FALSE(checkCanShutdown(SD_NOW));
+
+ // Set shutdown flag to true.
+ setShutdownFlag(true);
+
+ // Queue Manager is running, queue is empty, no transactions.
+ // Only SD_NOW should return true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Tell queue manager to stop.
+ queue_mgr->stopListening();
+ // Verify that the queue manager is stopping.
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr->getMgrState());
+
+ // Queue Manager is stopping, queue is empty, no transactions.
+ // Only SD_NOW should return true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Allow cancel event to process.
+ ASSERT_NO_THROW(runIO());
+ // Verify that queue manager is stopped.
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr->getMgrState());
+
+ // Queue Manager is stopped, queue is empty, no transactions.
+ // All types should return true.
+ EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
+ EXPECT_TRUE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ const char* test_msg =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"fish.example.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ // Manually enqueue a request. This lets us test logic with queue
+ // not empty.
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(test_msg));
+ ASSERT_NO_THROW(queue_mgr->enqueue(ncr));
+ ASSERT_EQ(1, queue_mgr->getQueueSize());
+
+ // Queue Manager is stopped. Queue is not empty, no transactions.
+ // SD_DRAIN_FIRST should be false, SD_NORMAL and SD_NOW should be true.
+ EXPECT_TRUE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+
+ // Now use update manager to dequeue the request and make a transaction.
+ // This lets us verify transaction list not empty logic.
+ const D2UpdateMgrPtr& update_mgr = getD2UpdateMgr();
+ ASSERT_TRUE(update_mgr);
+ ASSERT_NO_THROW(update_mgr->sweep());
+ ASSERT_EQ(0, queue_mgr->getQueueSize());
+ ASSERT_EQ(1, update_mgr->getTransactionCount());
+
+ // Queue Manager is stopped. Queue is empty, one transaction.
+ // Only SD_NOW should be true.
+ EXPECT_FALSE(checkCanShutdown(SD_NORMAL));
+ EXPECT_FALSE(checkCanShutdown(SD_DRAIN_FIRST));
+ EXPECT_TRUE(checkCanShutdown(SD_NOW));
+}
+
+/// @brief Verifies that an "external" call to shutdown causes the run method
+/// to exit gracefully.
+TEST_F(D2ProcessTest, normalShutdown) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // shutdown invocation. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIoService());
+ timer.setup(std::bind(&D2ProcessTest::genShutdownCallback, this),
+ 2 * 1000);
+
+ // Record start time, and invoke run().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_NO_THROW(run());
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the shutdown was driven
+ // by an io_service event and callback.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2200);
+}
+
+/// @brief Verifies that an "uncaught" exception thrown during event loop
+/// execution is treated as a fatal error.
+TEST_F(D2ProcessTest, fatalErrorShutdown) {
+ // Use an asiolink IntervalTimer and callback to generate the
+ // the exception. (Note IntervalTimer setup is in milliseconds).
+ isc::asiolink::IntervalTimer timer(*getIoService());
+ timer.setup(std::bind(&D2ProcessTest::genFatalErrorCallback, this),
+ 2 * 1000);
+
+ // Record start time, and invoke run().
+ ptime start = microsec_clock::universal_time();
+ EXPECT_THROW(run(), DProcessBaseError);
+
+ // Record stop time.
+ ptime stop = microsec_clock::universal_time();
+
+ // Verify that duration of the run invocation is the same as the
+ // timer duration. This demonstrates that the anomaly occurred
+ // during io callback processing.
+ time_duration elapsed = stop - start;
+ EXPECT_TRUE(elapsed.total_milliseconds() >= 1900 &&
+ elapsed.total_milliseconds() <= 2200);
+}
+
+/// @brief Used to permit visual inspection of logs to ensure
+/// DHCP_DDNS_NOT_ON_LOOPBACK is issued when ip_address is not
+/// loopback.
+TEST_F(D2ProcessTest, notLoopbackTest) {
+ const char* config = "{ "
+ "\"ip-address\" : \"0.0.0.0\" , "
+ "\"port\" : 53001, "
+ "\"tsig-keys\": [],"
+ "\"forward-ddns\" : {},"
+ "\"reverse-ddns\" : {}"
+ "}";
+
+ // Note we don't care nor can we predict if this
+ // succeeds or fails. The address and port may or may
+ // not be valid on the test host.
+ runWithConfig(config);
+}
+
+/// @brief Used to permit visual inspection of logs to ensure
+/// DHCP_DDNS_NOT_ON_LOOPBACK is not issued.
+TEST_F(D2ProcessTest, v4LoopbackTest) {
+ const char* config = "{ "
+ "\"ip-address\" : \"127.0.0.1\" , "
+ "\"port\" : 53001, "
+ "\"tsig-keys\": [],"
+ "\"forward-ddns\" : {},"
+ "\"reverse-ddns\" : {}"
+ "}";
+ ASSERT_TRUE(runWithConfig(config));
+}
+
+/// @brief Used to permit visual inspection of logs to ensure
+/// DHCP_DDNS_NOT_ON_LOOPBACK is not issued.
+TEST_F(D2ProcessTest, v6LoopbackTest) {
+ const char* config = "{ "
+ "\"ip-address\" : \"::1\" , "
+ "\"port\" : 53001, "
+ "\"tsig-keys\": [],"
+ "\"forward-ddns\" : {},"
+ "\"reverse-ddns\" : {}"
+ "}";
+ ASSERT_TRUE(runWithConfig(config));
+}
+
+/// @brief Check the configured callout (positive case).
+TEST_F(D2ProcessTest, configuredNoFail) {
+ const char* config = "{\n"
+ "\"hooks-libraries\": [ {\n"
+ " \"library\": \"%LIBRARY%\",\n"
+ " \"parameters\": {\n"
+ " } } ] }\n";
+ string cfg = pathReplacer(config, CONFIGURED_LIBRARY);
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(cfg));
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = configure(json, false));
+ int rcode = -1;
+ ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(0, rcode) << comment->str();
+}
+
+/// @brief Check the configured callout (negative case).
+TEST_F(D2ProcessTest, configuredFail) {
+ const char* config = "{\n"
+ "\"user-context\": { \"error\": \"Fail!\" },\n"
+ "\"hooks-libraries\": [ {\n"
+ " \"library\": \"%LIBRARY%\",\n"
+ " \"parameters\": {\n"
+ " } } ] }\n";
+ string cfg = pathReplacer(config, CONFIGURED_LIBRARY);
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(cfg));
+ ConstElementPtr answer;
+ ASSERT_NO_THROW(answer = configure(json, false));
+ int rcode = -1;
+ ConstElementPtr comment;
+ comment = isc::config::parseAnswer(rcode, answer);
+ EXPECT_EQ(1, rcode);
+ ASSERT_TRUE(comment);
+ ASSERT_EQ(Element::string, comment->getType());
+ EXPECT_EQ("Fail!", comment->stringValue());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_queue_mgr_unittests.cc b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
new file mode 100644
index 0000000..42177e6
--- /dev/null
+++ b/src/bin/d2/tests/d2_queue_mgr_unittests.cc
@@ -0,0 +1,457 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+#include <d2/d2_queue_mgr.h>
+#include <d2srv/testutils/stats_test_utils.h>
+#include <dhcp_ddns/ncr_udp.h>
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <functional>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::d2;
+using namespace isc::d2::test;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+ // Valid Add.
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}",
+ // Valid Remove.
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}",
+ // Valid Add with IPv6 address
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"walah.walah.com\" , "
+ " \"ip-address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+ " \"dhcid\" : \"010203040A7F8E3D\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}"
+};
+
+static const int VALID_MSG_CNT = sizeof(valid_msgs)/sizeof(char*);
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief Tests that construction with max queue size of zero is not allowed.
+TEST(D2QueueMgrBasicTest, construction1) {
+ asiolink::IOServicePtr io_service;
+
+ // Verify that constructing with null IOServicePtr is not allowed.
+ EXPECT_THROW((D2QueueMgr(io_service)), D2QueueMgrError);
+
+ io_service.reset(new isc::asiolink::IOService());
+ // Verify that constructing with max queue size of zero is not allowed.
+ EXPECT_THROW(D2QueueMgr(io_service, 0), D2QueueMgrError);
+}
+
+/// @brief Tests default construction works.
+TEST(D2QueueMgrBasicTest, construction2) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+
+ // Verify that valid constructor works.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service)));
+ // Verify queue max is defaulted correctly.
+ EXPECT_EQ(D2QueueMgr::MAX_QUEUE_DEFAULT, queue_mgr->getMaxQueueSize());
+}
+
+/// @brief Tests construction with custom queue size works properly
+TEST(D2QueueMgrBasicTest, construction3) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+
+ // Verify that custom queue size constructor works.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service, 100)));
+ // Verify queue max is the custom value.
+ EXPECT_EQ(100, queue_mgr->getMaxQueueSize());
+}
+
+/// @brief Tests QueueMgr's basic queue functions
+/// This test verifies that:
+/// 1. Following construction queue is empty
+/// 2. Attempting to peek at an empty queue is not allowed
+/// 3. Attempting to dequeue an empty queue is not allowed
+/// 4. Peek returns the first entry on the queue without altering queue content
+/// 5. Dequeue removes the first entry on the queue
+TEST(D2QueueMgrBasicTest, basicQueue) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+
+ // Construct the manager with max queue size set to number of messages
+ // we'll use.
+ D2QueueMgrPtr queue_mgr;
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service, VALID_MSG_CNT)));
+ ASSERT_EQ(VALID_MSG_CNT, queue_mgr->getMaxQueueSize());
+
+ // Verify queue is empty after construction.
+ EXPECT_EQ(0, queue_mgr->getQueueSize());
+
+ // Verify that peek and dequeue both throw when queue is empty.
+ EXPECT_THROW(queue_mgr->peek(), D2QueueMgrQueueEmpty);
+ EXPECT_THROW(queue_mgr->dequeue(), D2QueueMgrQueueEmpty);
+
+ // Vector to keep track of the NCRs we que.
+ std::vector<NameChangeRequestPtr>ref_msgs;
+ NameChangeRequestPtr ncr;
+
+ // Iterate over the list of requests and add each to the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ref_msgs.push_back(ncr);
+
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ EXPECT_NO_THROW(queue_mgr->enqueue(ncr));
+ EXPECT_EQ(i+1, queue_mgr->getQueueSize());
+ }
+
+ // Loop through and verify that the queue contents match the
+ // reference list.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Verify that peek on a non-empty queue returns first entry
+ // without altering queue content.
+ EXPECT_NO_THROW(ncr = queue_mgr->peek());
+
+ // Verify the peeked entry is the one it should be.
+ ASSERT_TRUE(ncr);
+ EXPECT_TRUE (*(ref_msgs[i]) == *ncr);
+
+ // Verify that peek did not alter the queue size.
+ EXPECT_EQ(VALID_MSG_CNT - i, queue_mgr->getQueueSize());
+
+ // Verify the dequeuing from non-empty queue works
+ EXPECT_NO_THROW(queue_mgr->dequeue());
+
+ // Verify queue size decrements following dequeue.
+ EXPECT_EQ(VALID_MSG_CNT - (i + 1), queue_mgr->getQueueSize());
+ }
+
+ // Iterate over the list of requests and add each to the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ref_msgs.push_back(ncr);
+ EXPECT_NO_THROW(queue_mgr->enqueue(ncr));
+ }
+
+ // Verify queue count is correct.
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr->getQueueSize());
+
+ // Verify that peekAt returns the correct entry.
+ EXPECT_NO_THROW(ncr = queue_mgr->peekAt(1));
+ EXPECT_TRUE (*(ref_msgs[1]) == *ncr);
+
+ // Verify that dequeueAt removes the correct entry.
+ // Removing it, this should shift the queued entries forward by one.
+ EXPECT_NO_THROW(queue_mgr->dequeueAt(1));
+ EXPECT_NO_THROW(ncr = queue_mgr->peekAt(1));
+ EXPECT_TRUE (*(ref_msgs[2]) == *ncr);
+
+ // Verify the peekAt and dequeueAt throw when given indexes beyond the end.
+ EXPECT_THROW(queue_mgr->peekAt(VALID_MSG_CNT + 1), D2QueueMgrInvalidIndex);
+ EXPECT_THROW(queue_mgr->dequeueAt(VALID_MSG_CNT + 1),
+ D2QueueMgrInvalidIndex);
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+ NameChangeRequestPtr received_ncr) {
+ return ((sent_ncr && received_ncr) &&
+ (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class QueueMgrUDPTest : public virtual ::testing::Test, public D2StatTest,
+ NameChangeSender::RequestSendHandler {
+public:
+ asiolink::IOServicePtr io_service_;
+ NameChangeSenderPtr sender_;
+ isc::asiolink::IntervalTimer test_timer_;
+ D2QueueMgrPtr queue_mgr_;
+
+ NameChangeSender::Result send_result_;
+ std::vector<NameChangeRequestPtr> sent_ncrs_;
+ std::vector<NameChangeRequestPtr> received_ncrs_;
+
+ QueueMgrUDPTest() : io_service_(new isc::asiolink::IOService()),
+ test_timer_(*io_service_),
+ send_result_(NameChangeSender::SUCCESS) {
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ // Create our sender instance. Note that reuse_address is true.
+ sender_.reset(new NameChangeUDPSender(addr, SENDER_PORT,
+ addr, LISTENER_PORT,
+ FMT_JSON, *this, 100, true));
+
+ // Set the test timeout to break any running tasks if they hang.
+ test_timer_.setup(std::bind(&QueueMgrUDPTest::testTimeoutHandler,
+ this),
+ TEST_TIMEOUT);
+ }
+
+ void reset_results() {
+ sent_ncrs_.clear();
+ received_ncrs_.clear();
+ }
+
+ /// @brief Implements the send completion handler.
+ virtual void operator ()(const NameChangeSender::Result result,
+ NameChangeRequestPtr& ncr) {
+ // save the result and the NCR sent.
+ send_result_ = result;
+ sent_ncrs_.push_back(ncr);
+ }
+
+ /// @brief Handler invoked when test timeout is hit.
+ ///
+ /// This callback stops all running (hanging) tasks on IO service.
+ void testTimeoutHandler() {
+ io_service_->stop();
+ FAIL() << "Test timeout hit.";
+ }
+};
+
+/// @brief Tests D2QueueMgr's state model.
+/// This test verifies that:
+/// 1. Upon construction, initial state is NOT_INITTED.
+/// 2. Cannot start listening from while state is NOT_INITTED.
+/// 3. Successful listener initialization transitions from NOT_INITTED
+/// to INITTED.
+/// 4. Attempting to initialize the listener from INITTED state is not
+/// allowed.
+/// 5. Starting listener from INITTED transitions to RUNNING.
+/// 6. Stopping the listener transitions from RUNNING to STOPPED.
+/// 7. Starting listener from STOPPED transitions to RUNNING.
+TEST_F (QueueMgrUDPTest, stateModel) {
+ // Create the queue manager.
+ ASSERT_NO_THROW(queue_mgr_.reset(new D2QueueMgr(io_service_,
+ VALID_MSG_CNT)));
+
+ // Verify that the initial state is NOT_INITTED.
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+
+ // Verify that trying to listen before when not initialized fails.
+ EXPECT_THROW(queue_mgr_->startListening(), D2QueueMgrError);
+
+ // Verify that initializing the listener moves us to INITTED state.
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ EXPECT_NO_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true));
+ EXPECT_EQ(D2QueueMgr::INITTED, queue_mgr_->getMgrState());
+
+ // Verify that attempting to initialize the listener, from INITTED
+ // is not allowed.
+ EXPECT_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true),
+ D2QueueMgrError);
+
+ // Verify that we can enter the RUNNING from INITTED by starting the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that we can move from RUNNING to STOPPING by stopping the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->stopListening());
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr_->getMgrState());
+
+ // Stopping requires IO cancel, which result in a callback.
+ // So process one event and verify we are STOPPED.
+ io_service_->run_one();
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
+
+ // Verify that we can re-enter the RUNNING from STOPPED by starting the
+ // listener.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that we cannot remove the listener in the RUNNING state
+ EXPECT_THROW(queue_mgr_->removeListener(), D2QueueMgrError);
+
+ // Stop the listener.
+ EXPECT_NO_THROW(queue_mgr_->stopListening());
+ EXPECT_EQ(D2QueueMgr::STOPPING, queue_mgr_->getMgrState());
+
+ // Stopping requires IO cancel, which result in a callback.
+ // So process one event and verify we are STOPPED.
+ io_service_->run_one();
+ EXPECT_EQ(D2QueueMgr::STOPPED, queue_mgr_->getMgrState());
+
+ // Verify that we can remove the listener in the STOPPED state and
+ // end up back in NOT_INITTED.
+ EXPECT_NO_THROW(queue_mgr_->removeListener());
+ EXPECT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+}
+
+/// @brief Tests D2QueueMgr's ability to manage received requests
+/// This test verifies that:
+/// 1. Requests can be received, queued, and dequeued
+/// 2. Once the queue is full, a subsequent request transitions
+/// manager to STOPPED_QUEUE_FULL state.
+/// 3. Starting listener returns manager to the RUNNING state.
+/// 4. Queue contents are preserved across state transitions.
+/// 5. Clearing the queue via the clearQueue() method works.
+/// 6. Requests can be received and queued normally after the queue
+/// has been emptied.
+/// 7. setQueueMax disallows values of 0 or less than current queue size.
+TEST_F (QueueMgrUDPTest, liveFeed) {
+ NameChangeRequestPtr send_ncr;
+ NameChangeRequestPtr received_ncr;
+
+ // Create the queue manager and start listening..
+ ASSERT_NO_THROW(queue_mgr_.reset(new D2QueueMgr(io_service_,
+ VALID_MSG_CNT)));
+ ASSERT_EQ(D2QueueMgr::NOT_INITTED, queue_mgr_->getMgrState());
+
+ // Verify that setting max queue size to 0 is not allowed.
+ EXPECT_THROW(queue_mgr_->setMaxQueueSize(0), D2QueueMgrError);
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getMaxQueueSize());
+
+ isc::asiolink::IOAddress addr(TEST_ADDRESS);
+ ASSERT_NO_THROW(queue_mgr_->initUDPListener(addr, LISTENER_PORT,
+ FMT_JSON, true));
+ ASSERT_EQ(D2QueueMgr::INITTED, queue_mgr_->getMgrState());
+
+ ASSERT_NO_THROW(queue_mgr_->startListening());
+ ASSERT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Place the sender into sending state.
+ ASSERT_NO_THROW(sender_->startSending(*io_service_));
+ ASSERT_TRUE(sender_->amSending());
+
+ // Iterate over the list of requests sending and receiving
+ // each one. Verify and dequeue as they arrive.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(send_ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+
+ // running two should do the send then the receive
+ io_service_->run_one();
+ io_service_->run_one();
+
+ // Verify that the request can be added to the queue and queue
+ // size increments accordingly.
+ EXPECT_EQ(1, queue_mgr_->getQueueSize());
+
+ // Verify that peek shows the NCR we just sent
+ EXPECT_NO_THROW(received_ncr = queue_mgr_->peek());
+ EXPECT_TRUE(checkSendVsReceived(send_ncr, received_ncr));
+
+ // Verify that we and dequeue the request.
+ EXPECT_NO_THROW(queue_mgr_->dequeue());
+ EXPECT_EQ(0, queue_mgr_->getQueueSize());
+ }
+
+ StatMap stats_ncr = {
+ { "ncr-received", 3},
+ { "ncr-invalid", 0},
+ { "ncr-error", 0}
+ };
+ checkStats(stats_ncr);
+
+ // Iterate over the list of requests, sending and receiving
+ // each one. Allow them to accumulate in the queue.
+ for (int i = 0; i < VALID_MSG_CNT; i++) {
+ // Create the ncr and add to our reference list.
+ ASSERT_NO_THROW(send_ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+
+ // running two should do the send then the receive
+ EXPECT_NO_THROW(io_service_->run_one());
+ EXPECT_NO_THROW(io_service_->run_one());
+ EXPECT_EQ(i+1, queue_mgr_->getQueueSize());
+ }
+
+ StatMap stats_ncr_new = {
+ { "ncr-received", 6},
+ { "ncr-invalid", 0},
+ { "ncr-error", 0}
+ };
+ checkStats(stats_ncr_new);
+
+ // Verify that the queue is at max capacity.
+ EXPECT_EQ(queue_mgr_->getMaxQueueSize(), queue_mgr_->getQueueSize());
+
+ // Send another. The send should succeed.
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+ EXPECT_NO_THROW(io_service_->run_one());
+
+ // Now execute the receive which should not throw but should move us
+ // to STOPPED_QUEUE_FULL state.
+ EXPECT_NO_THROW(io_service_->run_one());
+ EXPECT_EQ(D2QueueMgr::STOPPED_QUEUE_FULL, queue_mgr_->getMgrState());
+
+ // Verify queue size did not increase beyond max.
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getQueueSize());
+
+ // Verify that setting max queue size to a value less than current size of
+ // the queue is not allowed.
+ EXPECT_THROW(queue_mgr_->setMaxQueueSize(VALID_MSG_CNT-1), D2QueueMgrError);
+ EXPECT_EQ(VALID_MSG_CNT, queue_mgr_->getQueueSize());
+
+ // Verify that we can re-enter RUNNING from STOPPED_QUEUE_FULL.
+ EXPECT_NO_THROW(queue_mgr_->startListening());
+ EXPECT_EQ(D2QueueMgr::RUNNING, queue_mgr_->getMgrState());
+
+ // Verify that the queue contents were preserved.
+ EXPECT_EQ(queue_mgr_->getMaxQueueSize(), queue_mgr_->getQueueSize());
+
+ // Verify that clearQueue works.
+ EXPECT_NO_THROW(queue_mgr_->clearQueue());
+ EXPECT_EQ(0, queue_mgr_->getQueueSize());
+
+ // Verify that we can again receive requests.
+ // Send should be fine.
+ ASSERT_NO_THROW(sender_->sendRequest(send_ncr));
+ EXPECT_NO_THROW(io_service_->run_one());
+
+ // Receive should succeed.
+ EXPECT_NO_THROW(io_service_->run_one());
+ EXPECT_EQ(1, queue_mgr_->getQueueSize());
+}
+
+} // end of anonymous namespace
diff --git a/src/bin/d2/tests/d2_simple_parser_unittest.cc b/src/bin/d2/tests/d2_simple_parser_unittest.cc
new file mode 100644
index 0000000..b236469
--- /dev/null
+++ b/src/bin/d2/tests/d2_simple_parser_unittest.cc
@@ -0,0 +1,1194 @@
+// 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.com/MPL/2.0/.
+
+#include <config.h>
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <d2/tests/parser_unittest.h>
+#include <d2srv/d2_simple_parser.h>
+#include <testutils/test_to_element.h>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::d2;
+using namespace isc::test;
+
+namespace {
+
+/// @brief Checks if specified element matches the given integer default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkIntegerValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it is an integer.
+ ASSERT_EQ(Element::integer, element->getType());
+
+ // Turn default value string into an int.
+ int64_t default_value = 0;
+ ASSERT_NO_THROW(default_value = boost::lexical_cast<int64_t>(deflt.value_));
+
+ // Verify it has the expected value.
+ EXPECT_EQ(default_value, element->intValue());
+}
+
+/// @brief Checks if specified element matches the given boolean default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkBooleanValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it is a bool.
+ ASSERT_EQ(Element::boolean, element->getType());
+
+ // Turn default value string into a bool.
+ bool default_value = false;
+ ASSERT_NO_THROW(boost::lexical_cast<bool>(deflt.value_));
+
+ // Verify it has the expected value.
+ EXPECT_EQ(default_value, element->boolValue());
+}
+
+/// @brief Checks if specified element matches the given string default
+///
+/// @param element defaulted element to check
+/// @param deflt SimpleDefault which supplied the default value
+void checkStringValue(const ConstElementPtr& element,
+ const SimpleDefault& deflt) {
+ ASSERT_TRUE(element);
+
+ // Verify it's a string
+ ASSERT_EQ(Element::string, element->getType());
+
+ // Verify it has the expected value
+ EXPECT_EQ(deflt.value_, element->stringValue());
+ }
+
+/// TSIGKeyInfo against the given set of values, and that the TSIGKey
+/// member points to a key.
+///
+/// @param key is a pointer to the TSIGKeyInfo instance to verify
+/// @param name is the value to compare against key's name_.
+/// @param algorithm is the string value to compare against key's algorithm.
+/// @param secret is the value to compare against key's secret.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkKey(TSIGKeyInfoPtr key, const std::string& name,
+ const std::string& algorithm, const std::string& secret,
+ uint32_t digestbits = 0) {
+ // Return value, assume its a match.
+ return (((key) &&
+ (key->getName() == name) &&
+ (key->getAlgorithm() == algorithm) &&
+ (key->getDigestbits() == digestbits) &&
+ (key->getSecret() == secret) &&
+ (key->getTSIGKey())));
+}
+
+/// @brief Convenience function which compares the contents of the given
+/// DnsServerInfo against the given set of values.
+///
+/// It is structured in such a way that each value is checked, and output
+/// is generated for all that do not match.
+///
+/// @param server is a pointer to the server to check against.
+/// @param hostname is the value to compare against server's hostname_.
+/// @param ip_address is the string value to compare against server's
+/// ip_address_.
+/// @param port is the value to compare against server's port.
+///
+/// @return returns true if there is a match across the board, otherwise it
+/// returns false.
+bool checkServer(DnsServerInfoPtr server, const char* hostname,
+ const char *ip_address, uint32_t port)
+{
+ // Return value, assume its a match.
+ bool result = true;
+
+ if (!server) {
+ EXPECT_TRUE(server);
+ return false;
+ }
+
+ // Check hostname.
+ if (server->getHostname() != hostname) {
+ EXPECT_EQ(hostname, server->getHostname());
+ result = false;
+ }
+
+ // Check IP address.
+ if (server->getIpAddress().toText() != ip_address) {
+ EXPECT_EQ(ip_address, server->getIpAddress().toText());
+ result = false;
+ }
+
+ // Check port.
+ if (server->getPort() != port) {
+ EXPECT_EQ (port, server->getPort());
+ result = false;
+ }
+
+ return (result);
+}
+
+/// @brief Base class test fixture for testing JSON and element parsing
+/// for D2 configuration elements. It combines the three phases of
+/// configuration parsing normally orchestrated by D2CfgMgr:
+/// 1. Submit the JSON text to the JSON parser
+/// 2. Add defaults to the element tree produced by the JSON parser
+/// 3. Pass the element tree into the appropriate SimpleParser derivation
+/// to parse the element tree into D2 objects.
+class D2SimpleParserTest : public ::testing::Test {
+public:
+ /// @brief Constructor
+ ///
+ /// @param parser_type specifies the parsing starting point at which
+ /// the JSON parser should begin. It defaults to PARSER_JSON. See @c
+ /// D2ParserContext::ParserType for all possible values.
+ D2SimpleParserTest(const D2ParserContext::ParserType&
+ parser_type = D2ParserContext::PARSER_JSON)
+ : parser_type_(parser_type) {
+ reset();
+ }
+
+ /// @brief Destructor
+ virtual ~D2SimpleParserTest() {
+ reset();
+ }
+
+ /// @brief Parses JSON text and compares the results against an expected
+ /// outcome.
+ ///
+ /// The JSON text is submitted to the D2ParserContext for parsing. Any
+ /// errors emitted here are caught and compared against the expected
+ /// error or flagged as unexpected.
+ /// Next, the virtual method, setDefaults()is invoked. his method should
+ /// be used by derivations to add default values to the element tree
+ /// produced by the JSON parser.
+ /// Lastly, it passes the element tree into the virtual method,
+ /// parseElement(). This method should be used by derivations to create
+ /// the appropriate element parser to parse the element tree into the
+ /// appropriate D2 object(s).
+ ///
+ /// @param json JSON text to parse
+ /// @param exp_error exact text of the error message expected or ""
+ /// if parsing should succeed.
+ ::testing::AssertionResult parseOrFail(const std::string& json,
+ const std::string& exp_error) {
+ try {
+ // Free up objects created by previous invocation
+ reset();
+
+ // Submit JSON text to JSON parser. We convert the result to
+ // a mutable element tree to allow defaults to be added.
+ D2ParserContext context;
+ data::ElementPtr elem = boost::const_pointer_cast<Element>
+ (context.parseString(json, parser_type_));
+ // Add any defaults
+ setDefaults(elem);
+
+ // Now parse the element tree into object(s).
+ parseElement(elem);
+ } catch (const std::exception& ex) {
+ std::string caught_error = ex.what();
+ if (exp_error.empty()) {
+ return ::testing::AssertionFailure()
+ << "Unexpected error: " << caught_error
+ << "\n json: [" << json << "]";
+ }
+
+ if (exp_error != caught_error) {
+ return ::testing::AssertionFailure()
+ << "Wrong error detected, expected: "
+ << exp_error << ", got: " << caught_error
+ << "\n json: [" << json << "]";
+ }
+ return ::testing::AssertionSuccess();
+ }
+
+ if (!exp_error.empty()) {
+ return ::testing::AssertionFailure()
+ << "Unexpected parsing success "
+ << exp_error << "\n json: [" << json << "]";
+ }
+
+ return ::testing::AssertionSuccess();
+ }
+
+
+protected:
+ /// @brief Free up objects created by element parsing
+ /// This method is invoked at the beginning of @c parseOrFail() to
+ /// ensure any D2 object(s) that were created by a prior invocation are
+ /// destroyed. This permits parsing to be conducted more than once
+ /// in the same test.
+ virtual void reset(){};
+
+ /// @brief Adds default values to the given element tree
+ ///
+ /// Derivations are expected to use the appropriate methods in
+ /// D2SimpleParser to add defaults values.
+ ///
+ /// @param config element tree in which defaults should be added
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ static_cast<void>(config);
+ return (0);
+ }
+
+ /// @brief Parses a given element tree into D2 object(s)
+ ///
+ /// Derivations are expected to create the appropriate element
+ /// parser and pass it the element tree for parsing. Any object(s)
+ /// created should likely be saved for content verification
+ /// outside of this method.
+ ///
+ /// @param config element tree to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ static_cast<void>(config);
+ }
+
+ D2ParserContext::ParserType parser_type_;
+};
+
+/// @brief Convenience macros for calling parseOrFail
+#define PARSE_OK(a) EXPECT_TRUE((parseOrFail(a, "")))
+#define PARSE_FAIL(a,b) EXPECT_TRUE((parseOrFail(a, b)))
+
+// This test checks if global defaults are properly set for D2.
+TEST_F(D2SimpleParserTest, globalD2Defaults) {
+
+ ElementPtr empty = isc::d2::test::parseJSON("{ }");
+ size_t num = 0;
+
+ EXPECT_NO_THROW(num = D2SimpleParser::setAllDefaults(empty));
+
+ // We expect 5 parameters to be inserted.
+ EXPECT_EQ(num, 8);
+
+ // Let's go over all parameters we have defaults for.
+ BOOST_FOREACH(SimpleDefault deflt, D2SimpleParser::D2_GLOBAL_DEFAULTS) {
+ ConstElementPtr x;
+ ASSERT_NO_THROW(x = empty->get(deflt.name_));
+
+ EXPECT_TRUE(x);
+ if (x) {
+ if (deflt.type_ == Element::integer) {
+ checkIntegerValue(x, deflt);
+ } else if (deflt.type_ == Element::boolean) {
+ checkBooleanValue(x, deflt);
+ } else if (deflt.type_ == Element::string) {
+ checkStringValue(x, deflt);
+ } else {
+ // add them if we need to. Like what do you if it's a map?
+ ADD_FAILURE() << "default type not supported:" << deflt.name_
+ << " ,type: " << deflt.type_;
+ }
+ }
+ }
+}
+
+/// @brief Test fixture class for testing TSIGKeyInfo parsing.
+class TSIGKeyInfoParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ TSIGKeyInfoParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_TSIG_KEY) {
+ }
+
+ /// @brief Free up the keys created by parsing
+ virtual void reset() {
+ key_.reset();
+ };
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoParserTest() {
+ reset();
+ };
+
+ /// @brief Adds TSIG Key default values to the given TSIG Key element
+ ///
+ /// @param config TSIG Key element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setDefaults(config, D2SimpleParser::
+ TSIG_KEY_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a TSIGKeyInfo
+ ///
+ /// Assumes the given element is a Map containing the attributes for
+ /// a TSIG Key. If parsing is successful the new TSIGKeyInfo instance
+ /// is retained in the member, key_;
+ ///
+ /// @param config element to parse
+ void parseElement(data::ConstElementPtr config) {
+ TSIGKeyInfoParser parser;
+ key_ = parser.parse(config);
+ }
+
+ /// @brief Retains the TSIGKeyInfo created by a successful parsing
+ TSIGKeyInfoPtr key_;
+};
+
+
+/// @brief Test fixture class for testing TSIGKeyInfo list parsing.
+class TSIGKeyInfoListParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ TSIGKeyInfoListParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_TSIG_KEYS) {
+ }
+
+ /// @brief Destructor
+ virtual ~TSIGKeyInfoListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the keys created by parsing
+ virtual void reset() {
+ keys_.reset();
+ };
+
+ /// @brief Adds TSIG Key default values to a list of TSIG Key elements
+ ///
+ /// @param config list of TSIG Key elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setListDefaults(config, D2SimpleParser::
+ TSIG_KEY_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a list of TSIGKeyInfos
+ ///
+ /// Assumes the given element is a list containing one or more TSIG Keys
+ /// elements. If parsing is successful the list of TSIGKeyInfo instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ void parseElement(data::ConstElementPtr config) {
+ TSIGKeyInfoListParser parser;
+ keys_ = parser.parse(config);
+ }
+
+ /// @brief Retains the TSIGKeyInfos created by a successful parsing
+ TSIGKeyInfoMapPtr keys_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfo parsing.
+class DnsServerInfoParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ DnsServerInfoParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_DNS_SERVER) {
+ }
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the server created by parsing
+ virtual void reset() {
+ server_.reset();
+ }
+
+ /// @brief Adds DNS Server default values to the given DNS Server element
+ ///
+ /// @param config DNS Server element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setDefaults(config, D2SimpleParser::
+ DNS_SERVER_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a DnsServerInfo
+ ///
+ /// Assumes the given element is a map containing the attributes for
+ /// a DNS Server. If parsing is successful the new DnsServerInfo instance
+ /// is retained in the member, server_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DnsServerInfoParser parser;
+ std::string domain = "{ \"key-name\": \"\" }";
+ server_ = parser.parse(config, Element::fromJSON(domain), {});
+ }
+
+ /// @brief Retains the DnsServerInfo created by a successful parsing
+ DnsServerInfoPtr server_;
+};
+
+/// @brief Test fixture class for testing DnsServerInfoList parsing.
+class DnsServerInfoListParserTest : public D2SimpleParserTest {
+public:
+ /// @brief Constructor
+ DnsServerInfoListParserTest()
+ : D2SimpleParserTest(D2ParserContext::PARSER_DNS_SERVERS) {
+ }
+
+ /// @brief Destructor
+ virtual ~DnsServerInfoListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the servers created by parsing
+ virtual void reset() {
+ servers_.reset();
+ }
+
+ /// @brief Adds DNS Server default values to a list of DNS Server elements
+ ///
+ /// @param config list of DNS Server elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (SimpleParser::setListDefaults(config, D2SimpleParser::
+ DNS_SERVER_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a list of DnsServerInfos
+ ///
+ /// Assumes the given element is a list containing one or more DNS Servers
+ /// elements. If parsing is successful the list of DnsServerInfo instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DnsServerInfoListParser parser;
+ std::string domain = "{ \"key-name\": \"\" }";
+ servers_ = parser.parse(config, Element::fromJSON(domain), {});
+ }
+
+ /// @brief Retains the DnsServerInfos created by a successful parsing
+ DnsServerInfoStoragePtr servers_;
+};
+
+
+/// @brief Test fixture class for testing DDnsDomain parsing.
+class DdnsDomainParserTest : public D2SimpleParserTest {
+public:
+
+ /// @brief Constructor
+ DdnsDomainParserTest(const D2ParserContext::ParserType& parser_type
+ = D2ParserContext::PARSER_DDNS_DOMAIN)
+ : D2SimpleParserTest(parser_type), keys_(new TSIGKeyInfoMap()) {
+ }
+
+ /// @brief Destructor
+ virtual ~DdnsDomainParserTest() {
+ reset();
+ }
+
+ /// @brief Free up the domain created by parsing
+ virtual void reset() {
+ domain_.reset();
+ }
+
+ /// @brief Add TSIGKeyInfos to the key map
+ ///
+ /// @param name the name of the key
+ /// @param algorithm the algorithm of the key
+ /// @param secret the secret value of the key
+ void addKey(const std::string& name, const std::string& algorithm,
+ const std::string& secret) {
+ TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
+ (*keys_)[name]=key_info;
+ }
+
+ /// @brief Adds DDNS Domain values to the given DDNS Domain element
+ ///
+ /// @param config DDNS Domain element to which defaults should be added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ return (D2SimpleParser::setDdnsDomainDefaults(config, D2SimpleParser::
+ DDNS_DOMAIN_DEFAULTS));
+ }
+
+ /// @brief Attempts to parse the given element into a DdnsDomain
+ ///
+ /// Assumes the given element is a map containing the attributes for
+ /// a DDNS Domain. If parsing is successful the new DdnsDomain instance
+ /// is retained in the member, server_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DdnsDomainParser parser;
+ domain_ = parser.parse(config, keys_);
+ }
+
+ /// @brief Retains the DdnsDomain created by a successful parsing
+ DdnsDomainPtr domain_;
+
+ /// @brief Storage for TSIGKeys, used by DdnsDomainParser to validate
+ /// domain keys
+ TSIGKeyInfoMapPtr keys_;
+};
+
+class DdnsDomainListParserTest : public DdnsDomainParserTest {
+public:
+ /// @brief Constructor
+ DdnsDomainListParserTest()
+ // We need the list context type to parse lists correctly
+ : DdnsDomainParserTest(D2ParserContext::PARSER_DDNS_DOMAINS) {
+ }
+
+ /// @brief Destructor
+ virtual ~DdnsDomainListParserTest() {
+ reset();
+ }
+
+ /// @brief Free up domains created by parsing
+ virtual void reset() {
+ domains_.reset();
+ }
+
+ /// @brief Adds DDNS Domain default values to a list of DDNS Domain elements
+ ///
+ /// @param config list of DDNS Domain elements to which defaults should be
+ /// added
+ ///
+ /// @return the number of default items added to the tree
+ virtual size_t setDefaults(data::ElementPtr config) {
+ size_t cnt = 0;
+ // We don't use SimpleParser::setListDefaults() as this does
+ // not handle sub-lists or sub-maps
+ BOOST_FOREACH(ElementPtr domain, config->listValue()) {
+ cnt += D2SimpleParser::
+ setDdnsDomainDefaults(domain, D2SimpleParser::
+ DDNS_DOMAIN_DEFAULTS);
+ }
+
+ return (cnt);
+ }
+
+ /// @brief Attempts to parse the given element into a list of DdnsDomains
+ ///
+ /// Assumes the given element is a list containing one or more DDNS Domains
+ /// elements. If parsing is successful the list of DdnsDomain instances
+ /// is retained in the member, keys_;
+ ///
+ /// @param config element to parse
+ virtual void parseElement(data::ConstElementPtr config) {
+ DdnsDomainListParser parser;
+ domains_ = parser.parse(config, keys_);
+ }
+
+ /// @brief Retains the DdnsDomains created by a successful parsing
+ DdnsDomainMapPtr domains_;
+};
+
+/// @brief Tests the enforcement of data validation when parsing TSIGKeyInfos.
+/// It verifies that:
+/// 1. Name cannot be blank.
+/// 2. Algorithm cannot be blank.
+/// 3. Secret cannot be blank.
+TEST_F(TSIGKeyInfoParserTest, invalidEntry) {
+
+ // Name cannot be blank.
+ std::string config = "{"
+ " \"name\": \"\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.9: TSIG key name cannot be blank");
+
+ // Algorithm cannot be be blank.
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.38: TSIG key algorithm cannot be blank");
+
+ // Algorithm must be a valid algorithm
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"bogus\" , "
+ " \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+ "}";
+ PARSE_FAIL(config, "tsig-key : Unknown TSIG Key algorithm:"
+ " bogus (<string>:1:40)");
+
+ // Secret cannot be blank
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"secret\": \"\" "
+ "}";
+ PARSE_FAIL(config, "<string>:1.62: TSIG key secret cannot be blank");
+
+ // Secret must be valid for algorithm
+ config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"bogus\" "
+ "}";
+ PARSE_FAIL(config, "Cannot make D2TsigKey: Incomplete input for base64:"
+ " bogus (<string>:1:1)");
+}
+
+
+/// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
+/// when given a valid combination of entries.
+TEST_F(TSIGKeyInfoParserTest, validEntry) {
+ // Valid entries for TSIG key, all items are required.
+ std::string config = "{"
+ " \"name\": \"d2_key_one\" , "
+ " \"algorithm\": \"HMAC-MD5\" , "
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ "}";
+ // Verify that it parses.
+ PARSE_OK(config);
+ ASSERT_TRUE(key_);
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key_, "d2_key_one", "HMAC-MD5",
+ "dGhpcyBrZXkgd2lsbCBtYXRjaA==", 120));
+
+ // Verify unparsing.
+ runToElementTest<TSIGKeyInfo>(config, *key_);
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoListParserTest, invalidTSIGKeyList) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ // this entry has an invalid algorithm
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " }"
+ " ]";
+
+ PARSE_FAIL(config, "<string>:1.151: TSIG key algorithm cannot be blank");
+}
+
+/// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
+/// entries is detected.
+TEST_F(TSIGKeyInfoListParserTest, duplicateTSIGKey) {
+ // Construct a list of keys with an invalid key entry.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 120 , "
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " },"
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"secret\": \"GWG/Xfbju4O2iXGqkSu4PQ==\" "
+ " }"
+ " ]";
+
+ PARSE_FAIL(config,
+ "Duplicate TSIG key name specified : key1 (<string>:1:239)");
+}
+
+/// @brief Verifies a valid list of TSIG Keys parses correctly.
+/// Also verifies that all of the supported algorithm names work.
+TEST_F(TSIGKeyInfoListParserTest, validTSIGKeyList) {
+ // Construct a valid list of keys.
+ std::string config = "["
+ " { \"name\": \"key1\" , "
+ " \"algorithm\": \"HMAC-MD5\" ,"
+ " \"digest-bits\": 80 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key2\" , "
+ " \"algorithm\": \"HMAC-SHA1\" ,"
+ " \"digest-bits\": 80 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key3\" , "
+ " \"algorithm\": \"HMAC-SHA256\" ,"
+ " \"digest-bits\": 128 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key4\" , "
+ " \"algorithm\": \"HMAC-SHA224\" ,"
+ " \"digest-bits\": 112 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key5\" , "
+ " \"algorithm\": \"HMAC-SHA384\" ,"
+ " \"digest-bits\": 192 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " },"
+ " { \"name\": \"key6\" , "
+ " \"algorithm\": \"HMAC-SHA512\" ,"
+ " \"digest-bits\": 256 , "
+ " \"secret\": \"dGhpcyBrZXkgd2lsbCBtYXRjaA==\" "
+ " }"
+ " ]";
+
+ PARSE_OK(config);
+ ASSERT_TRUE(keys_);
+
+ std::string ref_secret = "dGhpcyBrZXkgd2lsbCBtYXRjaA==";
+ // Verify the correct number of keys are present
+ int count = keys_->size();
+ ASSERT_EQ(6, count);
+
+ // Find the 1st key and retrieve it.
+ TSIGKeyInfoMap::iterator gotit = keys_->find("key1");
+ ASSERT_TRUE(gotit != keys_->end());
+ TSIGKeyInfoPtr& key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key1", TSIGKeyInfo::HMAC_MD5_STR,
+ ref_secret, 80));
+
+ // Find the 2nd key and retrieve it.
+ gotit = keys_->find("key2");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key2", TSIGKeyInfo::HMAC_SHA1_STR,
+ ref_secret, 80));
+
+ // Find the 3rd key and retrieve it.
+ gotit = keys_->find("key3");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key3", TSIGKeyInfo::HMAC_SHA256_STR,
+ ref_secret, 128));
+
+ // Find the 4th key and retrieve it.
+ gotit = keys_->find("key4");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key4", TSIGKeyInfo::HMAC_SHA224_STR,
+ ref_secret, 112));
+
+ // Find the 5th key and retrieve it.
+ gotit = keys_->find("key5");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key5", TSIGKeyInfo::HMAC_SHA384_STR,
+ ref_secret, 192));
+
+ // Find the 6th key and retrieve it.
+ gotit = keys_->find("key6");
+ ASSERT_TRUE(gotit != keys_->end());
+ key = gotit->second;
+
+ // Verify the key contents.
+ EXPECT_TRUE(checkKey(key, "key6", TSIGKeyInfo::HMAC_SHA512_STR,
+ ref_secret, 256));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DnsServerInfos.
+/// It verifies that:
+/// 1. Specifying both a hostname and an ip address is not allowed.
+/// 2. Specifying both blank a hostname and blank ip address is not allowed.
+/// 3. Specifying a negative port number is not allowed.
+
+TEST_F(DnsServerInfoParserTest, invalidEntry) {
+ // Create a config in which both host and ip address are supplied.
+ // Verify that parsing fails.
+ std::string config = "{ \"hostname\": \"pegasus.example\", "
+ " \"ip-address\": \"127.0.0.1\", "
+ " \"port\": 100} ";
+ PARSE_FAIL(config, "<string>:1.13: hostname is not yet supported");
+
+
+ // Neither host nor ip address supplied
+ // Verify that builds fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip-address\": \"\", "
+ " \"port\": 100} ";
+ PARSE_FAIL(config, "Dns Server must specify one or the other"
+ " of hostname or IP address (<string>:1:1)");
+
+ // Create a config with a negative port number.
+ // Verify that build fails.
+ config = "{ \"hostname\": \"\", "
+ " \"ip-address\": \"192.168.5.6\" ,"
+ " \"port\": -100 }";
+ PARSE_FAIL(config, "<string>:1.60-63: port must be greater than zero but less than 65536");
+}
+
+
+/// @brief Verifies that DnsServerInfo parsing creates a proper DnsServerInfo
+/// when given a valid combination of entries.
+/// It verifies that:
+/// 1. A DnsServerInfo entry is correctly made, when given only a hostname.
+/// 2. A DnsServerInfo entry is correctly made, when given ip address and port.
+/// 3. A DnsServerInfo entry is correctly made, when given only an ip address.
+TEST_F(DnsServerInfoParserTest, validEntry) {
+ /// @todo When resolvable hostname is supported you'll need this test.
+ /// // Valid entries for dynamic host
+ /// std::string config = "{ \"hostname\": \"pegasus.example\" }";
+ /// ASSERT_TRUE(fromJSON(config));
+
+ /// // Verify that it builds and commits without throwing.
+ /// ASSERT_NO_THROW(parser_->build(config_set_));
+ /// ASSERT_NO_THROW(parser_->commit());
+
+ /// //Verify the correct number of servers are present
+ /// int count = servers_->size();
+ /// EXPECT_EQ(1, count);
+
+ /// Verify the server exists and has the correct values.
+ /// DnsServerInfoPtr server = (*servers_)[0];
+ /// EXPECT_TRUE(checkServer(server, "pegasus.example",
+ /// DnsServerInfo::EMPTY_IP_STR,
+ /// DnsServerInfo::STANDARD_DNS_PORT));
+
+ /// // Start over for a new test.
+ /// reset();
+
+ // Valid entries for static ip
+ std::string config = " { \"hostname\" : \"\", "
+ " \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 }";
+ PARSE_OK(config);
+ ASSERT_TRUE(server_);
+ EXPECT_TRUE(checkServer(server_, "", "127.0.0.1", 100));
+
+ // Verify unparsing.
+ runToElementTest<DnsServerInfo>(config, *server_);
+
+ // Valid entries for static ip, no port
+ // This will fail without invoking set defaults
+ config = " { \"ip-address\": \"192.168.2.5\" }";
+ PARSE_OK(config);
+ ASSERT_TRUE(server_);
+ EXPECT_TRUE(checkServer(server_, "", "192.168.2.5",
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Verifies that attempting to parse an invalid list of DnsServerInfo
+/// entries is detected.
+TEST_F(DnsServerInfoListParserTest, invalidServerList) {
+ // Construct a list of servers with an invalid server entry.
+ std::string config = "[ { \"ip-address\": \"127.0.0.1\" }, "
+ "{ \"ip-address\": \"\" }, "
+ "{ \"ip-address\": \"127.0.0.2\" } ]";
+ PARSE_FAIL(config, "Dns Server must specify one or the other"
+ " of hostname or IP address (<string>:1:34)");
+ ASSERT_FALSE(servers_);
+}
+
+/// @brief Verifies that a list of DnsServerInfo entries parses correctly given
+/// a valid configuration.
+TEST_F(DnsServerInfoListParserTest, validServerList) {
+ // Create a valid list of servers.
+ std::string config = "[ { \"ip-address\": \"127.0.0.1\" }, "
+ "{ \"ip-address\": \"127.0.0.2\" }, "
+ "{ \"ip-address\": \"127.0.0.3\" } ]";
+ PARSE_OK(config);
+
+ // Verify that the server storage contains the correct number of servers.
+ ASSERT_EQ(3, servers_->size());
+
+ // Verify the first server exists and has the correct values.
+ DnsServerInfoPtr server = (*servers_)[0];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1",
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the second server exists and has the correct values.
+ server = (*servers_)[1];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2",
+ DnsServerInfo::STANDARD_DNS_PORT));
+
+ // Verify the third server exists and has the correct values.
+ server = (*servers_)[2];
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3",
+ DnsServerInfo::STANDARD_DNS_PORT));
+}
+
+/// @brief Tests the enforcement of data validation when parsing DdnsDomains.
+/// It verifies that:
+/// 1. Domain storage cannot be null when constructing a DdnsDomainParser.
+/// 2. The name entry is not optional.
+/// 3. The server list may not be empty.
+/// 4. That a mal-formed server entry is detected.
+/// 5. That an undefined key name is detected.
+TEST_F(DdnsDomainParserTest, invalidDomain) {
+ // Create a domain configuration without a name
+ std::string config = "{ \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip-address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ PARSE_FAIL(config, "missing parameter 'name' (<string>:1:1)");
+
+ // Create a domain configuration with an empty server list.
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"\" , "
+ " \"dns-servers\" : [ "
+ " ] } ";
+ PARSE_FAIL(config, "<string>:1.69: syntax error, unexpected ], expecting {");
+
+ // Create a domain configuration with a mal-formed server entry.
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": -1 } ] } ";
+ PARSE_FAIL(config, "<string>:1.111-112: port must be greater than zero but less than 65536");
+
+ // Create a domain configuration without an defined key name
+ config = "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ PARSE_FAIL(config, "DdnsDomain : specifies"
+ " an undefined key: d2_key.example.com (<string>:1:41)");
+}
+
+/// @brief Verifies the basics of parsing of a DdnsDomain.
+TEST_F(DdnsDomainParserTest, validDomain) {
+ // Add a TSIG key to the test key map, so key validation will pass.
+ addKey("d2_key.example.com", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+
+ // Create a valid domain configuration entry containing three valid
+ // servers.
+ std::string config =
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip-address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } ";
+ PARSE_OK(config);
+
+ // Domain should exist
+ ASSERT_TRUE(domain_);
+
+ // Verify the name and key_name values.
+ EXPECT_EQ("example.com", domain_->getName());
+ EXPECT_EQ("d2_key.example.com", domain_->getKeyName());
+
+ // Verify that the server list exists and contains the correct number of
+ // servers.
+ const DnsServerInfoStoragePtr& servers = domain_->getServers();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ // Fetch each server and verify its contents.
+ DnsServerInfoPtr server = (*servers)[0];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ // Verify unparsing.
+ ElementPtr json;
+ ASSERT_NO_THROW(json = Element::fromJSON(config));
+ ConstElementPtr servers_json;
+ ASSERT_NO_THROW(servers_json = json->get("dns-servers"));
+ ASSERT_TRUE(servers_json);
+ ASSERT_EQ(Element::list, servers_json->getType());
+ for (size_t i = 0; i < servers_json->size(); ++i) {
+ ElementPtr server_json;
+ ASSERT_NO_THROW(server_json = servers_json->getNonConst(i));
+ ASSERT_NO_THROW(server_json->set("hostname",
+ Element::create(std::string())));
+ }
+ runToElementTest<DdnsDomain>(json, *domain_);
+}
+
+/// @brief Tests the fundamentals of parsing DdnsDomain lists.
+/// This test verifies that given a valid domain list configuration
+/// it will accurately parse and populate each domain in the list.
+TEST_F(DdnsDomainListParserTest, validList) {
+ // Add keys to key map so key validation passes.
+ addKey("d2_key.example.com", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+ addKey("d2_key.billcat.net", "HMAC-MD5", "GWG/Xfbju4O2iXGqkSu4PQ==");
+
+ // Create a valid domain list configuration, with two domains
+ // that have three servers each.
+ std::string config =
+ "[ "
+ "{ \"name\": \"example.com\" , "
+ " \"key-name\": \"d2_key.example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" , "
+ " \"port\": 100 },"
+ " { \"ip-address\": \"127.0.0.2\" , "
+ " \"port\": 200 },"
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"billcat.net\" , "
+ " \"key-name\": \"d2_key.billcat.net\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.4\" , "
+ " \"port\": 400 },"
+ " { \"ip-address\": \"127.0.0.5\" , "
+ " \"port\": 500 },"
+ " { \"ip-address\": \"127.0.0.6\" , "
+ " \"port\": 600 } ] } "
+ "] ";
+
+ // Verify that the domain list parses without error.
+ PARSE_OK(config);
+ ASSERT_TRUE(domains_);
+ EXPECT_EQ(2, domains_->size());
+
+ // Verify that the first domain exists and can be retrieved.
+ DdnsDomainMap::iterator gotit = domains_->find("example.com");
+ ASSERT_TRUE(gotit != domains_->end());
+ DdnsDomainPtr& domain = gotit->second;
+
+ // Verify the name and key_name values of the first domain.
+ EXPECT_EQ("example.com", domain->getName());
+ EXPECT_EQ("d2_key.example.com", domain->getKeyName());
+
+ // Verify the each of the first domain's servers
+ DnsServerInfoStoragePtr servers = domain->getServers();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ DnsServerInfoPtr server = (*servers)[0];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.1", 100));
+
+ // Verify the TSIGKeyInfo name and that the actual key was created
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.2", 200));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.3", 300));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ // Verify second domain
+ gotit = domains_->find("billcat.net");
+ ASSERT_TRUE(gotit != domains_->end());
+ domain = gotit->second;
+
+ // Verify the name and key_name values of the second domain.
+ EXPECT_EQ("billcat.net", domain->getName());
+ EXPECT_EQ("d2_key.billcat.net", domain->getKeyName());
+
+ // Verify the each of second domain's servers
+ servers = domain->getServers();
+ ASSERT_TRUE(servers);
+ EXPECT_EQ(3, servers->size());
+
+ server = (*servers)[0];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.4", 400));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[1];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.5", 500));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+
+ server = (*servers)[2];
+ ASSERT_TRUE(server);
+ EXPECT_TRUE(checkServer(server, "", "127.0.0.6", 600));
+ ASSERT_TRUE(server->getTSIGKeyInfo());
+ EXPECT_EQ(domain->getKeyName(), server->getKeyName());
+ EXPECT_EQ(domain->getKeyName(), server->getTSIGKeyInfo()->getName());
+ EXPECT_TRUE(server->getTSIGKeyInfo()->getTSIGKey());
+}
+
+/// @brief Tests that a domain list configuration cannot contain duplicates.
+TEST_F(DdnsDomainListParserTest, duplicateDomain) {
+ // Create a domain list configuration that contains two domains with
+ // the same name.
+ std::string config =
+ "[ "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ ", "
+ "{ \"name\": \"example.com\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.3\" , "
+ " \"port\": 300 } ] } "
+ "] ";
+ // Verify that the parsing fails.
+ PARSE_FAIL(config,
+ "Duplicate domain specified:example.com (<string>:1:115)");
+}
+
+}
diff --git a/src/bin/d2/tests/d2_unittests.cc b/src/bin/d2/tests/d2_unittests.cc
new file mode 100644
index 0000000..4607550
--- /dev/null
+++ b/src/bin/d2/tests/d2_unittests.cc
@@ -0,0 +1,28 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <d2srv/d2_log.h>
+#include <log/logger_support.h>
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+
+ ::testing::InitGoogleTest(&argc, argv);
+
+ // See the documentation of the KEA_* environment variables in
+ // src/lib/log/README for info on how to tweak logging
+ isc::log::initLogger();
+
+ // Override --localstatedir value for PID files
+ setenv("KEA_PIDFILE_DIR", TEST_DATA_BUILDDIR, 1);
+
+ int result = RUN_ALL_TESTS();
+
+ return (result);
+}
diff --git a/src/bin/d2/tests/d2_update_mgr_unittests.cc b/src/bin/d2/tests/d2_update_mgr_unittests.cc
new file mode 100644
index 0000000..d79b453
--- /dev/null
+++ b/src/bin/d2/tests/d2_update_mgr_unittests.cc
@@ -0,0 +1,989 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <d2/d2_update_mgr.h>
+#include <d2/nc_add.h>
+#include <d2/nc_remove.h>
+#include <d2/simple_add.h>
+#include <d2/simple_remove.h>
+#include <process/testutils/d_test_stubs.h>
+#include <util/time_utilities.h>
+
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <vector>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp_ddns;
+using namespace isc::d2;
+using namespace isc::process;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Wrapper class for D2UpdateMgr providing access to non-public methods.
+///
+/// This class facilitates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines.
+class D2UpdateMgrWrapper : public D2UpdateMgr {
+public:
+ /// @brief Constructor
+ ///
+ /// Parameters match those needed by D2UpdateMgr.
+ D2UpdateMgrWrapper(D2QueueMgrPtr& queue_mgr, D2CfgMgrPtr& cfg_mgr,
+ asiolink::IOServicePtr& io_service,
+ const size_t max_transactions = MAX_TRANSACTIONS_DEFAULT)
+ : D2UpdateMgr(queue_mgr, cfg_mgr, io_service, max_transactions) {
+ }
+
+ /// @brief Destructor
+ virtual ~D2UpdateMgrWrapper() {
+ }
+
+ // Expose the protected methods to be tested.
+ using D2UpdateMgr::checkFinishedTransactions;
+ using D2UpdateMgr::pickNextJob;
+ using D2UpdateMgr::makeTransaction;
+};
+
+/// @brief Defines a pointer to a D2UpdateMgr instance.
+typedef boost::shared_ptr<D2UpdateMgrWrapper> D2UpdateMgrWrapperPtr;
+
+/// @brief Test fixture for testing D2UpdateMgr.
+///
+/// Note this class uses D2UpdateMgrWrapper class to exercise non-public
+/// aspects of D2UpdateMgr. D2UpdateMgr depends on both D2QueueMgr and
+/// D2CfgMgr. This fixture provides an instance of each, plus a canned,
+/// valid DHCP_DDNS configuration sufficient to test D2UpdateMgr's basic
+/// functions.
+class D2UpdateMgrTest : public TimedIO, public ConfigParseTest {
+public:
+ D2QueueMgrPtr queue_mgr_;
+ D2CfgMgrPtr cfg_mgr_;
+ D2UpdateMgrWrapperPtr update_mgr_;
+ std::vector<NameChangeRequestPtr> canned_ncrs_;
+ size_t canned_count_;
+
+ D2UpdateMgrTest() {
+ queue_mgr_.reset(new D2QueueMgr(io_service_));
+ cfg_mgr_.reset(new D2CfgMgr());
+ update_mgr_.reset(new D2UpdateMgrWrapper(queue_mgr_, cfg_mgr_,
+ io_service_));
+ makeCannedNcrs();
+ makeCannedConfig();
+ }
+
+ ~D2UpdateMgrTest() {
+ }
+
+ /// @brief Creates a list of valid NameChangeRequest.
+ ///
+ /// This method builds a list of NameChangeRequests from a single
+ /// JSON string request. Each request is assigned a unique DHCID.
+ void makeCannedNcrs() {
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : false , "
+ " \"fqdn\" : \"my.example.com.\" , "
+ " \"ip-address\" : \"192.168.1.2\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ const char* dhcids[] = { "111111", "222222", "333333", "444444" };
+ canned_count_ = 4;
+ for (int i = 0; i < canned_count_; i++) {
+ dhcp_ddns::NameChangeRequestPtr ncr = NameChangeRequest::
+ fromJSON(msg_str);
+ ncr->setDhcid(dhcids[i]);
+ ncr->setChangeType(i % 2 == 0 ?
+ dhcp_ddns::CHG_ADD : dhcp_ddns::CHG_REMOVE);
+ canned_ncrs_.push_back(ncr);
+ }
+ }
+
+ /// @brief Seeds configuration manager with a valid DHCP_DDNS configuration.
+ void makeCannedConfig() {
+ std::string canned_config_ =
+ "{ "
+ "\"ip-address\" : \"192.168.1.33\" , "
+ "\"port\" : 88 , "
+ "\"tsig-keys\": [] ,"
+ "\"forward-ddns\" : {"
+ " \"ddns-domains\": [ "
+ " { \"name\": \"example.com.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\", \"port\" : 5301 } "
+ " ] },"
+ " { \"name\": \"org.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] }"
+ " ] }, "
+ "\"reverse-ddns\" : { "
+ " \"ddns-domains\": [ "
+ " { \"name\": \"1.168.192.in-addr.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\", \"port\" : 5301 } "
+ " ] }, "
+ " { \"name\": \"2.0.3.0.8.B.D.0.1.0.0.2.ip6.arpa.\" , "
+ " \"dns-servers\" : [ "
+ " { \"ip-address\": \"127.0.0.1\" } "
+ " ] } "
+ " ] } }";
+
+ // If this configuration fails to parse most tests will fail.
+ ASSERT_TRUE(fromJSON(canned_config_));
+ answer_ = cfg_mgr_->simpleParseConfig(config_set_);
+ ASSERT_TRUE(checkAnswer(0));
+ }
+
+ /// @brief Fakes the completion of a given transaction.
+ ///
+ /// @param index index of the request from which the transaction was formed.
+ /// @param status completion status to assign to the request
+ void completeTransaction(const size_t index,
+ const dhcp_ddns::NameChangeStatus& status) {
+ // add test on index
+ if (index >= canned_count_) {
+ ADD_FAILURE() << "request index is out of range: " << index;
+ }
+
+ const dhcp_ddns::D2Dhcid key = canned_ncrs_[index]->getDhcid();
+
+ // locate the transaction based on the request DHCID
+ TransactionList::iterator pos = update_mgr_->findTransaction(key);
+ if (pos == update_mgr_->transactionListEnd()) {
+ ADD_FAILURE() << "cannot find transaction for key: " << key.toStr();
+ }
+
+ NameChangeTransactionPtr trans = (*pos).second;
+ // Update the status of the request
+ trans->getNcr()->setStatus(status);
+ // End the model.
+ trans->endModel();
+ }
+
+ /// @brief Determines if any transactions are waiting for IO completion.
+ ///
+ /// @returns True if isModelWaiting() is true for at least one of the current
+ /// transactions.
+ bool anyoneWaiting() {
+ TransactionList::iterator it = update_mgr_->transactionListBegin();
+ while (it != update_mgr_->transactionListEnd()) {
+ if (((*it).second)->isModelWaiting()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// @brief Process events until all requests have been completed.
+ ///
+ /// This method iteratively calls D2UpdateMgr::sweep and executes
+ /// IOService calls until both the request queue and transaction list
+ /// are empty or a timeout occurs. Note that in addition to the safety
+ /// timer, the number of passes through the loop is also limited to
+ /// a given number. This is a failsafe to guard against an infinite loop
+ /// in the test.
+ void processAll(size_t max_passes = 100) {
+ // Loop until all the transactions have been dequeued and run through to
+ // completion.
+ size_t passes = 0;
+ size_t handlers = 0;
+
+ // Set the timeout to slightly more than DNSClient timeout to allow
+ // timeout processing to occur naturally.
+ size_t timeout = cfg_mgr_->getD2Params()->getDnsServerTimeout() + 100;
+ while (update_mgr_->getQueueCount() ||
+ update_mgr_->getTransactionCount()) {
+ ++passes;
+ update_mgr_->sweep();
+ // If any transactions are waiting on IO, run the service.
+ if (anyoneWaiting()) {
+ int cnt = runTimedIO(timeout);
+
+ // If cnt is zero then the service stopped unexpectedly.
+ if (cnt == 0) {
+ ADD_FAILURE()
+ << "processALL: IO service stopped unexpectedly,"
+ << " passes: " << passes << ", handlers executed: "
+ << handlers;
+ }
+
+ handlers += cnt;
+ }
+
+ // This is a last resort fail safe to ensure we don't go around
+ // forever. We cut it off the number of passes at 100 (default
+ // value). This is roughly ten times the number for the longest
+ // test (currently, multiTransactionTimeout).
+ if (passes > max_passes) {
+ FAIL() << "processALL failed, too many passes: "
+ << passes << ", total handlers executed: " << handlers;
+ }
+ }
+ }
+
+};
+
+/// @brief Tests the D2UpdateMgr construction.
+/// This test verifies that:
+/// 1. Construction with invalid queue manager is not allowed
+/// 2. Construction with invalid configuration manager is not allowed
+/// 3. Construction with max transactions of zero is not allowed
+/// 4. Default construction works and max transactions is defaulted properly
+/// 5. Construction with custom max transactions works properly
+TEST(D2UpdateMgr, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2QueueMgrPtr queue_mgr;
+ D2CfgMgrPtr cfg_mgr;
+ D2UpdateMgrPtr update_mgr;
+
+ // Verify that constructor fails if given an invalid queue manager.
+ ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+ D2UpdateMgrError);
+
+ // Verify that constructor fails if given an invalid config manager.
+ ASSERT_NO_THROW(queue_mgr.reset(new D2QueueMgr(io_service)));
+ ASSERT_NO_THROW(cfg_mgr.reset());
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+ D2UpdateMgrError);
+
+ ASSERT_NO_THROW(cfg_mgr.reset(new D2CfgMgr()));
+
+ // Verify that constructor fails with invalid io_service.
+ io_service.reset();
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service),
+ D2UpdateMgrError);
+ io_service.reset(new isc::asiolink::IOService());
+
+ // Verify that max transactions cannot be zero.
+ EXPECT_THROW(D2UpdateMgr(queue_mgr, cfg_mgr, io_service, 0),
+ D2UpdateMgrError);
+
+ // Verify that given valid values, constructor works.
+ ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr,
+ io_service)));
+
+ // Verify that max transactions defaults properly.
+ EXPECT_EQ(D2UpdateMgr::MAX_TRANSACTIONS_DEFAULT,
+ update_mgr->getMaxTransactions());
+
+
+ // Verify that constructor permits custom max transactions.
+ ASSERT_NO_THROW(update_mgr.reset(new D2UpdateMgr(queue_mgr, cfg_mgr,
+ io_service, 100)));
+
+ // Verify that max transactions is correct.
+ EXPECT_EQ(100, update_mgr->getMaxTransactions());
+}
+
+/// @brief Tests the D2UpdateManager's transaction list services
+/// This test verifies that:
+/// 1. A transaction can be added to the list.
+/// 2. Finding a transaction in the list by key works correctly.
+/// 3. Looking for a non-existent transaction works properly.
+/// 4. Attempting to add a transaction for a DHCID already in the list fails.
+/// 5. Removing a transaction by key works properly.
+/// 6. Attempting to remove an non-existent transaction does no harm.
+TEST_F(D2UpdateMgrTest, transactionList) {
+ // Grab a canned request for test purposes.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ TransactionList::iterator pos;
+
+ // Verify that we can add a transaction.
+ EXPECT_NO_THROW(update_mgr_->makeTransaction(ncr));
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+
+ // Verify that we can find a transaction by key.
+ EXPECT_NO_THROW(pos = update_mgr_->findTransaction(ncr->getDhcid()));
+ EXPECT_TRUE(pos != update_mgr_->transactionListEnd());
+
+ // Verify that convenience method has same result.
+ EXPECT_TRUE(update_mgr_->hasTransaction(ncr->getDhcid()));
+
+ // Verify that we will not find a transaction that isn't there.
+ dhcp_ddns::D2Dhcid bogus_id("FFFF");
+ EXPECT_NO_THROW(pos = update_mgr_->findTransaction(bogus_id));
+ EXPECT_TRUE(pos == update_mgr_->transactionListEnd());
+
+ // Verify that convenience method has same result.
+ EXPECT_FALSE(update_mgr_->hasTransaction(bogus_id));
+
+ // Verify that adding a transaction for the same key fails.
+ EXPECT_THROW(update_mgr_->makeTransaction(ncr), D2UpdateMgrError);
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+
+ // Verify the we can remove a transaction by key.
+ EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+
+ // Verify the we can try to remove a non-existent transaction without harm.
+ EXPECT_NO_THROW(update_mgr_->removeTransaction(ncr->getDhcid()));
+}
+
+/// @brief Checks transaction creation when both update directions are enabled.
+/// Verifies that when both directions are enabled and servers are matched to
+/// the request, that the transaction is created with both directions turned on.
+TEST_F(D2UpdateMgrTest, bothEnabled) {
+ // Grab a canned request for test purposes.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ ncr->setReverseChange(true);
+
+ // Verify we are requesting both directions.
+ ASSERT_TRUE(ncr->isForwardChange());
+ ASSERT_TRUE(ncr->isReverseChange());
+
+ // Verify both both directions are enabled.
+ ASSERT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+ ASSERT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Attempt to make a transaction.
+ ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+ // Verify we create a transaction with both directions turned on.
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(ncr->isForwardChange());
+ EXPECT_TRUE(ncr->isReverseChange());
+}
+
+/// @brief Checks transaction creation when reverse updates are disabled.
+/// Verifies that when reverse updates are disabled, and there matching forward
+/// servers, that the transaction is still created but with only the forward
+/// direction turned on.
+TEST_F(D2UpdateMgrTest, reverseDisable) {
+ // Make a NCR which requests both directions.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ ncr->setReverseChange(true);
+
+ // Wipe out forward domain list.
+ DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+ cfg_mgr_->getD2CfgContext()->getReverseMgr()->setDomains(emptyDomains);
+
+ // Verify enable methods are correct.
+ ASSERT_TRUE(cfg_mgr_->forwardUpdatesEnabled());
+ ASSERT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Attempt to make a transaction.
+ ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+ // Verify we create a transaction with only forward turned on.
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(ncr->isForwardChange());
+ EXPECT_FALSE(ncr->isReverseChange());
+}
+
+/// @brief Checks transaction creation when forward updates are disabled.
+/// Verifies that when forward updates are disabled, and there matching reverse
+/// servers, that the transaction is still created but with only the reverse
+/// direction turned on.
+TEST_F(D2UpdateMgrTest, forwardDisabled) {
+ // Make a NCR which requests both directions.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ ncr->setReverseChange(true);
+
+ // Wipe out forward domain list.
+ DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+ cfg_mgr_->getD2CfgContext()->getForwardMgr()->setDomains(emptyDomains);
+
+ // Verify enable methods are correct.
+ ASSERT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+ ASSERT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Attempt to make a transaction.
+ ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+ // Verify we create a transaction with only reverse turned on.
+ EXPECT_EQ(1, update_mgr_->getTransactionCount());
+ EXPECT_FALSE(ncr->isForwardChange());
+ EXPECT_TRUE(ncr->isReverseChange());
+}
+
+
+/// @brief Checks transaction creation when neither update direction is enabled.
+/// Verifies that transactions are not created when both forward and reverse
+/// directions are disabled.
+TEST_F(D2UpdateMgrTest, bothDisabled) {
+ // Grab a canned request for test purposes.
+ NameChangeRequestPtr& ncr = canned_ncrs_[0];
+ ncr->setReverseChange(true);
+ TransactionList::iterator pos;
+
+ // Wipe out both forward and reverse domain lists.
+ DdnsDomainMapPtr emptyDomains(new DdnsDomainMap());
+ cfg_mgr_->getD2CfgContext()->getForwardMgr()->setDomains(emptyDomains);
+ cfg_mgr_->getD2CfgContext()->getReverseMgr()->setDomains(emptyDomains);
+
+ // Verify enable methods are correct.
+ EXPECT_FALSE(cfg_mgr_->forwardUpdatesEnabled());
+ EXPECT_FALSE(cfg_mgr_->reverseUpdatesEnabled());
+
+ // Attempt to make a transaction.
+ ASSERT_NO_THROW(update_mgr_->makeTransaction(ncr));
+
+ // Verify that do not create a transaction.
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+}
+
+/// @brief Tests D2UpdateManager's checkFinishedTransactions method.
+/// This test verifies that:
+/// 1. Completed transactions are removed from the transaction list.
+/// 2. Failed transactions are removed from the transaction list.
+/// @todo This test will need to expand if and when checkFinishedTransactions
+/// method expands to do more than remove them from the list.
+TEST_F(D2UpdateMgrTest, checkFinishedTransaction) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Create a transaction for each canned request.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->makeTransaction(canned_ncrs_[i]));
+ }
+ // Verify we have that the transaction count is correct.
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+
+ // Verify that all four transactions have been started.
+ TransactionList::iterator pos;
+ EXPECT_NO_THROW(pos = update_mgr_->transactionListBegin());
+ while (pos != update_mgr_->transactionListEnd()) {
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_EQ(dhcp_ddns::ST_PENDING, trans->getNcrStatus());
+ ASSERT_TRUE(trans->isModelRunning());
+ ++pos;
+ }
+
+ // Verify that invoking checkFinishedTransactions does not throw.
+ EXPECT_NO_THROW(update_mgr_->checkFinishedTransactions());
+
+ // Since nothing is running IOService, the all four transactions should
+ // still be in the list.
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+
+ // Now "complete" two of the four.
+ // Simulate a successful completion.
+ completeTransaction(1, dhcp_ddns::ST_COMPLETED);
+
+ // Simulate a failed completion.
+ completeTransaction(3, dhcp_ddns::ST_FAILED);
+
+ // Verify that invoking checkFinishedTransactions does not throw.
+ EXPECT_NO_THROW(update_mgr_->checkFinishedTransactions());
+
+ // Verify that the list of transactions has decreased by two.
+ EXPECT_EQ(canned_count_ - 2, update_mgr_->getTransactionCount());
+
+ // Verify that the transaction list is correct.
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[0]->getDhcid()));
+ EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[1]->getDhcid()));
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[2]->getDhcid()));
+ EXPECT_FALSE(update_mgr_->hasTransaction(canned_ncrs_[3]->getDhcid()));
+}
+
+/// @brief Tests D2UpdateManager's pickNextJob method.
+/// This test verifies that:
+/// 1. pickNextJob will select and make transactions from NCR queue.
+/// 2. Requests are removed from the queue once selected
+/// 3. Requests for DHCIDs with transactions already in progress are not
+/// selected.
+/// 4. Requests with no matching servers are removed from the queue and
+/// discarded.
+TEST_F(D2UpdateMgrTest, pickNextJob) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Put each transaction on the queue.
+ for (int i = 0; i < canned_count_; i++) {
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i]));
+ }
+
+ // Invoke pickNextJob canned_count_ times which should create a
+ // transaction for each canned ncr.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(i + 1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid()));
+ }
+
+ // Verify that the queue has been drained.
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Now verify that a subsequent request for a DCHID for which a
+ // transaction is in progress, is not dequeued.
+ // First add the "subsequent" request.
+ dhcp_ddns::NameChangeRequestPtr
+ subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2])));
+ EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that invoking pickNextJob:
+ // 1. Does not throw
+ // 2. Does not make a new transaction
+ // 3. Does not dequeue the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Clear out the queue and transaction list.
+ queue_mgr_->clearQueue();
+ update_mgr_->clearTransactionList();
+
+ // Make a forward change NCR with an FQDN that has no forward match.
+ dhcp_ddns::NameChangeRequestPtr
+ bogus_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ bogus_ncr->setForwardChange(true);
+ bogus_ncr->setReverseChange(false);
+ bogus_ncr->setFqdn("bogus.forward.domain.com");
+
+ // Put it on the queue up
+ ASSERT_NO_THROW(queue_mgr_->enqueue(bogus_ncr));
+
+ // Verify that invoking pickNextJob:
+ // 1. Does not throw
+ // 2. Does not make a new transaction
+ // 3. Does dequeue the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Make a reverse change NCR with an FQDN that has no reverse match.
+ bogus_ncr.reset(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ bogus_ncr->setForwardChange(false);
+ bogus_ncr->setReverseChange(true);
+ bogus_ncr->setIpAddress("77.77.77.77");
+
+ // Verify that invoking pickNextJob:
+ // 1. does not throw
+ // 2. Does not make a new transaction
+ // 3. Does dequeue the entry
+ EXPECT_NO_THROW(update_mgr_->pickNextJob());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+}
+
+/// @brief Tests D2UpdateManager's sweep method.
+/// Since sweep is primarily a wrapper around checkFinishedTransactions and
+/// pickNextJob, along with checks on maximum transaction limits, it mostly
+/// verifies that these three pieces work together to move process jobs.
+/// Most of what is tested here is tested above.
+TEST_F(D2UpdateMgrTest, sweep) {
+ // Ensure we have at least 4 canned requests with which to work.
+ ASSERT_TRUE(canned_count_ >= 4);
+
+ // Set max transactions to same as current transaction count.
+ EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_));
+ EXPECT_EQ(canned_count_, update_mgr_->getMaxTransactions());
+
+ // Put each transaction on the queue.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i]));
+ }
+
+ // Invoke sweep canned_count_ times which should create a
+ // transaction for each canned ncr.
+ for (int i = 0; i < canned_count_; i++) {
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(i + 1, update_mgr_->getTransactionCount());
+ EXPECT_TRUE(update_mgr_->hasTransaction(canned_ncrs_[i]->getDhcid()));
+ }
+
+ // Verify that the queue has been drained.
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Verify max transactions can't be less than current transaction count.
+ EXPECT_THROW(update_mgr_->setMaxTransactions(1), D2UpdateMgrError);
+
+ // Queue up a request for a DHCID which has a transaction in progress.
+ dhcp_ddns::NameChangeRequestPtr
+ subsequent_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[2])));
+ EXPECT_NO_THROW(queue_mgr_->enqueue(subsequent_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that invoking sweep, does not dequeue the job nor make a
+ // transaction for it.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Mark the transaction complete.
+ completeTransaction(2, dhcp_ddns::ST_COMPLETED);
+
+ // Verify that invoking sweep, cleans up the completed transaction,
+ // dequeues the queued job and adds its transaction to the list.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Queue up a request from a new DHCID.
+ dhcp_ddns::NameChangeRequestPtr
+ another_ncr(new dhcp_ddns::NameChangeRequest(*(canned_ncrs_[0])));
+ another_ncr->setDhcid("AABBCCDDEEFF");
+ EXPECT_NO_THROW(queue_mgr_->enqueue(another_ncr));
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Verify that sweep does not dequeue the new request as we are at
+ // maximum transaction count.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_, update_mgr_->getTransactionCount());
+ EXPECT_EQ(1, update_mgr_->getQueueCount());
+
+ // Set max transactions to same as current transaction count.
+ EXPECT_NO_THROW(update_mgr_->setMaxTransactions(canned_count_ + 1));
+
+ // Verify that invoking sweep, dequeues the request and creates
+ // a transaction for it.
+ EXPECT_NO_THROW(update_mgr_->sweep());
+ EXPECT_EQ(canned_count_ + 1, update_mgr_->getTransactionCount());
+ EXPECT_EQ(0, update_mgr_->getQueueCount());
+
+ // Verify that clearing transaction list works.
+ EXPECT_NO_THROW(update_mgr_->clearTransactionList());
+ EXPECT_EQ(0, update_mgr_->getTransactionCount());
+}
+
+/// @brief Tests integration of NameAddTransaction
+/// This test verifies that update manager can create and manage a
+/// NameAddTransaction from start to finish. It utilizes a fake server
+/// which responds to all requests sent with NOERROR, simulating a
+/// successful addition. The transaction processes both forward and
+/// reverse changes.
+TEST_F(D2UpdateMgrTest, addTransaction) {
+ // Put each transaction on the queue.
+ canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_ADD);
+ canned_ncrs_[0]->setReverseChange(true);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ // Verify the correct type of transaction was created.
+ NameAddTransaction* t = dynamic_cast<NameAddTransaction*>(trans.get());
+ ASSERT_TRUE(t);
+
+ // At this point the transaction should have constructed
+ // and sent the DNS request.
+ ASSERT_TRUE(trans->getCurrentServer());
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+
+ // Create a server based on the transaction's current server, and
+ // start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be true.
+ EXPECT_TRUE(trans->getForwardChangeCompleted());
+ EXPECT_TRUE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ trans->getLastEvent());
+}
+
+/// @brief Tests integration of NameRemoveTransaction
+/// This test verifies that update manager can create and manage a
+/// NameRemoveTransaction from start to finish. It utilizes a fake server
+/// which responds to all requests sent with NOERROR, simulating a
+/// successful addition. The transaction processes both forward and
+/// reverse changes.
+TEST_F(D2UpdateMgrTest, removeTransaction) {
+ // Put each transaction on the queue.
+ canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_REMOVE);
+ canned_ncrs_[0]->setReverseChange(true);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ // Verify the correct type of transaction was created.
+ NameRemoveTransaction* t = dynamic_cast<NameRemoveTransaction*>(trans.get());
+ ASSERT_TRUE(t);
+
+ // At this point the transaction should have constructed
+ // and sent the DNS request.
+ ASSERT_TRUE(trans->getCurrentServer());
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+
+ // Create a server based on the transaction's current server,
+ // and start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be true.
+ EXPECT_TRUE(trans->getForwardChangeCompleted());
+ EXPECT_TRUE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ trans->getLastEvent());
+}
+
+
+/// @brief Tests handling of a transaction which fails.
+/// This test verifies that update manager correctly concludes a transaction
+/// which fails to complete successfully. The failure simulated is repeated
+/// corrupt responses from the server, which causes an exhaustion of the
+/// available servers.
+TEST_F(D2UpdateMgrTest, errorTransaction) {
+ // Put each transaction on the queue.
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+ ASSERT_TRUE(trans->getCurrentServer());
+
+ // Create a server and start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::CORRUPT_RESP);
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be false.
+ EXPECT_FALSE(trans->getForwardChangeCompleted());
+ EXPECT_FALSE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ trans->getLastEvent());
+
+
+}
+
+/// @brief Tests processing of multiple transactions.
+/// This test verifies that update manager can create and manage a multiple
+/// transactions, concurrently. It uses a fake server that responds to all
+/// requests sent with NOERROR, simulating successful DNS updates. The
+/// transactions are a mix of both adds and removes.
+TEST_F(D2UpdateMgrTest, multiTransaction) {
+ // Queue up all the requests.
+ int test_count = canned_count_;
+ for (int i = test_count; i > 0; i--) {
+ canned_ncrs_[i-1]->setReverseChange(true);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i-1]));
+ }
+
+ // Create a server and start it listening. Note this relies on the fact
+ // that all of configured servers have the same address.
+ // and start it listening.
+ asiolink::IOAddress server_ip("127.0.0.1");
+ FauxServer server(*io_service_, server_ip, 5301);
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ for (int i = 0; i < test_count; i++) {
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, canned_ncrs_[i]->getStatus());
+ }
+}
+
+/// @brief Tests processing of multiple transactions.
+/// This test verifies that update manager can create and manage a multiple
+/// transactions, concurrently. It uses a fake server that responds to all
+/// requests sent with NOERROR, simulating successful DNS updates. The
+/// transactions are a mix of both adds and removes.
+TEST_F(D2UpdateMgrTest, multiTransactionTimeout) {
+ // Queue up all the requests.
+ int test_count = canned_count_;
+ for (int i = test_count; i > 0; i--) {
+ canned_ncrs_[i-1]->setReverseChange(true);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[i-1]));
+ }
+
+ // No server is running, so everything will time out.
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ for (int i = 0; i < test_count; i++) {
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, canned_ncrs_[i]->getStatus());
+ }
+}
+
+/// @brief Tests integration of SimpleAddTransaction
+/// This test verifies that update manager can create and manage a
+/// SimpleAddTransaction from start to finish. It utilizes a fake server
+/// which responds to all requests sent with NOERROR, simulating a
+/// successful addition. The transaction processes both forward and
+/// reverse changes.
+TEST_F(D2UpdateMgrTest, simpleAddTransaction) {
+ // Put each transaction on the queue.
+ canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_ADD);
+ canned_ncrs_[0]->setReverseChange(true);
+ canned_ncrs_[0]->setConflictResolution(false);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ // Verify the correct type of transaction was created.
+ SimpleAddTransaction* t = dynamic_cast<SimpleAddTransaction*>(trans.get());
+ ASSERT_TRUE(t);
+
+ // At this point the transaction should have constructed
+ // and sent the DNS request.
+ ASSERT_TRUE(trans->getCurrentServer());
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+
+ // Create a server based on the transaction's current server, and
+ // start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be true.
+ EXPECT_TRUE(trans->getForwardChangeCompleted());
+ EXPECT_TRUE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ trans->getLastEvent());
+}
+
+/// @brief Tests integration of SimpleRemoveTransaction
+/// This test verifies that update manager can create and manage a
+/// SimpleRemoveTransaction from start to finish. It utilizes a fake server
+/// which responds to all requests sent with NOERROR, simulating a
+/// successful addition. The transaction processes both forward and
+/// reverse changes.
+TEST_F(D2UpdateMgrTest, simpleRemoveTransaction) {
+ // Put each transaction on the queue.
+ canned_ncrs_[0]->setChangeType(dhcp_ddns::CHG_REMOVE);
+ canned_ncrs_[0]->setReverseChange(true);
+ canned_ncrs_[0]->setConflictResolution(false);
+ ASSERT_NO_THROW(queue_mgr_->enqueue(canned_ncrs_[0]));
+
+ // Call sweep once, this should:
+ // 1. Dequeue the request
+ // 2. Create the transaction
+ // 3. Start the transaction
+ ASSERT_NO_THROW(update_mgr_->sweep());
+
+ // Get a copy of the transaction.
+ TransactionList::iterator pos = update_mgr_->transactionListBegin();
+ ASSERT_TRUE (pos != update_mgr_->transactionListEnd());
+ NameChangeTransactionPtr trans = (*pos).second;
+ ASSERT_TRUE(trans);
+
+ // Verify the correct type of transaction was created.
+ SimpleRemoveTransaction* t = dynamic_cast<SimpleRemoveTransaction*>(trans.get());
+ ASSERT_TRUE(t);
+
+ // At this point the transaction should have constructed
+ // and sent the DNS request.
+ ASSERT_TRUE(trans->getCurrentServer());
+ ASSERT_TRUE(trans->isModelRunning());
+ ASSERT_EQ(1, trans->getUpdateAttempts());
+ ASSERT_EQ(StateModel::NOP_EVT, trans->getNextEvent());
+
+ // Create a server based on the transaction's current server,
+ // and start it listening.
+ FauxServer server(*io_service_, *(trans->getCurrentServer()));
+ server.receive(FauxServer::USE_RCODE, dns::Rcode::NOERROR());
+
+ // Run sweep and IO until everything is done.
+ processAll();
+
+ // Verify that model succeeded.
+ EXPECT_FALSE(trans->didModelFail());
+
+ // Both completion flags should be true.
+ EXPECT_TRUE(trans->getForwardChangeCompleted());
+ EXPECT_TRUE(trans->getReverseChangeCompleted());
+
+ // Verify that we went through success state.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ trans->getPrevState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ trans->getLastEvent());
+}
+
+}
diff --git a/src/bin/d2/tests/get_config_unittest.cc b/src/bin/d2/tests/get_config_unittest.cc
new file mode 100644
index 0000000..0936e82
--- /dev/null
+++ b/src/bin/d2/tests/get_config_unittest.cc
@@ -0,0 +1,293 @@
+// 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/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <d2/parser_context.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/d2_config.h>
+#include <process/testutils/d_test_stubs.h>
+#include <testutils/user_context_utils.h>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <sstream>
+
+#include "test_data_files_config.h"
+#include "test_callout_libraries.h"
+
+using namespace isc::config;
+using namespace isc::d2;
+using namespace isc::data;
+using namespace isc::process;
+using namespace isc::test;
+
+namespace {
+
+/// @name How to generate the testdata/get_config.json file
+///
+/// Define GENERATE_ACTION and recompile. Run d2_unittests on
+/// D2GetConfigTest redirecting the standard error to a temporary
+/// file, e.g. by
+/// @code
+/// ./d2_unittests --gtest_filter="D2GetConfig*" > /dev/null 2> u
+/// @endcode
+///
+/// Update testdata/get_config.json using the temporary file content,
+/// recompile without GENERATE_ACTION.
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+#endif
+
+/// @brief Read a file into a string
+std::string
+readFile(const std::string& file_path) {
+ std::ifstream ifs(file_path);
+ if (!ifs.is_open()) {
+ ADD_FAILURE() << "readFile cannot open " << file_path;
+ isc_throw(isc::Unexpected, "readFile cannot open " << file_path);
+ }
+ std::string lines;
+ std::string line;
+ while (std::getline(ifs, line)) {
+ lines += line + "\n";
+ }
+ ifs.close();
+ return (lines);
+}
+
+/// @brief Runs parser in JSON mode
+ElementPtr
+parseJSON(const std::string& in, bool verbose = false) {
+ try {
+ D2ParserContext ctx;
+ return (ctx.parseString(in, D2ParserContext::PARSER_JSON));
+ } catch (const std::exception& ex) {
+ if (verbose) {
+ std::cout << "EXCEPTION: " << ex.what() << std::endl;
+ }
+ throw;
+ }
+}
+
+/// @brief Runs parser in DHCPDDNS mode
+ElementPtr
+parseDHCPDDNS(const std::string& in, bool verbose = false) {
+ try {
+ D2ParserContext ctx;
+ return (ctx.parseString(in, D2ParserContext::PARSER_DHCPDDNS));
+ } catch (const std::exception& ex) {
+ if (verbose) {
+ std::cout << "EXCEPTION: " << ex.what() << std::endl;
+ }
+ throw;
+ }
+}
+
+/// @brief Replace the library path
+void pathReplacer(ConstElementPtr d2_cfg) {
+ ConstElementPtr hooks_libs = d2_cfg->get("hooks-libraries");
+ if (!hooks_libs || hooks_libs->empty()) {
+ return;
+ }
+ ElementPtr first_lib = hooks_libs->getNonConst(0);
+ std::string lib_path(CALLOUT_LIBRARY);
+ first_lib->set("library", Element::create(lib_path));
+}
+
+}
+
+/// Test fixture class
+class D2GetConfigTest : public ConfigParseTest {
+public:
+ D2GetConfigTest()
+ : rcode_(-1) {
+ srv_.reset(new D2CfgMgr());
+ // Enforce not verbose mode.
+ Daemon::setVerbose(false);
+ // Create fresh context.
+ resetConfiguration();
+ }
+
+ ~D2GetConfigTest() {
+ resetConfiguration();
+ }
+
+ /// @brief Parse and Execute configuration
+ ///
+ /// Parses a configuration and executes a configuration of the server.
+ /// If the operation fails, the current test will register a failure.
+ ///
+ /// @param config Configuration to parse
+ /// @param operation Operation being performed. In the case of an error,
+ /// the error text will include the string "unable to <operation>.".
+ ///
+ /// @return true if the configuration succeeded, false if not.
+ bool
+ executeConfiguration(const std::string& config, const char* operation) {
+ // try JSON parser
+ ConstElementPtr json;
+ try {
+ json = parseJSON(config, true);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "invalid JSON for " << operation
+ << " failed with " << ex.what()
+ << " on\n" << config << "\n";
+ return (false);
+ }
+
+ // try DHCPDDNS parser
+ try {
+ json = parseDHCPDDNS(config, true);
+ } catch (...) {
+ ADD_FAILURE() << "parsing failed for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // get DhcpDdns element
+ ConstElementPtr d2 = json->get("DhcpDdns");
+ if (!d2) {
+ ADD_FAILURE() << "cannot get DhcpDdns for " << operation
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // update hooks-libraries
+ pathReplacer(d2);
+
+ // try DHCPDDNS configure
+ ConstElementPtr status;
+ try {
+ status = srv_->simpleParseConfig(d2, false);
+ } catch (const std::exception& ex) {
+ ADD_FAILURE() << "configure for " << operation
+ << " failed with " << ex.what()
+ << " on\n" << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // The status object must not be NULL
+ if (!status) {
+ ADD_FAILURE() << "configure for " << operation
+ << " returned null on\n"
+ << prettyPrint(json) << "\n";
+ return (false);
+ }
+
+ // Returned value should be 0 (configuration success)
+ comment_ = parseAnswer(rcode_, status);
+ if (rcode_ != 0) {
+ string reason = "";
+ if (comment_) {
+ reason = string(" (") + comment_->stringValue() + string(")");
+ }
+ ADD_FAILURE() << "configure for " << operation
+ << " returned error code "
+ << rcode_ << reason << " on\n"
+ << prettyPrint(json) << "\n";
+ return (false);
+ }
+ return (true);
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by
+ /// removing control sockets, hooks, etc. Reset must
+ /// be performed after each test to make sure that
+ /// contents of the database do not affect result of
+ /// subsequent tests.
+ void resetConfiguration() {
+ string config = "{ \"DhcpDdns\": {"
+ " \"ip-address\": \"127.0.0.1\","
+ " \"port\": 53001,"
+ " \"dns-server-timeout\": 100,"
+ " \"ncr-protocol\": \"UDP\","
+ " \"ncr-format\": \"JSON\","
+ " \"tsig-keys\": [ ],"
+ " \"forward-ddns\": { },"
+ " \"reverse-ddns\": { } } }";
+ EXPECT_TRUE(executeConfiguration(config, "reset config"));
+ }
+
+ boost::scoped_ptr<D2CfgMgr> srv_; ///< D2 server under test
+ int rcode_; ///< Return code from element parsing
+ ConstElementPtr comment_; ///< Reason for parse fail
+};
+
+/// Test a configuration
+TEST_F(D2GetConfigTest, sample1) {
+
+ // get the sample1 configuration
+ std::string sample1_file = string(CFG_EXAMPLES) + "/" + "sample1.json";
+ std::string config;
+ ASSERT_NO_THROW(config = readFile(sample1_file));
+
+ // get the expected configuration
+ std::string expected_file =
+ std::string(D2_TEST_DATA_DIR) + "/" + "get_config.json";
+ std::string expected;
+ ASSERT_NO_THROW(expected = readFile(expected_file));
+
+ // execute the sample configuration
+ ASSERT_TRUE(executeConfiguration(config, "sample1 config"));
+
+ // unparse it
+ D2CfgContextPtr context = srv_->getD2CfgContext();
+ ConstElementPtr unparsed;
+ ASSERT_NO_THROW(unparsed = context->toElement());
+
+ // dump if wanted else check
+ if (generate_action) {
+ std::cerr << "/ Generated Configuration (remove this line)\n";
+ ASSERT_NO_THROW(expected = prettyPrint(unparsed));
+ prettyPrint(unparsed, std::cerr, 0, 4);
+ std::cerr << "\n";
+ } else {
+ // get the expected config using the d2 syntax parser
+ ElementPtr jsond;
+ ASSERT_NO_THROW(jsond = parseDHCPDDNS(expected, true));
+ // get the expected config using the generic JSON syntax parser
+ ElementPtr jsonj;
+ ASSERT_NO_THROW(jsonj = parseJSON(expected));
+ // the generic JSON parser does not handle comments
+ EXPECT_TRUE(isEquivalent(jsond, moveComments(jsonj)));
+ // replace the path by its actual value
+ ConstElementPtr d2;
+ ASSERT_NO_THROW(d2 = jsonj->get("DhcpDdns"));
+ ASSERT_TRUE(d2);
+ pathReplacer(d2);
+ // check that unparsed and expected values match
+ EXPECT_TRUE(isEquivalent(unparsed, jsonj));
+ // check on pretty prints too
+ std::string current = prettyPrint(unparsed, 0, 4);
+ std::string expected2 = prettyPrint(jsonj, 0, 4);
+ EXPECT_EQ(expected2, current);
+ if (expected2 != current) {
+ expected = current + "\n";
+ }
+ }
+
+ // execute the d2 configuration
+ EXPECT_TRUE(executeConfiguration(expected, "unparsed config"));
+
+ // is it a fixed point?
+ D2CfgContextPtr context2 = srv_->getD2CfgContext();
+ ConstElementPtr unparsed2;
+ ASSERT_NO_THROW(unparsed2 = context2->toElement());
+ ASSERT_TRUE(unparsed2);
+ EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}
diff --git a/src/bin/d2/tests/nc_add_unittests.cc b/src/bin/d2/tests/nc_add_unittests.cc
new file mode 100644
index 0000000..3f36b46
--- /dev/null
+++ b/src/bin/d2/tests/nc_add_unittests.cc
@@ -0,0 +1,1713 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2/nc_add.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test class derived from NameAddTransaction to provide visibility
+// to protected methods.
+class NameAddStub : public NameAddTransaction {
+public:
+ NameAddStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameAddTransaction(io_service, ncr, forward_domain, reverse_domain,
+ cfg_mgr),
+ simulate_send_exception_(false),
+ simulate_build_request_exception_(false) {
+ }
+
+ virtual ~NameAddStub() {
+ }
+
+ /// @brief Simulates sending update requests to the DNS server
+ ///
+ /// This method simulates the initiation of an asynchronous send of
+ /// a DNS update request. It overrides the actual sendUpdate method in
+ /// the base class, thus avoiding an actual send, yet still increments
+ /// the update attempt count and posts a next event of NOP_EVT.
+ ///
+ /// It will also simulate an exception-based failure of sendUpdate, if
+ /// the simulate_send_exception_ flag is true.
+ ///
+ /// @param comment Parameter is unused, but present in base class method.
+ ///
+ virtual void sendUpdate(const std::string& /*comment*/) {
+ if (simulate_send_exception_) {
+ // Make the flag a one-shot by resetting it.
+ simulate_send_exception_ = false;
+ // Transition to failed.
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ return;
+ }
+
+ // Update send attempt count and post a NOP_EVT.
+ setUpdateAttempts(getUpdateAttempts() + 1);
+ postNextEvent(StateModel::NOP_EVT);
+ }
+
+ /// @brief Prepares the initial D2UpdateMessage
+ ///
+ /// This method overrides the NameChangeTransaction implementation to
+ /// provide the ability to simulate an exception throw in the build
+ /// request logic.
+ /// If the one-shot flag, simulate_build_request_exception_ is true,
+ /// this method will throw an exception, otherwise it will invoke the
+ /// base class method, providing normal functionality.
+ ///
+ /// For parameter description see the NameChangeTransaction implementation.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+ if (simulate_build_request_exception_) {
+ simulate_build_request_exception_ = false;
+ isc_throw (NameAddTransactionError,
+ "Simulated build requests exception");
+ }
+
+ return (NameChangeTransaction::prepNewRequest(domain));
+ }
+
+ /// @brief Simulates receiving a response
+ ///
+ /// This method simulates the completion of a DNSClient send. This allows
+ /// the state handler logic devoted to dealing with IO completion to be
+ /// fully exercised without requiring any actual IO. The two primary
+ /// pieces of information gleaned from IO completion are the DNSClient
+ /// status which indicates whether or not the IO exchange was successful
+ /// and the rcode, which indicates the server's reaction to the request.
+ ///
+ /// This method updates the transaction's DNS status value to that of the
+ /// given parameter, and then constructs and DNS update response message
+ /// with the given rcode value. To complete the simulation it then posts
+ /// a next event of IO_COMPLETED_EVT.
+ ///
+ /// @param status simulated DNSClient status
+ /// @param rcode simulated server response code
+ void fakeResponse(const DNSClient::Status& status,
+ const dns::Rcode& rcode) {
+ // Set the DNS update status. This is normally set in
+ // DNSClient IO completion handler.
+ setDnsUpdateStatus(status);
+
+ // Construct an empty message with the given Rcode.
+ D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+ msg->setRcode(rcode);
+
+ // Set the update response to the message.
+ setDnsUpdateResponse(msg);
+
+ // Post the IO completion event.
+ postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+ }
+
+ /// @brief Selects the first forward server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Selects the first reverse server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectRevServer() {
+ if (getReverseDomain()) {
+ initServerSelection(getReverseDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief One-shot flag which will simulate sendUpdate failure if true.
+ bool simulate_send_exception_;
+
+ /// @brief One-shot flag which will simulate an exception when sendUpdate
+ /// failure if true.
+ bool simulate_build_request_exception_;
+
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using NameAddTransaction::defineEvents;
+ using NameAddTransaction::verifyEvents;
+ using NameAddTransaction::defineStates;
+ using NameAddTransaction::verifyStates;
+ using NameAddTransaction::readyHandler;
+ using NameAddTransaction::selectingFwdServerHandler;
+ using NameAddTransaction::getCurrentServer;
+ using NameAddTransaction::addingFwdAddrsHandler;
+ using NameAddTransaction::setDnsUpdateStatus;
+ using NameAddTransaction::replacingFwdAddrsHandler;
+ using NameAddTransaction::selectingRevServerHandler;
+ using NameAddTransaction::replacingRevPtrsHandler;
+ using NameAddTransaction::processAddOkHandler;
+ using NameAddTransaction::processAddFailedHandler;
+ using NameAddTransaction::buildAddFwdAddressRequest;
+ using NameAddTransaction::buildReplaceFwdAddressRequest;
+ using NameAddTransaction::buildReplaceRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<NameAddStub> NameAddStubPtr;
+
+/// @brief Test fixture for testing NameAddTransaction
+///
+/// Note this class uses NameAddStub class to exercise non-public
+/// aspects of NameAddTransaction.
+class NameAddTransactionTest : public TransactionTest {
+public:
+
+ NameAddTransactionTest() {
+ }
+
+ virtual ~NameAddTransactionTest() {
+ }
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ NameAddStubPtr makeTransaction4(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (NameAddStubPtr(new NameAddStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_, cfg_mgr_)));
+ }
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ NameAddStubPtr makeTransaction6(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (NameAddStubPtr(new NameAddStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Create a test transaction at a known point in the state model.
+ ///
+ /// Method prepares a new test transaction and sets its state and next
+ /// event values to those given. This makes the transaction appear to
+ /// be at that point in the state model without having to transition it
+ /// through prerequisite states. It also provides the ability to set
+ /// which change directions are requested: forward change only, reverse
+ /// change only, or both.
+ ///
+ /// @param state value to set as the current state
+ /// @param event value to post as the next event
+ /// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
+ NameAddStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+ unsigned int change_mask = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ NameAddStubPtr name_add = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction4(change_mask));
+ name_add->initDictionaries();
+ name_add->postNextEvent(event);
+ name_add->setState(state);
+ return (name_add);
+ }
+};
+
+/// @brief Tests NameAddTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(NameAddTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with wrong change type fails.
+ EXPECT_THROW(NameAddTransaction(io_service, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ NameAddTransactionError);
+
+ // Verify that a valid construction attempt works.
+ ncr->setChangeType(isc::dhcp_ddns::CHG_ADD);
+ EXPECT_NO_THROW(NameAddTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameAddTransactionTest, dictionaryCheck) {
+ NameAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_add->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_add->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_add->defineEvents());
+ ASSERT_NO_THROW(name_add->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_add->verifyEvents());
+ ASSERT_NO_THROW(name_add->verifyStates());
+}
+
+/// @brief Tests construction of a DNS update request for adding a forward
+/// dns entry.
+TEST_F(NameAddTransactionTest, buildForwardAdd) {
+ // Create a IPv4 forward add transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildAddFwdAddressRequest());
+ checkAddFwdAddressRequest(*name_add);
+
+ // Create a IPv6 forward add transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildAddFwdAddressRequest());
+ checkAddFwdAddressRequest(*name_add);
+}
+
+/// @brief Tests construction of a DNS update request for replacing a forward
+/// dns entry.
+TEST_F(NameAddTransactionTest, buildReplaceFwdAddressRequest) {
+ // Create a IPv4 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+ checkReplaceFwdAddressRequest(*name_add);
+
+ // Create a IPv6 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+ checkReplaceFwdAddressRequest(*name_add);
+}
+
+/// @brief Tests the construction of a DNS update request for replacing a
+/// reverse dns entry.
+TEST_F(NameAddTransactionTest, buildReplaceRevPtrsRequest) {
+ // Create a IPv4 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+ checkReplaceRevPtrsRequest(*name_add);
+
+ // Create a IPv6 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+ checkReplaceRevPtrsRequest(*name_add);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, readyHandler) {
+ NameAddStubPtr name_add;
+
+ // Create a transaction which includes only a forward change.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FORWARD_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring only a forward change, transitions to
+ // selecting a forward server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+
+
+ // Create a transaction which includes both a forward and a reverse change.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FWD_AND_REV_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring both forward and reverse, starts with
+ // the forward change by transitioning to selecting a forward server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+
+
+ // Create and prep a reverse only transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, REVERSE_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring only a reverse change, transitions to
+ // selecting a reverse server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::NOP_EVT));
+
+ // Running the readyHandler should throw.
+ EXPECT_THROW(name_add->readyHandler(), NameAddTransactionError);
+}
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, selectingFwdServerHandler) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT));
+
+ // Call selectingFwdServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_add->getForwardDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ // Run selectingFwdServerHandler.
+ ASSERT_NO_THROW(name_add->selectingFwdServerHandler())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_add->getCurrentServer())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Verify that we transitioned correctly.
+ ASSERT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState())
+ << " num_servers: " << num_servers << " selections: " << i;
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT))
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_add->selectingFwdServerHandler());
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ name_add->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_FWD_SERVER_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->selectingFwdServerHandler(),
+ NameAddTransactionError);
+}
+
+// ************************ addingFwdAddrHandler Tests *****************
+
+// Tests that addingFwdAddrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_InvalidEvent) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->addingFwdAddrsHandler(),
+ NameAddTransactionError);
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_FwdOnlyAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run addingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkAddFwdAddressRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_add->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_fwdAndRevAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FWD_AND_REV_CHG));
+
+ // Run addingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Forward change completion should be true, reverse flag should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since the request also includes a reverse change we should
+ // be poised to start it. Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the FQDN is in use.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_FqdnInUse) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Run addingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate receiving a FQDN in use response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::YXDOMAIN());
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since the FQDN is in use, per the RFC we must attempt to replace it.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameAddTransaction::FQDN_IN_USE_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_OtherRcode) {
+ NameAddStubPtr name_add;
+
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Run addingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error or FQDN in use is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_Timeout) {
+ NameAddStubPtr name_add;
+
+ // Create and prep a transaction, poised to run the handler.
+ // The log message issued when this test succeeds, displays the
+ // selected server, so we need to select a server before running this
+ // test.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run addingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_InvalidResponse) {
+ NameAddStubPtr name_add;
+
+ // Create and prep a transaction, poised to run the handler.
+ // The log message issued when this test succeeds, displays the
+ // selected server, so we need to select a server before running this
+ // test.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run addingFwdAddrsHandler to construct send the request.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Simulate a corrupt server response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run addingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+
+}
+
+// ************************ replacingFwdAddrHandler Tests *****************
+
+// Tests that replacingFwdAddrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_InvalidEvent) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->replacingFwdAddrsHandler(),
+ NameAddTransactionError);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FORWARD_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkReplaceFwdAddressRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_add->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK2) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdAndRevAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Forward change completion should be true, reverse flag should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since the request also includes a reverse change we should
+ // be poised to start it. Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_add->getNextEvent());
+}
+
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the FQDN is NOT in use.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FqdnNotInUse) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving a FQDN not in use response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXDOMAIN());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since the FQDN is no longer in use, per the RFC, try to add it.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+}
+
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_OtherRcode) {
+ NameAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error or FQDN in use is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_Timeout) {
+ NameAddStubPtr name_add;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is FQDN_IN_USE_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_CorruptResponse) {
+ NameAddStubPtr name_add;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameAddTransaction::
+ FQDN_IN_USE_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate a corrupt server response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_FWD_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, selectingRevServerHandler) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT));
+
+ // Call selectingRevServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_add->getReverseDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ // Run selectingRevServerHandler.
+ ASSERT_NO_THROW(name_add->selectingRevServerHandler())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_add->getCurrentServer())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that we transitioned correctly.
+ ASSERT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ name_add->getCurrState())
+ << " num_servers: " << num_servers << " selections: " << i;
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT))
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_add->selectingRevServerHandler());
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ name_add->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_REV_SERVER_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->selectingRevServerHandler(),
+ NameAddTransactionError);
+}
+
+//************************** replacingRevPtrsHandler tests *****************
+
+// Tests that replacingRevPtrsHandler rejects invalid events.
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_InvalidEvent) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->replacingRevPtrsHandler(),
+ NameAddTransactionError);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_FwdOnlyAddOK) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameAddTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkReplaceRevPtrsRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_add->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_TRUE(name_add->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_OtherRcode) {
+ NameAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameAddTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_Timeout) {
+ NameAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameAddTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_CorruptResponse) {
+ NameAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameAddTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate a server corrupt response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_add->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameAddTransaction::SELECTING_REV_SERVER_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_add->getNextEvent());
+ }
+ }
+}
+
+// Tests the processAddOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, processAddOkHandler) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT));
+ // Run processAddOkHandler.
+ EXPECT_NO_THROW(name_add->processAddOkHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_add->getNcrStatus());
+
+ // Verify that the model has ended.
+ EXPECT_EQ(StateModel::END_ST, name_add->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_add->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ StateModel::NOP_EVT));
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->processAddOkHandler(), NameAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameAddTransactionTest, processAddFailedHandler) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT));
+ // Run processAddFailedHandler.
+ EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_add->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_add->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ StateModel::NOP_EVT));
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->processAddFailedHandler(), NameAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(NameAddTransactionTest, processAddFailedHandler_NoMoreServers) {
+ NameAddStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::
+ NO_MORE_SERVERS_EVT));
+
+ // Run processAddFailedHandler.
+ EXPECT_NO_THROW(name_remove->processAddFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_sendUpdateException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_SendUpdateException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_SendUpdateException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_BuildRequestException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::ADDING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->addingFwdAddrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_BuildRequestException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_BuildRequestException) {
+ NameAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_add =
+ prepHandlerTest(NameAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_add->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_add->getNextEvent());
+}
+
+
+}
diff --git a/src/bin/d2/tests/nc_remove_unittests.cc b/src/bin/d2/tests/nc_remove_unittests.cc
new file mode 100644
index 0000000..4373811
--- /dev/null
+++ b/src/bin/d2/tests/nc_remove_unittests.cc
@@ -0,0 +1,1796 @@
+// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2/nc_remove.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+
+namespace {
+
+/// @brief Test class derived from NameRemoveTransaction to provide visibility
+// to protected methods.
+class NameRemoveStub : public NameRemoveTransaction {
+public:
+ NameRemoveStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : NameRemoveTransaction(io_service, ncr, forward_domain,
+ reverse_domain, cfg_mgr),
+ simulate_send_exception_(false),
+ simulate_build_request_exception_(false) {
+ }
+
+ virtual ~NameRemoveStub() {
+ }
+
+ /// @brief Simulates sending update requests to the DNS server
+ ///
+ /// This method simulates the initiation of an asynchronous send of
+ /// a DNS update request. It overrides the actual sendUpdate method in
+ /// the base class, thus avoiding an actual send, yet still increments
+ /// the update attempt count and posts a next event of NOP_EVT.
+ ///
+ /// It will also simulate an exception-based failure of sendUpdate, if
+ /// the simulate_send_exception_ flag is true.
+ ///
+ /// @param comment Parameter is unused, but present in base class method
+ ///
+ virtual void sendUpdate(const std::string& /* comment */) {
+ if (simulate_send_exception_) {
+ // Make the flag a one-shot by resetting it.
+ simulate_send_exception_ = false;
+ // Transition to failed.
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ return;
+ }
+
+ // Update send attempt count and post a NOP_EVT.
+ setUpdateAttempts(getUpdateAttempts() + 1);
+ postNextEvent(StateModel::NOP_EVT);
+ }
+
+ /// @brief Prepares the initial D2UpdateMessage
+ ///
+ /// This method overrides the NameChangeTransaction implementation to
+ /// provide the ability to simulate an exception throw in the build
+ /// request logic.
+ /// If the one-shot flag, simulate_build_request_exception_ is true,
+ /// this method will throw an exception, otherwise it will invoke the
+ /// base class method, providing normal functionality.
+ ///
+ /// For parameter description see the NameChangeTransaction implementation.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+ if (simulate_build_request_exception_) {
+ simulate_build_request_exception_ = false;
+ isc_throw (NameRemoveTransactionError,
+ "Simulated build requests exception");
+ }
+
+ return (NameChangeTransaction::prepNewRequest(domain));
+ }
+
+ /// @brief Simulates receiving a response
+ ///
+ /// This method simulates the completion of a DNSClient send. This allows
+ /// the state handler logic devoted to dealing with IO completion to be
+ /// fully exercised without requiring any actual IO. The two primary
+ /// pieces of information gleaned from IO completion are the DNSClient
+ /// status which indicates whether or not the IO exchange was successful
+ /// and the rcode, which indicates the server's reaction to the request.
+ ///
+ /// This method updates the transaction's DNS status value to that of the
+ /// given parameter, and then constructs and DNS update response message
+ /// with the given rcode value. To complete the simulation it then posts
+ /// a next event of IO_COMPLETED_EVT.
+ ///
+ /// @param status simulated DNSClient status
+ /// @param rcode simulated server response code
+ void fakeResponse(const DNSClient::Status& status,
+ const dns::Rcode& rcode) {
+ // Set the DNS update status. This is normally set in
+ // DNSClient IO completion handler.
+ setDnsUpdateStatus(status);
+
+ // Construct an empty message with the given Rcode.
+ D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+ msg->setRcode(rcode);
+
+ // Set the update response to the message.
+ setDnsUpdateResponse(msg);
+
+ // Post the IO completion event.
+ postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+ }
+
+ /// @brief Selects the first forward server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Selects the first reverse server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectRevServer() {
+ if (getReverseDomain()) {
+ initServerSelection(getReverseDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief One-shot flag which will simulate sendUpdate failure if true.
+ bool simulate_send_exception_;
+
+ /// @brief One-shot flag which will simulate an exception when sendUpdate
+ /// failure if true.
+ bool simulate_build_request_exception_;
+
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using NameRemoveTransaction::defineEvents;
+ using NameRemoveTransaction::verifyEvents;
+ using NameRemoveTransaction::defineStates;
+ using NameRemoveTransaction::verifyStates;
+ using NameRemoveTransaction::readyHandler;
+ using NameRemoveTransaction::selectingFwdServerHandler;
+ using NameRemoveTransaction::getCurrentServer;
+ using NameRemoveTransaction::removingFwdAddrsHandler;
+ using NameRemoveTransaction::setDnsUpdateStatus;
+ using NameRemoveTransaction::removingFwdRRsHandler;
+ using NameRemoveTransaction::selectingRevServerHandler;
+ using NameRemoveTransaction::removingRevPtrsHandler;
+ using NameRemoveTransaction::processRemoveOkHandler;
+ using NameRemoveTransaction::processRemoveFailedHandler;
+ using NameRemoveTransaction::buildRemoveFwdAddressRequest;
+ using NameRemoveTransaction::buildRemoveFwdRRsRequest;
+ using NameRemoveTransaction::buildRemoveRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<NameRemoveStub> NameRemoveStubPtr;
+
+/// @brief Test fixture for testing NameRemoveTransaction
+///
+/// Note this class uses NameRemoveStub class to exercise non-public
+/// aspects of NameRemoveTransaction.
+class NameRemoveTransactionTest : public TransactionTest {
+public:
+ NameRemoveTransactionTest() {
+ }
+
+ virtual ~NameRemoveTransactionTest() {
+ }
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ NameRemoveStubPtr makeTransaction4(int change_mask) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (NameRemoveStubPtr(new NameRemoveStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ NameRemoveStubPtr makeTransaction6(int change_mask) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (NameRemoveStubPtr(new NameRemoveStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Create a test transaction at a known point in the state model.
+ ///
+ /// Method prepares a new test transaction and sets its state and next
+ /// event values to those given. This makes the transaction appear to
+ /// be at that point in the state model without having to transition it
+ /// through prerequisite states. It also provides the ability to set
+ /// which change directions are requested: forward change only, reverse
+ /// change only, or both.
+ ///
+ /// @param state value to set as the current state
+ /// @param event value to post as the next event
+ /// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
+ NameRemoveStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+ unsigned int change_mask
+ = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ NameRemoveStubPtr name_remove = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction6(change_mask));
+ name_remove->initDictionaries();
+ name_remove->postNextEvent(event);
+ name_remove->setState(state);
+ return (name_remove);
+ }
+
+};
+
+/// @brief Tests NameRemoveTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(NameRemoveTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with wrong change type fails.
+ EXPECT_THROW(NameRemoveTransaction(io_service, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ NameRemoveTransactionError);
+
+ // Verify that a valid construction attempt works.
+ ncr->setChangeType(isc::dhcp_ddns::CHG_REMOVE);
+ EXPECT_NO_THROW(NameRemoveTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameRemoveTransactionTest, dictionaryCheck) {
+ NameRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FWD_AND_REV_CHG));
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_remove->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_remove->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_remove->defineEvents());
+ ASSERT_NO_THROW(name_remove->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_remove->verifyEvents());
+ ASSERT_NO_THROW(name_remove->verifyStates());
+}
+
+
+/// @brief Tests construction of a DNS update request for removing forward
+/// DNS address RRs.
+TEST_F(NameRemoveTransactionTest, buildRemoveFwdAddressRequest) {
+ // Create a IPv4 forward add transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdAddressRequest());
+ checkRemoveFwdAddressRequest(*name_remove);
+
+ // Create a IPv6 forward add transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdAddressRequest());
+ checkRemoveFwdAddressRequest(*name_remove);
+}
+
+/// @brief Tests construction of a DNS update request for removing forward
+/// dns RR entries.
+TEST_F(NameRemoveTransactionTest, buildRemoveFwdRRsRequest) {
+ // Create a IPv4 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+ checkRemoveFwdRRsRequest(*name_remove);
+
+ // Create a IPv6 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+ checkRemoveFwdRRsRequest(*name_remove);
+}
+
+/// @brief Tests the construction of a DNS update request for removing a
+/// reverse dns entry.
+TEST_F(NameRemoveTransactionTest, buildRemoveRevPtrsRequest) {
+ // Create a IPv4 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ NameRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(REVERSE_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+ checkRemoveRevPtrsRequest(*name_remove);
+
+ // Create a IPv6 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(REVERSE_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+ checkRemoveRevPtrsRequest(*name_remove);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, readyHandler) {
+ NameRemoveStubPtr name_remove;
+
+ // Create a transaction which includes only a forward change.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FORWARD_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring only a forward change, transitions to
+ // selecting a forward server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_remove->getNextEvent());
+
+ // Create a transaction which includes both a forward and a reverse change.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FWD_AND_REV_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring both forward and reverse, starts with
+ // the forward change by transitioning to selecting a forward server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_remove->getNextEvent());
+
+ // Create and prep a reverse only transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, REVERSE_CHG));
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring only a reverse change, transitions to
+ // selecting a reverse server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_remove->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::NOP_EVT));
+
+ // Running the readyHandler should throw.
+ EXPECT_THROW(name_remove->readyHandler(), NameRemoveTransactionError);
+}
+
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, selectingFwdServerHandler) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT));
+
+ // Call selectingFwdServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_remove->getForwardDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ // Run selectingFwdServerHandler.
+ ASSERT_NO_THROW(name_remove->selectingFwdServerHandler())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_remove->getCurrentServer())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Verify that we transitioned correctly.
+ ASSERT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState())
+ << " num_servers: " << num_servers << " selections: " << i;
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT))
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_remove->selectingFwdServerHandler());
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ name_remove->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_FWD_SERVER_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->selectingFwdServerHandler(),
+ NameRemoveTransactionError);
+}
+
+// ************************ addingFwdAddrHandler Tests *****************
+
+// Tests that removingFwdAddrsHandler rejects invalid events.
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_InvalidEvent) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingFwdAddrsHandler(),
+ NameRemoveTransactionError);
+}
+
+
+// Tests addingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_FwdOnlyOK) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkRemoveFwdAddressRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_remove->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should both still be false, as we are only partly
+ // done with forward updates.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since we succeeded, we should now attempt to remove any remaining
+ // forward RRs.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_RRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameRemoveTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates FQDN is not in use.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_FqdnNotInUse) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Simulate receiving a RRSET does not exist.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should both still be false, as we are only partly
+ // done with forward updates.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // There was no address RR to remove, but we will still make sure there
+ // are no other RRs for this FQDN.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_RRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameRemoveTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_OtherRcode) {
+ NameRemoveStubPtr name_remove;
+
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error or FQDN not in use is failure. Arbitrarily choosing
+ // refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_Timeout) {
+ NameRemoveStubPtr name_remove;
+
+ // Create and prep a transaction, poised to run the handler.
+ // The log message issued when this test succeeds, displays the
+ // selected server, so we need to select a server before running this
+ // test.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameRemoveTransaction::SELECTING_FWD_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_InvalidResponse) {
+ NameRemoveStubPtr name_remove;
+
+ // Create and prep a transaction, poised to run the handler.
+ // The log message issued when this test succeeds, displays the
+ // selected server, so we need to select a server before running this
+ // test.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdAddrsHandler to construct send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Simulate a corrupt server response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameRemoveTransaction::SELECTING_FWD_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+
+}
+
+// ************************ removingFwdRRsHandler Tests *****************
+
+// Tests that removingFwdRRsHandler rejects invalid events.
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_InvalidEvent) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingFwdRRsHandler(),
+ NameRemoveTransactionError);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FwdOnlyOK) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FORWARD_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkRemoveFwdRRsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_ADDRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NOP_EVT,
+ name_remove->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FwdOnlyOK2) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FwdAndRevOK) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FWD_AND_REV_CHG));
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward change completion should be true, reverse flag should be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since the request also includes a reverse change we should
+ // be poised to start it. Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SELECT_SERVER_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the FQDN is NOT in use.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_FqdnNotInUse) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FORWARD_CHG));
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving a RRSET does not exist response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward completion flag should be true, reverse should still be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // The FQDN is no longer in use, RFC is unclear about this,
+ // but we will treat this as success.
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_OtherRcode) {
+ NameRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure (we are also treating FQDN not in use is
+ // success). Arbitrarily choosing refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_Timeout) {
+ NameRemoveStubPtr name_remove;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdRRsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_RRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted.
+ // We should abandon the transaction.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_InvalidResponse) {
+ NameRemoveStubPtr name_remove;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::
+ UPDATE_OK_EVT, FWD_AND_REV_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdRRsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate a corrupt server response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_FWD_RRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted.
+ // We should abandon the transaction.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, selectingRevServerHandler) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT));
+
+ // Call selectingRevServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_remove->getReverseDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ // Run selectingRevServerHandler.
+ ASSERT_NO_THROW(name_remove->selectingRevServerHandler())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_remove->getCurrentServer())
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+
+ // Verify that we transitioned correctly.
+ ASSERT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState())
+ << " num_servers: " << num_servers << " selections: " << i;
+ ASSERT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent())
+ << " num_servers: " << num_servers << " selections: " << i;
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT))
+ << " num_servers: " << num_servers
+ << " selections: " << i;
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_remove->selectingRevServerHandler());
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::NO_MORE_SERVERS_EVT,
+ name_remove->getNextEvent());
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ SELECTING_REV_SERVER_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->selectingRevServerHandler(),
+ NameRemoveTransactionError);
+}
+
+//************************** removingRevPtrsHandler tests *****************
+
+// Tests that removingRevPtrsHandler rejects invalid events.
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_InvalidEvent) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ StateModel::NOP_EVT));
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingRevPtrsHandler(),
+ NameRemoveTransactionError);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_RevOnlyOK) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkRemoveRevPtrsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(StateModel::NOP_EVT,
+ name_remove->getNextEvent());
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates FQDN is NOT in use.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_FqdnNotInUse) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkRemoveRevPtrsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(StateModel::NOP_EVT,
+ name_remove->getNextEvent());
+
+ // Simulate receiving a RRSET does not exist.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_OK_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_OtherRcode) {
+ NameRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure. Arbitrarily choosing refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_Timeout) {
+ NameRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_CorruptResponse) {
+ NameRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate a server corrupt response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ EXPECT_EQ(NameRemoveTransaction::REMOVING_REV_PTRS_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_SELECTED_EVT,
+ name_remove->getNextEvent());
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ EXPECT_EQ(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::SERVER_IO_ERROR_EVT,
+ name_remove->getNextEvent());
+ }
+ }
+}
+
+// Tests the processRemoveOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, processRemoveOkHandler) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT));
+ // Run processRemoveOkHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveOkHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended.
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ StateModel::NOP_EVT));
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->processRemoveOkHandler(),
+ NameRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(NameRemoveTransactionTest, processRemoveFailedHandler) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT));
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ StateModel::NOP_EVT));
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->processRemoveFailedHandler(),
+ NameRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(NameRemoveTransactionTest, processRemoveFailedHandler_NoMoreServers) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::
+ NO_MORE_SERVERS_EVT));
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of SERVER_IO_ERROR_EVT.
+TEST_F(NameRemoveTransactionTest, processRemoveFailedHandler_ServerIOError) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameChangeTransaction::
+ PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+ EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdAddrsHandler_sendUpdateException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest, removingFwdRRsHandler_SendUpdateException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest, removingRevPtrsHandler_SendUpdateException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, REVERSE_CHG));
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest,
+ removingFwdAddrsHandler_BuildRequestException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_ADDRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingFwdAddrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingFwdAddrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest,
+ removingFwdRRsHandler_BuildRequestException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+// Tests removingRevPTRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(NameRemoveTransactionTest,
+ removingRevPTRsHandler_BuildRequestException) {
+ NameRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(name_remove =
+ prepHandlerTest(NameRemoveTransaction::
+ REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::
+ SERVER_SELECTED_EVT, FORWARD_CHG));
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ EXPECT_EQ(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ name_remove->getCurrState());
+ EXPECT_EQ(NameChangeTransaction::UPDATE_FAILED_EVT,
+ name_remove->getNextEvent());
+}
+
+}
diff --git a/src/bin/d2/tests/parser_unittest.cc b/src/bin/d2/tests/parser_unittest.cc
new file mode 100644
index 0000000..9100511
--- /dev/null
+++ b/src/bin/d2/tests/parser_unittest.cc
@@ -0,0 +1,871 @@
+// 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/.
+#include <config.h>
+
+#include <cc/data.h>
+#include <d2/parser_context.h>
+#include <d2/tests/parser_unittest.h>
+#include <testutils/io_utils.h>
+#include <testutils/log_utils.h>
+#include <testutils/user_context_utils.h>
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <set>
+
+#include <boost/algorithm/string.hpp>
+
+#include "test_data_files_config.h"
+
+using namespace isc::data;
+using namespace isc::test;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+/// @brief compares two JSON trees
+///
+/// If differences are discovered, gtest failure is reported (using EXPECT_EQ)
+///
+/// @param a first to be compared
+/// @param b second to be compared
+void compareJSON(ConstElementPtr a, ConstElementPtr b) {
+ ASSERT_TRUE(a);
+ ASSERT_TRUE(b);
+ EXPECT_EQ(a->str(), b->str());
+}
+
+/// @brief Tests if the input string can be parsed with specific parser
+///
+/// The input text will be passed to bison parser of specified type.
+/// Then the same input text is passed to legacy JSON parser and outputs
+/// from both parsers are compared. The legacy comparison can be disabled,
+/// if the feature tested is not supported by the old parser (e.g.
+/// new comment styles)
+///
+/// @param txt text to be compared
+/// @param parser_type bison parser type to be instantiated
+/// @param compare whether to compare the output with legacy JSON parser
+void testParser(const std::string& txt, D2ParserContext::ParserType parser_type,
+ bool compare = true) {
+ SCOPED_TRACE("\n=== tested config ===\n" + txt + "=====================");
+
+ ConstElementPtr test_json;
+ ASSERT_NO_THROW({
+ try {
+ D2ParserContext ctx;
+ test_json = ctx.parseString(txt, parser_type);
+ } catch (const std::exception &e) {
+ cout << "EXCEPTION: " << e.what() << endl;
+ throw;
+ }
+
+ });
+
+ if (!compare) {
+ return;
+ }
+
+ // Now compare if both representations are the same.
+ ElementPtr reference_json;
+ ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
+ compareJSON(reference_json, test_json);
+}
+
+// Generic JSON parsing tests
+TEST(ParserTest, mapInMap) {
+ string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, listInList) {
+ string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
+ "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedMaps) {
+ string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedLists) {
+ string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, listsInMaps) {
+ string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelgeuse\" ], "
+ "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, mapsInLists) {
+ string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
+ " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, types) {
+ string txt = "{ \"string\": \"foo\","
+ "\"integer\": 42,"
+ "\"boolean\": true,"
+ "\"map\": { \"foo\": \"bar\" },"
+ "\"list\": [ 1, 2, 3 ],"
+ "\"null\": null }";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordJSON) {
+ string txt = "{ \"name\": \"user\","
+ "\"type\": \"password\","
+ "\"user\": \"name\","
+ "\"password\": \"type\" }";
+ testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+// PARSER_DHCPDDNS parser tests
+TEST(ParserTest, keywordDhcpDdns) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777 , \n "
+ " \"ncr-protocol\": \"UDP\", \n"
+ " \"tsig-keys\": [], \n"
+ " \"forward-ddns\" : {}, \n"
+ " \"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+// Tests if bash (#) comments are supported. That's the only comment type that
+// was supported by the old parser.
+TEST(ParserTest, bashComments) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ "# this is a comment\n"
+ " \"port\": 777, \n "
+ " \"ncr-protocol\": \"UDP\", \n"
+ "# lots of comments here\n"
+ "# and here\n"
+ "\"tsig-keys\": [], \n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+// Tests if C++ (//) comments can start anywhere, not just in the first line.
+TEST(ParserTest, cppComments) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, // this is a comment \n"
+ " \"ncr-protocol\": \"UDP\", // everything after // is ignored\n"
+ "\"tsig-keys\": [], // this will be ignored, too\n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+// Tests if bash (#) comments can start anywhere, not just in the first line.
+TEST(ParserTest, bashCommentsInline) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, # this is a comment \n"
+ " \"ncr-protocol\": \"UDP\", # everything after # is ignored\n"
+ "\"tsig-keys\": [], # this will be ignored, too\n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+// Tests if multi-line C style comments are handled correctly.
+TEST(ParserTest, multilineComments) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, /* this is a C style comment\n"
+ "that\n can \n span \n multiple \n lines */ \n"
+ " \"ncr-protocol\": \"UDP\", \n"
+ "\"tsig-keys\": [], \n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {} \n"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+// Tests if embedded comments are handled correctly.
+TEST(ParserTest, embbededComments) {
+ string txt =
+ "{ \"DhcpDdns\" : \n"
+ "{ \n"
+ "\"comment\": \"a comment\",\n"
+ " \"ip-address\": \"192.168.77.1\", \n"
+ " \"port\": 777, \n "
+ " \"ncr-protocol\": \"UDP\", \n"
+ "\"tsig-keys\" : [ { \n"
+ " \"name\" : \"d2.md5.key\", \n"
+ " \"user-context\" : { \"comment\" : \"indirect\" } } ], \n"
+ "\"forward-ddns\" : {}, \n"
+ "\"reverse-ddns\" : {}, \n"
+ "\"user-context\": { \"compatible\": true }"
+ "} \n"
+ "} \n";
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+/// @brief Loads specified example config file
+///
+/// This test loads specified example file twice: first, using the legacy
+/// JSON file and then second time using bison parser. Two created Element
+/// trees are then compared. The input is decommented before it is passed
+/// to legacy parser (as legacy support for comments is very limited).
+///
+/// @param fname name of the file to be loaded
+void testFile(const std::string& fname) {
+ ElementPtr json;
+ ElementPtr reference_json;
+ ConstElementPtr test_json;
+
+ string decommented = decommentJSONfile(fname);
+
+ cout << "Parsing file " << fname << " (" << decommented << ")" << endl;
+
+ ASSERT_NO_THROW(json = Element::fromJSONFile(decommented, true));
+ reference_json = moveComments(json);
+
+ // remove the temporary file
+ EXPECT_NO_THROW(::remove(decommented.c_str()));
+
+ EXPECT_NO_THROW(
+ try {
+ D2ParserContext ctx;
+ test_json = ctx.parseFile(fname, D2ParserContext::PARSER_DHCPDDNS);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+
+ ASSERT_TRUE(reference_json);
+ ASSERT_TRUE(test_json);
+
+ compareJSON(reference_json, test_json);
+}
+
+// This test loads all available existing files. Each config is loaded
+// twice: first with the existing Element::fromJSONFile() and then
+// the second time with D2Parser. Both JSON trees are then compared.
+TEST(ParserTest, file) {
+ vector<string> configs;
+ configs.push_back("all-keys.json");
+ configs.push_back("all-keys-netconf.json");
+ configs.push_back("comments.json");
+ configs.push_back("gss-tsig.json");
+ configs.push_back("sample1.json");
+ configs.push_back("template.json");
+
+ for (int i = 0; i<configs.size(); i++) {
+ testFile(string(CFG_EXAMPLES) + "/" + configs[i]);
+ }
+}
+
+/// @brief Tests error conditions in D2Parser
+///
+/// @param txt text to be parsed
+/// @param parser_type type of the parser to be used in the test
+/// @param msg expected content of the exception
+void testError(const std::string& txt,
+ D2ParserContext::ParserType parser_type,
+ const std::string& msg) {
+ SCOPED_TRACE("\n=== tested config ===\n" + txt + "=====================");
+
+ try {
+ D2ParserContext ctx;
+ ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+ FAIL() << "Expected D2ParseError but nothing was raised (expected: "
+ << msg << ")";
+ }
+ catch (const D2ParseError& ex) {
+ EXPECT_EQ(msg, ex.what());
+ }
+ catch (...) {
+ FAIL() << "Expected D2ParseError but something else was raised";
+ }
+}
+
+// Verify that error conditions are handled correctly.
+TEST(ParserTest, errors) {
+ // no input
+ testError("", D2ParserContext::PARSER_JSON,
+ "<string>:1.1: syntax error, unexpected end of file");
+ testError(" ", D2ParserContext::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+ testError("\n", D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("\t", D2ParserContext::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+ testError("\r", D2ParserContext::PARSER_JSON,
+ "<string>:1.2: syntax error, unexpected end of file");
+
+ // comments
+ testError("# nothing\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError(" #\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("// nothing\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("/* nothing */\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file");
+ testError("/* no\nthing */\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:3.1: syntax error, unexpected end of file");
+ testError("/* no\nthing */\n\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:4.1: syntax error, unexpected end of file");
+ testError("/* nothing\n",
+ D2ParserContext::PARSER_JSON,
+ "Comment not closed. (/* in line 1");
+ testError("\n\n\n/* nothing\n",
+ D2ParserContext::PARSER_JSON,
+ "Comment not closed. (/* in line 4");
+ testError("{ /* */*/ }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3-8: Invalid character: *");
+ testError("{ /* // *// }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3-11: Invalid character: /");
+ testError("{ /* // */// }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:2.1: syntax error, unexpected end of file, "
+ "expecting }");
+
+ // includes
+ testError("<?\n",
+ D2ParserContext::PARSER_JSON,
+ "Directive not closed.");
+ testError("<?include\n",
+ D2ParserContext::PARSER_JSON,
+ "Directive not closed.");
+ string file = string(CFG_EXAMPLES) + "/" + "sample1.json";
+ testError("<?include \"" + file + "\"\n",
+ D2ParserContext::PARSER_JSON,
+ "Directive not closed.");
+ testError("<?include \"/foo/bar\" ?>/n",
+ D2ParserContext::PARSER_JSON,
+ "Can't open include file /foo/bar");
+
+ // JSON keywords
+ testError("{ \"foo\": True }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10-13: JSON true reserved keyword is lower case only");
+ testError("{ \"foo\": False }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10-14: JSON false reserved keyword is lower case only");
+ testError("{ \"foo\": NULL }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10-13: JSON null reserved keyword is lower case only");
+ testError("{ \"foo\": Tru }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10: Invalid character: T");
+ testError("{ \"foo\": nul }",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.10: Invalid character: n");
+
+ // numbers
+ testError("123",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-3: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-456",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-4: syntax error, unexpected integer, "
+ "expecting {");
+ testError("-0001",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-5: syntax error, unexpected integer, "
+ "expecting {");
+ testError("1234567890123456789012345678901234567890",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-40: Failed to convert "
+ "1234567890123456789012345678901234567890"
+ " to an integer.");
+ testError("-3.14e+0",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-8: syntax error, unexpected floating point, "
+ "expecting {");
+ testError("1e50000",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-7: Failed to convert 1e50000 "
+ "to a floating point.");
+
+ // strings
+ testError("\"aabb\"",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-6: syntax error, unexpected constant string, "
+ "expecting {");
+ testError("{ \"aabb\"err",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.9: Invalid character: e");
+ testError("{ err\"aabb\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3: Invalid character: e");
+ testError("\"a\n\tb\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-6 (near 2): Invalid control in \"a\n\tb\"");
+ testError("\"a\n\\u12\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-8 (near 2): Invalid control in \"a\n\\u12\"");
+ testError("\"a\\n\\tb\"",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1-8: syntax error, unexpected constant string, "
+ "expecting {");
+ testError("\"a\\x01b\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-8 (near 3): Bad escape in \"a\\x01b\"");
+ testError("\"a\\u0162\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-9 (near 4): Unsupported unicode escape "
+ "in \"a\\u0162\"");
+ testError("\"a\\u062z\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-9 (near 3): Bad escape in \"a\\u062z\"");
+ testError("\"abc\\\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-6 (near 6): Overflow escape in \"abc\\\"");
+ testError("\"a\\u006\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-8 (near 3): Overflow unicode escape "
+ "in \"a\\u006\"");
+ testError("\"\\u\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-4 (near 2): Overflow unicode escape in \"\\u\"");
+ testError("\"\\u\x02\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-5 (near 2): Bad escape in \"\\u\x02\"");
+ testError("\"\\u\\\"foo\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-5 (near 2): Bad escape in \"\\u\\\"...");
+ testError("\"\x02\\u\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1-5 (near 1): Invalid control in \"\x02\\u\"");
+
+ // from data_unittest.c
+ testError("\\a",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+ testError("\\",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+ testError("\\\"\\\"",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.1: Invalid character: \\");
+
+ // want a map
+ testError("[]\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("[]\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.1: syntax error, unexpected [, "
+ "expecting {");
+ testError("{ 123 }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting }");
+ testError("{ 123 }\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting DhcpDdns");
+ testError("{ \"foo\" }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.9: syntax error, unexpected }, "
+ "expecting :");
+ testError("{ \"foo\" }\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting DhcpDdns");
+ testError("{ \"foo\":null }\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting DhcpDdns");
+ testError("{ \"Logging\":null }\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:1.3-11: syntax error, unexpected constant string, "
+ "expecting DhcpDdns");
+ testError("{}{}\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected {, "
+ "expecting end of file");
+
+ // duplicate in map
+ testError("{ \"foo\": 1, \"foo\": true }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1:13: duplicate foo entries in "
+ "JSON map (previous at <string>:1:10)");
+
+ // bad commas
+ testError("{ , }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected \",\", "
+ "expecting }");
+ testError("{ , \"foo\":true }\n",
+ D2ParserContext::PARSER_JSON,
+ "<string>:1.3: syntax error, unexpected \",\", "
+ "expecting }");
+
+ // bad type
+ testError("{ \"DhcpDdns\":{\n"
+ " \"dns-server-timeout\":false }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:2.24-28: syntax error, unexpected boolean, "
+ "expecting integer");
+
+ // unknown keyword
+ testError("{ \"DhcpDdns\":{\n"
+ " \"totally-bogus\":600 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:2.2-16: got unexpected keyword "
+ "\"totally-bogus\" in DhcpDdns map.");
+
+ // user context and embedded comments
+ testError("{ \"DhcpDdns\":{\n"
+ " \"comment\": true,\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:2.14-17: syntax error, unexpected boolean, "
+ "expecting constant string");
+
+ testError("{ \"DhcpDdns\":{\n"
+ " \"user-context\": \"a comment\",\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:2.19-29: syntax error, unexpected constant string, "
+ "expecting {");
+
+ testError("{ \"DhcpDdns\":{\n"
+ " \"comment\": \"a comment\",\n"
+ " \"comment\": \"another one\",\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:3)");
+
+ testError("{ \"DhcpDdns\":{\n"
+ " \"user-context\": { \"version\": 1 },\n"
+ " \"user-context\": { \"one\": \"only\" },\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3.3-16: duplicate user-context entries "
+ "(previous at <string>:2:19)");
+
+ testError("{ \"DhcpDdns\":{\n"
+ " \"user-context\": { \"comment\": \"indirect\" },\n"
+ " \"comment\": \"a comment\",\n"
+ " \"dns-server-timeout\": 1000 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3.3-11: duplicate user-context/comment entries "
+ "(previous at <string>:2:19)");
+
+ // duplicate DhcpDdns entries
+ testError("{ \"DhcpDdns\":{\n"
+ " \"comment\": \"first\" },\n"
+ " \"DhcpDdns\":{\n"
+ " \"comment\": \"second\" }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3.3-12: syntax error, unexpected DhcpDdns, expecting \",\" or }");
+
+ // duplicate of not string entries
+ testError("{ \"DhcpDdns\":{\n"
+ " \"port\": 53001,\n"
+ " \"port\": 53002 }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3:3: duplicate port entries in "
+ "DhcpDdns map (previous at <string>:2:11)");
+
+ // duplicate of string entries
+ testError("{ \"DhcpDdns\":{\n"
+ " \"ip-address\": \"127.0.0.1\",\n"
+ " \"ip-address\": \"::1\" }}\n",
+ D2ParserContext::PARSER_DHCPDDNS,
+ "<string>:3:3: duplicate ip-address entries in "
+ "DhcpDdns map (previous at <string>:2:17)");
+}
+
+// Check unicode escapes
+TEST(ParserTest, unicodeEscapes) {
+ ConstElementPtr result;
+ string json;
+
+ // check we can reread output
+ for (char c = -128; c < 127; ++c) {
+ string ins(" ");
+ ins[1] = c;
+ ConstElementPtr e(new StringElement(ins));
+ json = e->str();
+ ASSERT_NO_THROW(
+ try {
+ D2ParserContext ctx;
+ result = ctx.parseString(json, D2ParserContext::PARSER_JSON);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+ ASSERT_EQ(Element::string, result->getType());
+ EXPECT_EQ(ins, result->stringValue());
+ }
+}
+
+// This test checks that all representations of a slash is recognized properly.
+TEST(ParserTest, unicodeSlash) {
+ // check the 4 possible encodings of solidus '/'
+ ConstElementPtr result;
+ string json = "\"/\\/\\u002f\\u002F\"";
+ ASSERT_NO_THROW(
+ try {
+ D2ParserContext ctx;
+ result = ctx.parseString(json, D2ParserContext::PARSER_JSON);
+ } catch (const std::exception &x) {
+ cout << "EXCEPTION: " << x.what() << endl;
+ throw;
+ });
+ ASSERT_EQ(Element::string, result->getType());
+ EXPECT_EQ("////", result->stringValue());
+}
+
+/// @brief Load a file into a JSON element.
+///
+/// @param fname The name of the file to load.
+/// @param list The JSON element list to add the parsing result to.
+void loadFile(const string& fname, ElementPtr list) {
+ D2ParserContext ctx;
+ ElementPtr json;
+ EXPECT_NO_THROW(json = ctx.parseFile(fname, D2ParserContext::PARSER_DHCPDDNS));
+ ASSERT_TRUE(json);
+ list->add(json);
+}
+
+// This test checks that all map entries are in the sample file.
+TEST(ParserTest, mapEntries) {
+ // Type of keyword set.
+ typedef set<string> KeywordSet;
+
+ // Get keywords from the syntax file (d2_parser.yy).
+ ifstream syntax_file(SYNTAX_FILE);
+ EXPECT_TRUE(syntax_file.is_open());
+ string line;
+ KeywordSet syntax_keys = { "user-context" };
+ // Code setting the map entry.
+ const string pattern = "ctx.stack_.back()->set(\"";
+ while (getline(syntax_file, line)) {
+ // Skip comments.
+ size_t comment = line.find("//");
+ if (comment <= pattern.size()) {
+ continue;
+ }
+ if (comment != string::npos) {
+ line.resize(comment);
+ }
+ // Search for the code pattern.
+ size_t key_begin = line.find(pattern);
+ if (key_begin == string::npos) {
+ continue;
+ }
+ // Extract keywords.
+ line = line.substr(key_begin + pattern.size());
+ size_t key_end = line.find_first_of('"');
+ EXPECT_NE(string::npos, key_end);
+ string keyword = line.substr(0, key_end);
+ // Ignore result when adding the keyword to the syntax keyword set.
+ static_cast<void>(syntax_keys.insert(keyword));
+ }
+ syntax_file.close();
+
+ // Get keywords from the example files.
+ string sample_dir(CFG_EXAMPLES);
+ sample_dir += "/";
+ ElementPtr sample_json = Element::createList();
+ loadFile(sample_dir + "all-keys.json", sample_json);
+ loadFile(sample_dir + "all-keys-netconf.json", sample_json);
+ KeywordSet sample_keys = {
+ "hostname"
+ };
+ // Recursively extract keywords.
+ static void (*extract)(ConstElementPtr, KeywordSet&) =
+ [] (ConstElementPtr json, KeywordSet& set) {
+ if (json->getType() == Element::list) {
+ // Handle lists.
+ for (auto elem : json->listValue()) {
+ extract(elem, set);
+ }
+ } else if (json->getType() == Element::map) {
+ // Handle maps.
+ for (auto elem : json->mapValue()) {
+ static_cast<void>(set.insert(elem.first));
+ // Skip entries with free content.
+ if ((elem.first != "user-context") &&
+ (elem.first != "parameters")) {
+ extract(elem.second, set);
+ }
+ }
+ }
+ };
+ extract(sample_json, sample_keys);
+
+ // Compare.
+ EXPECT_EQ(syntax_keys, sample_keys);
+}
+
+/// @brief Tests a duplicate entry.
+///
+/// The entry was duplicated by adding a new <name>DDDD entry.
+/// An error is expected, usually it is a duplicate but there are
+/// a few syntax errors when the syntax allows only one parameter.
+///
+/// @param json the JSON configuration with the duplicate entry.
+void testDuplicate(ConstElementPtr json) {
+ string config = json->str();
+ size_t where = config.find("DDDD");
+ ASSERT_NE(string::npos, where);
+ string before = config.substr(0, where);
+ string after = config.substr(where + 4, string::npos);
+ D2ParserContext ctx;
+ EXPECT_THROW(ctx.parseString(before + after,
+ D2ParserContext::PARSER_DHCPDDNS),
+ D2ParseError) << "config: " << config;
+}
+
+// This test checks that duplicate entries make parsing to fail.
+TEST(ParserTest, duplicateMapEntries) {
+ // Get the config to work with from the sample file.
+ string sample_fname(CFG_EXAMPLES);
+ sample_fname += "/all-keys.json";
+ D2ParserContext ctx;
+ ElementPtr sample_json;
+ EXPECT_NO_THROW(sample_json =
+ ctx.parseFile(sample_fname, D2ParserContext::PARSER_DHCPDDNS));
+ ASSERT_TRUE(sample_json);
+
+ // Recursively check duplicates.
+ static void (*test)(ElementPtr, ElementPtr, size_t&) =
+ [] (ElementPtr config, ElementPtr json, size_t& cnt) {
+ if (json->getType() == Element::list) {
+ // Handle lists.
+ for (auto elem : json->listValue()) {
+ test(config, elem, cnt);
+ }
+ } else if (json->getType() == Element::map) {
+ // Handle maps.
+ for (auto elem : json->mapValue()) {
+ // Skip entries with free content.
+ if ((elem.first == "user-context") ||
+ (elem.first == "parameters")) {
+ continue;
+ }
+
+ // Perform tests.
+ string dup = elem.first + "DDDD";
+ json->set(dup, elem.second);
+ testDuplicate(config);
+ json->remove(dup);
+ ++cnt;
+
+ // Recursive call.
+ ElementPtr mutable_json =
+ boost::const_pointer_cast<Element>(elem.second);
+ ASSERT_TRUE(mutable_json);
+ test(config, mutable_json, cnt);
+ }
+ }
+ };
+ size_t cnt = 0;
+ test(sample_json, sample_json, cnt);
+ cout << "checked " << cnt << " duplicated map entries\n";
+}
+
+/// @brief Test fixture for trailing commas.
+class TrailingCommasTest : public isc::dhcp::test::LogContentTest {
+public:
+ /// @brief Add a log entry.
+ ///
+ /// @param loc Location of the trailing comma.
+ void addLog(const string& loc) {
+ string log = "DHCP_DDNS_CONFIG_SYNTAX_WARNING DHCP-DDNS server ";
+ log += "configuration syntax warning: " + loc;
+ log += ": Extraneous comma. ";
+ log += "A piece of configuration may have been omitted.";
+ addString(log);
+ }
+};
+
+// Test that trailing commas are allowed.
+TEST_F(TrailingCommasTest, tests) {
+ string txt(R"({
+ "DhcpDdns": {
+ "forward-ddns": {},
+ "ip-address": "127.0.0.1",
+ "loggers": [
+ {
+ "name": "kea-dhcp-ddns",
+ "output_options": [
+ {
+ "output": "stdout"
+ },
+ ],
+ "severity": "DEBUG",
+ },
+ ],
+ "port": 53001,
+ "reverse-ddns": {},
+ "tsig-keys": [
+ {
+ "algorithm": "HMAC-MD5",
+ "name": "d2.md5.key",
+ "secret": "sensitivejdPJI5QxlpnfQ==",
+ },
+ ]
+ },
+})");
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+
+ addLog("<string>:11.12");
+ addLog("<string>:13.28");
+ addLog("<string>:14.8");
+ addLog("<string>:22.45");
+ addLog("<string>:23.8");
+ addLog("<string>:25.4");
+ EXPECT_TRUE(checkFile());
+
+ // Test with many consecutive commas.
+ boost::replace_all(txt, ",", ",,,,");
+ testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+} // namespace test
+} // namespace d2
+} // namespace isc
diff --git a/src/bin/d2/tests/parser_unittest.h b/src/bin/d2/tests/parser_unittest.h
new file mode 100644
index 0000000..bcefcb7
--- /dev/null
+++ b/src/bin/d2/tests/parser_unittest.h
@@ -0,0 +1,36 @@
+// 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/.
+
+#ifndef PARSER_UNITTEST_H
+#define PARSER_UNITTEST_H
+
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <d2/parser_context.h>
+
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+/// @brief Runs parser in JSON mode, useful for parser testing
+///
+/// @param in string to be parsed
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ElementPtr
+parseJSON(const std::string& in)
+{
+ isc::d2::D2ParserContext ctx;
+ return (ctx.parseString(in, isc::d2::D2ParserContext::PARSER_JSON));
+}
+
+}
+}
+}
+
+#endif // PARSER_UNITTEST_H
diff --git a/src/bin/d2/tests/simple_add_unittests.cc b/src/bin/d2/tests/simple_add_unittests.cc
new file mode 100644
index 0000000..c47a2b6
--- /dev/null
+++ b/src/bin/d2/tests/simple_add_unittests.cc
@@ -0,0 +1,1155 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2/simple_add.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test class derived from SimpleAddTransaction to provide visibility
+// to protected methods.
+class SimpleAddStub : public SimpleAddTransaction {
+public:
+ SimpleAddStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : SimpleAddTransaction(io_service, ncr, forward_domain, reverse_domain,
+ cfg_mgr),
+ simulate_send_exception_(false),
+ simulate_build_request_exception_(false) {
+ }
+
+ virtual ~SimpleAddStub() {
+ }
+
+ /// @brief Simulates sending update requests to the DNS server
+ ///
+ /// This method simulates the initiation of an asynchronous send of
+ /// a DNS update request. It overrides the actual sendUpdate method in
+ /// the base class, thus avoiding an actual send, yet still increments
+ /// the update attempt count and posts a next event of NOP_EVT.
+ ///
+ /// It will also simulate an exception-based failure of sendUpdate, if
+ /// the simulate_send_exception_ flag is true.
+ ///
+ /// @param comment Parameter is unused, but present in base class method.
+ ///
+ virtual void sendUpdate(const std::string& /*comment*/) {
+ if (simulate_send_exception_) {
+ // Make the flag a one-shot by resetting it.
+ simulate_send_exception_ = false;
+ // Transition to failed.
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ return;
+ }
+
+ // Update send attempt count and post a NOP_EVT.
+ setUpdateAttempts(getUpdateAttempts() + 1);
+ postNextEvent(StateModel::NOP_EVT);
+ }
+
+ /// @brief Prepares the initial D2UpdateMessage
+ ///
+ /// This method overrides the NameChangeTransaction implementation to
+ /// provide the ability to simulate an exception throw in the build
+ /// request logic.
+ /// If the one-shot flag, simulate_build_request_exception_ is true,
+ /// this method will throw an exception, otherwise it will invoke the
+ /// base class method, providing normal functionality.
+ ///
+ /// For parameter description see the NameChangeTransaction implementation.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+ if (simulate_build_request_exception_) {
+ simulate_build_request_exception_ = false;
+ isc_throw (SimpleAddTransactionError,
+ "Simulated build requests exception");
+ }
+
+ return (NameChangeTransaction::prepNewRequest(domain));
+ }
+
+ /// @brief Simulates receiving a response
+ ///
+ /// This method simulates the completion of a DNSClient send. This allows
+ /// the state handler logic devoted to dealing with IO completion to be
+ /// fully exercised without requiring any actual IO. The two primary
+ /// pieces of information gleaned from IO completion are the DNSClient
+ /// status which indicates whether or not the IO exchange was successful
+ /// and the rcode, which indicates the server's reaction to the request.
+ ///
+ /// This method updates the transaction's DNS status value to that of the
+ /// given parameter, and then constructs and DNS update response message
+ /// with the given rcode value. To complete the simulation it then posts
+ /// a next event of IO_COMPLETED_EVT.
+ ///
+ /// @param status simulated DNSClient status
+ /// @param rcode simulated server response code
+ void fakeResponse(const DNSClient::Status& status,
+ const dns::Rcode& rcode) {
+ // Set the DNS update status. This is normally set in
+ // DNSClient IO completion handler.
+ setDnsUpdateStatus(status);
+
+ // Construct an empty message with the given Rcode.
+ D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+ msg->setRcode(rcode);
+
+ // Set the update response to the message.
+ setDnsUpdateResponse(msg);
+
+ // Post the IO completion event.
+ postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+ }
+
+ /// @brief Selects the first forward server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Selects the first reverse server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectRevServer() {
+ if (getReverseDomain()) {
+ initServerSelection(getReverseDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief One-shot flag which will simulate sendUpdate failure if true.
+ bool simulate_send_exception_;
+
+ /// @brief One-shot flag which will simulate an exception when sendUpdate
+ /// failure if true.
+ bool simulate_build_request_exception_;
+
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using SimpleAddTransaction::defineEvents;
+ using SimpleAddTransaction::verifyEvents;
+ using SimpleAddTransaction::defineStates;
+ using SimpleAddTransaction::verifyStates;
+ using SimpleAddTransaction::readyHandler;
+ using SimpleAddTransaction::selectingFwdServerHandler;
+ using SimpleAddTransaction::getCurrentServer;
+ using SimpleAddTransaction::setDnsUpdateStatus;
+ using SimpleAddTransaction::replacingFwdAddrsHandler;
+ using SimpleAddTransaction::selectingRevServerHandler;
+ using SimpleAddTransaction::replacingRevPtrsHandler;
+ using SimpleAddTransaction::processAddOkHandler;
+ using SimpleAddTransaction::processAddFailedHandler;
+ using SimpleAddTransaction::buildReplaceFwdAddressRequest;
+ using SimpleAddTransaction::buildReplaceRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<SimpleAddStub> SimpleAddStubPtr;
+
+/// @brief Test fixture for testing SimpleAddTransaction
+///
+/// Note this class uses SimpleAddStub class to exercise non-public
+/// aspects of SimpleAddTransaction.
+class SimpleAddTransactionTest : public TransactionTest {
+public:
+
+ SimpleAddTransactionTest() {
+ }
+
+ virtual ~SimpleAddTransactionTest() {
+ }
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ SimpleAddStubPtr makeTransaction4(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (SimpleAddStubPtr(new SimpleAddStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_, cfg_mgr_)));
+ }
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ SimpleAddStubPtr makeTransaction6(int change_mask = FWD_AND_REV_CHG) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_ADD, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (SimpleAddStubPtr(new SimpleAddStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Create a test transaction at a known point in the state model.
+ ///
+ /// Method prepares a new test transaction and sets its state and next
+ /// event values to those given. This makes the transaction appear to
+ /// be at that point in the state model without having to transition it
+ /// through prerequisite states. It also provides the ability to set
+ /// which change directions are requested: forward change only, reverse
+ /// change only, or both.
+ ///
+ /// @param state value to set as the current state
+ /// @param event value to post as the next event
+ /// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
+ SimpleAddStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+ unsigned int change_mask = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ SimpleAddStubPtr name_add = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction6(change_mask));
+ name_add->initDictionaries();
+ name_add->postNextEvent(event);
+ name_add->setState(state);
+ return (name_add);
+ }
+};
+
+/// @brief Tests SimpleAddTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(SimpleAddTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 1 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with wrong change type fails.
+ EXPECT_THROW(SimpleAddTransaction(io_service, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ SimpleAddTransactionError);
+
+ // Verify that a valid construction attempt works.
+ ncr->setChangeType(isc::dhcp_ddns::CHG_ADD);
+ EXPECT_NO_THROW(SimpleAddTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(SimpleAddTransactionTest, dictionaryCheck) {
+ SimpleAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_add->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_add->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_add->defineEvents());
+ ASSERT_NO_THROW(name_add->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_add->verifyEvents());
+ ASSERT_NO_THROW(name_add->verifyStates());
+}
+
+/// @brief Tests construction of a DNS update request for replacing a forward
+/// dns entry.
+TEST_F(SimpleAddTransactionTest, buildReplaceFwdAddressRequest) {
+ // Create a IPv4 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ SimpleAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+ checkSimpleReplaceFwdAddressRequest(*name_add);
+
+ // Create a IPv6 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
+ checkSimpleReplaceFwdAddressRequest(*name_add);
+}
+
+/// @brief Tests the construction of a DNS update request for replacing a
+/// reverse dns entry.
+TEST_F(SimpleAddTransactionTest, buildReplaceRevPtrsRequest) {
+ // Create a IPv4 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ SimpleAddStubPtr name_add;
+ ASSERT_NO_THROW(name_add = makeTransaction4());
+ ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+ checkReplaceRevPtrsRequest(*name_add);
+
+ // Create a IPv6 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_add = makeTransaction6());
+ ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
+ checkReplaceRevPtrsRequest(*name_add);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, readyHandler) {
+ SimpleAddStubPtr name_add;
+
+ // Create a transaction which includes only a forward change.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FORWARD_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring only a forward change, transitions to
+ // selecting a forward server.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create a transaction which includes both a forward and a reverse change.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring both forward and reverse, starts with
+ // the forward change by transitioning to selecting a forward server.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create and prep a reverse only transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, REVERSE_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_add->readyHandler());
+
+ // Verify that a request requiring only a reverse change, transitions to
+ // selecting a reverse server.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::READY_ST, StateModel::NOP_EVT)
+ );
+
+ // Running the readyHandler should throw.
+ EXPECT_THROW(name_add->readyHandler(), SimpleAddTransactionError);
+}
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, selectingFwdServerHandler) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT)
+ );
+
+ // Call selectingFwdServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_add->getForwardDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+ << " selections: " << i);
+ // Run selectingFwdServerHandler.
+ ASSERT_NO_THROW(name_add->selectingFwdServerHandler());
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_add->getCurrentServer());
+
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_add->selectingFwdServerHandler());
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->selectingFwdServerHandler(),
+ SimpleAddTransactionError);
+}
+
+// ************************ replacingFwdAddrHandler Tests *****************
+
+// Tests that replacingFwdAddrsHandler rejects invalid events.
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_InvalidEvent) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->replacingFwdAddrsHandler(), SimpleAddTransactionError);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkSimpleReplaceFwdAddressRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::NOP_EVT);
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_OtherRcode) {
+ SimpleAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error or FQDN in use is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_Timeout) {
+ SimpleAddStubPtr name_add;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_CorruptResponse) {
+ SimpleAddStubPtr name_add;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingFwdAddrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Simulate a corrupt server response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingFwdAddrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, selectingRevServerHandler) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT)
+ );
+
+ // Call selectingRevServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_add->getReverseDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+ << " selections: " << i);
+ // Run selectingRevServerHandler.
+ ASSERT_NO_THROW(name_add->selectingRevServerHandler());
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_add->getCurrentServer());
+
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_add->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_add->selectingRevServerHandler());
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->selectingRevServerHandler(),
+ SimpleAddTransactionError);
+}
+
+//************************** replacingRevPtrsHandler tests *****************
+
+// Tests that replacingRevPtrsHandler rejects invalid events.
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_InvalidEvent) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->replacingRevPtrsHandler(),
+ SimpleAddTransactionError);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_FwdOnlyAddOK) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_add->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkReplaceRevPtrsRequest(*name_add);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::NOP_EVT);
+
+ // Simulate receiving a successful update response.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_TRUE(name_add->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_OtherRcode) {
+ SimpleAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure. Arbitrarily choosing refused.
+ name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_Timeout) {
+ SimpleAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate a server IO timeout.
+ name_add->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests replacingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_CorruptResponse) {
+ SimpleAddStubPtr name_add;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ SimpleAddTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_add->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run replacingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Simulate a server corrupt response.
+ name_add->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_add->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run replacingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_add, SimpleAddTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests the processAddOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, processAddOkHandler) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT)
+ );
+
+ // Run processAddOkHandler.
+ EXPECT_NO_THROW(name_add->processAddOkHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_add->getNcrStatus());
+
+ // Verify that the model has ended.
+ CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->processAddOkHandler(), SimpleAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleAddTransactionTest, processAddFailedHandler) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT)
+ );
+
+ // Run processAddFailedHandler.
+ EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_add->processAddFailedHandler(), SimpleAddTransactionError);
+}
+
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(SimpleAddTransactionTest, processAddFailedHandler_NoMoreServers) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT)
+ );
+
+ // Run processAddFailedHandler.
+ EXPECT_NO_THROW(name_add->processAddFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_add->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_add, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_SendUpdateException) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_SendUpdateException) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ name_add->simulate_send_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingFwdAddrsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingFwdAddrsHandler_BuildRequestException) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_FWD_ADDRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingFwdAddrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->replacingFwdAddrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests replacingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleAddTransactionTest, replacingRevPtrsHandler_BuildRequestException) {
+ SimpleAddStubPtr name_add;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_add = prepHandlerTest(SimpleAddTransaction::REPLACING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Set the one-shot exception simulation flag.
+ name_add->simulate_build_request_exception_ = true;
+
+ // Run replacingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_add->replacingRevPtrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_add->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_add->getForwardChangeCompleted());
+ EXPECT_FALSE(name_add->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_add, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+}
diff --git a/src/bin/d2/tests/simple_remove_unittests.cc b/src/bin/d2/tests/simple_remove_unittests.cc
new file mode 100644
index 0000000..1d53400
--- /dev/null
+++ b/src/bin/d2/tests/simple_remove_unittests.cc
@@ -0,0 +1,1238 @@
+// Copyright (C) 2020-2021 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_service.h>
+#include <d2/simple_remove.h>
+#include <d2srv/d2_cfg_mgr.h>
+#include <d2srv/testutils/nc_test_utils.h>
+#include <dns/messagerenderer.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+using namespace isc::util;
+
+
+namespace {
+
+/// @brief Test class derived from SimpleRemoveTransaction to provide visibility
+// to protected methods.
+class SimpleRemoveStub : public SimpleRemoveTransaction {
+public:
+ SimpleRemoveStub(asiolink::IOServicePtr& io_service,
+ dhcp_ddns::NameChangeRequestPtr& ncr,
+ DdnsDomainPtr& forward_domain,
+ DdnsDomainPtr& reverse_domain,
+ D2CfgMgrPtr& cfg_mgr)
+ : SimpleRemoveTransaction(io_service, ncr, forward_domain,
+ reverse_domain, cfg_mgr),
+ simulate_send_exception_(false),
+ simulate_build_request_exception_(false) {
+ }
+
+ virtual ~SimpleRemoveStub() {
+ }
+
+ /// @brief Simulates sending update requests to the DNS server
+ ///
+ /// This method simulates the initiation of an asynchronous send of
+ /// a DNS update request. It overrides the actual sendUpdate method in
+ /// the base class, thus avoiding an actual send, yet still increments
+ /// the update attempt count and posts a next event of NOP_EVT.
+ ///
+ /// It will also simulate an exception-based failure of sendUpdate, if
+ /// the simulate_send_exception_ flag is true.
+ ///
+ /// @param comment Parameter is unused, but present in base class method
+ ///
+ virtual void sendUpdate(const std::string& /* comment */) {
+ if (simulate_send_exception_) {
+ // Make the flag a one-shot by resetting it.
+ simulate_send_exception_ = false;
+ // Transition to failed.
+ transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+ return;
+ }
+
+ // Update send attempt count and post a NOP_EVT.
+ setUpdateAttempts(getUpdateAttempts() + 1);
+ postNextEvent(StateModel::NOP_EVT);
+ }
+
+ /// @brief Prepares the initial D2UpdateMessage
+ ///
+ /// This method overrides the NameChangeTransaction implementation to
+ /// provide the ability to simulate an exception throw in the build
+ /// request logic.
+ /// If the one-shot flag, simulate_build_request_exception_ is true,
+ /// this method will throw an exception, otherwise it will invoke the
+ /// base class method, providing normal functionality.
+ ///
+ /// For parameter description see the NameChangeTransaction implementation.
+ virtual D2UpdateMessagePtr prepNewRequest(DdnsDomainPtr domain) {
+ if (simulate_build_request_exception_) {
+ simulate_build_request_exception_ = false;
+ isc_throw (SimpleRemoveTransactionError,
+ "Simulated build requests exception");
+ }
+
+ return (NameChangeTransaction::prepNewRequest(domain));
+ }
+
+ /// @brief Simulates receiving a response
+ ///
+ /// This method simulates the completion of a DNSClient send. This allows
+ /// the state handler logic devoted to dealing with IO completion to be
+ /// fully exercised without requiring any actual IO. The two primary
+ /// pieces of information gleaned from IO completion are the DNSClient
+ /// status which indicates whether or not the IO exchange was successful
+ /// and the rcode, which indicates the server's reaction to the request.
+ ///
+ /// This method updates the transaction's DNS status value to that of the
+ /// given parameter, and then constructs and DNS update response message
+ /// with the given rcode value. To complete the simulation it then posts
+ /// a next event of IO_COMPLETED_EVT.
+ ///
+ /// @param status simulated DNSClient status
+ /// @param rcode simulated server response code
+ void fakeResponse(const DNSClient::Status& status,
+ const dns::Rcode& rcode) {
+ // Set the DNS update status. This is normally set in
+ // DNSClient IO completion handler.
+ setDnsUpdateStatus(status);
+
+ // Construct an empty message with the given Rcode.
+ D2UpdateMessagePtr msg(new D2UpdateMessage(D2UpdateMessage::OUTBOUND));
+ msg->setRcode(rcode);
+
+ // Set the update response to the message.
+ setDnsUpdateResponse(msg);
+
+ // Post the IO completion event.
+ postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+ }
+
+ /// @brief Selects the first forward server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectFwdServer() {
+ if (getForwardDomain()) {
+ initServerSelection(getForwardDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief Selects the first reverse server.
+ /// Some state handlers require a server to have been selected.
+ /// This selects a server without going through the state
+ /// transition(s) to do so.
+ bool selectRevServer() {
+ if (getReverseDomain()) {
+ initServerSelection(getReverseDomain());
+ selectNextServer();
+ return (getCurrentServer().get() != 0);
+ }
+
+ return (false);
+ }
+
+ /// @brief One-shot flag which will simulate sendUpdate failure if true.
+ bool simulate_send_exception_;
+
+ /// @brief One-shot flag which will simulate an exception when sendUpdate
+ /// failure if true.
+ bool simulate_build_request_exception_;
+
+ using StateModel::postNextEvent;
+ using StateModel::setState;
+ using StateModel::initDictionaries;
+ using SimpleRemoveTransaction::defineEvents;
+ using SimpleRemoveTransaction::verifyEvents;
+ using SimpleRemoveTransaction::defineStates;
+ using SimpleRemoveTransaction::verifyStates;
+ using SimpleRemoveTransaction::readyHandler;
+ using SimpleRemoveTransaction::selectingFwdServerHandler;
+ using SimpleRemoveTransaction::getCurrentServer;
+ using SimpleRemoveTransaction::setDnsUpdateStatus;
+ using SimpleRemoveTransaction::removingFwdRRsHandler;
+ using SimpleRemoveTransaction::selectingRevServerHandler;
+ using SimpleRemoveTransaction::removingRevPtrsHandler;
+ using SimpleRemoveTransaction::processRemoveOkHandler;
+ using SimpleRemoveTransaction::processRemoveFailedHandler;
+ using SimpleRemoveTransaction::buildRemoveFwdRRsRequest;
+ using SimpleRemoveTransaction::buildRemoveRevPtrsRequest;
+};
+
+typedef boost::shared_ptr<SimpleRemoveStub> SimpleRemoveStubPtr;
+
+/// @brief Test fixture for testing SimpleRemoveTransaction
+///
+/// Note this class uses SimpleRemoveStub class to exercise non-public
+/// aspects of SimpleRemoveTransaction.
+class SimpleRemoveTransactionTest : public TransactionTest {
+public:
+ SimpleRemoveTransactionTest() {
+ }
+
+ virtual ~SimpleRemoveTransactionTest() {
+ }
+
+ /// @brief Creates a transaction which requests an IPv4 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ SimpleRemoveStubPtr makeTransaction4(int change_mask) {
+ // Creates IPv4 remove request, forward, and reverse domains.
+ setupForIPv4Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (SimpleRemoveStubPtr(new SimpleRemoveStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Creates a transaction which requests an IPv6 DNS update.
+ ///
+ /// The transaction is constructed around a predefined (i.e. "canned")
+ /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+ /// changes requested. Based upon the change mask, the transaction
+ /// will have either the forward, reverse, or both domains populated.
+ ///
+ /// @param change_mask determines which change directions are requested
+ SimpleRemoveStubPtr makeTransaction6(int change_mask) {
+ // Creates IPv6 remove request, forward, and reverse domains.
+ setupForIPv6Transaction(dhcp_ddns::CHG_REMOVE, change_mask);
+
+ // Now create the test transaction as would occur in update manager.
+ return (SimpleRemoveStubPtr(new SimpleRemoveStub(io_service_, ncr_,
+ forward_domain_,
+ reverse_domain_,
+ cfg_mgr_)));
+ }
+
+ /// @brief Create a test transaction at a known point in the state model.
+ ///
+ /// Method prepares a new test transaction and sets its state and next
+ /// event values to those given. This makes the transaction appear to
+ /// be at that point in the state model without having to transition it
+ /// through prerequisite states. It also provides the ability to set
+ /// which change directions are requested: forward change only, reverse
+ /// change only, or both.
+ ///
+ /// @param state value to set as the current state
+ /// @param event value to post as the next event
+ /// @param change_mask determines which change directions are requested
+ /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+ /// transaction.
+ SimpleRemoveStubPtr prepHandlerTest(unsigned int state, unsigned int event,
+ unsigned int change_mask = FWD_AND_REV_CHG,
+ short family = AF_INET) {
+ SimpleRemoveStubPtr name_remove = (family == AF_INET ?
+ makeTransaction4(change_mask) :
+ makeTransaction6(change_mask));
+ name_remove->initDictionaries();
+ name_remove->postNextEvent(event);
+ name_remove->setState(state);
+ return (name_remove);
+ }
+
+};
+
+/// @brief Tests SimpleRemoveTransaction construction.
+/// This test verifies that:
+/// 1. Construction with invalid type of request
+/// 2. Valid construction functions properly
+TEST(SimpleRemoveTransaction, construction) {
+ asiolink::IOServicePtr io_service(new isc::asiolink::IOService());
+ D2CfgMgrPtr cfg_mgr(new D2CfgMgr());
+
+ const char* msg_str =
+ "{"
+ " \"change-type\" : 0 , "
+ " \"forward-change\" : true , "
+ " \"reverse-change\" : true , "
+ " \"fqdn\" : \"example.com.\" , "
+ " \"ip-address\" : \"192.168.2.1\" , "
+ " \"dhcid\" : \"0102030405060708\" , "
+ " \"lease-expires-on\" : \"20130121132405\" , "
+ " \"lease-length\" : 1300, "
+ " \"use-conflict-resolution\" : true "
+ "}";
+
+ dhcp_ddns::NameChangeRequestPtr ncr;
+ DnsServerInfoStoragePtr servers;
+ DdnsDomainPtr forward_domain;
+ DdnsDomainPtr reverse_domain;
+ DdnsDomainPtr empty_domain;
+
+ ASSERT_NO_THROW(ncr = dhcp_ddns::NameChangeRequest::fromJSON(msg_str));
+ ASSERT_NO_THROW(forward_domain.reset(new DdnsDomain("*", servers)));
+ ASSERT_NO_THROW(reverse_domain.reset(new DdnsDomain("*", servers)));
+
+ // Verify that construction with wrong change type fails.
+ EXPECT_THROW(SimpleRemoveTransaction(io_service, ncr,
+ forward_domain, reverse_domain, cfg_mgr),
+ SimpleRemoveTransactionError);
+
+ // Verify that a valid construction attempt works.
+ ncr->setChangeType(isc::dhcp_ddns::CHG_REMOVE);
+ EXPECT_NO_THROW(SimpleRemoveTransaction(io_service, ncr,
+ forward_domain, reverse_domain,
+ cfg_mgr));
+}
+
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(SimpleRemoveTransactionTest, dictionaryCheck) {
+ SimpleRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FWD_AND_REV_CHG));
+ // Verify that the event and state dictionary validation fails prior
+ // dictionary construction.
+ ASSERT_THROW(name_remove->verifyEvents(), StateModelError);
+ ASSERT_THROW(name_remove->verifyStates(), StateModelError);
+
+ // Construct both dictionaries.
+ ASSERT_NO_THROW(name_remove->defineEvents());
+ ASSERT_NO_THROW(name_remove->defineStates());
+
+ // Verify both event and state dictionaries now pass validation.
+ ASSERT_NO_THROW(name_remove->verifyEvents());
+ ASSERT_NO_THROW(name_remove->verifyStates());
+}
+
+/// @brief Tests construction of a DNS update request for removing forward
+/// dns RR entries.
+TEST_F(SimpleRemoveTransactionTest, buildRemoveFwdRRsRequest) {
+ // Create a IPv4 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ SimpleRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+ checkSimpleRemoveFwdRRsRequest(*name_remove);
+
+ // Create a IPv6 forward replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(FORWARD_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveFwdRRsRequest());
+ checkSimpleRemoveFwdRRsRequest(*name_remove);
+}
+
+/// @brief Tests the construction of a DNS update request for removing a
+/// reverse dns entry.
+TEST_F(SimpleRemoveTransactionTest, buildRemoveRevPtrsRequest) {
+ // Create a IPv4 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ SimpleRemoveStubPtr name_remove;
+ ASSERT_NO_THROW(name_remove = makeTransaction4(REVERSE_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+ checkSimpleRemoveRevPtrsRequest(*name_remove);
+
+ // Create a IPv6 reverse replace transaction.
+ // Verify the request builds without error.
+ // and then verify the request contents.
+ ASSERT_NO_THROW(name_remove = makeTransaction6(REVERSE_CHG));
+ ASSERT_NO_THROW(name_remove->buildRemoveRevPtrsRequest());
+ checkSimpleRemoveRevPtrsRequest(*name_remove);
+}
+
+// Tests the readyHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is START_EVT and request includes only a forward change
+// 2. Posted event is START_EVT and request includes both a forward and a
+// reverse change
+// 3. Posted event is START_EVT and request includes only a reverse change
+// 4. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, readyHandler) {
+ SimpleRemoveStubPtr name_remove;
+
+ // Create a transaction which includes only a forward change.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FORWARD_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring only a forward change, transitions to
+ // selecting a forward server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create a transaction which includes both a forward and a reverse change.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring both forward and reverse, starts with
+ // the forward change by transitioning to selecting a forward server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create and prep a reverse only transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::START_EVT, REVERSE_CHG)
+ );
+
+ // Run readyHandler.
+ EXPECT_NO_THROW(name_remove->readyHandler());
+
+ // Verify that a request requiring only a reverse change, transitions to
+ // selecting a reverse server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::READY_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the readyHandler should throw.
+ EXPECT_THROW(name_remove->readyHandler(), SimpleRemoveTransactionError);
+}
+
+
+// Tests the selectingFwdServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, selectingFwdServerHandler) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT)
+ );
+
+ // Call selectingFwdServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_remove->getForwardDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+ << " selections:" << i);
+ // Run selectingFwdServerHandler.
+ ASSERT_NO_THROW(name_remove->selectingFwdServerHandler());
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_remove->getCurrentServer());
+
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_remove->selectingFwdServerHandler());
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_FWD_SERVER_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->selectingFwdServerHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// ************************ removingFwdRRsHandler Tests *****************
+
+// Tests that removingFwdRRsHandler rejects invalid events.
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_InvalidEvent) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingFwdRRsHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_FwdOnlyOK) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkSimpleRemoveFwdRRsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::NOP_EVT);
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Forward completion should be true, reverse should be false.
+ EXPECT_TRUE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a forward only change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_OtherRcode) {
+ SimpleRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT,
+ FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure (we are also treating FQDN not in use is
+ // success). Arbitrarily choosing refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_Timeout) {
+ SimpleRemoveStubPtr name_remove;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT,
+ FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdRRsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes a forward and reverse change.
+// Initial posted event is UPDATE_OK_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_InvalidResponse) {
+ SimpleRemoveStubPtr name_remove;
+
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::UPDATE_OK_EVT, FWD_AND_REV_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectFwdServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingFwdRRsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Simulate a corrupt server response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingFwdRRsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::SELECTING_FWD_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+
+// Tests the selectingRevServerHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is SELECT_SERVER_EVT
+// 2. Posted event is SERVER_IO_ERROR_EVT
+// 3. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, selectingRevServerHandler) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SELECT_SERVER_EVT)
+ );
+
+ // Call selectingRevServerHandler enough times to select all of the
+ // servers in it's current domain. The first time, it will be with
+ // next event of SELECT_SERVER_EVT. Thereafter it will be with a next
+ // event of SERVER_IO_ERROR_EVT.
+ int num_servers = name_remove->getReverseDomain()->getServers()->size();
+ for (int i = 0; i < num_servers; ++i) {
+ SCOPED_TRACE(testing::Message() << " num_servers: " << num_servers
+ << " selections:" << i);
+ // Run selectingRevServerHandler.
+ ASSERT_NO_THROW(name_remove->selectingRevServerHandler());
+
+ // Verify that a server was selected.
+ ASSERT_TRUE(name_remove->getCurrentServer());
+
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+
+ // Post a server IO error event. This simulates an IO error occurring
+ // and a need to select the new server.
+ ASSERT_NO_THROW(name_remove->postNextEvent(NameChangeTransaction::
+ SERVER_IO_ERROR_EVT));
+ }
+
+ // We should have exhausted the list of servers. Processing another
+ // SERVER_IO_ERROR_EVT should transition us to failure.
+ EXPECT_NO_THROW(name_remove->selectingRevServerHandler());
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->selectingRevServerHandler(),
+ SimpleRemoveTransactionError);
+}
+
+//************************** removingRevPtrsHandler tests *****************
+
+// Tests that removingRevPtrsHandler rejects invalid events.
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_InvalidEvent) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler but with
+ // an invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->removingRevPtrsHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates successful update.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_RevOnlyOK) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkSimpleRemoveRevPtrsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ StateModel::NOP_EVT);
+
+ // Simulate receiving a successful update response.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NOERROR());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates FQDN is NOT in use.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_FqdnNotInUse) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Should not be an update message yet.
+ D2UpdateMessagePtr update_msg = name_remove->getDnsUpdateRequest();
+ ASSERT_FALSE(update_msg);
+
+ // At this point completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify that an update message was constructed properly.
+ checkSimpleRemoveRevPtrsRequest(*name_remove);
+
+ // Verify that we are still in this state and next event is NOP_EVT.
+ // This indicates we "sent" the message and are waiting for IO completion.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ StateModel::NOP_EVT);
+
+ // Simulate receiving a RRSET does not exist.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::NXRRSET());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Forward completion should be false, reverse should be true.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_TRUE(name_remove->getReverseChangeCompleted());
+
+ // Since it is a reverse change, we should be done.
+ // Verify that we transitioned correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent without error.
+// A server response is received which indicates the update was rejected.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_OtherRcode) {
+ SimpleRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate receiving server rejection response. Per RFC, anything other
+ // than no error is failure. Arbitrarily choosing refused.
+ name_remove->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should still be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // We should have failed the transaction. Verify that we transitioned
+ // correctly.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request send times out MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_Timeout) {
+ SimpleRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT,
+ REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate a server IO timeout.
+ name_remove->setDnsUpdateStatus(DNSClient::TIMEOUT);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+
+// Tests removingRevPtrsHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The update request is sent but a corrupt response is received, this occurs
+// MAX_UPDATE_TRIES_PER_SERVER times.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_CorruptResponse) {
+ SimpleRemoveStubPtr name_remove;
+ // Create the transaction.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ // Select a server to satisfy log statements.
+ ASSERT_TRUE(name_remove->selectRevServer());
+
+ // Verify that we can make maximum number of update attempts permitted
+ // and then transition to selecting a new server.
+ int max_tries = NameChangeTransaction::MAX_UPDATE_TRIES_PER_SERVER;
+ for (int i = 1; i <= max_tries; ++i) {
+ // Run removingRevPtrsHandler to send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Simulate a server corrupt response.
+ name_remove->setDnsUpdateStatus(DNSClient::INVALID_RESPONSE);
+ name_remove->postNextEvent(NameChangeTransaction::IO_COMPLETED_EVT);
+
+ // Run removingRevPtrsHandler again to process the response.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ if (i < max_tries) {
+ // We should be ready to try again.
+ CHECK_CONTEXT(name_remove, SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT);
+ } else {
+ // Server retries should be exhausted, time for a new server.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::SELECTING_REV_SERVER_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT);
+ }
+ }
+}
+
+// Tests the processRemoveOkHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_OK_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, processRemoveOkHandler) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ NameChangeTransaction::UPDATE_OK_EVT)
+ );
+
+ // Run processRemoveOkHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveOkHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_COMPLETED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended.
+ CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_OK_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->processRemoveOkHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for the following scenarios:
+//
+// 1. Posted event is UPDATE_FAILED_EVT
+// 2. Posted event is invalid
+//
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT)
+ );
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+
+ // Create and prep transaction, poised to run the handler but with an
+ // invalid event.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ StateModel::NOP_EVT)
+ );
+
+ // Running the handler should throw.
+ EXPECT_THROW(name_remove->processRemoveFailedHandler(),
+ SimpleRemoveTransactionError);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler_NoMoreServers) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::NO_MORE_SERVERS_EVT)
+ );
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests the processRemoveFailedHandler functionality.
+// It verifies behavior for posted event of SERVER_IO_ERROR_EVT.
+TEST_F(SimpleRemoveTransactionTest, processRemoveFailedHandler_ServerIOError) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::SERVER_IO_ERROR_EVT)
+ );
+
+ // Run processRemoveFailedHandler.
+ EXPECT_NO_THROW(name_remove->processRemoveFailedHandler());
+
+ // Verify that a server was selected.
+ EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+ // Verify that the model has ended. (Remember, the transaction failed NOT
+ // the model. The model should have ended normally.)
+ CHECK_CONTEXT(name_remove, StateModel::END_ST, StateModel::END_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_SendUpdateException) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPtrHandler with the following scenario:
+//
+// The request includes only a reverse change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The send update request fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPtrsHandler_SendUpdateException) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, REVERSE_CHG)
+ );
+
+ name_remove->simulate_send_exception_ = true;
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ EXPECT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingFwdRRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingFwdRRsHandler_BuildRequestException) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_FWD_RRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingFwdRRsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingFwdRRsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+// Tests removingRevPTRsHandler with the following scenario:
+//
+// The request includes only a forward change.
+// Initial posted event is SERVER_SELECTED_EVT.
+// The request build fails due to an unexpected exception.
+//
+TEST_F(SimpleRemoveTransactionTest, removingRevPTRsHandler_BuildRequestException) {
+ SimpleRemoveStubPtr name_remove;
+ // Create and prep a transaction, poised to run the handler.
+ ASSERT_NO_THROW(
+ name_remove = prepHandlerTest(SimpleRemoveTransaction::REMOVING_REV_PTRS_ST,
+ NameChangeTransaction::SERVER_SELECTED_EVT, FORWARD_CHG)
+ );
+
+ // Set the one-shot exception simulation flag.
+ name_remove->simulate_build_request_exception_ = true;
+
+ // Run removingRevPtrsHandler to construct and send the request.
+ // This should fail with a build request throw which should be caught
+ // in the state handler.
+ ASSERT_NO_THROW(name_remove->removingRevPtrsHandler());
+
+ // Verify we did not attempt to send anything.
+ EXPECT_EQ(0, name_remove->getUpdateAttempts());
+
+ // Completion flags should be false.
+ EXPECT_FALSE(name_remove->getForwardChangeCompleted());
+ EXPECT_FALSE(name_remove->getReverseChangeCompleted());
+
+ // Since IO exceptions should be gracefully handled, any that occur
+ // are unanticipated, and deemed unrecoverable, so the transaction should
+ // be transitioned to failure.
+ CHECK_CONTEXT(name_remove, NameChangeTransaction::PROCESS_TRANS_FAILED_ST,
+ NameChangeTransaction::UPDATE_FAILED_EVT);
+}
+
+}
diff --git a/src/bin/d2/tests/test_callout_libraries.h.in b/src/bin/d2/tests/test_callout_libraries.h.in
new file mode 100644
index 0000000..a88d131
--- /dev/null
+++ b/src/bin/d2/tests/test_callout_libraries.h.in
@@ -0,0 +1,24 @@
+// 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 D2_TEST_CALLOUT_LIBRARIES_H
+#define D2_TEST_CALLOUT_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// .so file. Note that we access the .so file - libtool creates this as a
+// like to the real shared library.
+
+// Basic callout library with context_create and three "standard" callouts.
+static const char* CALLOUT_LIBRARY = "@abs_builddir@/.libs/libcallout.so";
+
+} // anonymous namespace
+
+#endif // D2_TEST_CALLOUT_LIBRARIES_H
diff --git a/src/bin/d2/tests/test_configured_libraries.h.in b/src/bin/d2/tests/test_configured_libraries.h.in
new file mode 100644
index 0000000..2b235ae
--- /dev/null
+++ b/src/bin/d2/tests/test_configured_libraries.h.in
@@ -0,0 +1,26 @@
+// 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 D2_TEST_CONFIGURED_LIBRARIES_H
+#define D2_TEST_CONFIGURED_LIBRARIES_H
+
+#include <config.h>
+
+namespace {
+
+// Names of the libraries used in these tests. These libraries are built using
+// libtool, so we need to look in the hidden ".libs" directory to locate the
+// .so file. Note that we access the .so file - libtool creates this as a
+// like to the real shared library.
+
+// Configured library with d2_srv_configured testing: if there is a toplevel
+// user context with an error entry the returned status is DROP with the
+// content of the error entry.
+static const char* CONFIGURED_LIBRARY = "@abs_builddir@/.libs/libconfigured.so";
+
+} // anonymous namespace
+
+#endif // D2_TEST_CONFIGURED_LIBRARIES_H
diff --git a/src/bin/d2/tests/test_data_files_config.h.in b/src/bin/d2/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..40122c6
--- /dev/null
+++ b/src/bin/d2/tests/test_data_files_config.h.in
@@ -0,0 +1,10 @@
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/// @brief Path to D2 source dir so tests against the dhcp-ddns.spec file
+/// can find it reliably.
+#define D2_SRC_DIR "@abs_top_srcdir@/src/bin/d2"
+#define D2_TEST_DATA_DIR "@abs_top_srcdir@/src/bin/d2/tests/testdata"
diff --git a/src/bin/d2/tests/testdata/d2_cfg_tests.json b/src/bin/d2/tests/testdata/d2_cfg_tests.json
new file mode 100644
index 0000000..6cb087f
--- /dev/null
+++ b/src/bin/d2/tests/testdata/d2_cfg_tests.json
@@ -0,0 +1,1577 @@
+# Copyright (C) 2014-2021 Internet Systems Consortium, Inc. ("ISC")
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+# File of DHCP-DDNS configuration permutation tests
+# Each test entry consists of:
+#
+# description - text describing the test (optional)
+# syntax-error - syntax error the JSON parsing should emit for this test
+# defaults to "" = no error
+# logic-error - indicates whether a post-JSON parsing logic error should occur
+# defaults to false
+# data {} - Configuration text to submit for parsing.
+#
+# The vast majority of the tests in this file are invalid and are expected
+# to fail either as a syntax error caught by the JSON parser or a logic error
+# caught during element processing. There are some that should succeed and are
+# used more or less as sanity checks.
+
+{ "test-list" : [
+#-----
+{
+# This test is a bit of sanity check for the "file of configs" test mechanism,
+# as well as validating this as the smallest config which makes writing
+# permutations easier.
+"description" : "D2 smallest, valid config",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+# Map should be supplied through setDefaults
+,{
+"description" : "D2 missing forward-ddns map",
+"data" :
+ {
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+# Map should be supplied through setDefaults
+,{
+"description" : "D2 missing reverse-ddns map",
+"data" :
+ {
+ "forward-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+
+#-----
+# Map should be supplied through setDefaults
+,{
+"description" : "D2 missing tsig-keys list",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {}
+ }
+}
+
+#-----
+,{
+"description" : "D2 unknown scalar",
+"syntax-error" : "<string>:1.3-16: got unexpected keyword \"bogus-scalar\" in DhcpDdns map.",
+"data" :
+ {
+ "bogus-scalar" : true,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2 unknown map",
+"syntax-error" : "<string>:1.3-13: got unexpected keyword \"bogus-map\" in DhcpDdns map.",
+"data" :
+ {
+ "bogus-map" : {},
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2 unknown list",
+"syntax-error" : "<string>:1.3-14: got unexpected keyword \"bogus-list\" in DhcpDdns map.",
+"data" :
+ {
+ "bogus-list" : [],
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2Params Test
+
+#----- D2Params.ip-address
+,{
+"description" : "D2Params.ip-address: valid v4",
+"data" :
+ {
+ "ip-address" : "192.168.0.1",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.ip-address: valid v6",
+"data" :
+ {
+ "ip-address" : "2001:db8::1",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.ip-address invalid value",
+"logic-error" : "Failed to convert 'bogus' to address: Failed to convert string to address 'bogus': Invalid argument(<string>:1:39)",
+"data" :
+ {
+ "ip-address" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+#-----
+}
+
+#----- D2Params.port
+,{
+"description" : "D2Params.port, valid value",
+"data" :
+ {
+ "port" : 100,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.port can't be 0",
+"syntax-error" : "<string>:1.33: port must be greater than zero but less than 65536",
+"data" :
+ {
+ "port" : 0,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.port, non numeric",
+"syntax-error" : "<string>:1.33-39: syntax error, unexpected constant string, expecting integer",
+"data" :
+ {
+ "port" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2Params.dns-server-timeout
+,{
+"description" : "D2Params.dns-server-timeout, valid value",
+"data" :
+ {
+ "dns-server-timeout" : 1000,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.dns-server-timeout can't be 0",
+"syntax-error" : "<string>:1.25: dns-server-timeout must be greater than zero",
+"data" :
+ {
+ "dns-server-timeout" : 0,
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.dns-server-timeout, non numeric",
+"syntax-error" : "<string>:1.25-31: syntax error, unexpected constant string, expecting integer",
+"data" :
+ {
+ "dns-server-timeout" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+#-----
+
+#----- D2Params.ncr-protocol
+,{
+"description" : "D2Params.ncr-protocol, valid UDP",
+"data" :
+ {
+ "ncr-protocol" : "UDP",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.ncr-protocol, unsupported TCP",
+"logic-error" : "ncr-protocol : TCP is not yet supported (<string>:1:41)",
+"data" :
+ {
+ "ncr-protocol" : "TCP",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+
+#-----
+,{
+"description" : "D2Params.ncr-protocol, invalid value",
+"syntax-error" : "<string>:1.41-47: syntax error, unexpected constant string, expecting UDP or TCP",
+"data" :
+ {
+ "ncr-protocol" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+
+#----- D2Params.ncr-format tests
+
+,{
+"description" : "D2Params.ncr-format, valid JSON",
+"data" :
+ {
+ "ncr-format" : "JSON",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2Params.ncr-format, invalid value",
+"syntax-error" : "<string>:1.39-45: syntax error, unexpected constant string, expecting JSON",
+"data" :
+ {
+ "ncr-format" : "bogus",
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- TSIGKey Tests
+
+#-----
+,{
+# This test is a sanity check that valid TSIG entries work.
+"description" : "D2.tsig-keys, valid key",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, missing key name",
+"logic-error" : "missing parameter 'name' (<string>:1:62)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, blank key name",
+"syntax-error" : "<string>:1.95: TSIG key name cannot be blank",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, duplicate key name",
+"logic-error" : "Duplicate TSIG key name specified : first.key (<string>:1:185)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ },
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.tsig-keys, algorithm tests
+
+,{
+"description" : "D2.tsig-keys, all valid algorithms",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ },
+ {
+ "name" : "d2.sha1.key",
+ "algorithm" : "HMAC-SHA1",
+ "secret" : "hRrp29wzUv3uzSNRLlY68w=="
+ },
+ {
+ "name" : "d2.sha224.key",
+ "algorithm" : "HMAC-SHA224",
+ "secret" : "bZEG7Ow8OgAUPfLWV3aAUQ=="
+ },
+ {
+ "name" : "d2.sha256.key",
+ "algorithm" : "hmac-sha256",
+ "secret" : "bjF4hYhTfQ5MX0siagelsw=="
+ },
+ {
+ "name" : "d2.sha384.key",
+ "algorithm" : "hmac-sha384",
+ "secret" : "Gwk53fvy3CmbupoI9TgigA=="
+ },
+ {
+ "name" : "d2.sha512.key",
+ "algorithm" : "hmac-sha512",
+ "secret" : "wP+5FqMnKXCxBWljU/BZAA=="
+ }
+ ]
+ }
+}
+
+#----- D2.tsig-keys, algorithm tests
+,{
+"description" : "D2.tsig-keys, missing algorithm",
+"logic-error" : "missing parameter 'algorithm' (<string>:1:62)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, blank algorithm",
+"syntax-error" : "<string>:1.75: TSIG key algorithm cannot be blank",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, invalid algorithm",
+"logic-error" : "tsig-key : Unknown TSIG Key algorithm: bogus (<string>:1:77)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "bogus",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.tsig-keys, digest-bits tests
+,{
+"description" : "D2.tsig-keys, all valid algorithms",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "digest-bits" : 80,
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ },
+ {
+ "name" : "d2.sha1.key",
+ "algorithm" : "HMAC-SHA1",
+ "digest-bits" : 80,
+ "secret" : "hRrp29wzUv3uzSNRLlY68w=="
+ },
+ {
+ "name" : "d2.sha224.key",
+ "algorithm" : "HMAC-SHA224",
+ "digest-bits" : 112,
+ "secret" : "bZEG7Ow8OgAUPfLWV3aAUQ=="
+ },
+ {
+ "name" : "d2.sha256.key",
+ "algorithm" : "hmac-sha256",
+ "digest-bits" : 128,
+ "secret" : "bjF4hYhTfQ5MX0siagelsw=="
+ },
+ {
+ "name" : "d2.sha384.key",
+ "algorithm" : "hmac-sha384",
+ "digest-bits" : 192,
+ "secret" : "Gwk53fvy3CmbupoI9TgigA=="
+ },
+ {
+ "name" : "d2.sha512.key",
+ "algorithm" : "hmac-sha512",
+ "digest-bits" : 256,
+ "secret" : "wP+5FqMnKXCxBWljU/BZAA=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, invalid digest-bits",
+"syntax-error" : "<string>:1.104-105: TSIG key digest-bits must either be zero or a positive, multiple of eight",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "digest-bits" : 84,
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-MD5",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:104)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "digest-bits" : 72,
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA1",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:105)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha1.key",
+ "algorithm" : "HMAC-SHA1",
+ "digest-bits" : 72,
+ "secret" : "hRrp29wzUv3uzSNRLlY68w=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA224",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:107)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha224.key",
+ "algorithm" : "HMAC-SHA224",
+ "digest-bits" : 104,
+ "secret" : "bZEG7Ow8OgAUPfLWV3aAUQ=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA256",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:107)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha256.key",
+ "algorithm" : "hmac-sha256",
+ "digest-bits" : 120,
+ "secret" : "bjF4hYhTfQ5MX0siagelsw=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA384",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:107)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha384.key",
+ "algorithm" : "hmac-sha384",
+ "digest-bits" : 184,
+ "secret" : "Gwk53fvy3CmbupoI9TgigA=="
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, too small truncated HMAC-SHA512",
+"logic-error" : "tsig-key: digest-bits too small : (<string>:1:107)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.sha512.key",
+ "algorithm" : "hmac-sha512",
+ "digest-bits" : 248,
+ "secret" : "wP+5FqMnKXCxBWljU/BZAA=="
+ }
+ ]
+ }
+}
+
+#----- D2.tsig-keys, secret tests
+,{
+"description" : "D2.tsig-keys, missing secret",
+"logic-error" : "missing parameter 'secret' (<string>:1:62)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5"
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, blank secret",
+"syntax-error" : "<string>:1.118: TSIG key secret cannot be blank",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : ""
+ }
+ ]
+ }
+}
+
+#-----
+,{
+"description" : "D2.tsig-keys, invalid secret",
+"logic-error" : "Cannot make D2TsigKey: Incomplete input for base64: bogus (<string>:1:62)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "first.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "bogus"
+ }
+ ]
+ }
+}
+
+#----- D2.forward-ddns tests
+,{
+"description" : "D2.forward-ddns, valid, empty ddns-domains",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" : []
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#------
+,{
+"description" : "D2.forward-ddns, unknown parameter",
+"syntax-error" : "<string>:1.21-27: got unexpected keyword \"bogus\" in forward-ddns map.",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "bogus" : true,
+ "ddns-domains" : []
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#------
+,{
+"description" : "D2.forward-ddns, one valid, domain",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "key-name" : "d2.md5.key",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#------
+,{
+"description" : "D2.forward-ddns, duplicate domain",
+"logic-error" : "Duplicate domain specified:four.example.com. (<string>:1:184)",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ },
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.2"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.forward-ddns.dhcp-ddns tests
+,{
+"description" : "D2.forward-ddns.dhcp-ddns, unknown parameter",
+"syntax-error" : "<string>:1.41-47: got unexpected keyword \"bogus\" in ddns-domains map.",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "bogus" : true
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.forward-ddns.dhcp-ddns.name tests
+,{
+"description" : "D2.forward-ddns.dhcp-ddns, empty domain",
+"syntax-error" : "<string>:1.42: syntax error, unexpected }",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns, blank name",
+"syntax-error" : "<string>:1.47: Ddns domain name cannot be blank",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : ""
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#------ "D2.forward-ddns.dhcp-ddns, key-name tests
+,{
+"description" : "D2.forward-ddns, no matching key name",
+"logic-error" : "DdnsDomain : specifies an undefined key: no.such.key (<string>:1:104)",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "key-name" : "no.such.key",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.forward-ddns.dhcp-ddns.dns-servers tests
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers, no servers",
+"syntax-error" : "<string>:1.59: syntax error, unexpected ], expecting {",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" : []
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.forward-ddns.dhcp-ddns.dns-servers tests
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers, unknown parameter",
+"syntax-error" : "<string>:1.60-66: got unexpected keyword \"bogus\" in dns-servers map.",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "bogus" : true
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.hostname unsupported",
+"syntax-error" : "<string>:1.70: hostname is not yet supported",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "hostname" : "myhost.com"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.ip-address v4 address ",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.ip-address v6 address ",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.ip-address invalid address ",
+"logic-error" : "Dns Server : invalid IP address : bogus (<string>:1:74)",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "bogus"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.port valid value ",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1",
+ "port" : 77
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.port cannot be 0 ",
+"syntax-error" : "<string>:1.97: port must be greater than zero but less than 65536",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1",
+ "port" : 0
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.forward-ddns.dhcp-ddns.dns-servers.key-name, no matching key name",
+"logic-error" : "Dns Server : specifies an undefined key: no.such.key (<string>:1:100)",
+"data" :
+ {
+ "forward-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "four.example.com.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1",
+ "key-name" : "no.such.key"
+ }
+ ]
+ }
+ ]
+ },
+ "reverse-ddns" : {},
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.reverse-ddns tests
+,{
+"description" : "D2.reverse-ddns, valid, empty ddns-domains",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" : []
+ },
+ "tsig-keys" : []
+ }
+}
+
+#------
+,{
+"description" : "D2.reverse-ddns, unknown parameter",
+"syntax-error" : "<string>:1.43-49: got unexpected keyword \"bogus\" in reverse-ddns map.",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "bogus" : true,
+ "ddns-domains" : []
+ },
+ "tsig-keys" : []
+ }
+}
+
+#------
+,{
+"description" : "D2.reverse-ddns, one valid, domain",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addra.arpa.",
+ "key-name" : "d2.md5.key",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#------
+,{
+"description" : "D2.reverse-ddns, duplicate domain",
+"logic-error" : "Duplicate domain specified:2.0.192.in-addra.arpa. (<string>:1:211)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addra.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ },
+ {
+ "name" : "2.0.192.in-addra.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.2"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.reverse-ddns.dhcp-ddns tests
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns, unknown parameter",
+"syntax-error" : "<string>:1.63-69: got unexpected keyword \"bogus\" in ddns-domains map.",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "bogus" : true
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.reverse-ddns.dhcp-ddns.name tests
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns, no name",
+"syntax-error" : "<string>:1.64: syntax error, unexpected }",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns, blank name",
+"syntax-error" : "<string>:1.69: Ddns domain name cannot be blank",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : ""
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#------ "D2.reverse-ddns.dhcp-ddns, key-name tests
+,{
+"description" : "D2.reverse-ddns, no matching key name",
+"logic-error" : "DdnsDomain : specifies an undefined key: no.such.key (<string>:1:126)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "key-name" : "no.such.key",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+#----- D2.reverse-ddns.dhcp-ddns.dns-servers tests
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers, no servers",
+"syntax-error" : "<string>:1.81: syntax error, unexpected ], expecting {",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" : []
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#----- D2.reverse-ddns.dhcp-ddns.dns-servers tests
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers, unknown parameter",
+"syntax-error" : "<string>:1.82-88: got unexpected keyword \"bogus\" in dns-servers map.",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "bogus" : true
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.hostname unsupported",
+"syntax-error" : "<string>:1.92: hostname is not yet supported",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "hostname" : "myhost.com"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.ip-address v4 address ",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.ip-address v6 address ",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.ip-address invalid value",
+"logic-error" : "Dns Server : invalid IP address : bogus (<string>:1:96)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "bogus"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.port valid value ",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1",
+ "port" : 77
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.port cannot be 0 ",
+"syntax-error" : "<string>:1.119: port must be greater than zero but less than 65536",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "2001:db8::1",
+ "port" : 0
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" : []
+ }
+}
+
+#-----
+,{
+"description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.key-name, no matching key name",
+"logic-error" : "Dns Server : specifies an undefined key: no.such.key (<string>:1:122)",
+"data" :
+ {
+ "forward-ddns" : {},
+ "reverse-ddns" :
+ {
+ "ddns-domains" :
+ [
+ {
+ "name" : "2.0.192.in-addr.arpa.",
+ "dns-servers" :
+ [
+ {
+ "ip-address" : "172.16.1.1",
+ "key-name" : "no.such.key"
+ }
+ ]
+ }
+ ]
+ },
+ "tsig-keys" :
+ [
+ {
+ "name" : "d2.md5.key",
+ "algorithm" : "HMAC-MD5",
+ "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
+ }
+ ]
+ }
+}
+
+# ----- End of Tests
+]}
diff --git a/src/bin/d2/tests/testdata/get_config.json b/src/bin/d2/tests/testdata/get_config.json
new file mode 100644
index 0000000..d9613a3
--- /dev/null
+++ b/src/bin/d2/tests/testdata/get_config.json
@@ -0,0 +1,106 @@
+{
+ "DhcpDdns": {
+ "control-socket": {
+ "socket-name": "/tmp/kea-ddns-ctrl-socket",
+ "socket-type": "unix"
+ },
+ "dns-server-timeout": 1000,
+ "forward-ddns": {
+ "ddns-domains": [
+ {
+ "dns-servers": [
+ {
+ "hostname": "",
+ "ip-address": "172.16.1.1",
+ "port": 53
+ }
+ ],
+ "key-name": "d2.md5.key",
+ "name": "four.example.com.",
+ "user-context": {
+ "comment": "DdnsDomain example"
+ }
+ },
+ {
+ "dns-servers": [
+ {
+ "hostname": "",
+ "ip-address": "2001:db8:1::10",
+ "port": 7802
+ }
+ ],
+ "name": "six.example.com."
+ }
+ ]
+ },
+ "hooks-libraries": [
+ {
+ "library": "/opt/local/ddns-server-commands.so",
+ "parameters": {
+ "param1": "foo"
+ }
+ }
+ ],
+ "ip-address": "127.0.0.1",
+ "loggers": [
+ {
+ "debuglevel": 0,
+ "name": "kea-dhcp-ddns",
+ "output_options": [
+ {
+ "flush": true,
+ "output": "stdout",
+ "pattern": "%d [%c/%i] %m\n"
+ }
+ ],
+ "severity": "INFO"
+ }
+ ],
+ "ncr-format": "JSON",
+ "ncr-protocol": "UDP",
+ "port": 53001,
+ "reverse-ddns": {
+ "ddns-domains": [
+ {
+ "dns-servers": [
+ {
+ "hostname": "",
+ "ip-address": "172.16.1.1",
+ "port": 53001
+ },
+ {
+ "hostname": "",
+ "ip-address": "192.168.2.10",
+ "port": 53
+ }
+ ],
+ "key-name": "d2.sha1.key",
+ "name": "2.0.192.in-addr.arpa."
+ }
+ ]
+ },
+ "tsig-keys": [
+ {
+ "algorithm": "HMAC-MD5",
+ "digest-bits": 0,
+ "name": "d2.md5.key",
+ "secret": "LSWXnfkKZjdPJI5QxlpnfQ=="
+ },
+ {
+ "algorithm": "HMAC-SHA1",
+ "digest-bits": 0,
+ "name": "d2.sha1.key",
+ "secret": "hRrp29wzUv3uzSNRLlY68w=="
+ },
+ {
+ "algorithm": "HMAC-SHA512",
+ "digest-bits": 256,
+ "name": "d2.sha512.key",
+ "secret": "/4wklkm04jeH4anx2MKGJLcya+ZLHldL5d6mK+4q6UXQP7KJ9mS2QG29hh0SJR4LA0ikxNJTUMvir42gLx6fGQ=="
+ }
+ ],
+ "user-context": {
+ "version": 1
+ }
+ }
+}