From f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 14:15:43 +0200 Subject: Adding upstream version 2.4.1. Signed-off-by: Daniel Baumann --- src/lib/cc/Makefile.am | 45 + src/lib/cc/Makefile.in | 994 +++++++++ src/lib/cc/base_stamped_element.cc | 28 + src/lib/cc/base_stamped_element.h | 80 + src/lib/cc/cc.dox | 107 + src/lib/cc/cfg_to_element.h | 48 + src/lib/cc/command_interpreter.cc | 318 +++ src/lib/cc/command_interpreter.h | 225 +++ src/lib/cc/data.cc | 1633 +++++++++++++++ src/lib/cc/data.h | 1075 ++++++++++ src/lib/cc/dhcp_config_error.h | 72 + src/lib/cc/element_value.h | 117 ++ src/lib/cc/json_feed.cc | 563 ++++++ src/lib/cc/json_feed.h | 351 ++++ src/lib/cc/server_tag.cc | 53 + src/lib/cc/server_tag.h | 82 + src/lib/cc/simple_parser.cc | 369 ++++ src/lib/cc/simple_parser.h | 339 ++++ src/lib/cc/stamped_element.cc | 50 + src/lib/cc/stamped_element.h | 91 + src/lib/cc/stamped_value.cc | 193 ++ src/lib/cc/stamped_value.h | 237 +++ src/lib/cc/tests/Makefile.am | 40 + src/lib/cc/tests/Makefile.in | 1071 ++++++++++ src/lib/cc/tests/command_interpreter_unittests.cc | 249 +++ src/lib/cc/tests/data_file_unittests.cc | 98 + src/lib/cc/tests/data_unittests.cc | 2232 +++++++++++++++++++++ src/lib/cc/tests/element_value_unittests.cc | 43 + src/lib/cc/tests/json_feed_unittests.cc | 453 +++++ src/lib/cc/tests/run_unittests.cc | 20 + src/lib/cc/tests/server_tag_unittest.cc | 97 + src/lib/cc/tests/simple_parser_unittest.cc | 364 ++++ src/lib/cc/tests/stamped_element_unittest.cc | 132 ++ src/lib/cc/tests/stamped_value_unittest.cc | 175 ++ src/lib/cc/tests/user_context_unittests.cc | 132 ++ src/lib/cc/user_context.cc | 30 + src/lib/cc/user_context.h | 59 + 37 files changed, 12265 insertions(+) create mode 100644 src/lib/cc/Makefile.am create mode 100644 src/lib/cc/Makefile.in create mode 100644 src/lib/cc/base_stamped_element.cc create mode 100644 src/lib/cc/base_stamped_element.h create mode 100644 src/lib/cc/cc.dox create mode 100644 src/lib/cc/cfg_to_element.h create mode 100644 src/lib/cc/command_interpreter.cc create mode 100644 src/lib/cc/command_interpreter.h create mode 100644 src/lib/cc/data.cc create mode 100644 src/lib/cc/data.h create mode 100644 src/lib/cc/dhcp_config_error.h create mode 100644 src/lib/cc/element_value.h create mode 100644 src/lib/cc/json_feed.cc create mode 100644 src/lib/cc/json_feed.h create mode 100644 src/lib/cc/server_tag.cc create mode 100644 src/lib/cc/server_tag.h create mode 100644 src/lib/cc/simple_parser.cc create mode 100644 src/lib/cc/simple_parser.h create mode 100644 src/lib/cc/stamped_element.cc create mode 100644 src/lib/cc/stamped_element.h create mode 100644 src/lib/cc/stamped_value.cc create mode 100644 src/lib/cc/stamped_value.h create mode 100644 src/lib/cc/tests/Makefile.am create mode 100644 src/lib/cc/tests/Makefile.in create mode 100644 src/lib/cc/tests/command_interpreter_unittests.cc create mode 100644 src/lib/cc/tests/data_file_unittests.cc create mode 100644 src/lib/cc/tests/data_unittests.cc create mode 100644 src/lib/cc/tests/element_value_unittests.cc create mode 100644 src/lib/cc/tests/json_feed_unittests.cc create mode 100644 src/lib/cc/tests/run_unittests.cc create mode 100644 src/lib/cc/tests/server_tag_unittest.cc create mode 100644 src/lib/cc/tests/simple_parser_unittest.cc create mode 100644 src/lib/cc/tests/stamped_element_unittest.cc create mode 100644 src/lib/cc/tests/stamped_value_unittest.cc create mode 100644 src/lib/cc/tests/user_context_unittests.cc create mode 100644 src/lib/cc/user_context.cc create mode 100644 src/lib/cc/user_context.h (limited to 'src/lib/cc') diff --git a/src/lib/cc/Makefile.am b/src/lib/cc/Makefile.am new file mode 100644 index 0000000..a24af62 --- /dev/null +++ b/src/lib/cc/Makefile.am @@ -0,0 +1,45 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +lib_LTLIBRARIES = libkea-cc.la +libkea_cc_la_SOURCES = base_stamped_element.cc base_stamped_element.h +libkea_cc_la_SOURCES += data.cc data.h +libkea_cc_la_SOURCES += element_value.h +libkea_cc_la_SOURCES += cfg_to_element.h dhcp_config_error.h +libkea_cc_la_SOURCES += command_interpreter.cc command_interpreter.h +libkea_cc_la_SOURCES += json_feed.cc json_feed.h +libkea_cc_la_SOURCES += server_tag.cc server_tag.h +libkea_cc_la_SOURCES += simple_parser.cc simple_parser.h +libkea_cc_la_SOURCES += stamped_element.cc stamped_element.h +libkea_cc_la_SOURCES += stamped_value.cc stamped_value.h +libkea_cc_la_SOURCES += user_context.cc user_context.h + +libkea_cc_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libkea_cc_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la +libkea_cc_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libkea_cc_la_LIBADD += $(BOOST_LIBS) + +libkea_cc_la_LDFLAGS = -no-undefined -version-info 54:0:0 + +# Specify the headers for copying into the installation directory tree. +libkea_cc_includedir = $(pkgincludedir)/cc +libkea_cc_include_HEADERS = \ + base_stamped_element.h \ + cfg_to_element.h \ + command_interpreter.h \ + data.h \ + dhcp_config_error.h \ + element_value.h \ + json_feed.h \ + server_tag.h \ + simple_parser.h \ + stamped_element.h \ + stamped_value.h \ + user_context.h + +EXTRA_DIST = cc.dox + +CLEANFILES = *.gcno *.gcda diff --git a/src/lib/cc/Makefile.in b/src/lib/cc/Makefile.in new file mode 100644 index 0000000..dcd6501 --- /dev/null +++ b/src/lib/cc/Makefile.in @@ -0,0 +1,994 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +subdir = src/lib/cc +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp11.m4 \ + $(top_srcdir)/m4macros/ax_cpp20.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_netconf.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(libkea_cc_include_HEADERS) \ + $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +am__installdirs = "$(DESTDIR)$(libdir)" \ + "$(DESTDIR)$(libkea_cc_includedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libkea_cc_la_DEPENDENCIES = \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(am__DEPENDENCIES_1) +am_libkea_cc_la_OBJECTS = base_stamped_element.lo data.lo \ + command_interpreter.lo json_feed.lo server_tag.lo \ + simple_parser.lo stamped_element.lo stamped_value.lo \ + user_context.lo +libkea_cc_la_OBJECTS = $(am_libkea_cc_la_OBJECTS) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libkea_cc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(libkea_cc_la_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/base_stamped_element.Plo \ + ./$(DEPDIR)/command_interpreter.Plo ./$(DEPDIR)/data.Plo \ + ./$(DEPDIR)/json_feed.Plo ./$(DEPDIR)/server_tag.Plo \ + ./$(DEPDIR)/simple_parser.Plo ./$(DEPDIR)/stamped_element.Plo \ + ./$(DEPDIR)/stamped_value.Plo ./$(DEPDIR)/user_context.Plo +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(libkea_cc_la_SOURCES) +DIST_SOURCES = $(libkea_cc_la_SOURCES) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +HEADERS = $(libkea_cc_include_HEADERS) +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@ +DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@ +DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_NETCONF = @HAVE_NETCONF@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@ +LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@ +LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@ +LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@ +LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@ +LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@ +LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@ +LIBYANG_LIBS = @LIBYANG_LIBS@ +LIBYANG_PREFIX = @LIBYANG_PREFIX@ +LIBYANG_VERSION = @LIBYANG_VERSION@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_PLUGINS_PATH = @SR_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@ +SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@ +SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@ +SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_PREFIX = @SYSREPO_PREFIX@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . tests +AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \ + $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) +lib_LTLIBRARIES = libkea-cc.la +libkea_cc_la_SOURCES = base_stamped_element.cc base_stamped_element.h \ + data.cc data.h element_value.h cfg_to_element.h \ + dhcp_config_error.h command_interpreter.cc \ + command_interpreter.h json_feed.cc json_feed.h server_tag.cc \ + server_tag.h simple_parser.cc simple_parser.h \ + stamped_element.cc stamped_element.h stamped_value.cc \ + stamped_value.h user_context.cc user_context.h +libkea_cc_la_LIBADD = \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(BOOST_LIBS) +libkea_cc_la_LDFLAGS = -no-undefined -version-info 54:0:0 + +# Specify the headers for copying into the installation directory tree. +libkea_cc_includedir = $(pkgincludedir)/cc +libkea_cc_include_HEADERS = \ + base_stamped_element.h \ + cfg_to_element.h \ + command_interpreter.h \ + data.h \ + dhcp_config_error.h \ + element_value.h \ + json_feed.h \ + server_tag.h \ + simple_parser.h \ + stamped_element.h \ + stamped_value.h \ + user_context.h + +EXTRA_DIST = cc.dox +CLEANFILES = *.gcno *.gcda +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/cc/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/cc/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +install-libLTLIBRARIES: $(lib_LTLIBRARIES) + @$(NORMAL_INSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + list2=; for p in $$list; do \ + if test -f $$p; then \ + list2="$$list2 $$p"; \ + else :; fi; \ + done; \ + test -z "$$list2" || { \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdir)" || exit 1; \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(libdir)'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(libdir)"; \ + } + +uninstall-libLTLIBRARIES: + @$(NORMAL_UNINSTALL) + @list='$(lib_LTLIBRARIES)'; test -n "$(libdir)" || list=; \ + for p in $$list; do \ + $(am__strip_dir) \ + echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$f'"; \ + $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$f"; \ + done + +clean-libLTLIBRARIES: + -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) + @list='$(lib_LTLIBRARIES)'; \ + locs=`for p in $$list; do echo $$p; done | \ + sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \ + sort -u`; \ + test -z "$$locs" || { \ + echo rm -f $${locs}; \ + rm -f $${locs}; \ + } + +libkea-cc.la: $(libkea_cc_la_OBJECTS) $(libkea_cc_la_DEPENDENCIES) $(EXTRA_libkea_cc_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libkea_cc_la_LINK) -rpath $(libdir) $(libkea_cc_la_OBJECTS) $(libkea_cc_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base_stamped_element.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/command_interpreter.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/data.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/json_feed.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server_tag.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/simple_parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stamped_element.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/stamped_value.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/user_context.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-libkea_cc_includeHEADERS: $(libkea_cc_include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libkea_cc_include_HEADERS)'; test -n "$(libkea_cc_includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libkea_cc_includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libkea_cc_includedir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(libkea_cc_includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_cc_includedir)" || exit $$?; \ + done + +uninstall-libkea_cc_includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libkea_cc_include_HEADERS)'; test -n "$(libkea_cc_includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libkea_cc_includedir)'; $(am__uninstall_files_from_dir) + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-recursive +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libkea_cc_includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/base_stamped_element.Plo + -rm -f ./$(DEPDIR)/command_interpreter.Plo + -rm -f ./$(DEPDIR)/data.Plo + -rm -f ./$(DEPDIR)/json_feed.Plo + -rm -f ./$(DEPDIR)/server_tag.Plo + -rm -f ./$(DEPDIR)/simple_parser.Plo + -rm -f ./$(DEPDIR)/stamped_element.Plo + -rm -f ./$(DEPDIR)/stamped_value.Plo + -rm -f ./$(DEPDIR)/user_context.Plo + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: install-libkea_cc_includeHEADERS + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: install-libLTLIBRARIES + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/base_stamped_element.Plo + -rm -f ./$(DEPDIR)/command_interpreter.Plo + -rm -f ./$(DEPDIR)/data.Plo + -rm -f ./$(DEPDIR)/json_feed.Plo + -rm -f ./$(DEPDIR)/server_tag.Plo + -rm -f ./$(DEPDIR)/simple_parser.Plo + -rm -f ./$(DEPDIR)/stamped_element.Plo + -rm -f ./$(DEPDIR)/stamped_value.Plo + -rm -f ./$(DEPDIR)/user_context.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES \ + uninstall-libkea_cc_includeHEADERS + +.MAKE: $(am__recursive_targets) install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--depfiles check check-am clean clean-generic \ + clean-libLTLIBRARIES clean-libtool cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-libLTLIBRARIES \ + install-libkea_cc_includeHEADERS install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-libLTLIBRARIES uninstall-libkea_cc_includeHEADERS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib/cc/base_stamped_element.cc b/src/lib/cc/base_stamped_element.cc new file mode 100644 index 0000000..c3b6f38 --- /dev/null +++ b/src/lib/cc/base_stamped_element.cc @@ -0,0 +1,28 @@ +// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include + +namespace isc { +namespace data { + +BaseStampedElement::BaseStampedElement() + /// @todo Change it to microsec_clock once we transition to subsecond + /// precision. + : id_(0), timestamp_(boost::posix_time::second_clock::local_time()) { +} + +void +BaseStampedElement::updateModificationTime() { + /// @todo Change it to microsec_clock once we transition to subsecond + /// precision. + setModificationTime(boost::posix_time::second_clock::local_time()); +} + +} // end of namespace isc::data +} // end of namespace isc diff --git a/src/lib/cc/base_stamped_element.h b/src/lib/cc/base_stamped_element.h new file mode 100644 index 0000000..beb3090 --- /dev/null +++ b/src/lib/cc/base_stamped_element.h @@ -0,0 +1,80 @@ +// Copyright (C) 2019,2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef BASE_STAMPED_ELEMENT_H +#define BASE_STAMPED_ELEMENT_H + +#include +#include +#include + +namespace isc { +namespace data { + +/// @brief This class represents configuration element which is +/// associated with database identifier and the modification +/// timestamp. +/// +/// The @c StampedElement class derives from this class to extend +/// it with the capability to associate the configuration elements +/// with server tags. The @c db::Server class derives from it to +/// store a single server tag identifying a server it describes. +/// +/// @note This class is not derived from @c Element and should not +/// be confused with the classes being derived from @c Element class. +/// Those classes are used to represent JSON structures, whereas this +/// class represents data fetched from the database. +class BaseStampedElement { +public: + + /// @brief Constructor. + /// + /// Sets timestamp to the current time. + BaseStampedElement(); + + /// @brief Sets element's database identifier. + /// + /// @param id New id. + void setId(const uint64_t id) { + id_ = id; + } + + /// @brief Returns element's database identifier. + uint64_t getId() const { + return (id_); + } + + /// @brief Sets timestamp to the explicitly provided value. + /// + /// @param timestamp New timestamp value. + void setModificationTime(const boost::posix_time::ptime& timestamp) { + timestamp_ = timestamp; + } + + /// @brief Sets timestamp to the current time. + void updateModificationTime(); + + /// @brief Returns timestamp. + boost::posix_time::ptime getModificationTime() const { + return (timestamp_); + } + +protected: + + /// @brief Database identifier of the configuration element. + /// + /// The default value of 0 indicates that the identifier is + /// not set. + uint64_t id_; + + /// @brief Holds timestamp value. + boost::posix_time::ptime timestamp_; +}; + +} // end of namespace isc::data +} // end of namespace isc + +#endif diff --git a/src/lib/cc/cc.dox b/src/lib/cc/cc.dox new file mode 100644 index 0000000..da8d79b --- /dev/null +++ b/src/lib/cc/cc.dox @@ -0,0 +1,107 @@ +// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/** + @page libcc libkea-cc - Kea Configuration Utilities Library + +@section ccSimpleParser Simple JSON Parser + +Since the early beginnings, our configuration parsing code was a mess. It +started back in 2011 when Tomek joined ISC recently and was told to implement +Kea configuration handling in similar way as DNS Auth module. The code grew +over time (DHCP configuration is significantly more complex than DNS, with +more interdependent values) and as of Kea 1.1 release it became very difficult +to manage. The decision has been made to significantly refactor or even +partially rewrite the parser code. The design for this effort is documented +here: https://gitlab.isc.org/isc-projects/kea/wikis/designs/simple-parser-design. It discusses the original issues +and the proposed architecture. + +There are several aspects of this new approach. The base class for all +parsers is @ref isc::data::SimpleParser. It simplifies the parsers +based on DhcpConfigParser by rejecting the concept of build/commit +phases. Instead, there should be a single method called parse that +takes ConstElementPtr as a single parameter (that's the JSON +structures to be parsed) and returns the config structure to be used +in CfgMgr. An example of such a method can be the following: + +@code +std::pair +OptionDataParser::parse(isc::data::ConstElementPtr single_option) +@endcode + +Since each derived class will have the same parameter, but a different return +type, it's not possible to use virtual methods mechanism. That's perfectly +ok, though, as there is only a single instance of the class needed to parse +arbitrary number of parameters of the same type. There is no need to +keep pointers to the parser object. As such there are fewer incentives to have +one generic way to handle all parsers. + +@subsection ccSimpleParserDefaults Default values in Simple Parser + +Another simplification comes from the fact that almost all parameters +are mandatory in SimpleParser. One source of complexities in the old +parser was the necessity to deal with optional parameters. Simple +parser deals with that by explicitly requiring the input structure to +have all parameters filled. Obviously, it's not feasible to expect +everyone to always specify all parameters, therefore there's an easy +way to fill missing parameters with their default values. There are +several methods to do this, but the most generic one is: + +@code +static size_t +isc::data::SimpleParser::setDefaults(isc::data::ElementPtr scope, + const SimpleDefaults& default_values); +@endcode + +It takes a pointer to element to be filled with default values and +vector of default values. Having those values specified in a single +place in a way that can easily be read even by non-programmers is a +big advantage of this approach. Here's an example from simple_parser.cc file: + +@code +/// This table defines default values for option definitions in DHCPv6 +const SimpleDefaults OPTION6_DEF_DEFAULTS = { + { "record-types", Element::string, ""}, + { "space", Element::string, "dhcp6"}, + { "array", Element::boolean, "false"}, + { "encapsulate", Element::string, "" } +}; +@endcode + +This array (which technically is implemented as a vector and +initialized the C++11 way) can be passed to the aforementioned +setDefaults. That code will iterate over all default values and see if +there are explicit values provided. If not, the gaps will be filled +with default values. There are also convenience methods specified for +filling in option data defaults, option definition defaults and +setAllDefaults that sets all defaults (starts with global, but then +walks down the Element tree and fills defaults in subsequent scopes). + +@subsection ccSimpleParserInherits Inheriting parameters between scopes + +SimpleParser provides a mechanism to inherit parameters between scopes, +e.g. to inherit global parameters in the subnet scope if more specific +values are not defined in the subnet scope. This is achieved by calling +@code +static size_t SimpleParser::deriveParams(isc::data::ConstElementPtr parent, + isc::data::ElementPtr child, + const ParamsList& params); + +@endcode + +ParamsList is a simple vector. There will be more specific +methods implemented in the future, but for the time being only +@ref isc::data::SimpleParser::deriveParams is implemented. + +@subsection ccMTConsiderations Multi-Threading Consideration for Configuration Utilities + +No configuration utility is thread safe. For instance stamped values are +not thread safe so any read access must be done in a context where write +access at the same time is not possible. Note that configuration is +performed by the main thread with service threads stopped so this constraint +is fulfilled. + +*/ diff --git a/src/lib/cc/cfg_to_element.h b/src/lib/cc/cfg_to_element.h new file mode 100644 index 0000000..480dd1f --- /dev/null +++ b/src/lib/cc/cfg_to_element.h @@ -0,0 +1,48 @@ +// 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 CFG_TO_ELEMENT_H +#define CFG_TO_ELEMENT_H + +#include +#include + +namespace isc { + +/// @brief Cannot unparse error +/// +/// This exception is expected to be thrown when toElement fails +/// and to skip flawed elements is not wanted. +class ToElementError : public isc::Exception { +public: + ToElementError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +namespace data { + +/// @brief Abstract class for configuration Cfg_* classes +/// +struct CfgToElement { + /// Destructor + virtual ~CfgToElement() { } + + /// @brief Unparse a configuration object + /// + /// Returns an element which must parse into the same object, i.e. + /// @code + /// for all valid config C parse(parse(C)->toElement()) == parse(C) + /// @endcode + /// + /// @return a pointer to a configuration which can be parsed into + /// the initial configuration object + virtual isc::data::ElementPtr toElement() const = 0; +}; + +}; // namespace isc::dhcp +}; // namespace isc + +#endif // CFG_TO_ELEMENT_H diff --git a/src/lib/cc/command_interpreter.cc b/src/lib/cc/command_interpreter.cc new file mode 100644 index 0000000..0ea03bc --- /dev/null +++ b/src/lib/cc/command_interpreter.cc @@ -0,0 +1,318 @@ +// Copyright (C) 2009-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include +#include + +using namespace std; + +using isc::data::Element; +using isc::data::ConstElementPtr; +using isc::data::ElementPtr; +using isc::data::JSONError; + +namespace isc { +namespace config { + +const char *CONTROL_COMMAND = "command"; +const char *CONTROL_RESULT = "result"; +const char *CONTROL_TEXT = "text"; +const char *CONTROL_ARGUMENTS = "arguments"; +const char *CONTROL_SERVICE = "service"; +const char *CONTROL_REMOTE_ADDRESS = "remote-address"; + +// Full version, with status, text and arguments +ConstElementPtr +createAnswer(const int status_code, const std::string& text, + const ConstElementPtr& arg) { + if (status_code != 0 && text.empty()) { + isc_throw(CtrlChannelError, "Text has to be provided for status_code != 0"); + } + + ElementPtr answer = Element::createMap(); + ElementPtr result = Element::create(status_code); + answer->set(CONTROL_RESULT, result); + + if (!text.empty()) { + answer->set(CONTROL_TEXT, Element::create(text)); + } + if (arg) { + answer->set(CONTROL_ARGUMENTS, arg); + } + return (answer); +} + +ConstElementPtr +createAnswer() { + return (createAnswer(CONTROL_RESULT_SUCCESS, string(""), ConstElementPtr())); +} + +ConstElementPtr +createAnswer(const int status_code, const std::string& text) { + return (createAnswer(status_code, text, ElementPtr())); +} + +ConstElementPtr +createAnswer(const int status_code, const ConstElementPtr& arg) { + return (createAnswer(status_code, "", arg)); +} + +ConstElementPtr +parseAnswerText(int &rcode, const ConstElementPtr& msg) { + if (!msg) { + isc_throw(CtrlChannelError, "invalid answer: no answer specified"); + } + if (msg->getType() != Element::map) { + isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got " + << Element::typeToName(msg->getType()) << " instead"); + } + if (!msg->contains(CONTROL_RESULT)) { + isc_throw(CtrlChannelError, + "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'"); + } + + ConstElementPtr result = msg->get(CONTROL_RESULT); + if (result->getType() != Element::integer) { + isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT + << "' to be an integer, got " + << Element::typeToName(result->getType()) << " instead"); + } + + rcode = result->intValue(); + + // If there are arguments, return them. + return (msg->get(CONTROL_TEXT)); +} + +ConstElementPtr +parseAnswer(int &rcode, const ConstElementPtr& msg) { + if (!msg) { + isc_throw(CtrlChannelError, "invalid answer: no answer specified"); + } + if (msg->getType() != Element::map) { + isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got " + << Element::typeToName(msg->getType()) << " instead"); + } + if (!msg->contains(CONTROL_RESULT)) { + isc_throw(CtrlChannelError, + "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'"); + } + + ConstElementPtr result = msg->get(CONTROL_RESULT); + if (result->getType() != Element::integer) { + isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT + << "' to be an integer, got " + << Element::typeToName(result->getType()) << " instead"); + } + + rcode = result->intValue(); + + // If there are arguments, return them. + ConstElementPtr args = msg->get(CONTROL_ARGUMENTS); + if (args) { + return (args); + } + + // There are no arguments, let's try to return just the text status + return (msg->get(CONTROL_TEXT)); +} + + +std::string +answerToText(const ConstElementPtr& msg) { + if (!msg) { + isc_throw(CtrlChannelError, "invalid answer: no answer specified"); + } + if (msg->getType() != Element::map) { + isc_throw(CtrlChannelError, "invalid answer: expected toplevel entry to be a map, got " + << Element::typeToName(msg->getType()) << " instead"); + } + if (!msg->contains(CONTROL_RESULT)) { + isc_throw(CtrlChannelError, + "invalid answer: does not contain mandatory '" << CONTROL_RESULT << "'"); + } + + ConstElementPtr result = msg->get(CONTROL_RESULT); + if (result->getType() != Element::integer) { + isc_throw(CtrlChannelError, "invalid answer: expected '" << CONTROL_RESULT + << "' to be an integer, got " << Element::typeToName(result->getType()) + << " instead"); + } + + stringstream txt; + int rcode = result->intValue(); + if (rcode == 0) { + txt << "success(0)"; + } else { + txt << "failure(" << rcode << ")"; + } + + // Was any text provided? If yes, include it. + ConstElementPtr txt_elem = msg->get(CONTROL_TEXT); + if (txt_elem) { + txt << ", text=" << txt_elem->stringValue(); + } + + return (txt.str()); +} + +ConstElementPtr +createCommand(const std::string& command) { + return (createCommand(command, ElementPtr(), "")); +} + +ConstElementPtr +createCommand(const std::string& command, ConstElementPtr arg) { + return (createCommand(command, arg, "")); +} + +ConstElementPtr +createCommand(const std::string& command, const std::string& service) { + return (createCommand(command, ElementPtr(), service)); +} + +ConstElementPtr +createCommand(const std::string& command, + ConstElementPtr arg, + const std::string& service) { + ElementPtr query = Element::createMap(); + ElementPtr cmd = Element::create(command); + query->set(CONTROL_COMMAND, cmd); + if (arg) { + query->set(CONTROL_ARGUMENTS, arg); + } + if (!service.empty()) { + ElementPtr services = Element::createList(); + services->add(Element::create(service)); + query->set(CONTROL_SERVICE, services); + } + return (query); +} + +std::string +parseCommand(ConstElementPtr& arg, ConstElementPtr command) { + if (!command) { + isc_throw(CtrlChannelError, "invalid command: no command specified"); + } + if (command->getType() != Element::map) { + isc_throw(CtrlChannelError, "invalid command: expected toplevel entry to be a map, got " + << Element::typeToName(command->getType()) << " instead"); + } + if (!command->contains(CONTROL_COMMAND)) { + isc_throw(CtrlChannelError, + "invalid command: does not contain mandatory '" << CONTROL_COMMAND << "'"); + } + + // Make sure that all specified parameters are supported. + auto command_params = command->mapValue(); + for (auto param : command_params) { + if ((param.first != CONTROL_COMMAND) && + (param.first != CONTROL_ARGUMENTS) && + (param.first != CONTROL_SERVICE) && + (param.first != CONTROL_REMOTE_ADDRESS)) { + isc_throw(CtrlChannelError, + "invalid command: unsupported parameter '" << param.first << "'"); + } + } + + ConstElementPtr cmd = command->get(CONTROL_COMMAND); + if (cmd->getType() != Element::string) { + isc_throw(CtrlChannelError, "invalid command: expected '" + << CONTROL_COMMAND << "' to be a string, got " + << Element::typeToName(command->getType()) << " instead"); + } + + arg = command->get(CONTROL_ARGUMENTS); + + return (cmd->stringValue()); +} + +std::string +parseCommandWithArgs(ConstElementPtr& arg, ConstElementPtr command) { + std::string command_name = parseCommand(arg, command); + + // This function requires arguments within the command. + if (!arg) { + isc_throw(CtrlChannelError, + "invalid command '" << command_name << "': no arguments specified"); + } + + // Arguments must be a map. + if (arg->getType() != Element::map) { + isc_throw(CtrlChannelError, + "invalid command '" << command_name << "': expected '" + << CONTROL_ARGUMENTS << "' to be a map, got " + << Element::typeToName(arg->getType()) << " instead"); + } + + // At least one argument is required. + if (arg->size() == 0) { + isc_throw(CtrlChannelError, + "invalid command '" << command_name << "': '" + << CONTROL_ARGUMENTS << "' is empty"); + } + + return (command_name); +} + +ConstElementPtr +combineCommandsLists(const ConstElementPtr& response1, + const ConstElementPtr& response2) { + // Usually when this method is called there should be two non-null + // responses. If there is just a single response, return this + // response. + if (!response1 && response2) { + return (response2); + + } else if (response1 && !response2) { + return (response1); + + } else if (!response1 && !response2) { + return (ConstElementPtr()); + + } else { + // Both responses are non-null so we need to combine the lists + // of supported commands if the status codes are 0. + int status_code; + ConstElementPtr args1 = parseAnswer(status_code, response1); + if (status_code != 0) { + return (response1); + } + + ConstElementPtr args2 = parseAnswer(status_code, response2); + if (status_code != 0) { + return (response2); + } + + const std::vector vec1 = args1->listValue(); + const std::vector vec2 = args2->listValue(); + + // Storing command names in a set guarantees that the non-unique + // command names are aggregated. + std::set combined_set; + for (auto v = vec1.cbegin(); v != vec1.cend(); ++v) { + combined_set.insert((*v)->stringValue()); + } + for (auto v = vec2.cbegin(); v != vec2.cend(); ++v) { + combined_set.insert((*v)->stringValue()); + } + + // Create a combined list of commands. + ElementPtr combined_list = Element::createList(); + for (auto s = combined_set.cbegin(); s != combined_set.cend(); ++s) { + combined_list->add(Element::create(*s)); + } + return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list)); + } +} + +} // namespace config +} // namespace isc diff --git a/src/lib/cc/command_interpreter.h b/src/lib/cc/command_interpreter.h new file mode 100644 index 0000000..f834f34 --- /dev/null +++ b/src/lib/cc/command_interpreter.h @@ -0,0 +1,225 @@ +// Copyright (C) 2009-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef COMMAND_INTERPRETER_H +#define COMMAND_INTERPRETER_H + +#include +#include + +/// @file command_interpreter.h +/// +/// This file contains several functions and constants that are used for +/// handling commands and responses sent over control channel. The design +/// is described here: https://gitlab.isc.org/isc-projects/kea/wikis/Stats-design, but also +/// in @ref ctrlSocket section in the Developer's Guide. + +namespace isc { +namespace config { + +/// @brief String used for commands ("command") +extern const char *CONTROL_COMMAND; + +/// @brief String used for result, i.e. integer status ("result") +extern const char *CONTROL_RESULT; + +/// @brief String used for storing textual description ("text") +extern const char *CONTROL_TEXT; + +/// @brief String used for arguments map ("arguments") +extern const char *CONTROL_ARGUMENTS; + +/// @brief String used for service list ("service") +extern const char *CONTROL_SERVICE; + +/// @brief String used for remote address ("remote-address") +extern const char *CONTROL_REMOTE_ADDRESS; + +/// @brief Status code indicating a successful operation +const int CONTROL_RESULT_SUCCESS = 0; + +/// @brief Status code indicating a general failure +const int CONTROL_RESULT_ERROR = 1; + +/// @brief Status code indicating that the specified command is not supported. +const int CONTROL_RESULT_COMMAND_UNSUPPORTED = 2; + +/// @brief Status code indicating that the specified command was completed +/// correctly, but failed to produce any results. For example, get +/// completed the search, but couldn't find the object it was looking for. +const int CONTROL_RESULT_EMPTY = 3; + +/// @brief Status code indicating that the command was unsuccessful due to a +/// conflict between the command arguments and the server state. For example, +/// a lease4-add fails when the subnet identifier in the command does not +/// match the subnet identifier in the server configuration. +const int CONTROL_RESULT_CONFLICT = 4; + +/// @brief A standard control channel exception that is thrown if a function +/// is there is a problem with one of the messages +class CtrlChannelError : public isc::Exception { +public: + CtrlChannelError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// @brief Creates a standard config/command level success answer message +/// (i.e. of the form { "result": 0 } +/// @return Standard command/config success answer message +isc::data::ConstElementPtr createAnswer(); + +/// @brief Creates a standard config/command level answer message +/// (i.e. of the form { "result": 1, "text": "Invalid command received" } +/// +/// @param status_code The return code (0 for success) +/// @param status_text A string to put into the "text" argument +/// @return Standard command/config answer message +isc::data::ConstElementPtr createAnswer(const int status_code, + const std::string& status_text); + +/// @brief Creates a standard config/command level answer message +/// (i.e. of the form { "result": status_code, "arguments": arg } +/// +/// @param status_code The return code (0 for success) +/// @param arg The optional argument for the answer. This can be of +/// any Element type. May be NULL. +/// @return Standard command/config answer message +isc::data::ConstElementPtr createAnswer(const int status_code, + const isc::data::ConstElementPtr& arg); + +/// @brief Creates a standard config/command level answer message +/// +/// @param status_code The return code (0 for success) +/// @param status textual representation of the status (used mostly for errors) +/// @param arg The optional argument for the answer. This can be of +/// any Element type. May be NULL. +/// @return Standard command/config answer message +isc::data::ConstElementPtr createAnswer(const int status_code, + const std::string& status, + const isc::data::ConstElementPtr& arg); + +/// @brief Parses a standard config/command level answer and returns arguments +/// or text status code. +/// +/// If you need to get the text status, please use @ref parseAnswerText. +/// +/// @param status_code This value will be set to the return code contained in +/// the message +/// @param msg The message to parse +/// @return The optional argument in the message (or null) +isc::data::ConstElementPtr +parseAnswer(int &status_code, const isc::data::ConstElementPtr& msg); + +/// @brief Parses a standard config/command level answer and returns text status. +/// +/// This method returns the text status. If you need to get the arguments provided, +/// please use @ref parseAnswer. +/// +/// @param status_code This value will be set to the return code contained in +/// the message +/// @param msg The message to parse +/// @return The optional argument in the message (or null) +isc::data::ConstElementPtr +parseAnswerText(int &rcode, const isc::data::ConstElementPtr& msg); + +/// @brief Converts answer to printable text +/// +/// @param msg answer to be parsed +/// @return printable string +std::string answerToText(const isc::data::ConstElementPtr& msg); + +/// @brief Creates a standard command message with no +/// argument (of the form { "command": "my_command" }) +/// +/// @param command The command string +/// @return The created message +isc::data::ConstElementPtr createCommand(const std::string& command); + +/// @brief Creates a standard command message with the +/// given argument (of the form { "command": "my_command", "arguments": arg } +/// +/// @param command The command string +/// @param arg The optional argument for the command. This can be of +/// any Element type. May be NULL. +/// @return The created message +isc::data::ConstElementPtr createCommand(const std::string& command, + isc::data::ConstElementPtr arg); + +/// @brief Creates a standard config/command command message with no +/// argument and with the given service (of the form +/// { "command": "my_command", "service": [ service ] }) +/// +/// @param command The command string +/// @param service The target service. May be empty. +/// @return The created message + isc::data::ConstElementPtr createCommand(const std::string& command, + const std::string& service); + +/// @brief Creates a standard config/command command message with the +/// given argument and given service (of the form +/// { "command": "my_command", "arguments": arg, "service": [ service ] } +/// +/// @param command The command string +/// @param arg The optional argument for the command. This can be of +/// any Element type. May be NULL. +/// @param service The target service. May be empty. +/// @return The created message +isc::data::ConstElementPtr createCommand(const std::string& command, + isc::data::ConstElementPtr arg, + const std::string& service); + +/// @brief Parses the given command into a string containing the actual +/// command and an ElementPtr containing the optional argument. +/// +/// @throw CtrlChannelError if this is not a well-formed command +/// +/// @param arg This value will be set to the ElementPtr pointing to +/// the argument, or to an empty Map (ElementPtr) if there was none. +/// @param command The command message containing the command (as made +/// by createCommand() +/// @return The command name. +std::string parseCommand(isc::data::ConstElementPtr& arg, + isc::data::ConstElementPtr command); + + +/// @brief Parses the given command into a string containing the command +/// name and an ElementPtr containing the mandatory argument. +/// +/// This function expects that command arguments are specified and are +/// a map. +/// +/// @throw CtrlChannelError if this is not a well-formed command, +/// arguments are not specified or are not a map. +/// +/// @param arg Reference to the data element to which command arguments +/// will be assigned. +/// @param command The command message containing the command and +/// the arguments. +/// @return Command name. +std::string parseCommandWithArgs(isc::data::ConstElementPtr& arg, + isc::data::ConstElementPtr command); + +/// @brief Combines lists of commands carried in two responses. +/// +/// This method is used to combine list of commands returned by the +/// two command managers. +/// +/// If the same command appears in two responses only a single +/// instance is returned in the combined response. +/// +/// @param response1 First command response. +/// @param response2 Second command response. +/// +/// @return Pointer to the 'list-commands' response holding combined +/// list of commands. +isc::data::ConstElementPtr +combineCommandsLists(const isc::data::ConstElementPtr& response1, + const isc::data::ConstElementPtr& response2); + +}; // end of namespace isc::config +}; // end of namespace isc + +#endif // COMMAND_INTERPRETER_H diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc new file mode 100644 index 0000000..2f69f27 --- /dev/null +++ b/src/lib/cc/data.cc @@ -0,0 +1,1633 @@ +// Copyright (C) 2010-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace std; + +namespace { +const char* const WHITESPACE = " \b\f\n\r\t"; +} // end anonymous namespace + +namespace isc { +namespace data { + +std::string +Element::Position::str() const { + std::ostringstream ss; + ss << file_ << ":" << line_ << ":" << pos_; + return (ss.str()); +} + +std::ostream& +operator<<(std::ostream& out, const Element::Position& pos) { + out << pos.str(); + return (out); +} + +std::string +Element::str() const { + std::stringstream ss; + toJSON(ss); + return (ss.str()); +} + +std::string +Element::toWire() const { + std::stringstream ss; + toJSON(ss); + return (ss.str()); +} + +void +Element::toWire(std::ostream& ss) const { + toJSON(ss); +} + +bool +Element::getValue(int64_t&) const { + return (false); +} + +bool +Element::getValue(double&) const { + return (false); +} + +bool +Element::getValue(bool&) const { + return (false); +} + +bool +Element::getValue(std::string&) const { + return (false); +} + +bool +Element::getValue(std::vector&) const { + return (false); +} + +bool +Element::getValue(std::map&) const { + return (false); +} + +bool +Element::setValue(const long long int) { + return (false); +} + +bool +Element::setValue(isc::util::int128_t const&) { + return (false); +} + +bool +Element::setValue(const double) { + return (false); +} + +bool +Element::setValue(const bool) { + return (false); +} + +bool +Element::setValue(const std::string&) { + return (false); +} + +bool +Element::setValue(const std::vector&) { + return (false); +} + +bool +Element::setValue(const std::map&) { + return (false); +} + +ConstElementPtr +Element::get(const int) const { + throwTypeError("get(int) called on a non-container Element"); +} + +ElementPtr +Element::getNonConst(const int) const { + throwTypeError("get(int) called on a non-container Element"); +} + +void +Element::set(const size_t, ElementPtr) { + throwTypeError("set(int, element) called on a non-list Element"); +} + +void +Element::add(ElementPtr) { + throwTypeError("add() called on a non-list Element"); +} + +void +Element::remove(const int) { + throwTypeError("remove(int) called on a non-container Element"); +} + +size_t +Element::size() const { + throwTypeError("size() called on a non-list Element"); +} + +bool +Element::empty() const { + throwTypeError("empty() called on a non-container Element"); +} + +ConstElementPtr +Element::get(const std::string&) const { + throwTypeError("get(string) called on a non-map Element"); +} + +void +Element::set(const std::string&, ConstElementPtr) { + throwTypeError("set(name, element) called on a non-map Element"); +} + +void +Element::remove(const std::string&) { + throwTypeError("remove(string) called on a non-map Element"); +} + +bool +Element::contains(const std::string&) const { + throwTypeError("contains(string) called on a non-map Element"); +} + +ConstElementPtr +Element::find(const std::string&) const { + throwTypeError("find(string) called on a non-map Element"); +} + +bool +Element::find(const std::string&, ConstElementPtr&) const { + return (false); +} + +namespace { +inline void +throwJSONError(const std::string& error, const std::string& file, int line, + int pos) { + std::stringstream ss; + ss << error << " in " + file + ":" << line << ":" << pos; + isc_throw(JSONError, ss.str()); +} +} // end anonymous namespace + +std::ostream& +operator<<(std::ostream& out, const Element& e) { + return (out << e.str()); +} + +bool +operator==(const Element& a, const Element& b) { + return (a.equals(b)); +} + +bool operator!=(const Element& a, const Element& b) { + return (!a.equals(b)); +} + +bool +operator<(Element const& a, Element const& b) { + if (a.getType() != b.getType()) { + isc_throw(BadValue, "cannot compare Elements of different types"); + } + switch (a.getType()) { + case Element::integer: + return a.intValue() < b.intValue(); + case Element::real: + return a.doubleValue() < b.doubleValue(); + case Element::boolean: + return b.boolValue() || !a.boolValue(); + case Element::string: + return std::strcmp(a.stringValue().c_str(), b.stringValue().c_str()) < 0; + default: + isc_throw(BadValue, "cannot compare Elements of type " << to_string(a.getType())); + } +} + +// +// factory functions +// +ElementPtr +Element::create(const Position& pos) { + return (ElementPtr(new NullElement(pos))); +} + +ElementPtr +Element::create(const long long int i, const Position& pos) { + return (ElementPtr(new IntElement(static_cast(i), pos))); +} + +ElementPtr +Element::create(const isc::util::int128_t& i, const Position& pos) { + return (ElementPtr(new BigIntElement(i, pos))); +} + +ElementPtr +Element::create(const int i, const Position& pos) { + return (create(static_cast(i), pos)); +} + +ElementPtr +Element::create(const long int i, const Position& pos) { + return (create(static_cast(i), pos)); +} + +ElementPtr +Element::create(const uint32_t i, const Position& pos) { + return (create(static_cast(i), pos)); +} + +ElementPtr +Element::create(const double d, const Position& pos) { + return (ElementPtr(new DoubleElement(d, pos))); +} + +ElementPtr +Element::create(const bool b, const Position& pos) { + return (ElementPtr(new BoolElement(b, pos))); +} + +ElementPtr +Element::create(const std::string& s, const Position& pos) { + return (ElementPtr(new StringElement(s, pos))); +} + +ElementPtr +Element::create(const char *s, const Position& pos) { + return (create(std::string(s), pos)); +} + +ElementPtr +Element::createList(const Position& pos) { + return (ElementPtr(new ListElement(pos))); +} + +ElementPtr +Element::createMap(const Position& pos) { + return (ElementPtr(new MapElement(pos))); +} + + +// +// helper functions for fromJSON factory +// +namespace { +bool +charIn(const int c, const char* chars) { + const size_t chars_len = std::strlen(chars); + for (size_t i = 0; i < chars_len; ++i) { + if (chars[i] == c) { + return (true); + } + } + return (false); +} + +void +skipChars(std::istream& in, const char* chars, int& line, int& pos) { + int c = in.peek(); + while (charIn(c, chars) && c != EOF) { + if (c == '\n') { + ++line; + pos = 1; + } else { + ++pos; + } + in.ignore(); + c = in.peek(); + } +} + +// skip on the input stream to one of the characters in chars +// if another character is found this function throws JSONError +// unless that character is specified in the optional may_skip +// +// It returns the found character (as an int value). +int +skipTo(std::istream& in, const std::string& file, int& line, int& pos, + const char* chars, const char* may_skip="") { + int c = in.get(); + ++pos; + while (c != EOF) { + if (c == '\n') { + pos = 1; + ++line; + } + if (charIn(c, may_skip)) { + c = in.get(); + ++pos; + } else if (charIn(c, chars)) { + while (charIn(in.peek(), may_skip)) { + if (in.peek() == '\n') { + pos = 1; + ++line; + } else { + ++pos; + } + in.ignore(); + } + return (c); + } else { + throwJSONError(std::string("'") + std::string(1, c) + "' read, one of \"" + chars + "\" expected", file, line, pos); + } + } + throwJSONError(std::string("EOF read, one of \"") + chars + "\" expected", file, line, pos); + return (c); // shouldn't reach here, but some compilers require it +} + +// TODO: Should we check for all other official escapes here (and +// error on the rest)? +std::string +strFromStringstream(std::istream& in, const std::string& file, + const int line, int& pos) { + std::stringstream ss; + int c = in.get(); + ++pos; + if (c == '"') { + c = in.get(); + ++pos; + } else { + throwJSONError("String expected", file, line, pos); + } + + while (c != EOF && c != '"') { + if (c == '\\') { + // see the spec for allowed escape characters + int d; + switch (in.peek()) { + case '"': + c = '"'; + break; + case '/': + c = '/'; + break; + case '\\': + c = '\\'; + break; + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'u': + // skip first 0 + in.ignore(); + ++pos; + c = in.peek(); + if (c != '0') { + throwJSONError("Unsupported unicode escape", file, line, pos); + } + // skip second 0 + in.ignore(); + ++pos; + c = in.peek(); + if (c != '0') { + throwJSONError("Unsupported unicode escape", file, line, pos - 2); + } + // get first digit + in.ignore(); + ++pos; + d = in.peek(); + if ((d >= '0') && (d <= '9')) { + c = (d - '0') << 4; + } else if ((d >= 'A') && (d <= 'F')) { + c = (d - 'A' + 10) << 4; + } else if ((d >= 'a') && (d <= 'f')) { + c = (d - 'a' + 10) << 4; + } else { + throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 3); + } + // get second digit + in.ignore(); + ++pos; + d = in.peek(); + if ((d >= '0') && (d <= '9')) { + c |= d - '0'; + } else if ((d >= 'A') && (d <= 'F')) { + c |= d - 'A' + 10; + } else if ((d >= 'a') && (d <= 'f')) { + c |= d - 'a' + 10; + } else { + throwJSONError("Not hexadecimal in unicode escape", file, line, pos - 4); + } + break; + default: + throwJSONError("Bad escape", file, line, pos); + } + // drop the escaped char + in.ignore(); + ++pos; + } + ss.put(c); + c = in.get(); + ++pos; + } + if (c == EOF) { + throwJSONError("Unterminated string", file, line, pos); + } + return (ss.str()); +} + +std::string +wordFromStringstream(std::istream& in, int& pos) { + std::stringstream ss; + while (isalpha(in.peek())) { + ss << (char) in.get(); + } + pos += ss.str().size(); + return (ss.str()); +} + +std::string +numberFromStringstream(std::istream& in, int& pos) { + std::stringstream ss; + while (isdigit(in.peek()) || in.peek() == '+' || in.peek() == '-' || + in.peek() == '.' || in.peek() == 'e' || in.peek() == 'E') { + ss << (char) in.get(); + } + pos += ss.str().size(); + return (ss.str()); +} + +// Should we change from IntElement and DoubleElement to NumberElement +// that can also hold an e value? (and have specific getters if the +// value is larger than an int can handle) +// +ElementPtr +fromStringstreamNumber(std::istream& in, const std::string& file, + const int line, int& pos) { + // Remember position where the value starts. It will be set in the + // Position structure of the Element to be created. + const uint32_t start_pos = pos; + // This will move the pos to the end of the value. + const std::string number = numberFromStringstream(in, pos); + + if (number.find_first_of(".eE") < number.size()) { + try { + return (Element::create(boost::lexical_cast(number), + Element::Position(file, line, start_pos))); + } catch (const boost::bad_lexical_cast&) { + throwJSONError(std::string("Number overflow: ") + number, + file, line, start_pos); + } + } else { + try { + return (Element::create(boost::lexical_cast(number), + Element::Position(file, line, start_pos))); + } catch (const boost::bad_lexical_cast&) { + throwJSONError(std::string("Number overflow: ") + number, file, + line, start_pos); + } + } + return (ElementPtr()); +} + +ElementPtr +fromStringstreamBool(std::istream& in, const std::string& file, + const int line, int& pos) { + // Remember position where the value starts. It will be set in the + // Position structure of the Element to be created. + const uint32_t start_pos = pos; + // This will move the pos to the end of the value. + const std::string word = wordFromStringstream(in, pos); + + if (word == "true") { + return (Element::create(true, Element::Position(file, line, + start_pos))); + } else if (word == "false") { + return (Element::create(false, Element::Position(file, line, + start_pos))); + } else { + throwJSONError(std::string("Bad boolean value: ") + word, file, + line, start_pos); + } + return (ElementPtr()); +} + +ElementPtr +fromStringstreamNull(std::istream& in, const std::string& file, + const int line, int& pos) { + // Remember position where the value starts. It will be set in the + // Position structure of the Element to be created. + const uint32_t start_pos = pos; + // This will move the pos to the end of the value. + const std::string word = wordFromStringstream(in, pos); + if (word == "null") { + return (Element::create(Element::Position(file, line, start_pos))); + } else { + throwJSONError(std::string("Bad null value: ") + word, file, + line, start_pos); + return (ElementPtr()); + } +} + +ElementPtr +fromStringstreamString(std::istream& in, const std::string& file, int& line, + int& pos) { + // Remember position where the value starts. It will be set in the + // Position structure of the Element to be created. + const uint32_t start_pos = pos; + // This will move the pos to the end of the value. + const std::string string_value = strFromStringstream(in, file, line, pos); + return (Element::create(string_value, Element::Position(file, line, + start_pos))); +} + +ElementPtr +fromStringstreamList(std::istream& in, const std::string& file, int& line, + int& pos) { + int c = 0; + ElementPtr list = Element::createList(Element::Position(file, line, pos)); + ElementPtr cur_list_element; + + skipChars(in, WHITESPACE, line, pos); + while (c != EOF && c != ']') { + if (in.peek() != ']') { + cur_list_element = Element::fromJSON(in, file, line, pos); + list->add(cur_list_element); + c = skipTo(in, file, line, pos, ",]", WHITESPACE); + } else { + c = in.get(); + ++pos; + } + } + return (list); +} + +ElementPtr +fromStringstreamMap(std::istream& in, const std::string& file, int& line, + int& pos) { + ElementPtr map = Element::createMap(Element::Position(file, line, pos)); + skipChars(in, WHITESPACE, line, pos); + int c = in.peek(); + if (c == EOF) { + throwJSONError(std::string("Unterminated map, or } expected"), file, line, pos); + } else if (c == '}') { + // empty map, skip closing curly + in.ignore(); + } else { + while (c != EOF && c != '}') { + std::string key = strFromStringstream(in, file, line, pos); + + skipTo(in, file, line, pos, ":", WHITESPACE); + // skip the : + + ConstElementPtr value = Element::fromJSON(in, file, line, pos); + map->set(key, value); + + c = skipTo(in, file, line, pos, ",}", WHITESPACE); + } + } + return (map); +} +} // end anonymous namespace + +std::string +Element::typeToName(Element::types type) { + switch (type) { + case Element::integer: + return (std::string("integer")); + case Element::bigint: + return (std::string("bigint")); + case Element::real: + return (std::string("real")); + case Element::boolean: + return (std::string("boolean")); + case Element::string: + return (std::string("string")); + case Element::list: + return (std::string("list")); + case Element::map: + return (std::string("map")); + case Element::null: + return (std::string("null")); + case Element::any: + return (std::string("any")); + default: + return (std::string("unknown")); + } +} + +Element::types +Element::nameToType(const std::string& type_name) { + if (type_name == "integer") { + return (Element::integer); + } else if (type_name == "bigint") { + return (Element::bigint); + } else if (type_name == "real") { + return (Element::real); + } else if (type_name == "boolean") { + return (Element::boolean); + } else if (type_name == "string") { + return (Element::string); + } else if (type_name == "list") { + return (Element::list); + } else if (type_name == "map") { + return (Element::map); + } else if (type_name == "named_set") { + return (Element::map); + } else if (type_name == "null") { + return (Element::null); + } else if (type_name == "any") { + return (Element::any); + } else { + isc_throw(TypeError, type_name + " is not a valid type name"); + } +} + +ElementPtr +Element::fromJSON(std::istream& in, bool preproc) { + + int line = 1, pos = 1; + stringstream filtered; + if (preproc) { + preprocess(in, filtered); + } + + ElementPtr value = fromJSON(preproc ? filtered : in, "", line, pos); + + return (value); +} + +ElementPtr +Element::fromJSON(std::istream& in, const std::string& file_name, bool preproc) { + int line = 1, pos = 1; + stringstream filtered; + if (preproc) { + preprocess(in, filtered); + } + return (fromJSON(preproc ? filtered : in, file_name, line, pos)); +} + +ElementPtr +Element::fromJSON(std::istream& in, const std::string& file, int& line, + int& pos) { + int c = 0; + ElementPtr element; + bool el_read = false; + skipChars(in, WHITESPACE, line, pos); + while (c != EOF && !el_read) { + c = in.get(); + pos++; + switch(c) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '0': + case '-': + case '+': + case '.': + in.putback(c); + --pos; + element = fromStringstreamNumber(in, file, line, pos); + el_read = true; + break; + case 't': + case 'f': + in.putback(c); + --pos; + element = fromStringstreamBool(in, file, line, pos); + el_read = true; + break; + case 'n': + in.putback(c); + --pos; + element = fromStringstreamNull(in, file, line, pos); + el_read = true; + break; + case '"': + in.putback('"'); + --pos; + element = fromStringstreamString(in, file, line, pos); + el_read = true; + break; + case '[': + element = fromStringstreamList(in, file, line, pos); + el_read = true; + break; + case '{': + element = fromStringstreamMap(in, file, line, pos); + el_read = true; + break; + case EOF: + break; + default: + throwJSONError(std::string("error: unexpected character ") + std::string(1, c), file, line, pos); + break; + } + } + if (el_read) { + return (element); + } else { + isc_throw(JSONError, "nothing read"); + } +} + +ElementPtr +Element::fromJSON(const std::string& in, bool preproc) { + std::stringstream ss; + ss << in; + + int line = 1, pos = 1; + stringstream filtered; + if (preproc) { + preprocess(ss, filtered); + } + ElementPtr result(fromJSON(preproc ? filtered : ss, "", line, pos)); + skipChars(ss, WHITESPACE, line, pos); + // ss must now be at end + if (ss.peek() != EOF) { + throwJSONError("Extra data", "", line, pos); + } + return result; +} + +ElementPtr +Element::fromJSONFile(const std::string& file_name, bool preproc) { + // zero out the errno to be safe + errno = 0; + + std::ifstream infile(file_name.c_str(), std::ios::in | std::ios::binary); + if (!infile.is_open()) { + const char* error = strerror(errno); + isc_throw(InvalidOperation, "failed to read file '" << file_name + << "': " << error); + } + + return (fromJSON(infile, file_name, preproc)); +} + +// to JSON format + +void +IntElement::toJSON(std::ostream& ss) const { + ss << intValue(); +} + +void +BigIntElement::toJSON(std::ostream& ss) const { + ss << bigIntValue(); +} + +void +DoubleElement::toJSON(std::ostream& ss) const { + // The default output for doubles nicely drops off trailing + // zeros, however this produces strings without decimal points + // for whole number values. When reparsed this will create + // IntElements not DoubleElements. Rather than used a fixed + // precision, we'll just tack on an ".0" when the decimal point + // is missing. + ostringstream val_ss; + val_ss << doubleValue(); + ss << val_ss.str(); + if (val_ss.str().find_first_of('.') == string::npos) { + ss << ".0"; + } +} + +void +BoolElement::toJSON(std::ostream& ss) const { + if (boolValue()) { + ss << "true"; + } else { + ss << "false"; + } +} + +void +NullElement::toJSON(std::ostream& ss) const { + ss << "null"; +} + +void +StringElement::toJSON(std::ostream& ss) const { + ss << "\""; + const std::string& str = stringValue(); + for (size_t i = 0; i < str.size(); ++i) { + const char c = str[i]; + // Escape characters as defined in JSON spec + // Note that we do not escape forward slash; this + // is allowed, but not mandatory. + switch (c) { + case '"': + ss << '\\' << c; + break; + case '\\': + ss << '\\' << c; + break; + case '\b': + ss << '\\' << 'b'; + break; + case '\f': + ss << '\\' << 'f'; + break; + case '\n': + ss << '\\' << 'n'; + break; + case '\r': + ss << '\\' << 'r'; + break; + case '\t': + ss << '\\' << 't'; + break; + default: + if (((c >= 0) && (c < 0x20)) || (c < 0) || (c >= 0x7f)) { + std::ostringstream esc; + esc << "\\u" + << hex + << setw(4) + << setfill('0') + << (static_cast(c) & 0xff); + ss << esc.str(); + } else { + ss << c; + } + } + } + ss << "\""; +} + +void +ListElement::toJSON(std::ostream& ss) const { + ss << "[ "; + + const std::vector& v = listValue(); + for (auto it = v.begin(); it != v.end(); ++it) { + if (it != v.begin()) { + ss << ", "; + } + (*it)->toJSON(ss); + } + ss << " ]"; +} + +void +MapElement::toJSON(std::ostream& ss) const { + ss << "{ "; + + const std::map& m = mapValue(); + for (auto it = m.begin(); it != m.end(); ++it) { + if (it != m.begin()) { + ss << ", "; + } + ss << "\"" << (*it).first << "\": "; + if ((*it).second) { + (*it).second->toJSON(ss); + } else { + ss << "None"; + } + } + ss << " }"; +} + +// throws when one of the types in the path (except the one +// we're looking for) is not a MapElement +// returns 0 if it could simply not be found +// should that also be an exception? +ConstElementPtr +MapElement::find(const std::string& id) const { + const size_t sep = id.find('/'); + if (sep == std::string::npos) { + return (get(id)); + } else { + ConstElementPtr ce = get(id.substr(0, sep)); + if (ce) { + // ignore trailing slash + if (sep + 1 != id.size()) { + return (ce->find(id.substr(sep + 1))); + } else { + return (ce); + } + } else { + return (ElementPtr()); + } + } +} + +ElementPtr +Element::fromWire(const std::string& s) { + std::stringstream ss; + ss << s; + int line = 0, pos = 0; + return (fromJSON(ss, "", line, pos)); +} + +ElementPtr +Element::fromWire(std::stringstream& in, int) { + // + // Check protocol version + // + //for (int i = 0 ; i < 4 ; ++i) { + // const unsigned char version_byte = get_byte(in); + // if (PROTOCOL_VERSION[i] != version_byte) { + // throw DecodeError("Protocol version incorrect"); + // } + //} + //length -= 4; + int line = 0, pos = 0; + return (fromJSON(in, "", line, pos)); +} + +void +MapElement::set(const std::string& key, ConstElementPtr value) { + m[key] = value; +} + +bool +MapElement::find(const std::string& id, ConstElementPtr& t) const { + try { + ConstElementPtr p = find(id); + if (p) { + t = p; + return (true); + } + } catch (const TypeError&) { + // ignore + } + return (false); +} + +bool +IntElement::equals(const Element& other) const { + // Let's not be very picky with constraining the integer types to be the + // same. Equality is sometimes checked from high-up in the Element hierarcy. + // That is a context which, most of the time, does not have information on + // the type of integers stored on Elements lower in the hierarchy. So it + // would be difficult to differentiate between the integer types. + return (other.getType() == Element::integer && i == other.intValue()) || + (other.getType() == Element::bigint && i == other.bigIntValue()); +} + +bool +BigIntElement::equals(const Element& other) const { + // Let's not be very picky with constraining the integer types to be the + // same. Equality is sometimes checked from high-up in the Element hierarcy. + // That is a context which, most of the time, does not have information on + // the type of integers stored on Elements lower in the hierarchy. So it + // would be difficult to differentiate between the integer types. + return (other.getType() == Element::bigint && i_ == other.bigIntValue()) || + (other.getType() == Element::integer && i_ == other.intValue()); +} + +bool +DoubleElement::equals(const Element& other) const { + return (other.getType() == Element::real) && + (fabs(d - other.doubleValue()) < 1e-14); +} + +bool +BoolElement::equals(const Element& other) const { + return (other.getType() == Element::boolean) && + (b == other.boolValue()); +} + +bool +NullElement::equals(const Element& other) const { + return (other.getType() == Element::null); +} + +bool +StringElement::equals(const Element& other) const { + return (other.getType() == Element::string) && + (s == other.stringValue()); +} + +bool +ListElement::equals(const Element& other) const { + if (other.getType() == Element::list) { + const size_t s = size(); + if (s != other.size()) { + return (false); + } + for (size_t i = 0; i < s; ++i) { + if (!get(i)->equals(*other.get(i))) { + return (false); + } + } + return (true); + } else { + return (false); + } +} + +void +ListElement::sort(std::string const& index /* = std::string() */) { + if (l.empty()) { + return; + } + + int const t(l.at(0)->getType()); + std::function comparator; + if (t == map) { + if (index.empty()) { + isc_throw(BadValue, "index required when sorting maps"); + } + comparator = [&](ElementPtr const& a, ElementPtr const& b) { + ConstElementPtr const& ai(a->get(index)); + ConstElementPtr const& bi(b->get(index)); + if (ai && bi) { + return *ai < *bi; + } + return true; + }; + } else if (t == list) { + // Nested lists. Not supported. + return; + } else { + // Assume scalars. + if (!index.empty()) { + isc_throw(BadValue, "index given when sorting scalars?"); + } + comparator = [&](ElementPtr const& a, ElementPtr const& b) { + return *a < *b; + }; + } + + std::sort(l.begin(), l.end(), comparator); +} + +bool +MapElement::equals(const Element& other) const { + if (other.getType() == Element::map) { + if (size() != other.size()) { + return (false); + } + for (auto kv : mapValue()) { + auto key = kv.first; + if (other.contains(key)) { + if (!get(key)->equals(*other.get(key))) { + return (false); + } + } else { + return (false); + } + } + return (true); + } else { + return (false); + } +} + +bool +isNull(ConstElementPtr p) { + return (!p); +} + +void +removeIdentical(ElementPtr a, ConstElementPtr b) { + if (!b) { + return; + } + if (a->getType() != Element::map || b->getType() != Element::map) { + isc_throw(TypeError, "Non-map Elements passed to removeIdentical"); + } + + // As maps do not allow entries with multiple keys, we can either iterate + // over a checking for identical entries in b or vice-versa. As elements + // are removed from a if a match is found, we choose to iterate over b to + // avoid problems with element removal affecting the iterator. + for (auto kv : b->mapValue()) { + auto key = kv.first; + if (a->contains(key)) { + if (a->get(key)->equals(*b->get(key))) { + a->remove(key); + } + } + } +} + +ConstElementPtr +removeIdentical(ConstElementPtr a, ConstElementPtr b) { + ElementPtr result = Element::createMap(); + + if (!b) { + return (result); + } + + if (a->getType() != Element::map || b->getType() != Element::map) { + isc_throw(TypeError, "Non-map Elements passed to removeIdentical"); + } + + for (auto kv : a->mapValue()) { + auto key = kv.first; + if (!b->contains(key) || + !a->get(key)->equals(*b->get(key))) { + result->set(key, kv.second); + } + } + + return (result); +} + +void +merge(ElementPtr element, ConstElementPtr other) { + if (element->getType() != Element::map || + other->getType() != Element::map) { + isc_throw(TypeError, "merge arguments not MapElements"); + } + + for (auto kv : other->mapValue()) { + auto key = kv.first; + auto value = kv.second; + if (value && value->getType() != Element::null) { + element->set(key, value); + } else if (element->contains(key)) { + element->remove(key); + } + } +} + +void +mergeDiffAdd(ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, size_t idx) { + if (element->getType() != other->getType()) { + isc_throw(TypeError, "mergeDiffAdd arguments not same type"); + } + + if (element->getType() == Element::list) { + // Store new elements in a separate container so we don't overwrite + // options as we add them (if there are duplicates). + ElementPtr new_elements = Element::createList(); + for (auto& right : other->listValue()) { + // Check if we have any description of the key in the configuration + // hierarchy. + auto f = hierarchy[idx].find(key); + if (f != hierarchy[idx].end()) { + bool found = false; + ElementPtr mutable_right = boost::const_pointer_cast(right); + for (auto& left : element->listValue()) { + ElementPtr mutable_left = boost::const_pointer_cast(left); + // Check if the elements refer to the same configuration + // entity. + if (f->second.match_(mutable_left, mutable_right)) { + found = true; + mergeDiffAdd(mutable_left, mutable_right, hierarchy, key, idx); + } + } + if (!found) { + new_elements->add(right); + } + } else { + new_elements->add(right); + } + } + // Finally add the new elements. + for (auto& right : new_elements->listValue()) { + element->add(right); + } + return; + } + + if (element->getType() == Element::map) { + for (auto kv : other->mapValue()) { + auto current_key = kv.first; + auto value = boost::const_pointer_cast(kv.second); + if (value && value->getType() != Element::null) { + if (element->contains(current_key) && + (value->getType() == Element::map || + value->getType() == Element::list)) { + ElementPtr mutable_element = boost::const_pointer_cast(element->get(current_key)); + mergeDiffAdd(mutable_element, value, hierarchy, current_key, idx + 1); + } else { + element->set(current_key, value); + } + } + } + return; + } + element = other; +} + +void +mergeDiffDel(ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, size_t idx) { + if (element->getType() != other->getType()) { + isc_throw(TypeError, "mergeDiffDel arguments not same type"); + } + + if (element->getType() == Element::list) { + for (auto const& value : other->listValue()) { + ElementPtr mutable_right = boost::const_pointer_cast(value); + for (uint32_t iter = 0; iter < element->listValue().size();) { + bool removed = false; + // Check if we have any description of the key in the + // configuration hierarchy. + auto f = hierarchy[idx].find(key); + if (f != hierarchy[idx].end()) { + ElementPtr mutable_left = boost::const_pointer_cast(element->listValue().at(iter)); + // Check if the elements refer to the same configuration + // entity. + if (f->second.match_(mutable_left, mutable_right)) { + // Check if the user supplied data only contains + // identification information, so the intent is to + // delete the element, not just element data. + if (f->second.no_data_(mutable_right)) { + element->remove(iter); + removed = true; + } else { + mergeDiffDel(mutable_left, mutable_right, hierarchy, key, idx); + if (mutable_left->empty()) { + element->remove(iter); + removed = true; + } + } + } + } else if (element->listValue().at(iter)->equals(*value)) { + element->remove(iter); + removed = true; + } + if (!removed) { + ++iter; + } + } + } + return; + } + + if (element->getType() == Element::map) { + // If the resulting element still contains data, we need to restore the + // key parameters, so we store them here. + ElementPtr new_elements = Element::createMap(); + for (auto kv : other->mapValue()) { + auto current_key = kv.first; + auto value = boost::const_pointer_cast(kv.second); + if (value && value->getType() != Element::null) { + if (element->contains(current_key)) { + ElementPtr mutable_element = boost::const_pointer_cast(element->get(current_key)); + if (mutable_element->getType() == Element::map || + mutable_element->getType() == Element::list) { + mergeDiffDel(mutable_element, value, hierarchy, current_key, idx + 1); + if (mutable_element->empty()) { + element->remove(current_key); + } + } else { + // Check if we have any description of the key in the + // configuration hierarchy. + auto f = hierarchy[idx].find(key); + if (f != hierarchy[idx].end()) { + // Check if the key is used for element + // identification. + if (f->second.is_key_(current_key)) { + // Store the key parameter. + new_elements->set(current_key, mutable_element); + } + } + element->remove(current_key); + } + } + } + } + // If the element still contains data, restore the key elements. + if (element->size()) { + for (auto kv : new_elements->mapValue()) { + element->set(kv.first, kv.second); + } + } + return; + } + element = ElementPtr(new NullElement); +} + +void +extend(const std::string& container, const std::string& extension, + ElementPtr& element, ElementPtr& other, HierarchyDescriptor& hierarchy, + std::string key, size_t idx, bool alter) { + if (element->getType() != other->getType()) { + isc_throw(TypeError, "extend arguments not same type"); + } + + if (element->getType() == Element::list) { + for (auto& right : other->listValue()) { + // Check if we have any description of the key in the configuration + // hierarchy. + auto f = hierarchy[idx].find(key); + if (f != hierarchy[idx].end()) { + ElementPtr mutable_right = boost::const_pointer_cast(right); + for (auto& left : element->listValue()) { + ElementPtr mutable_left = boost::const_pointer_cast(left); + if (container == key) { + alter = true; + } + if (f->second.match_(mutable_left, mutable_right)) { + extend(container, extension, mutable_left, mutable_right, + hierarchy, key, idx, alter); + } + } + } + } + return; + } + + if (element->getType() == Element::map) { + for (auto kv : other->mapValue()) { + auto current_key = kv.first; + auto value = boost::const_pointer_cast(kv.second); + if (value && value->getType() != Element::null) { + if (element->contains(current_key) && + (value->getType() == Element::map || + value->getType() == Element::list)) { + ElementPtr mutable_element = boost::const_pointer_cast(element->get(current_key)); + if (container == key) { + alter = true; + } + extend(container, extension, mutable_element, value, hierarchy, current_key, idx + 1, alter); + } else if (alter && current_key == extension) { + element->set(current_key, value); + } + } + } + return; + } +} + +ElementPtr +copy(ConstElementPtr from, int level) { + if (!from) { + isc_throw(BadValue, "copy got a null pointer"); + } + int from_type = from->getType(); + if (from_type == Element::integer) { + return (ElementPtr(new IntElement(from->intValue()))); + } else if (from_type == Element::real) { + return (ElementPtr(new DoubleElement(from->doubleValue()))); + } else if (from_type == Element::boolean) { + return (ElementPtr(new BoolElement(from->boolValue()))); + } else if (from_type == Element::null) { + return (ElementPtr(new NullElement())); + } else if (from_type == Element::string) { + return (ElementPtr(new StringElement(from->stringValue()))); + } else if (from_type == Element::list) { + ElementPtr result = ElementPtr(new ListElement()); + for (auto elem : from->listValue()) { + if (level == 0) { + result->add(elem); + } else { + result->add(copy(elem, level - 1)); + } + } + return (result); + } else if (from_type == Element::map) { + ElementPtr result = ElementPtr(new MapElement()); + for (auto kv : from->mapValue()) { + auto key = kv.first; + auto value = kv.second; + if (level == 0) { + result->set(key, value); + } else { + result->set(key, copy(value, level - 1)); + } + } + return (result); + } else { + isc_throw(BadValue, "copy got an element of type: " << from_type); + } +} + +namespace { + +// Helper function which blocks infinite recursion +bool +isEquivalent0(ConstElementPtr a, ConstElementPtr b, unsigned level) { + // check looping forever on cycles + if (!level) { + isc_throw(BadValue, "isEquivalent got infinite recursion: " + "arguments include cycles"); + } + if (!a || !b) { + isc_throw(BadValue, "isEquivalent got a null pointer"); + } + // check types + if (a->getType() != b->getType()) { + return (false); + } + if (a->getType() == Element::list) { + // check empty + if (a->empty()) { + return (b->empty()); + } + // check size + if (a->size() != b->size()) { + return (false); + } + + // copy b into a list + const size_t s = a->size(); + std::list l; + for (size_t i = 0; i < s; ++i) { + l.push_back(b->get(i)); + } + + // iterate on a + for (size_t i = 0; i < s; ++i) { + ConstElementPtr item = a->get(i); + // lookup this item in the list + bool found = false; + for (auto it = l.begin(); it != l.end(); ++it) { + // if found in the list remove it + if (isEquivalent0(item, *it, level - 1)) { + found = true; + l.erase(it); + break; + } + } + // if not found argument differs + if (!found) { + return (false); + } + } + + // sanity check: the list must be empty + if (!l.empty()) { + isc_throw(Unexpected, "isEquivalent internal error"); + } + return (true); + } else if (a->getType() == Element::map) { + // check sizes + if (a->size() != b->size()) { + return (false); + } + // iterate on the first map + for (auto kv : a->mapValue()) { + // get the b value for the given keyword and recurse + ConstElementPtr item = b->get(kv.first); + if (!item || !isEquivalent0(kv.second, item, level - 1)) { + return (false); + } + } + return (true); + } else { + return (a->equals(*b)); + } +} + +} // end anonymous namespace + +bool +isEquivalent(ConstElementPtr a, ConstElementPtr b) { + return (isEquivalent0(a, b, 100)); +} + +void +prettyPrint(ConstElementPtr element, std::ostream& out, + unsigned indent, unsigned step) { + if (!element) { + isc_throw(BadValue, "prettyPrint got a null pointer"); + } + if (element->getType() == Element::list) { + // empty list case + if (element->empty()) { + out << "[ ]"; + return; + } + + // complex ? multiline : oneline + if (!element->get(0)) { + isc_throw(BadValue, "prettyPrint got a null pointer"); + } + int first_type = element->get(0)->getType(); + bool complex = false; + if ((first_type == Element::list) || (first_type == Element::map)) { + complex = true; + } + std::string separator = complex ? ",\n" : ", "; + + // open the list + out << "[" << (complex ? "\n" : " "); + + // iterate on items + const auto& l = element->listValue(); + for (auto it = l.begin(); it != l.end(); ++it) { + // add the separator if not the first item + if (it != l.begin()) { + out << separator; + } + // add indentation + if (complex) { + out << std::string(indent + step, ' '); + } + // recursive call + prettyPrint(*it, out, indent + step, step); + } + + // close the list + if (complex) { + out << "\n" << std::string(indent, ' '); + } else { + out << " "; + } + out << "]"; + } else if (element->getType() == Element::map) { + // empty map case + if (element->size() == 0) { + out << "{ }"; + return; + } + + // open the map + out << "{\n"; + + // iterate on keyword: value + const auto& m = element->mapValue(); + bool first = true; + for (auto it = m.begin(); it != m.end(); ++it) { + // add the separator if not the first item + if (first) { + first = false; + } else { + out << ",\n"; + } + // add indentation + out << std::string(indent + step, ' '); + // add keyword: + out << "\"" << it->first << "\": "; + // recursive call + prettyPrint(it->second, out, indent + step, step); + } + + // close the map + out << "\n" << std::string(indent, ' ') << "}"; + } else { + // not a list or a map + element->toJSON(out); + } +} + +std::string +prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) { + std::stringstream ss; + prettyPrint(element, ss, indent, step); + return (ss.str()); +} + +void Element::preprocess(std::istream& in, std::stringstream& out) { + + std::string line; + + while (std::getline(in, line)) { + // If this is a comments line, replace it with empty line + // (so the line numbers will still match + if (!line.empty() && line[0] == '#') { + line = ""; + } + + // getline() removes end line characters. Unfortunately, we need + // it for getting the line numbers right (in case we report an + // error. + out << line; + out << "\n"; + } +} + +} // end of isc::data namespace +} // end of isc namespace diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h new file mode 100644 index 0000000..e954ea9 --- /dev/null +++ b/src/lib/cc/data.h @@ -0,0 +1,1075 @@ +// Copyright (C) 2010-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef ISC_DATA_H +#define ISC_DATA_H 1 + +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace isc { namespace data { + +class Element; +// todo: describe the rationale behind ElementPtr? +typedef boost::shared_ptr ElementPtr; +typedef boost::shared_ptr ConstElementPtr; + +/// +/// @brief A standard Data module exception that is thrown if a function +/// is called for an Element that has a wrong type (e.g. int_value on a +/// ListElement) +/// +class TypeError : public isc::Exception { +public: + TypeError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// +/// @brief A standard Data module exception that is thrown if a parse +/// error is encountered when constructing an Element from a string +/// +// i'd like to use Exception here but we need one that is derived from +// runtime_error (as this one is directly based on external data, and +// i want to add some values to any static data string that is provided) +class JSONError : public isc::Exception { +public: + JSONError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// +/// @brief The @c Element class represents a piece of data, used by +/// the command channel and configuration parts. +/// +/// An @c Element can contain simple types (int, real, string, bool and +/// None), and composite types (list and string->element maps) +/// +/// Elements should in calling functions usually be referenced through +/// an @c ElementPtr, which can be created using the factory functions +/// @c Element::create() and @c Element::fromJSON() +/// +/// Notes to developers: Element is a base class, implemented by a +/// specific subclass for each type (IntElement, BoolElement, etc). +/// Element does define all functions for all types, and defaults to +/// raising a @c TypeError for functions that are not supported for +/// the type in question. +/// +class Element { + +public: + /// @brief Represents the position of the data element within a + /// configuration string. + /// + /// Position comprises a file name, line number and an offset within this + /// line where the element value starts. For example, if the JSON string is + /// + /// @code + /// { "foo": "some string", + /// "bar": 123 } + /// \endcode + /// + /// the position of the element "bar" is: line_ = 2; pos_ = 9, because + /// beginning of the value "123" is at offset 9 from the beginning of + /// the second line, including whitespaces. + /// + /// Note that the @c Position structure is used as an argument to @c Element + /// constructors and factory functions to avoid ambiguity and so that the + /// uint32_t arguments holding line number and position within the line are + /// not confused with the @c Element values passed to these functions. + struct Position { + std::string file_; ///< File name. + uint32_t line_; ///< Line number. + uint32_t pos_; ///< Position within the line. + + /// @brief Default constructor. + Position() : file_(""), line_(0), pos_(0) { + } + + /// @brief Constructor. + /// + /// @param file File name. + /// @param line Line number. + /// @param pos Position within the line. + Position(const std::string& file, const uint32_t line, + const uint32_t pos) + : file_(file), line_(line), pos_(pos) { + } + + /// @brief Returns the position in the textual format. + /// + /// The returned position has the following format: file:line:pos. + std::string str() const; + }; + + /// @brief Returns @c Position object with line_ and pos_ set to 0, and + /// with an empty file name. + /// + /// The object containing two zeros is a default for most of the + /// methods creating @c Element objects. The returned value is static + /// so as it is not created everytime the function with the default + /// position argument is called. + static const Position& ZERO_POSITION() { + static Position position("", 0, 0); + return (position); + } + + /// @brief The types that an Element can hold + /// + /// Some of these types need to match their associated integer from the + /// parameter_data_type database table, so let the enums be explicitly + /// mapped to integers, to reduce the chance of messing up. + /// + /// any is a special type used in list specifications, specifying that the + /// elements can be of any type. + enum types { + integer = 0, + real = 1, + boolean = 2, + null = 3, + string = 4, + bigint = 5, + list = 6, + map = 7, + any = 8, + }; + +private: + // technically the type could be omitted; is it useful? + // should we remove it or replace it with a pure virtual + // function getType? + types type_; + + /// @brief Position of the element in the configuration string. + Position position_; + +protected: + + /// @brief Constructor. + /// + /// @param t Element type. + /// @param pos Structure holding position of the value of the data element. + /// It comprises the line number and the position within this line. The values + /// held in this structure are used for error logging purposes. + Element(types t, const Position& pos = ZERO_POSITION()) + : type_(t), position_(pos) { + } + + +public: + // base class; make dtor virtual + virtual ~Element() {}; + + /// @return the type of this element + types getType() const { return (type_); } + + /// @brief Returns position where the data element's value starts in a + /// configuration string. + /// + /// @warning The returned reference is valid as long as the object which + /// created it lives. + const Position& getPosition() const { return (position_); } + + /// Returns a string representing the Element and all its + /// child elements; note that this is different from stringValue(), + /// which only returns the single value of a StringElement + /// + /// The resulting string will contain the Element in JSON format. + /// + /// @return std::string containing the string representation + std::string str() const; + + /// Returns the wireformat for the Element and all its child + /// elements. + /// + /// @return std::string containing the element in wire format + std::string toWire() const; + void toWire(std::ostream& out) const; + + /// @brief Add the position to a TypeError message + /// should be used in place of isc_throw(TypeError, error) +#define throwTypeError(error) \ + { \ + std::string msg_ = error; \ + if ((position_.file_ != "") || \ + (position_.line_ != 0) || \ + (position_.pos_ != 0)) { \ + msg_ += " in (" + position_.str() + ")"; \ + } \ + isc_throw(TypeError, msg_); \ + } + + /// @name pure virtuals, every derived class must implement these + + /// @return true if the other ElementPtr has the same value and the same + /// type (or a different and compatible type), false otherwise. + virtual bool equals(const Element& other) const = 0; + + /// Converts the Element to JSON format and appends it to + /// the given stringstream. + virtual void toJSON(std::ostream& ss) const = 0; + + /// @name Type-specific getters + /// + /// @brief These functions only + /// work on their corresponding Element type. For all other + /// types, a TypeError is thrown. + /// If you want an exception-safe getter method, use + /// getValue() below + //@{ + virtual int64_t intValue() const + { throwTypeError("intValue() called on non-integer Element"); }; + virtual isc::util::int128_t bigIntValue() const { + throwTypeError("bigIntValue() called on non-big-integer Element"); + } + virtual double doubleValue() const + { throwTypeError("doubleValue() called on non-double Element"); }; + virtual bool boolValue() const + { throwTypeError("boolValue() called on non-Bool Element"); }; + virtual std::string stringValue() const + { throwTypeError("stringValue() called on non-string Element"); }; + virtual const std::vector& listValue() const { + // replace with real exception or empty vector? + throwTypeError("listValue() called on non-list Element"); + }; + virtual const std::map& mapValue() const { + // replace with real exception or empty map? + throwTypeError("mapValue() called on non-map Element"); + }; + //@} + + /// @name Exception-safe getters + /// + /// @brief The getValue() functions return false if the given reference + /// is of another type than the element contains + /// By default it always returns false; the derived classes + /// override the function for their type, copying their + /// data to the given reference and returning true + /// + //@{ + virtual bool getValue(int64_t& t) const; + virtual bool getValue(double& t) const; + virtual bool getValue(bool& t) const; + virtual bool getValue(std::string& t) const; + virtual bool getValue(std::vector& t) const; + virtual bool getValue(std::map& t) const; + //@} + + /// + /// @name Exception-safe setters. + /// + /// @brief Return false if the Element is not + /// the right type. Set the value and return true if the Elements + /// is of the correct type + /// + /// Notes: Read notes of IntElement definition about the use of + /// long long int, long int and int. + //@{ + virtual bool setValue(const long long int v); + virtual bool setValue(const isc::util::int128_t& v); + bool setValue(const long int i) { return (setValue(static_cast(i))); }; + bool setValue(const int i) { return (setValue(static_cast(i))); }; + virtual bool setValue(const double v); + virtual bool setValue(const bool t); + virtual bool setValue(const std::string& v); + virtual bool setValue(const std::vector& v); + virtual bool setValue(const std::map& v); + //@} + + // Other functions for specific subtypes + + /// @name ListElement functions + /// + /// @brief If the Element on which these functions are called are not + /// an instance of ListElement, a TypeError exception is thrown. + //@{ + /// Returns the ElementPtr at the given index. If the index is out + /// of bounds, this function throws an std::out_of_range exception. + /// @param i The position of the ElementPtr to return + virtual ConstElementPtr get(const int i) const; + + /// @brief returns element as non-const pointer + /// + /// @param i The position of the ElementPtr to retrieve + /// @return specified element pointer + virtual ElementPtr getNonConst(const int i) const; + + /// Sets the ElementPtr at the given index. If the index is out + /// of bounds, this function throws an std::out_of_range exception. + /// @param i The position of the ElementPtr to set + /// @param element The ElementPtr to set at the position + virtual void set(const size_t i, ElementPtr element); + + /// Adds an ElementPtr to the list + /// @param element The ElementPtr to add + virtual void add(ElementPtr element); + + /// Removes the element at the given position. If the index is out + /// of nothing happens. + /// @param i The index of the element to remove. + virtual void remove(const int i); + + /// Returns the number of elements in the list. + virtual size_t size() const; + + /// Return true if there are no elements in the list. + virtual bool empty() const; + //@} + + + /// @name MapElement functions + /// + /// @brief If the Element on which these functions are called are not + /// an instance of MapElement, a TypeError exception is thrown. + //@{ + /// Returns the ElementPtr at the given key + /// @param name The key of the Element to return + /// @return The ElementPtr at the given key, or null if not present + virtual ConstElementPtr get(const std::string& name) const; + + /// Sets the ElementPtr at the given key + /// @param name The key of the Element to set + /// @param element The ElementPtr to set at the given key. + virtual void set(const std::string& name, ConstElementPtr element); + + /// Remove the ElementPtr at the given key + /// @param name The key of the Element to remove + virtual void remove(const std::string& name); + + /// Checks if there is data at the given key + /// @param name The key of the Element checked for existence + /// @return true if there is data at the key, false if not. + virtual bool contains(const std::string& name) const; + + /// Recursively finds any data at the given identifier. The + /// identifier is a /-separated list of names of nested maps, with + /// the last name being the leaf that is returned. + /// + /// For instance, if you have a MapElement that contains another + /// MapElement at the key "foo", and that second MapElement contains + /// Another Element at key "bar", the identifier for that last + /// element from the first is "foo/bar". + /// + /// @param identifier The identifier of the element to find + /// @return The ElementPtr at the given identifier. Returns a + /// null ElementPtr if it is not found, which can be checked with + /// Element::is_null(ElementPtr e). + virtual ConstElementPtr find(const std::string& identifier) const; + + /// See @c Element::find() + /// @param identifier The identifier of the element to find + /// @param t Reference to store the resulting ElementPtr, if found. + /// @return true if the element was found, false if not. + virtual bool find(const std::string& identifier, ConstElementPtr& t) const; + //@} + + /// @name Factory functions + + // TODO: should we move all factory functions to a different class + // so as not to burden the Element base with too many functions? + // and/or perhaps even to a separate header? + + /// @name Direct factory functions + /// @brief These functions simply wrap the given data directly + /// in an Element object, and return a reference to it, in the form + /// of an @c ElementPtr. + /// These factory functions are exception-free (unless there is + /// no memory available, in which case bad_alloc is raised by the + /// underlying system). + /// (Note that that is different from an NullElement, which + /// represents an empty value, and is created with Element::create()) + /// + /// Notes: Read notes of IntElement definition about the use of + /// long long int, long int and int. + //@{ + static ElementPtr create(const Position& pos = ZERO_POSITION()); + static ElementPtr create(const long long int i, + const Position& pos = ZERO_POSITION()); + static ElementPtr create(const isc::util::int128_t& i, + const Position& pos = ZERO_POSITION()); + static ElementPtr create(const int i, + const Position& pos = ZERO_POSITION()); + static ElementPtr create(const long int i, + const Position& pos = ZERO_POSITION()); + static ElementPtr create(const uint32_t i, + const Position& pos = ZERO_POSITION()); + static ElementPtr create(const double d, + const Position& pos = ZERO_POSITION()); + static ElementPtr create(const bool b, + const Position& pos = ZERO_POSITION()); + static ElementPtr create(const std::string& s, + const Position& pos = ZERO_POSITION()); + // need both std:string and char *, since c++ will match + // bool before std::string when you pass it a char * + static ElementPtr create(const char *s, + const Position& pos = ZERO_POSITION()); + + /// @brief Creates an empty ListElement type ElementPtr. + /// + /// @param pos A structure holding position of the data element value + /// in the configuration string. It is used for error logging purposes. + static ElementPtr createList(const Position& pos = ZERO_POSITION()); + + /// @brief Creates an empty MapElement type ElementPtr. + /// + /// @param pos A structure holding position of the data element value + /// in the configuration string. It is used for error logging purposes. + static ElementPtr createMap(const Position& pos = ZERO_POSITION()); + //@} + + /// @name Compound factory functions + + /// @brief These functions will parse the given string (JSON) + /// representation of a compound element. If there is a parse + /// error, an exception of the type isc::data::JSONError is thrown. + + //@{ + /// Creates an Element from the given JSON string + /// @param in The string to parse the element from + /// @param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed + /// @return An ElementPtr that contains the element(s) specified + /// in the given string. + static ElementPtr fromJSON(const std::string& in, bool preproc = false); + + /// Creates an Element from the given input stream containing JSON + /// formatted data. + /// + /// @param in The string to parse the element from + /// @param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed + /// @throw JSONError + /// @return An ElementPtr that contains the element(s) specified + /// in the given input stream. + static ElementPtr fromJSON(std::istream& in, bool preproc = false); + + /// Creates an Element from the given input stream containing JSON + /// formatted data. + /// + /// @param in The string to parse the element from + /// @param file_name specified input file name (used in error reporting) + /// @param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed + /// @throw JSONError + /// @return An ElementPtr that contains the element(s) specified + /// in the given input stream. + /// @throw JSONError + static ElementPtr fromJSON(std::istream& in, const std::string& file_name, + bool preproc = false); + + /// Creates an Element from the given input stream, where we keep + /// track of the location in the stream for error reporting. + /// + /// @param in The string to parse the element from. + /// @param file The input file name. + /// @param line A reference to the int where the function keeps + /// track of the current line. + /// @param pos A reference to the int where the function keeps + /// track of the current position within the current line. + /// @throw JSONError + /// @return An ElementPtr that contains the element(s) specified + /// in the given input stream. + // make this one private? + /// @throw JSONError + static ElementPtr fromJSON(std::istream& in, const std::string& file, + int& line, int &pos); + + /// Reads contents of specified file and interprets it as JSON. + /// + /// @param file_name name of the file to read + /// @param preproc specified whether preprocessing (e.g. comment removal) + /// should be performed + /// @return An ElementPtr that contains the element(s) specified + /// if the given file. + static ElementPtr fromJSONFile(const std::string& file_name, + bool preproc = false); + //@} + + /// @name Type name conversion functions + + /// Returns the name of the given type as a string + /// + /// @param type The type to return the name of + /// @return The name of the type, or "unknown" if the type + /// is not known. + static std::string typeToName(Element::types type); + + /// Converts the string to the corresponding type + /// Throws a TypeError if the name is unknown. + /// + /// @param type_name The name to get the type of + /// @return the corresponding type value + static Element::types nameToType(const std::string& type_name); + + /// @brief input text preprocessor + /// + /// This method performs preprocessing of the input stream (which is + /// expected to contain a text version of to be parsed JSON). For now the + /// sole supported operation is bash-style (line starting with #) comment + /// removal, but it will be extended later to cover more cases (C, C++ style + /// comments, file inclusions, maybe macro replacements?). + /// + /// This method processes the whole input stream. It reads all contents of + /// the input stream, filters the content and returns the result in a + /// different stream. + /// + /// @param in input stream to be preprocessed + /// @param out output stream (filtered content will be written here) + static void preprocess(std::istream& in, std::stringstream& out); + + /// @name Wire format factory functions + + /// These function pparse the wireformat at the given stringstream + /// (of the given length). If there is a parse error an exception + /// of the type isc::cc::DecodeError is raised. + + //@{ + /// Creates an Element from the wire format in the given + /// stringstream of the given length. + /// Since the wire format is JSON, this is the same as + /// fromJSON, and could be removed. + /// + /// @param in The input stringstream. + /// @param length The length of the wireformat data in the stream + /// @return ElementPtr with the data that is parsed. + static ElementPtr fromWire(std::stringstream& in, int length); + + /// Creates an Element from the wire format in the given string + /// Since the wire format is JSON, this is the same as + /// fromJSON, and could be removed. + /// + /// @param s The input string + /// @return ElementPtr with the data that is parsed. + static ElementPtr fromWire(const std::string& s); + //@} + + /// @brief Remove all empty maps and lists from this Element and its + /// descendants. + void removeEmptyContainersRecursively() { + if (type_ == list || type_ == map) { + size_t s(size()); + for (size_t i = 0; i < s; ++i) { + // Get child. + ElementPtr child; + if (type_ == list) { + child = getNonConst(i); + } else if (type_ == map) { + std::string const key(get(i)->stringValue()); + // The ElementPtr - ConstElementPtr disparity between + // ListElement and MapElement is forcing a const cast here. + // It's undefined behavior to modify it after const casting. + // The options are limited. I've tried templating, moving + // this function from a member function to free-standing and + // taking the Element template as argument. I've tried + // making it a virtual function with overridden + // implementations in ListElement and MapElement. Nothing + // works. + child = boost::const_pointer_cast(get(key)); + } + + // Makes no sense to continue for non-container children. + if (child->getType() != list && child->getType() != map) { + continue; + } + + // Recurse if not empty. + if (!child->empty()){ + child->removeEmptyContainersRecursively(); + } + + // When returning from recursion, remove if empty. + if (child->empty()) { + remove(i); + --i; + --s; + } + } + } + } +}; + +/// Notes: IntElement type is changed to int64_t. +/// Due to C++ problems on overloading and automatic type conversion, +/// (C++ tries to convert integer type values and reference/pointer +/// if value types do not match exactly) +/// We decided the storage as int64_t, +/// three (long long, long, int) override function definitions +/// and cast int/long/long long to int64_t via long long. +/// Therefore, call by value methods (create, setValue) have three +/// (int,long,long long) definitions. Others use int64_t. +/// +class IntElement : public Element { + int64_t i; +public: + IntElement(int64_t v, const Position& pos = ZERO_POSITION()) + : Element(integer, pos), i(v) { } + int64_t intValue() const { return (i); } + using Element::getValue; + bool getValue(int64_t& t) const { t = i; return (true); } + using Element::setValue; + bool setValue(long long int v) { i = v; return (true); } + void toJSON(std::ostream& ss) const; + bool equals(const Element& other) const; +}; + +/// @brief Wrapper over int128_t +class BigIntElement : public Element { + using int128_t = isc::util::int128_t; + using Element::getValue; + using Element::setValue; + +public: + /// @brief Constructor + BigIntElement(const int128_t& v, const Position& pos = ZERO_POSITION()) + : Element(bigint, pos), i_(v) { + } + + /// @brief Retrieve the underlying big integer value. + /// + /// @return the underlying value + int128_t bigIntValue() const override { + return (i_); + } + + /// @brief Sets the underlying big integer value. + /// + /// @return true for no reason + bool setValue(const int128_t& v) override { + i_ = v; + return (true); + } + + /// @brief Converts the Element to JSON format and appends it to the given + /// stringstream. + void toJSON(std::ostream& ss) const override; + + /// @brief Checks whether the other Element is equal. + /// + /// @return true if the other ElementPtr has the same value and the same + /// type (or a different and compatible type), false otherwise. + bool equals(const Element& other) const override; + +private: + /// @brief the underlying stored value + int128_t i_; +}; + +class DoubleElement : public Element { + double d; + +public: + DoubleElement(double v, const Position& pos = ZERO_POSITION()) + : Element(real, pos), d(v) {}; + double doubleValue() const { return (d); } + using Element::getValue; + bool getValue(double& t) const { t = d; return (true); } + using Element::setValue; + bool setValue(const double v) { d = v; return (true); } + void toJSON(std::ostream& ss) const; + bool equals(const Element& other) const; +}; + +class BoolElement : public Element { + bool b; + +public: + BoolElement(const bool v, const Position& pos = ZERO_POSITION()) + : Element(boolean, pos), b(v) {}; + bool boolValue() const { return (b); } + using Element::getValue; + bool getValue(bool& t) const { t = b; return (true); } + using Element::setValue; + bool setValue(const bool v) { b = v; return (true); } + void toJSON(std::ostream& ss) const; + bool equals(const Element& other) const; +}; + +class NullElement : public Element { +public: + NullElement(const Position& pos = ZERO_POSITION()) + : Element(null, pos) {}; + void toJSON(std::ostream& ss) const; + bool equals(const Element& other) const; +}; + +class StringElement : public Element { + std::string s; + +public: + StringElement(std::string v, const Position& pos = ZERO_POSITION()) + : Element(string, pos), s(v) {}; + std::string stringValue() const { return (s); } + using Element::getValue; + bool getValue(std::string& t) const { t = s; return (true); } + using Element::setValue; + bool setValue(const std::string& v) { s = v; return (true); } + void toJSON(std::ostream& ss) const; + bool equals(const Element& other) const; +}; + +class ListElement : public Element { + std::vector l; + +public: + ListElement(const Position& pos = ZERO_POSITION()) + : Element(list, pos) {} + const std::vector& listValue() const { return (l); } + using Element::getValue; + bool getValue(std::vector& t) const { + t = l; + return (true); + } + using Element::setValue; + bool setValue(const std::vector& v) { + l = v; + return (true); + } + using Element::get; + ConstElementPtr get(int i) const { return (l.at(i)); } + ElementPtr getNonConst(int i) const { return (l.at(i)); } + using Element::set; + void set(size_t i, ElementPtr e) { + l.at(i) = e; + } + void add(ElementPtr e) { l.push_back(e); }; + using Element::remove; + void remove(int i) { l.erase(l.begin() + i); }; + void toJSON(std::ostream& ss) const; + size_t size() const { return (l.size()); } + bool empty() const { return (l.empty()); } + bool equals(const Element& other) const; + + /// @brief Sorts the elements inside the list. + /// + /// The list must contain elements of the same type. + /// Call with the key by which you want to sort when the list contains maps. + /// Nested lists are not supported. + /// Call without a parameter when sorting any other type. + /// + /// @param index the key by which you want to sort when the list contains + /// maps + void sort(std::string const& index = std::string()); +}; + +class MapElement : public Element { + std::map m; + +public: + MapElement(const Position& pos = ZERO_POSITION()) : Element(map, pos) {} + // @todo should we have direct iterators instead of exposing the std::map + // here? + const std::map& mapValue() const override { + return (m); + } + using Element::getValue; + bool getValue(std::map& t) const override { + t = m; + return (true); + } + using Element::setValue; + bool setValue(const std::map& v) override { + m = v; + return (true); + } + using Element::get; + ConstElementPtr get(const std::string& s) const override { + auto found = m.find(s); + return (found != m.end() ? found->second : ConstElementPtr()); + } + + /// @brief Get the i-th element in the map. + /// + /// Useful when required to iterate with an index. + /// + /// @param i the position of the element you want to return + /// @return the element at position i + ConstElementPtr get(int const i) const override { + auto it(m.begin()); + std::advance(it, i); + return create(it->first); + } + + using Element::set; + void set(const std::string& key, ConstElementPtr value) override; + using Element::remove; + void remove(const std::string& s) override { m.erase(s); } + + /// @brief Remove the i-th element from the map. + /// + /// @param i the position of the element you want to remove + void remove(int const i) override { + auto it(m.begin()); + std::advance(it, i); + m.erase(it); + } + + bool contains(const std::string& s) const override { + return (m.find(s) != m.end()); + } + void toJSON(std::ostream& ss) const override; + + // we should name the two finds better... + // find the element at id; raises TypeError if one of the + // elements at path except the one we're looking for is not a + // mapelement. + // returns an empty element if the item could not be found + ConstElementPtr find(const std::string& id) const override; + + // find the Element at 'id', and store the element pointer in t + // returns true if found, or false if not found (either because + // it doesn't exist or one of the elements in the path is not + // a MapElement) + bool find(const std::string& id, ConstElementPtr& t) const override; + + /// @brief Returns number of stored elements + /// + /// @return number of elements. + size_t size() const override { + return (m.size()); + } + + bool equals(const Element& other) const override; + + bool empty() const override { return (m.empty()); } +}; + +/// Checks whether the given ElementPtr is a NULL pointer +/// @param p The ElementPtr to check +/// @return true if it is NULL, false if not. +bool isNull(ConstElementPtr p); + +/// +/// @brief Remove all values from the first ElementPtr that are +/// equal in the second. Both ElementPtrs MUST be MapElements +/// The use for this function is to end up with a MapElement that +/// only contains new and changed values (for ModuleCCSession and +/// configuration update handlers) +/// Raises a TypeError if a or b are not MapElements +void removeIdentical(ElementPtr a, ConstElementPtr b); + +/// @brief Create a new ElementPtr from the first ElementPtr, removing all +/// values that are equal in the second. Both ElementPtrs MUST be MapElements. +/// The returned ElementPtr will be a MapElement that only contains new and +/// changed values (for ModuleCCSession and configuration update handlers). +/// Raises a TypeError if a or b are not MapElements +ConstElementPtr removeIdentical(ConstElementPtr a, ConstElementPtr b); + +/// @brief Merges the data from other into element. (on the first level). Both +/// elements must be MapElements. Every string, value pair in other is copied +/// into element (the ElementPtr of value is copied, this is not a new object) +/// Unless the value is a NullElement, in which case the key is removed from +/// element, rather than setting the value to the given NullElement. +/// This way, we can remove values from for instance maps with configuration +/// data (which would then result in reverting back to the default). +/// Raises a TypeError if either ElementPtr is not a MapElement +void merge(ElementPtr element, ConstElementPtr other); + +/// @brief Function used to check if two MapElements refer to the same +/// configuration data. It can check if the two MapElements have the same or +/// have equivalent value for some members. +/// e.g. +/// ( +/// left->get("prefix")->stringValue() == right->get("prefix")->stringValue() && +/// left->get("prefix-len")->intValue() == right->get("prefix-len")->intValue() && +/// left->get("delegated-len")->intValue() == right->get("delegated-len")->intValue() +/// ) +typedef std::function MatchTestFunc; + +/// @brief Function used to check if the data provided for the element contains +/// only information used for identification, or it contains extra useful data. +typedef std::function NoDataTestFunc; + +/// @brief Function used to check if the key is used for identification +typedef std::function IsKeyTestFunc; + +/// @brief Structure holding the test functions used to traverse the element +/// hierarchy. +struct HierarchyTraversalTest { + MatchTestFunc match_; + NoDataTestFunc no_data_; + IsKeyTestFunc is_key_; +}; + +/// @brief Mapping between a container name and functions used to match elements +/// inside the container. +typedef std::map FunctionMap; + +/// @brief Hierarchy descriptor of the containers in a specific Element +/// hierarchy tree. The position inside the vector indicates the level at which +/// the respective containers are located. +/// +/// e.g. +/// { +/// { { "pools", { ... , ... } }, { "pd-pools", { ... , ... } }, { "option-data", { ... , ... } } }, +/// { { "option-data", { ... , ... } } } +/// } +/// At first subnet level the 'pools', 'pd-pools' and 'option-data' containers +/// can be found. +/// At second subnet level the 'option-data' container can be found +/// (obviously only inside 'pools' and 'pd-pools' containers). +typedef std::vector HierarchyDescriptor; + +/// @brief Merges the diff data by adding the missing elements from 'other' +/// to 'element' (recursively). Both elements must be the same Element type. +/// Raises a TypeError if elements are not the same Element type. +/// @note +/// for non map and list elements the values are updated with the new values +/// for maps: +/// - non map and list elements are added/updated with the new values +/// - list and map elements are processed recursively +/// for lists: +/// - regardless of the element type, all elements from 'other' are added to +/// 'element' +/// +/// @param element The element to which new data is added. +/// @param other The element containing the data which needs to be added. +/// @param hierarchy The hierarchy describing the elements relations and +/// identification keys. +/// @param key The container holding the current element. +/// @param idx The level inside the hierarchy the current element is located. +void mergeDiffAdd(ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, + size_t idx = 0); + +/// @brief Merges the diff data by removing the data present in 'other' from +/// 'element' (recursively). Both elements must be the same Element type. +/// Raises a TypeError if elements are not the same Element type. +/// for non map and list elements the values are set to NullElement +/// for maps: +/// - non map and list elements are removed from the map +/// - list and map elements are processed recursively +/// for lists: +/// - regardless of the element type, all elements from 'other' matching +/// elements in 'element' are removed +/// +/// @param element The element from which new data is removed. +/// @param other The element containing the data which needs to be removed. +/// @param hierarchy The hierarchy describing the elements relations and +/// identification keys. +/// @param key The container holding the current element. +/// @param idx The level inside the hierarchy the current element is located. +void mergeDiffDel(ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, + size_t idx = 0); + +/// @brief Extends data by adding the specified 'extension' elements from +/// 'other' inside the 'container' element (recursively). Both elements must be +/// the same Element type. +/// Raises a TypeError if elements are not the same Element type. +/// +/// @param container The container holding the data that must be extended. +/// @param extension The name of the element that contains the data that must be +/// added (if not already present) in order to extend the initial data. +/// @param element The element from which new data is added. +/// @param other The element containing the data which needs to be added. +/// @param hierarchy The hierarchy describing the elements relations and +/// identification keys. +/// @param key The container holding the current element. +/// @param idx The level inside the hierarchy the current element is located. +/// @param alter The flag which indicates if the current element should be +/// updated. +void extend(const std::string& container, const std::string& extension, + ElementPtr& element, ElementPtr& other, + HierarchyDescriptor& hierarchy, std::string key, size_t idx = 0, + bool alter = false); + +/// @brief Copy the data up to a nesting level. +/// +/// The copy is a deep copy so nothing is shared if it is not +/// under the given nesting level. +/// +/// @param from the pointer to the element to copy +/// @param level nesting level (default is 100, 0 means shallow copy, +/// negative means outbound and perhaps looping forever). +/// @return a pointer to a fresh copy +/// @throw raises a BadValue is a null pointer occurs. +ElementPtr copy(ConstElementPtr from, int level = 100); + +/// @brief Compares the data with other using unordered lists +/// +/// This comparison function handles lists (JSON arrays) as +/// unordered multi sets (multi means an item can occurs more +/// than once as soon as it occurs the same number of times). +bool isEquivalent(ConstElementPtr a, ConstElementPtr b); + +/// @brief Pretty prints the data into stream. +/// +/// This operator converts the @c ConstElementPtr into a string and +/// inserts it into the output stream @c out with an initial +/// indentation @c indent and add at each level @c step spaces. +/// For maps if there is a comment property it is printed first. +/// +/// @param element A @c ConstElementPtr to pretty print +/// @param out A @c std::ostream on which the print operation is performed +/// @param indent An initial number of spaces to add each new line +/// @param step A number of spaces to add to indentation at a new level +void prettyPrint(ConstElementPtr element, std::ostream& out, + unsigned indent = 0, unsigned step = 2); + +/// @brief Pretty prints the data into string +/// +/// This operator converts the @c ConstElementPtr into a string with +/// an initial indentation @c indent and add at each level @c step spaces. +/// For maps if there is a comment property it is printed first. +/// +/// @param element A @c ConstElementPtr to pretty print +/// @param indent An initial number of spaces to add each new line +/// @param step A number of spaces to add to indentation at a new level +/// @return a string where element was pretty printed +std::string prettyPrint(ConstElementPtr element, + unsigned indent = 0, unsigned step = 2); + +/// @brief Insert Element::Position as a string into stream. +/// +/// This operator converts the @c Element::Position into a string and +/// inserts it into the output stream @c out. +/// +/// @param out A @c std::ostream object on which the insertion operation is +/// performed. +/// @param pos The @c Element::Position structure to insert. +/// @return A reference to the same @c std::ostream object referenced by +/// parameter @c out after the insertion operation. +std::ostream& operator<<(std::ostream& out, const Element::Position& pos); + +/// @brief Insert the Element as a string into stream. +/// +/// This method converts the @c ElementPtr into a string with +/// @c Element::str() and inserts it into the +/// output stream @c out. +/// +/// This function overloads the global operator<< to behave as described in +/// ostream::operator<< but applied to @c ElementPtr objects. +/// +/// @param out A @c std::ostream object on which the insertion operation is +/// performed. +/// @param e The @c ElementPtr object to insert. +/// @return A reference to the same @c std::ostream object referenced by +/// parameter @c out after the insertion operation. +std::ostream& operator<<(std::ostream& out, const Element& e); + +bool operator==(const Element& a, const Element& b); +bool operator!=(const Element& a, const Element& b); +bool operator<(const Element& a, const Element& b); + +} // namespace data +} // namespace isc + +#endif // ISC_DATA_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/cc/dhcp_config_error.h b/src/lib/cc/dhcp_config_error.h new file mode 100644 index 0000000..1337dd8 --- /dev/null +++ b/src/lib/cc/dhcp_config_error.h @@ -0,0 +1,72 @@ +// 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 DHCP_CONFIG_ERROR_H +#define DHCP_CONFIG_ERROR_H + +#include + +namespace isc { + +/// @brief Evaluation error exception raised when trying to parse. +/// +/// This exception is expected to be thrown when parsing of the input +/// configuration has failed. This exception is used by parsers. +class ParseError : public isc::Exception { + public: + ParseError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// An exception that is thrown if an error occurs while configuring +/// any server. +/// By convention when this exception is thrown there is a position +/// between parentheses so the code style should be like this: +/// +/// try { +/// ... +/// } catch (const ConfigError&) { +/// throw; +/// } catch (const std::exception& ex) { +/// isc_throw(ConfigError, "message" << ex.what() +/// << " (" << getPosition(what) << ")"); +/// } + +/// @todo: move this header into simple_parser.h +/// @todo: create an isc_throw like macro to add the +/// position more easily. +/// @todo: replace all references to DhcpConfigError with ConfigError, +/// then remove DhcpConfigError. +class ConfigError : public isc::Exception { +public: + + /// @brief constructor + /// + /// @param file name of the file, where exception occurred + /// @param line line of the file, where exception occurred + /// @param what text description of the issue that caused exception + ConfigError(const char* file, size_t line, const char* what) + : isc::Exception(file, line, what) {} +}; + +namespace dhcp { + +/// @brief To be removed. Please use ConfigError instead. +class DhcpConfigError : public isc::Exception { +public: + /// @brief constructor + /// + /// @param file name of the file, where exception occurred + /// @param line line of the file, where exception occurred + /// @param what text description of the issue that caused exception + DhcpConfigError(const char* file, size_t line, const char* what) + : isc::Exception(file, line, what) {} +}; + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif // DHCP_CONFIG_ERROR_H diff --git a/src/lib/cc/element_value.h b/src/lib/cc/element_value.h new file mode 100644 index 0000000..ff48561 --- /dev/null +++ b/src/lib/cc/element_value.h @@ -0,0 +1,117 @@ +// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef ELEMENT_VALUE_H +#define ELEMENT_VALUE_H + +#include +#include +#include + +namespace isc { +namespace data { + +/// @brief Template class for converting a value encapsulated in the +/// @c Element object into a simple type. +/// +/// The @c Element object provides a set of accessors to retrieve +/// values of different types it encapsulates. These methods +/// however can't be always used in template methods and classes. +/// +/// Consider a template function which returns a value of a type +/// specified as template argument. In order to convert a value +/// held in the @c Element object it would have to conditionally +/// call this object's accessors to return the value of the +/// appropriate type. This would however fail to compile because +/// the compiler would check for all possible value types returned +/// by the @c Element accessors and report an error for those that +/// don't cast to the returned type. +/// +/// This class provides a mechanism to extract the value of the +/// appropriate type from the @c Element object within the +/// template function. It comes with a number of class specializations +/// for various data types to be returned. The default implementation +/// calls @c Element::intValue and casts it to the returned type. +/// There are class specializations for @c double, @c bool and +/// @c string. +/// +/// @tparam T Type of the value to be extracted. +template +class ElementValue { +public: + + /// @brief Function operator extracting an @c Element value as + /// integer. + /// + /// @param el Element holding a value to be extracted. + T operator()(ConstElementPtr el) const { + return (static_cast(el->intValue())); + } +}; + +/// @brief The @c ElementValue specialization for double. +template<> +class ElementValue { +public: + + /// @brief Function operator extracting an @c Element value as + /// double. + /// + /// @param el Element holding a value to be extracted. + double operator()(ConstElementPtr el) const { + return (el->doubleValue()); + } +}; + +/// @brief The @c ElementValue specialization for boolean. +template<> +class ElementValue { +public: + + /// @brief Function operator extracting an @c Element value as + /// boolean. + /// + /// @param el Element holding a value to be extracted. + bool operator()(ConstElementPtr el) const { + return (el->boolValue()); + } + +}; + +/// @brief The @c ElementValue specialization for string. +template<> +class ElementValue { +public: + + /// @brief Function operator extracting an @c Element value as + /// string. + /// + /// @param el Element holding a value to be extracted. + std::string operator()(ConstElementPtr el) const { + return (el->stringValue()); + } +}; + +/// @brief The @c ElementValue specialization for IOAddress. +template<> +class ElementValue { +public: + + /// @brief Function operator extracting an @c Element value as + /// IOAddress. + /// + /// @note This does NOT support empty string value. + /// + /// @param el Element holding a value to be extracted. + asiolink::IOAddress operator()(ConstElementPtr el) const { + return (asiolink::IOAddress(el->stringValue())); + } +}; + +} // end of namespace isc::data +} // end of namespace isc + +#endif // ELEMENT_VALUE_H diff --git a/src/lib/cc/json_feed.cc b/src/lib/cc/json_feed.cc new file mode 100644 index 0000000..7cd1e31 --- /dev/null +++ b/src/lib/cc/json_feed.cc @@ -0,0 +1,563 @@ +// 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 + +#include +#include +#include + +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace config { + +const int JSONFeed::RECEIVE_START_ST; +const int JSONFeed::WHITESPACE_BEFORE_JSON_ST; +const int JSONFeed::EOL_COMMENT_BEFORE_JSON_ST; +const int JSONFeed::START_COMMENT_BEFORE_JSON_ST; +const int JSONFeed::C_COMMENT_BEFORE_JSON_ST; +const int JSONFeed::STOP_COMMENT_BEFORE_JSON_ST; +const int JSONFeed::JSON_START_ST; +const int JSONFeed::INNER_JSON_ST; +const int JSONFeed::STRING_JSON_ST; +const int JSONFeed::ESCAPE_JSON_ST; +const int JSONFeed::EOL_COMMENT_ST; +const int JSONFeed::START_COMMENT_ST; +const int JSONFeed::C_COMMENT_ST; +const int JSONFeed::STOP_COMMENT_ST; +const int JSONFeed::JSON_END_ST; +const int JSONFeed::FEED_OK_ST; +const int JSONFeed::FEED_FAILED_ST; + +const int JSONFeed::DATA_READ_OK_EVT; +const int JSONFeed::NEED_MORE_DATA_EVT; +const int JSONFeed::MORE_DATA_PROVIDED_EVT; +const int JSONFeed::FEED_OK_EVT; +const int JSONFeed::FEED_FAILED_EVT; + +JSONFeed::JSONFeed() + : StateModel(), buffer_(), data_ptr_(0), error_message_(), open_scopes_(0), + output_() { +} + +void +JSONFeed::initModel() { + // Initialize dictionaries of events and states. + initDictionaries(); + + // Set the current state to starting state and enter the run loop. + setState(RECEIVE_START_ST); + + // Parsing starts from here. + postNextEvent(START_EVT); +} + +void +JSONFeed::poll() { + try { + // Process the input data until no more data is available or until + // JSON feed ends with matching closing brace. + do { + getState(getCurrState())->run(); + + } while (!isModelDone() && (getNextEvent() != NOP_EVT) && + (getNextEvent() != NEED_MORE_DATA_EVT)); + } catch (const std::exception& ex) { + abortModel(ex.what()); + } +} + +bool +JSONFeed::needData() const { + return ((getNextEvent() == NEED_MORE_DATA_EVT) || + (getNextEvent() == START_EVT)); +} + +bool +JSONFeed::feedOk() const { + return ((getNextEvent() == END_EVT) && + (getLastEvent() == FEED_OK_EVT)); +} + +ElementPtr +JSONFeed::toElement() const { + if (needData()) { + isc_throw(JSONFeedError, "unable to retrieve the data form the" + " JSON feed while parsing hasn't finished"); + } + try { + return (Element::fromWire(output_)); + + } catch (const std::exception& ex) { + isc_throw(JSONFeedError, ex.what()); + } +} + +void +JSONFeed::postBuffer(const void* buf, const size_t buf_size) { + if (buf_size > 0) { + // The next event is NEED_MORE_DATA_EVT when the parser wants to + // signal that more data is needed. This method is called to supply + // more data and thus it should change the next event to + // MORE_DATA_PROVIDED_EVT. + if (getNextEvent() == NEED_MORE_DATA_EVT) { + transition(getCurrState(), MORE_DATA_PROVIDED_EVT); + } + buffer_.assign(static_cast(buf), + static_cast(buf) + buf_size); + data_ptr_ = 0; + } +} + +void +JSONFeed::defineEvents() { + StateModel::defineEvents(); + + // Define JSONFeed specific events. + defineEvent(DATA_READ_OK_EVT, "DATA_READ_OK_EVT"); + defineEvent(NEED_MORE_DATA_EVT, "NEED_MORE_DATA_EVT"); + defineEvent(MORE_DATA_PROVIDED_EVT, "MORE_DATA_PROVIDED_EVT"); + defineEvent(FEED_OK_EVT, "FEED_OK_EVT"); + defineEvent(FEED_FAILED_EVT, "FEED_FAILED_EVT"); +} + +void +JSONFeed::verifyEvents() { + StateModel::verifyEvents(); + + getEvent(DATA_READ_OK_EVT); + getEvent(NEED_MORE_DATA_EVT); + getEvent(MORE_DATA_PROVIDED_EVT); + getEvent(FEED_OK_EVT); + getEvent(FEED_FAILED_EVT); +} + +void +JSONFeed::defineStates() { + // Call parent class implementation first. + StateModel::defineStates(); + + defineState(RECEIVE_START_ST, "RECEIVE_START_ST", + std::bind(&JSONFeed::receiveStartHandler, this)); + defineState(WHITESPACE_BEFORE_JSON_ST, "WHITESPACE_BEFORE_JSON_ST", + std::bind(&JSONFeed::whiteSpaceBeforeJSONHandler, this)); + defineState(EOL_COMMENT_BEFORE_JSON_ST, "EOL_COMMENT_BEFORE_JSON_ST", + std::bind(&JSONFeed::eolCommentBeforeJSONHandler, this)); + defineState(START_COMMENT_BEFORE_JSON_ST, "START_COMMENT_BEFORE_JSON_ST", + std::bind(&JSONFeed::startCommentBeforeJSONHandler, this)); + defineState(C_COMMENT_BEFORE_JSON_ST, "C_COMMENT_BEFORE_JSON_ST", + std::bind(&JSONFeed::cCommentBeforeJSONHandler, this)); + defineState(STOP_COMMENT_BEFORE_JSON_ST, "STOP_COMMENT_BEFORE_JSON_ST", + std::bind(&JSONFeed::stopCommentBeforeJSONHandler, this)); + defineState(INNER_JSON_ST, "INNER_JSON_ST", + std::bind(&JSONFeed::innerJSONHandler, this)); + defineState(STRING_JSON_ST, "STRING_JSON_ST", + std::bind(&JSONFeed::stringJSONHandler, this)); + defineState(ESCAPE_JSON_ST, "ESCAPE_JSON_ST", + std::bind(&JSONFeed::escapeJSONHandler, this)); + defineState(EOL_COMMENT_ST, "EOL_COMMENT_ST", + std::bind(&JSONFeed::eolCommentHandler, this)); + defineState(START_COMMENT_ST, "START_COMMENT_ST", + std::bind(&JSONFeed::startCommentHandler, this)); + defineState(C_COMMENT_ST, "C_COMMENT_ST", + std::bind(&JSONFeed::cCommentHandler, this)); + defineState(STOP_COMMENT_ST, "STOP_COMMENT_ST", + std::bind(&JSONFeed::stopCommentHandler, this)); + defineState(JSON_END_ST, "JSON_END_ST", + std::bind(&JSONFeed::endJSONHandler, this)); +} + +void +JSONFeed::feedFailure(const std::string& error_msg) { + error_message_ = error_msg; + transition(FEED_FAILED_ST, FEED_FAILED_EVT); +} + +void +JSONFeed::onModelFailure(const std::string& explanation) { + if (error_message_.empty()) { + error_message_ = explanation; + } +} + +bool +JSONFeed::popNextFromBuffer(char& next) { + // If there are any characters in the buffer, pop next. + if (!buffer_.empty() && (data_ptr_ < buffer_.size())) { + next = buffer_[data_ptr_++]; + return (true); + } + return (false); +} + +char +JSONFeed::getNextFromBuffer() { + unsigned int ev = getNextEvent(); + char c = '\0'; + // The caller should always provide additional data when the + // NEED_MORE_DATA_EVT occurs. If the next event is still + // NEED_MORE_DATA_EVT it indicates that the caller hasn't provided + // the data. + if (ev == NEED_MORE_DATA_EVT) { + isc_throw(JSONFeedError, + "JSONFeed requires new data to progress, but no data" + " have been provided. The transaction is aborted to avoid" + " a deadlock."); + + } else { + // Try to pop next character from the buffer. + const bool data_exist = popNextFromBuffer(c); + if (!data_exist) { + // There is no more data so it is really not possible that we're + // at MORE_DATA_PROVIDED_EVT. + if (ev == MORE_DATA_PROVIDED_EVT) { + isc_throw(JSONFeedError, + "JSONFeed state indicates that new data have been" + " provided to be parsed, but the transaction buffer" + " contains no new data."); + + } else { + // If there is no more data we should set NEED_MORE_DATA_EVT + // event to indicate that new data should be provided. + transition(getCurrState(), NEED_MORE_DATA_EVT); + } + } + } + return (c); +} + +void +JSONFeed::invalidEventError(const std::string& handler_name, + const unsigned int event) { + isc_throw(JSONFeedError, handler_name << ": invalid event " + << getEventLabel(static_cast(event))); +} + +void +JSONFeed::receiveStartHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (getNextEvent()) { + case START_EVT: + switch (c) { + case '\t': + case '\n': + case '\r': + case ' ': + transition(WHITESPACE_BEFORE_JSON_ST, DATA_READ_OK_EVT); + return; + + case '#': + transition(EOL_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT); + return; + + case '/': + transition(START_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT); + return; + + case '{': + case '[': + output_.push_back(c); + ++open_scopes_; + transition(INNER_JSON_ST, DATA_READ_OK_EVT); + return; + + // Cannot start by a string + case '"': + default: + feedFailure("invalid first character " + std::string(1, c)); + break; + } + break; + + default: + invalidEventError("receiveStartHandler", getNextEvent()); + } + } +} + +void +JSONFeed::whiteSpaceBeforeJSONHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '\t': + case '\n': + case '\r': + case ' ': + transition(getCurrState(), DATA_READ_OK_EVT); + break; + + case '#': + transition(EOL_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT); + return; + + case '/': + transition(START_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT); + return; + + case '{': + case '[': + output_.push_back(c); + ++open_scopes_; + transition(INNER_JSON_ST, DATA_READ_OK_EVT); + break; + + // Cannot start by a string + case '"': + default: + feedFailure("invalid character " + std::string(1, c)); + } + } +} + +void +JSONFeed::eolCommentBeforeJSONHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '\n': + transition(WHITESPACE_BEFORE_JSON_ST, DATA_READ_OK_EVT); + break; + + default: + postNextEvent(DATA_READ_OK_EVT); + break; + } + } +} + +void +JSONFeed::startCommentBeforeJSONHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '/': + transition(EOL_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT); + break; + + case '*': + transition(C_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT); + break; + + default: + feedFailure("invalid characters /" + std::string(1, c)); + } + } +} + +void +JSONFeed::cCommentBeforeJSONHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '*': + transition(STOP_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT); + break; + + default: + postNextEvent(DATA_READ_OK_EVT); + break; + } + } +} + +void +JSONFeed::stopCommentBeforeJSONHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '/': + transition(WHITESPACE_BEFORE_JSON_ST, DATA_READ_OK_EVT); + break; + + case '*': + transition(getCurrState(), DATA_READ_OK_EVT); + break; + + default: + transition(C_COMMENT_BEFORE_JSON_ST, DATA_READ_OK_EVT); + break; + } + } +} + +void +JSONFeed::innerJSONHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch(c) { + case '{': + case '[': + output_.push_back(c); + transition(getCurrState(), DATA_READ_OK_EVT); + ++open_scopes_; + break; + + case '}': + case ']': + output_.push_back(c); + if (--open_scopes_ == 0) { + transition(JSON_END_ST, FEED_OK_EVT); + + } else { + postNextEvent(DATA_READ_OK_EVT); + } + break; + + case '"': + output_.push_back(c); + transition(STRING_JSON_ST, DATA_READ_OK_EVT); + break; + + case '#': + transition(EOL_COMMENT_ST, DATA_READ_OK_EVT); + break; + + case '/': + transition(START_COMMENT_ST, DATA_READ_OK_EVT); + break; + + default: + output_.push_back(c); + postNextEvent(DATA_READ_OK_EVT); + } + } +} + +void +JSONFeed::stringJSONHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + output_.push_back(c); + + switch(c) { + case '"': + transition(INNER_JSON_ST, DATA_READ_OK_EVT); + break; + + case '\\': + transition(ESCAPE_JSON_ST, DATA_READ_OK_EVT); + break; + + default: + transition(getCurrState(), DATA_READ_OK_EVT); + } + } +} + +void +JSONFeed::escapeJSONHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + output_.push_back(c); + + transition(STRING_JSON_ST, DATA_READ_OK_EVT); + } +} + +void +JSONFeed::eolCommentHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '\n': + output_.push_back(c); + transition(INNER_JSON_ST, DATA_READ_OK_EVT); + break; + + default: + postNextEvent(DATA_READ_OK_EVT); + break; + } + } +} + +void +JSONFeed::startCommentHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '/': + transition(EOL_COMMENT_ST, DATA_READ_OK_EVT); + break; + + case '*': + transition(C_COMMENT_ST, DATA_READ_OK_EVT); + break; + + default: + feedFailure("invalid characters /" + std::string(1, c)); + } + } +} + +void +JSONFeed::cCommentHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '*': + transition(STOP_COMMENT_ST, DATA_READ_OK_EVT); + break; + + case '\n': + output_.push_back(c); + postNextEvent(DATA_READ_OK_EVT); + break; + + default: + postNextEvent(DATA_READ_OK_EVT); + break; + } + } +} + +void +JSONFeed::stopCommentHandler() { + char c = getNextFromBuffer(); + if (getNextEvent() != NEED_MORE_DATA_EVT) { + switch (c) { + case '/': + transition(INNER_JSON_ST, DATA_READ_OK_EVT); + break; + + case '*': + transition(getCurrState(), DATA_READ_OK_EVT); + break; + + case '\n': + output_.push_back(c); + transition(C_COMMENT_ST, DATA_READ_OK_EVT); + break; + + default: + transition(C_COMMENT_ST, DATA_READ_OK_EVT); + break; + } + } +} + +void +JSONFeed::endJSONHandler() { + switch (getNextEvent()) { + case FEED_OK_EVT: + transition(END_ST, END_EVT); + break; + + case FEED_FAILED_EVT: + abortModel("reading into JSON feed failed"); + break; + + default: + invalidEventError("endJSONHandler", getNextEvent()); + } +} + +} // end of namespace config +} // end of namespace isc diff --git a/src/lib/cc/json_feed.h b/src/lib/cc/json_feed.h new file mode 100644 index 0000000..abef6b0 --- /dev/null +++ b/src/lib/cc/json_feed.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 JSON_FEED_H +#define JSON_FEED_H + +#include +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace config { + +class JSONFeed; + +/// @brief Pointer to the @ref JSONFeed. +typedef boost::shared_ptr JSONFeedPtr; + +/// @brief Pointer to the const @ref JSONFeed. +typedef boost::shared_ptr ConstJSONFeedPtr; + +/// @brief A generic exception thrown upon an error in the @ref JSONFeed. +class JSONFeedError : public Exception { +public: + JSONFeedError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief State model for asynchronous read of data in JSON format. +/// +/// Kea control channel uses stream sockets for forwarding commands received +/// by the Kea Control Agent to respective Kea services. The responses may +/// contain large amounts of data (e.g. lease queries may return thousands +/// of leases). Such responses rarely fit into a single data buffer and +/// require multiple calls to receive/read or asynchronous receive/read. +/// +/// A receiver performing multiple reads from a socket must be able to +/// locate the boundaries of the command within the data stream. The +/// @ref JSONFeed state model solves this problem. +/// +/// When the partial data is read from the stream socket it should be provided +/// to the @ref JSONFeed using @ref JSONFeed::postBuffer and then the +/// @ref JSONFeed::poll should be called to start processing the received +/// data. The actual JSON structure can be preceded by whitespaces. When first +/// occurrence of one of the '{' or '[' characters is found in the stream it is +/// considered a beginning of the JSON structure. The model includes an internal +/// counter of new '{' and '[' occurrences. The counter increases one of these +/// characters is found. When any of the '}' or ']' is found, the counter +/// is decreased. When the counter is decreased to 0 it indicates that the +/// entire JSON structure has been received and processed. +/// +/// As '{', '}', '[' and ']' can be embedded in JSON strings two states +/// for strings and escape in strings are required. Note the processing +/// of escapes is greatly simplified compared to ECMA 404 figure 5. +/// +/// Added support for '#' to end of line (bash), '//' to end of line (C++) +/// and '/*' to '*/' (C) comments both before JSON and inside JSON. + +/// Note that this mechanism doesn't check if the JSON structure is well +/// formed. It merely detects the end of the JSON structure if this structure +/// is well formed. The structure is validated when @ref JSONFeed::toElement +/// is called to retrieve the data structures encapsulated with +/// @ref isc::data::Element objects. +class JSONFeed : public util::StateModel { +public: + + /// @name States supported by the @ref JSONFeed + /// + //@{ + + /// @brief State indicating a beginning of a feed. + static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 1; + + /// @brief Skipping whitespaces before actual JSON. + static const int WHITESPACE_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 2; + + /// @brief Skipping an end-of-line comment before actual JSON. + static const int EOL_COMMENT_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 3; + + /// @brief Starting one of the comments beginning with a slash before actual JSON. + static const int START_COMMENT_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 4; + + /// @brief Skipping a C style comment before actual JSON. + static const int C_COMMENT_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 5; + + /// @brief Stopping a C style comment before actual JSON. + static const int STOP_COMMENT_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 6; + + /// @brief Found first opening brace or square bracket. + static const int JSON_START_ST = SM_DERIVED_STATE_MIN + 7; + + /// @brief Parsing JSON. + static const int INNER_JSON_ST = SM_DERIVED_STATE_MIN + 8; + + /// @brief Parsing JSON string. + static const int STRING_JSON_ST = SM_DERIVED_STATE_MIN + 9; + + /// @brief JSON escape next character. + static const int ESCAPE_JSON_ST = SM_DERIVED_STATE_MIN + 10; + + /// @brief Skipping an end-of-line comment. + static const int EOL_COMMENT_ST = SM_DERIVED_STATE_MIN + 11; + + /// @brief Starting one of the comments beginning with a slash. + static const int START_COMMENT_ST = SM_DERIVED_STATE_MIN + 12; + + /// @brief Skipping a C style comment. + static const int C_COMMENT_ST = SM_DERIVED_STATE_MIN + 13; + + /// @brief Stopping a C style comment. + static const int STOP_COMMENT_ST = SM_DERIVED_STATE_MIN + 14; + + /// @brief Found last closing brace or square bracket. + static const int JSON_END_ST = SM_DERIVED_STATE_MIN + 15; + + /// @brief Found opening and closing brace or square bracket. + /// + /// This doesn't however indicate that the JSON is well formed. It + /// only means that matching closing brace or square bracket was + /// found. + static const int FEED_OK_ST = SM_DERIVED_STATE_MIN + 100; + + /// @brief Invalid syntax detected. + /// + /// For example, non matching braces or invalid characters found. + static const int FEED_FAILED_ST = SM_DERIVED_STATE_MIN + 101; + + //@} + + + /// @name Events used during data processing. + /// + //@{ + + /// @brief Chunk of data successfully read and parsed. + static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1; + + /// @brief Unable to proceed with parsing until new data is provided. + static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2; + + /// @brief New data provided and parsing should continue. + static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3; + + /// @brief Found opening brace and the matching closing brace. + static const int FEED_OK_EVT = SM_DERIVED_EVENT_MIN + 100; + + /// @brief Invalid syntax detected. + static const int FEED_FAILED_EVT = SM_DERIVED_EVENT_MIN + 101; + + //@} + + /// @brief Constructor. + JSONFeed(); + + /// @brief Initializes state model. + /// + /// Initializes events and states. It sets the model to @c RECEIVE_START_ST + /// and the next event to @c START_EVT. + void initModel(); + + /// @brief Runs the model as long as data is available. + /// + /// It processes the input data character by character until it reaches the + /// end of the input buffer, in which case it returns. The next event is set + /// to @c NEED_MORE_DATA_EVT to indicate the need for providing additional + /// data using @ref JSONFeed::postBuffer. This function also returns when + /// the end of the JSON structure has been detected or when an error has + /// occurred. + void poll(); + + /// @brief Checks if the model needs additional data to continue. + /// + /// The caller can use this method to check if the model expects additional + /// data to be provided to finish processing input data. + /// + /// @return true if more data is needed, false otherwise. + bool needData() const; + + /// @brief Checks if the data have been successfully processed. + bool feedOk() const; + + /// @brief Returns error string when data processing has failed. + std::string getErrorMessage() const { + return (error_message_); + } + + /// @brief Returns the text parsed into the buffer. + std::string getProcessedText() const { + return (output_); + } + + /// @brief Returns processed data as a structure of @ref isc::data::Element + /// objects. + /// + /// @throw JSONFeedError if the received JSON is not well formed. + data::ElementPtr toElement() const; + + /// @brief Receives additional data read from a data stream. + /// + /// A caller invokes this method to pass additional chunk of data received + /// from the stream. + /// + /// @param buf Pointer to a buffer holding additional input data. + /// @param buf_size Size of the data in the input buffer. + void postBuffer(const void* buf, const size_t buf_size); + + +private: + + /// @brief Make @ref runModel private to make sure that the caller uses + /// @ref poll method instead. + using StateModel::runModel; + + /// @brief Define events used by the feed. + virtual void defineEvents(); + + /// @brief Verifies events used by the feed. + virtual void verifyEvents(); + + /// @brief Defines states of the feed. + virtual void defineStates(); + + /// @brief Transition to failure state. + /// + /// This method transitions the model to @ref FEED_FAILED_ST and + /// sets next event to FEED_FAILED_EVT. + /// + /// @param error_msg Error message explaining the failure. + void feedFailure(const std::string& error_msg); + + /// @brief A method called when state model fails. + /// + /// @param explanation Error message explaining the reason for failure. + virtual void onModelFailure(const std::string& explanation); + + /// @brief Retrieves next byte of data from the buffer. + /// + /// During normal operation, when there is no more data in the buffer, + /// the NEED_MORE_DATA_EVT is set as next event to signal the need for + /// calling @ref JSONFeed::postBuffer. + /// + /// @throw JSONFeedError If current event is already set to + /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it + /// indicates that the caller failed to provide new data using + /// @ref JSONFeed::postBuffer. The latter case is highly unlikely + /// as it indicates that no new data were provided but the state of the + /// parser was changed from NEED_MORE_DATA_EVT or the data were provided + /// but the data buffer is empty. In both cases, it is a programming + /// error. + char getNextFromBuffer(); + + /// @brief This method is called when invalid event occurred in a particular + /// state. + /// + /// This method simply throws @ref JSONFeedError informing about invalid + /// event occurring for the particular state. The error message includes + /// the name of the handler in which the exception has been thrown. + /// It also includes the event which caused the exception. + /// + /// @param handler_name Name of the handler in which the exception is + /// thrown. + /// @param event An event which caused the exception. + /// + /// @throw JSONFeedError. + void invalidEventError(const std::string& handler_name, + const unsigned int event); + + /// @brief Tries to read next byte from buffer. + /// + /// @param [out] next A reference to the variable where read data should be + /// stored. + /// + /// @return true if character was successfully read, false otherwise. + bool popNextFromBuffer(char& next); + + /// @name State handlers. + /// + //@{ + + /// @brief Handler for RECEIVE_START_ST. + void receiveStartHandler(); + + /// @brief Handler for WHITESPACE_BEFORE_JSON_ST. + void whiteSpaceBeforeJSONHandler(); + + /// @brief Handler for EOL_COMMENT_BEFORE_JSON_ST. + void eolCommentBeforeJSONHandler(); + + /// @brief Handler for START_COMMENT_BEFORE_JSON_ST. + void startCommentBeforeJSONHandler(); + + /// @brief Handler for C_COMMENT_BEFORE_JSON_ST. + void cCommentBeforeJSONHandler(); + + /// @brief Handler for STOP_COMMENT_BEFORE_JSON_ST. + void stopCommentBeforeJSONHandler(); + + /// @brief Handler for the FIRST_BRACE_ST. + void innerJSONHandler(); + + /// @brief Handler for the STRING_JSON_ST. + void stringJSONHandler(); + + /// @brief Handler for the ESCAPE_JSON_ST; + void escapeJSONHandler(); + + /// @brief Handler for EOL_COMMENT_ST. + void eolCommentHandler(); + + /// @brief Handler for START_COMMENT_ST. + void startCommentHandler(); + + /// @brief Handler for C_COMMENT_ST. + void cCommentHandler(); + + /// @brief Handler for STOP_COMMENT_ST. + void stopCommentHandler(); + + /// @brief Handler for the JSON_END_ST. + void endJSONHandler(); + + //@} + + /// @brief Internal buffer from which the feed reads data. + std::vector buffer_; + + /// @brief Holds pointer to the next byte in the buffer to be read. + size_t data_ptr_; + + /// @brief Error message set by @ref onModelFailure. + std::string error_message_; + + /// @brief A counter increased when '{' or '[' is found and decreased when + /// '}' or ']' is found in the stream. + uint64_t open_scopes_; + + /// @brief Holds processed data. + std::string output_; +}; + +} // end of namespace config +} // end of namespace isc + +#endif // JSON_FEED_H diff --git a/src/lib/cc/server_tag.cc b/src/lib/cc/server_tag.cc new file mode 100644 index 0000000..80e5184 --- /dev/null +++ b/src/lib/cc/server_tag.cc @@ -0,0 +1,53 @@ +// Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include + +namespace isc { +namespace data { + +std::string ServerTag::ALL = "all"; + +ServerTag::ServerTag() + : tag_(ALL) { +} + +ServerTag::ServerTag(const std::string& tag) + : tag_(util::str::trim(tag)) { + if (tag_.empty()) { + isc_throw(BadValue, "server-tag must not be empty"); + + } else if (tag_.length() > 256) { + isc_throw(BadValue, "server-tag must not be longer than 256 characters"); + } + + boost::algorithm::to_lower(tag_); + + // ANY has a defined meaning for server selector and must not be used as + // a server tag. + if (tag_ == "any") { + isc_throw(BadValue, "'any' is reserved and must not be used as a server-tag"); + } +} + +bool +ServerTag::amAll() const { + return (tag_ == ALL); +} + +std::ostream& +operator<<(std::ostream& os, const ServerTag& server_tag) { + os << server_tag.get(); + return (os); +} + +} // end of namespace isc::data +} // end of namespace isc diff --git a/src/lib/cc/server_tag.h b/src/lib/cc/server_tag.h new file mode 100644 index 0000000..6f80015 --- /dev/null +++ b/src/lib/cc/server_tag.h @@ -0,0 +1,82 @@ +// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SERVER_TAG_H +#define SERVER_TAG_H + +#include + +namespace isc { +namespace data { + +/// @brief Represents a server tag. +/// +/// The server tag is a label identifying a server or all servers which +/// configuration is stored in the database. The label "all" is reserved +/// and it means "all servers". A configuration object in the database +/// associated with this server tag belongs to all servers. The server +/// tag must not be empty, must not be longer than 256 characters +/// (excluding leading and terminating whitespaces, which are trimmed) +/// and must not be set to "any" which has a special meaning for the +/// server selector. +class ServerTag { +public: + + /// @brief Server tag for all servers as text. + static std::string ALL; + + /// @brief Default constructor. + /// + /// Creates server tag for all servers. + ServerTag(); + + /// @brief Constructor. + /// + /// @param tag server tag provided as string. The tag is converted to + /// lower case. + explicit ServerTag(const std::string& tag); + + /// @brief Checks if the server tag is set to "all servers". + /// + /// @return true if the server tag is set to all servers, false + /// otherwise. + bool amAll() const; + + /// @brief Returns server tag as string. + /// + /// @return lower case server tag. + std::string get() const { + return (tag_); + } + + /// @brief Overload of the less operator for using @c ServerTag in sets. + /// + /// @param other other server tag to compare to. + /// @return true if this server tag is less than the other server tag. + bool operator<(const ServerTag& other) const { + return (tag_ < other.tag_); + } + +private: + + /// @brief Holds server tag as string. + std::string tag_; +}; + +/// @brief Insert the @c ServerTag as a string into stream. +/// +/// @param os stream to insert server tag into. +/// @param server_tag server tag to be converted to text and +/// inserted into a stream. +/// @return Reference to the stream object with inserted server +/// tag. +std::ostream& +operator<<(std::ostream& os, const ServerTag& server_tag); + +} // end of namespace isc::data +} // end of namespace isc + +#endif // SERVER_TAG_H diff --git a/src/lib/cc/simple_parser.cc b/src/lib/cc/simple_parser.cc new file mode 100644 index 0000000..8140d13 --- /dev/null +++ b/src/lib/cc/simple_parser.cc @@ -0,0 +1,369 @@ +// Copyright (C) 2016-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 + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace isc::asiolink; +using namespace isc::dhcp; +using isc::dhcp::DhcpConfigError; + +namespace isc { +namespace data { + +void +SimpleParser::checkRequired(const SimpleRequiredKeywords& required, + ConstElementPtr scope) { + for (auto name : required) { + if (scope->contains(name)) { + continue; + } + isc_throw(DhcpConfigError, "missing '" << name << "' parameter"); + } +} + +void +SimpleParser::checkKeywords(const SimpleKeywords& keywords, + ConstElementPtr scope) { + string spurious; + for (auto entry : scope->mapValue()) { + if (keywords.count(entry.first) == 0) { + if (spurious.empty()) { + spurious = entry.first; + } + continue; + } + Element::types expected = keywords.at(entry.first); + if ((expected == Element::any) || + (entry.second->getType() == expected)) { + continue; + } + isc_throw(DhcpConfigError, "'" << entry.first << "' parameter is not " + << (expected == Element::integer ? "an " : "a ") + << Element::typeToName(expected)); + } + if (!spurious.empty()) { + isc_throw(DhcpConfigError, "spurious '" << spurious << "' parameter"); + } +} + +std::string +SimpleParser::getString(ConstElementPtr scope, const std::string& name) { + ConstElementPtr x = scope->get(name); + if (!x) { + isc_throw(DhcpConfigError, + "missing parameter '" << name << "' (" + << scope->getPosition() << ")"); + } + if (x->getType() != Element::string) { + isc_throw(DhcpConfigError, + "invalid type specified for parameter '" << name + << "' (" << x->getPosition() << ")"); + } + + return (x->stringValue()); +} + +int64_t +SimpleParser::getInteger(ConstElementPtr scope, const std::string& name) { + ConstElementPtr x = scope->get(name); + if (!x) { + isc_throw(DhcpConfigError, + "missing parameter '" << name << "' (" + << scope->getPosition() << ")"); + } + if (x->getType() != Element::integer) { + isc_throw(DhcpConfigError, + "invalid type specified for parameter '" << name + << "' (" << x->getPosition() << ")"); + } + + return (x->intValue()); +} + +int64_t +SimpleParser::getInteger(isc::data::ConstElementPtr scope, const std::string& name, + int64_t min, int64_t max) { + int64_t tmp = getInteger(scope, name); + if (tmp < min || tmp > max) { + isc_throw(OutOfRange, + "The '" << name << "' value (" << tmp + << ") is not within expected range: (" << min << " - " << max + << ")"); + } + + return (tmp); +} + +bool +SimpleParser::getBoolean(ConstElementPtr scope, const std::string& name) { + ConstElementPtr x = scope->get(name); + if (!x) { + isc_throw(DhcpConfigError, + "missing parameter '" << name << "' (" + << scope->getPosition() << ")"); + } + if (x->getType() != Element::boolean) { + isc_throw(DhcpConfigError, + "invalid type specified for parameter '" << name + << "' (" << x->getPosition() << ")"); + } + + return (x->boolValue()); +} + +IOAddress +SimpleParser::getAddress(const ConstElementPtr& scope, + const std::string& name) { + std::string str = getString(scope, name); + try { + return (IOAddress(str)); + } catch (const std::exception& e) { + isc_throw(DhcpConfigError, "Failed to convert '" << str + << "' to address: " << e.what() << "(" + << getPosition(name, scope) << ")"); + } +} + +double +SimpleParser::getDouble(const ConstElementPtr& scope, + const std::string& name) { + ConstElementPtr x = scope->get(name); + if (!x) { + isc_throw(DhcpConfigError, + "missing parameter '" << name << "' (" + << scope->getPosition() << ")"); + } + + if (x->getType() != Element::real) { + isc_throw(DhcpConfigError, + "invalid type specified for parameter '" << name + << "' (" << x->getPosition() << ")"); + } + + return (x->doubleValue()); +} + + +const data::Element::Position& +SimpleParser::getPosition(const std::string& name, const data::ConstElementPtr parent) { + if (!parent) { + return (data::Element::ZERO_POSITION()); + } + ConstElementPtr elem = parent->get(name); + if (!elem) { + return (parent->getPosition()); + } + + return (elem->getPosition()); +} + +size_t SimpleParser::setDefaults(ElementPtr scope, + const SimpleDefaults& default_values) { + size_t cnt = 0; + + // This is the position representing a default value. As the values + // we're inserting here are not present in whatever the config file + // came from, we need to make sure it's clearly labeled as default. + const Element::Position pos("", 0, 0); + + // Let's go over all parameters we have defaults for. + BOOST_FOREACH(SimpleDefault def_value, default_values) { + + // Try if such a parameter is there. If it is, let's + // skip it, because user knows best *cough*. + ConstElementPtr x = scope->get(string(def_value.name_)); + if (x) { + // There is such a value already, skip it. + continue; + } + + // There isn't such a value defined, let's create the default + // value... + switch (def_value.type_) { + case Element::string: { + x.reset(new StringElement(def_value.value_, pos)); + break; + } + case Element::integer: { + try { + int int_value = boost::lexical_cast(def_value.value_); + x.reset(new IntElement(int_value, pos)); + } + catch (const std::exception& ex) { + isc_throw(BadValue, "Internal error. Integer value expected for: " + << def_value.name_ << ", value is: " + << def_value.value_ ); + } + + break; + } + case Element::boolean: { + bool bool_value; + if (def_value.value_ == string("true")) { + bool_value = true; + } else if (def_value.value_ == string("false")) { + bool_value = false; + } else { + isc_throw(DhcpConfigError, + "Internal error. Boolean value specified as " + << def_value.value_ << ", expected true or false"); + } + x.reset(new BoolElement(bool_value, pos)); + break; + } + case Element::real: { + double dbl_value = boost::lexical_cast(def_value.value_); + x.reset(new DoubleElement(dbl_value, pos)); + break; + } + default: + // No default values for null, list or map + isc_throw(DhcpConfigError, + "Internal error. Incorrect default value type."); + } + + // ... and insert it into the provided Element tree. + scope->set(def_value.name_, x); + ++cnt; + } + + return (cnt); +} + +size_t +SimpleParser::setListDefaults(ConstElementPtr list, + const SimpleDefaults& default_values) { + size_t cnt = 0; + BOOST_FOREACH(ElementPtr entry, list->listValue()) { + cnt += setDefaults(entry, default_values); + } + return (cnt); +} + +size_t +SimpleParser::deriveParams(ConstElementPtr parent, + ElementPtr child, + const ParamsList& params) { + if ( (parent->getType() != Element::map) || + (child->getType() != Element::map)) { + return (0); + } + + size_t cnt = 0; + BOOST_FOREACH(string param, params) { + ConstElementPtr x = parent->get(param); + if (!x) { + // Parent doesn't define this parameter, so there's + // nothing to derive from + continue; + } + + if (child->get(param)) { + // Child defines this parameter already. There's + // nothing to do here. + continue; + } + + // Copy the parameters to the child scope. + child->set(param, x); + cnt++; + } + + return (cnt); +} + +const util::Triplet +SimpleParser::parseIntTriplet(const ConstElementPtr& scope, + const std::string& name) { + // Initialize as some compilers complain otherwise. + uint32_t value = 0; + bool has_value = false; + uint32_t min_value = 0; + bool has_min = false; + uint32_t max_value = 0; + bool has_max = false; + if (scope->contains(name)) { + value = getInteger(scope, name); + has_value = true; + } + if (scope->contains("min-" + name)) { + min_value = getInteger(scope, "min-" + name); + has_min = true; + } + if (scope->contains("max-" + name)) { + max_value = getInteger(scope, "max-" + name); + has_max = true; + } + if (!has_value && !has_min && !has_max) { + return (util::Triplet()); + } + if (has_value) { + if (!has_min && !has_max) { + // default only. + min_value = value; + max_value = value; + } else if (!has_min) { + // default and max. + min_value = value; + } else if (!has_max) { + // default and min. + max_value = value; + } + } else if (has_min) { + // min only. + if (!has_max) { + value = min_value; + max_value = min_value; + } else { + // min and max. + isc_throw(DhcpConfigError, "have min-" << name << " and max-" + << name << " but no " << name << " (default) in " + << scope->getPosition()); + } + } else { + // max only. + min_value = max_value; + value = max_value; + } + // Check that min <= max. + if (min_value > max_value) { + if (has_min && has_max) { + isc_throw(DhcpConfigError, "the value of min-" << name << " (" + << min_value << ") is not less than max-" << name << " (" + << max_value << ")"); + } else if (has_min) { + // Only min and default so min > default. + isc_throw(DhcpConfigError, "the value of min-" << name << " (" + << min_value << ") is not less than (default) " << name + << " (" << value << ")"); + } else { + // Only default and max so default > max. + isc_throw(DhcpConfigError, "the value of (default) " << name + << " (" << value << ") is not less than max-" << name + << " (" << max_value << ")"); + } + } + // Check that value is between min and max. + if ((value < min_value) || (value > max_value)) { + isc_throw(DhcpConfigError, "the value of (default) " << name << " (" + << value << ") is not between min-" << name << " (" + << min_value << ") and max-" << name << " (" + << max_value << ")"); + } + + return (util::Triplet(min_value, value, max_value)); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/cc/simple_parser.h b/src/lib/cc/simple_parser.h new file mode 100644 index 0000000..cb78802 --- /dev/null +++ b/src/lib/cc/simple_parser.h @@ -0,0 +1,339 @@ +// Copyright (C) 2016-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef SIMPLE_PARSER_H +#define SIMPLE_PARSER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace data { + +/// This array defines a single entry of default values. +struct SimpleDefault { + SimpleDefault(const char* name, isc::data::Element::types type, const char* value) + :name_(name), type_(type), value_(value) {} + std::string name_; + const isc::data::Element::types type_; + const char* value_; +}; + +/// This specifies all required keywords. +typedef std::vector SimpleRequiredKeywords; + +/// This specifies all accepted keywords with their types. +typedef std::map SimpleKeywords; + +/// This specifies all default values in a given scope (e.g. a subnet). +typedef std::vector SimpleDefaults; + +/// This defines a list of all parameters that are derived (or inherited) between +/// contexts. +typedef std::vector ParamsList; + + +/// @brief A simple parser +/// +/// This class is intended to be a simpler replacement for DhcpConfigParser. +/// This class has been initially created to facilitate DHCPv4 and +/// DHCPv6 servers' configuration parsing. Thus examples provided +/// herein are related to DHCP configuration. Nevertheless, this is a +/// generic class to be used in other modules too. +/// +/// The simplification comes from several factors: +/// - no build/commit nonsense. There's a single step: +/// CfgStorage parse(ConstElementPtr json) +/// that converts JSON configuration into an object and returns it. +/// - no state kept. This greatly simplifies the parsers (no contexts, no child +/// parsers list, no separate storage for uint32, strings etc. In fact, +/// this base class is purely static. However, some derived classes may store +/// some state. Implementors are advised to store as little state as possible. +/// - no optional parameters (all are mandatory). This simplifies the parser, +/// but introduces a new step before parsing where we insert the default +/// values into client configuration before parsing. This is actually a good +/// thing, because we now have a clear picture of the default parameters as +/// they're defined in a single place (the DhcpConfigParser had the defaults +/// spread out in multiple files in multiple directories). +class SimpleParser { +public: + + /// @brief Checks that all required keywords are present. + /// + /// This method throws an exception when a required + /// entry is not present in the given scope. + /// + /// @param required Required keywords. + /// @param scope Specified parameters which are checked. + /// @throw DhcpConfigError if a required parameter is not present. + static void checkRequired(const SimpleRequiredKeywords& required, + isc::data::ConstElementPtr scope); + + /// @brief Checks acceptable keywords with their expected type. + /// + /// This methods throws an exception when a not acceptable keyword + /// is found or when an acceptable entry does not have the expected type. + /// + /// @param keywords The @c SimpleKeywords keywords and types map. + /// @param scope Specified parameters which are checked. + /// @throw DhcpConfigError if a not acceptable keyword is found. + /// @throw DhcpConfigError if an acceptable entry does not have + /// the expected type. + static void checkKeywords(const SimpleKeywords& keywords, + isc::data::ConstElementPtr scope); + + /// @brief Derives (inherits) parameters from parent scope to a child + /// + /// This method derives parameters from the parent scope to the child, + /// if there are no values specified in the child scope. For example, + /// this method can be used to derive timers from global scope (e.g. for + /// the whole DHCPv6 server) to a subnet scope. This method checks + /// if the child scope doesn't have more specific values defined. If + /// it doesn't, then the value from parent scope is copied over. + /// + /// @param parent scope to copy from (e.g. global) + /// @param child scope to copy from (e.g. subnet) + /// @param params names of the parameters to copy + /// @return number of parameters copied + static size_t deriveParams(isc::data::ConstElementPtr parent, + isc::data::ElementPtr child, + const ParamsList& params); + + /// @brief Sets the default values + /// + /// This method sets the default values for parameters that are not + /// defined. The list of default values is specified by default_values. + /// If not present, those will be inserted into the scope. If + /// a parameter is already present, the default value will not + /// be inserted. + /// + /// @param scope default values will be inserted here + /// @param default_values list of default values + /// @return number of parameters inserted + static size_t setDefaults(isc::data::ElementPtr scope, + const SimpleDefaults& default_values); + + /// @brief Sets the default values for all entries in a list + /// + /// This is a simple utility method that iterates over all + /// parameters in a list and calls setDefaults for each + /// entry. + /// + /// @param list list to be iterated over + /// @param default_values list of default values + /// @return number of parameters inserted + static size_t setListDefaults(isc::data::ConstElementPtr list, + const SimpleDefaults& default_values); + + /// @brief Utility method that returns position of an element + /// + /// It's mostly useful for logging. If the element is missing + /// the parent position is returned or ZERO_POSITION if parent + /// is null. + /// + /// @param name position of that element will be returned + /// @param parent parent element (optional) + /// @return position of the element specified. + static const data::Element::Position& + getPosition(const std::string& name, const data::ConstElementPtr parent); + + /// @brief Returns a string parameter from a scope + /// + /// Unconditionally returns a parameter. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return a string value of the parameter + /// @throw DhcpConfigError if the parameter is not there or is not of + /// appropriate type + static std::string getString(isc::data::ConstElementPtr scope, + const std::string& name); + + /// @brief Returns an integer parameter from a scope + /// + /// Unconditionally returns a parameter. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return an integer value of the parameter + /// @throw DhcpConfigError if the parameter is not there or is not of + /// appropriate type + static int64_t getInteger(isc::data::ConstElementPtr scope, + const std::string& name); + + /// @brief Returns an integer parameter from a scope and checks its range + /// + /// Unconditionally returns a parameter. Checks that the value specified + /// is in min =< X =< max range. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @param min minimum allowed value + /// @param max maximum allowed value + /// @return an integer value of the parameter + /// @throw DhcpConfigError if the parameter is not there or is not of + /// appropriate type. + /// @throw OutOfRange if the parameter is out of range + static int64_t getInteger(isc::data::ConstElementPtr scope, + const std::string& name, + int64_t min, int64_t max); + + /// @brief Returns a boolean parameter from a scope + /// + /// Unconditionally returns a parameter. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return a boolean value of the parameter + /// @throw DhcpConfigError if the parameter is not there or is not of + /// appropriate type + static bool getBoolean(isc::data::ConstElementPtr scope, + const std::string& name); + + + /// @brief Returns a IOAddress parameter from a scope + /// + /// Unconditionally returns a parameter. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return an IOAddress representing the value of the parameter + /// @throw DhcpConfigError if the parameter is not there or is not of + /// appropriate type (or its conversion to IOAddress fails due to not + /// being a proper address). + static isc::asiolink::IOAddress + getAddress(const ConstElementPtr& scope, const std::string& name); + + /// @brief Returns a floating point parameter from a scope + /// + /// Unconditionally returns a parameter. + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return a double value of the parameter + /// @throw DhcpConfigError if the parameter is not there or is not + /// an Element::real + static double getDouble(const ConstElementPtr& scope, + const std::string& name); + +protected: + + /// @brief Returns an integer value with range checking from a scope + /// + /// This template should be instantiated in parsers when useful + /// + /// @tparam int_type the integer type e.g. uint32_t + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter for error report + /// @return a value of int_type + /// @throw DhcpConfigError if the parameter is not there, is not of + /// appropriate type or is out of type value range + template int_type + getIntType(isc::data::ConstElementPtr scope, + const std::string& name) { + int64_t val_int = getInteger(scope, name); + if ((val_int < std::numeric_limits::min()) || + (val_int > std::numeric_limits::max())) { + isc_throw(isc::dhcp::DhcpConfigError, + "out of range value (" << val_int + << ") specified for parameter '" << name + << "' (" << getPosition(name, scope) << ")"); + } + return (static_cast(val_int)); + } + + /// @brief Returns a converted value from a scope + /// + /// This template should be instantiated in parsers when useful + /// + /// @tparam target_type the type of the result + /// @tparam convert the conversion function std::string -> target_type + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter for error report + /// @param type_name name of target_type for error report + /// @return a converted value of target_type + /// @throw DhcpConfigError if the parameter is not there, is not of + /// appropriate type or can not be converted + template target_type + getAndConvert(isc::data::ConstElementPtr scope, + const std::string& name, + const std::string& type_name) { + std::string str = getString(scope, name); + try { + return (convert(str)); + } catch (const std::exception&) { + isc_throw(isc::dhcp::DhcpConfigError, + "invalid " << type_name << " (" << str + << ") specified for parameter '" << name + << "' (" << getPosition(name, scope) << ")"); + } + } + +public: + /// @brief Returns a value converted to uint32_t + /// + /// Instantiation of getIntType() to uint32_t + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return an uint32_t value + /// @throw isc::dhcp::DhcpConfigError when it is not an uint32_t + uint32_t getUint32(isc::data::ConstElementPtr scope, + const std::string& name) { + return (getIntType(scope, name)); + } + + /// @brief Returns a value converted to uint16_t + /// + /// Instantiation of getIntType() to uint16_t + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return an uint16_t value + /// @throw isc::dhcp::DhcpConfigError when it is not an uint16_t + uint16_t getUint16(isc::data::ConstElementPtr scope, + const std::string& name) { + return (getIntType(scope, name)); + } + + /// @brief Get an uint8_t value + /// + /// Instantiation of getIntType() to uint8_t + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter + /// @return uint8_t value + /// @throw isc::dhcp::DhcpConfigError when it is not an uint8_t + uint8_t getUint8(ConstElementPtr scope, const std::string& name) { + return (getIntType(scope, name)); + } + + /// @brief Parses an integer triplet + /// + /// Parses an integer triplet parameter of the form: + /// + /// min-{name}, {name}, max-{name} + /// + /// @param scope Data element holding e.g. shared network configuration + /// to be parsed. + /// @param name Base name of the parameter. + /// @return A triplet with the parsed value. + const isc::util::Triplet parseIntTriplet(const data::ConstElementPtr& scope, + const std::string& name); +}; + +} +} + +#endif diff --git a/src/lib/cc/stamped_element.cc b/src/lib/cc/stamped_element.cc new file mode 100644 index 0000000..3c53b38 --- /dev/null +++ b/src/lib/cc/stamped_element.cc @@ -0,0 +1,50 @@ +// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include + +namespace isc { +namespace data { + +StampedElement::StampedElement() + : BaseStampedElement(), server_tags_() { +} + +bool +StampedElement::hasServerTag(const ServerTag& server_tag) const { + return (server_tags_.count(server_tag) > 0); +} + +void +StampedElement::delServerTag(const std::string& server_tag) { + if (!server_tags_.erase(ServerTag(server_tag))) { + isc_throw(NotFound, "can't find server tag '" << server_tag << "' to delete"); + } +} + +bool +StampedElement::hasAllServerTag() const { + return (hasServerTag(ServerTag(ServerTag::ALL))); +} + + +ElementPtr +StampedElement::getMetadata() const { + ElementPtr metadata = Element::createMap(); + ElementPtr tags = Element::createList(); + + for (auto server_tag : server_tags_) { + tags->add(Element::create(server_tag.get())); + } + + metadata->set("server-tags", tags); + return (metadata); +} + +} // end of namespace isc::data +} // end of namespace isc diff --git a/src/lib/cc/stamped_element.h b/src/lib/cc/stamped_element.h new file mode 100644 index 0000000..c676826 --- /dev/null +++ b/src/lib/cc/stamped_element.h @@ -0,0 +1,91 @@ +// Copyright (C) 2018-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 STAMPED_ELEMENT_H +#define STAMPED_ELEMENT_H + +#include +#include +#include + +namespace isc { +namespace data { + +/// @brief This class represents configuration element which is +/// associated with database identifier, modification timestamp +/// and servers. +/// +/// Classes storing Kea configuration should derive from this object +/// to track ids and modification times of the configuration objects. +/// This is specifically required by the Kea Configuration Backend +/// feature which stores and fetches configuration from the database. +/// The configuration elements must be accessible by their database +/// identifiers and modification times. +/// +/// @note This class is not derived from @c Element and should not +/// be confused with the classes being derived from @c Element class. +/// Those classes are used to represent JSON structures, whereas this +/// class represents data fetched from the database. +/// +/// @todo Find a better name for @c StampedElement. +class StampedElement : public BaseStampedElement { +public: + + /// @brief Constructor. + /// + /// Sets timestamp to the current time. + StampedElement(); + + /// @brief Adds new server tag. + /// + /// @param server_tag new server tag. + /// @throw BadValue if the server tag length exceeds 256 characters. + void setServerTag(const std::string& server_tag) { + server_tags_.insert(ServerTag(server_tag)); + } + + /// @brief Deletes server tag. + /// + /// Remove the first occurrence of the given server tag. + /// + /// @param server_tag server tag to delete. + /// @throw NotFound if the server tag cannot be found. + void delServerTag(const std::string& server_tag); + + /// @brief Returns server tags. + /// + /// @return Server tags. + std::set getServerTags() const { + return (server_tags_); + } + + /// @brief Checks if the element has the given server tag. + /// + /// @param server_tag Server tag to be found. + /// @return true if the server tag was found, false otherwise. + bool hasServerTag(const ServerTag& server_tag) const; + + /// @brief Checks if the element has 'all' server tag. + /// + /// @return true if the server tag was found, false otherwise. + bool hasAllServerTag() const; + + /// @brief Returns an object representing metadata to be returned + /// with objects from the configuration backend. + /// + /// @return Pointer to the metadata element. + isc::data::ElementPtr getMetadata() const; + +private: + + /// @brief Holds server tags. + std::set server_tags_; +}; + +} // end of namespace isc::data +} // end of namespace isc + +#endif diff --git a/src/lib/cc/stamped_value.cc b/src/lib/cc/stamped_value.cc new file mode 100644 index 0000000..077c688 --- /dev/null +++ b/src/lib/cc/stamped_value.cc @@ -0,0 +1,193 @@ +// Copyright (C) 2018-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include + +namespace isc { +namespace data { + +StampedValue::StampedValue(const std::string& name) + : StampedElement(), name_(name), value_() { +} + +StampedValue::StampedValue(const std::string& name, const ElementPtr& value) + : StampedElement(), name_(name), value_(value) { + validateConstruct(); +} + +StampedValue::StampedValue(const std::string& name, const std::string& value) + : StampedElement(), name_(name), value_(Element::create(value)) { + validateConstruct(); +} + +StampedValuePtr +StampedValue::create(const std::string& name) { + return (StampedValuePtr(new StampedValue(name))); +} + +StampedValuePtr +StampedValue::create(const std::string& name, const ElementPtr& value) { + return (StampedValuePtr(new StampedValue(name, value))); +} + +StampedValuePtr +StampedValue::create(const std::string& name, const std::string& value) { + return (StampedValuePtr(new StampedValue(name, value))); +} + +StampedValuePtr +StampedValue::create(const std::string& name, const std::string& value, + Element::types parameter_type) { + StampedValuePtr stamped_value; + + try { + switch (parameter_type) { + case Element::string: + stamped_value = StampedValue::create(name, value); + break; + + case Element::integer: + stamped_value = StampedValue::create(name, + Element::create(boost::lexical_cast(value))); + break; + + case Element::boolean: + // We only allow "1" and "0" as input to this function. + if ((value != "0") && (value != "1")) { + isc_throw(BadValue, "StampedValue: invalid value " << value + << " specified as boolean. Expected \"0\" or \"1\""); + } + stamped_value = StampedValue::create(name, + Element::create((value == "0") ? false : true)); + break; + + case Element::real: + stamped_value = StampedValue::create(name, + Element::create(boost::lexical_cast(value))); + break; + + default: + // Invalid data type provided as argument. + isc_throw(TypeError, "StampedValue: unsupported type '" + << Element::typeToName(parameter_type) + << " of the parameter '" << name); + } + + } catch (const boost::bad_lexical_cast& ex) { + // Failed to cast the value to a given type. + isc_throw(BadValue, "StampedValue: the value of the parameter '" + << Element::typeToName(parameter_type) + << "' can't be converted to " + << Element::typeToName(parameter_type) + << " type"); + } + + return (stamped_value); +} + +int +StampedValue::getType() const { + if (!value_) { + isc_throw(InvalidOperation, "StampedValue: attempt to retrieve the " + "type of the null value for the '" << name_ + << "' parameter"); + } + + return (value_->getType()); +} + +std::string +StampedValue::getValue() const { + validateAccess(Element::string); + + try { + switch (static_cast(value_->getType())) { + case Element::string: + return (value_->stringValue()); + case Element::integer: + return (boost::lexical_cast(value_->intValue())); + case Element::boolean: + return (value_->boolValue() ? "1" : "0"); + case Element::real: + { + std::string repr = + boost::lexical_cast(value_->doubleValue()); + if (repr.find_first_of('.') == std::string::npos) { + repr += ".0"; + } + return (repr); + } + default: + // Impossible condition. + isc_throw(TypeError, "StampedValue: invalid type of the '" + << name_ << "' parameter"); + } + + } catch (const boost::bad_lexical_cast& ex) { + isc_throw(BadValue, "StampedValue: unable to convert the value of " + "the parameter '" << name_ << "' to string"); + } + // unreachable + return (value_->stringValue()); +} + +int64_t +StampedValue::getIntegerValue() const { + validateAccess(Element::integer); + return (value_->intValue()); +} + +bool +StampedValue::getBoolValue() const { + validateAccess(Element::boolean); + return (value_->boolValue()); +} + +double +StampedValue::getDoubleValue() const { + validateAccess(Element::real); + return (value_->doubleValue()); +} + +void +StampedValue::validateConstruct() const { + if (!value_) { + isc_throw(BadValue, "StampedValue: provided value of the '" + << name_ << "' parameter is NULL"); + } + + if ((value_->getType() != Element::string) && + (value_->getType() != Element::integer) && + (value_->getType() != Element::boolean) && + (value_->getType() != Element::real)) { + isc_throw(TypeError, "StampedValue: provided value of the '" + << name_ << "' parameter has invalid type: " + << Element::typeToName(value_->getType())); + } +} + +void +StampedValue::validateAccess(Element::types type) const { + if (!value_) { + isc_throw(InvalidOperation, "StampedValue: attempt to get null value " + "of the '" << name_ << "' parameter"); + } + + if ((type != Element::string) && (type != value_->getType())) { + isc_throw(TypeError, "StampedValue: attempt to access a '" + << name_ << "' parameter as " << Element::typeToName(type) + << ", but this parameter has " + << Element::typeToName(value_->getType()) + << " type"); + } +} + +} // end of namespace isc::data +} // end of namespace isc diff --git a/src/lib/cc/stamped_value.h b/src/lib/cc/stamped_value.h new file mode 100644 index 0000000..2120593 --- /dev/null +++ b/src/lib/cc/stamped_value.h @@ -0,0 +1,237 @@ +// Copyright (C) 2018-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 STAMPED_VALUE_H +#define STAMPED_VALUE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace data { + +class StampedValue; + +/// @brief Pointer to the stamped value. +typedef boost::shared_ptr StampedValuePtr; + +/// @brief This class represents a named configuration parameter, +/// e.g. global parameter of the DHCP server. +/// +/// Global configuration elements having simple types, e.g. DHCP +/// timers, need to be associatied with modification timestamps. +/// This association is made by deriving from @c StampedElement. +/// The values can be strings, integers, booleans or real numbers. +/// +/// Because the strings are more flexible, configuration elements +/// are always held as strings in the configuration backends. This +/// class reflects a single value held in the database. The value +/// can be return in its orginal type or can be returned as a +/// string. Also the null values are allowed. +class StampedValue : public StampedElement { +public: + + /// @brief Constructor creating a null value. + /// + /// @param name Name of the value. + StampedValue(const std::string& name); + + /// @brief Constructor creating a value from the @c Element. + /// + /// @param name Name of the value. + /// @param value Value encapsulated in the @c Element object. + /// + /// @throw BadValue if the value is null. + /// @throw TypeError if the value is neither a string, integer, + /// bool nor real. + StampedValue(const std::string& name, const ElementPtr& value); + + /// @brief Constructor creating a string value. + /// + /// Creates stamped value from a string. + /// + /// @param name Name of the value. + /// @param value Value to be set. + StampedValue(const std::string& name, const std::string& value); + + /// @brief Factory function creating a null value. + /// + /// @param name Name of the value. + static StampedValuePtr create(const std::string& name); + + /// @brief Factory function creating a value from the @c Element. + /// + /// @param name Name of the value. + /// @param value Value encapsulated in the @c Element object. + /// + /// @throw BadValue if the value is null. + /// @throw TypeError if the value is neither a string, integer, + /// bool nor real. + static StampedValuePtr create(const std::string& name, + const ElementPtr& value); + + /// @brief Factory function creating a string value. + /// + /// Creates stamped value from a string. + /// + /// @param name Name of the value. + /// @param value Value to be set. + static StampedValuePtr create(const std::string& name, + const std::string& value); + + /// @brief Factory function which attempts to convert provided + /// string value to a given type. + /// + /// This factory function is useful in cases when the value is + /// read as a string from a database. The string value has to + /// be converted to the appropriate data type. The type is also + /// known from the database. + /// + /// @param name Name of the value. + /// @param value Value given as string to be converted. + /// @param type Type of the value to convert to. + static StampedValuePtr create(const std::string& name, + const std::string& value, + Element::types type); + + /// @brief Returns a type of the value. + /// + /// @return Type of the value as integer. It can be compared + /// with the @c Element::getType() output. + /// @throw InvalidOperation if the value is null. + int getType() const; + + /// @brief Returns value name. + /// + /// @return Value name. + std::string getName() const { + return (name_); + } + + /// @brief Returns value as string. + /// + /// It is allowed to call this function for all supported data + /// types. They are converted to a string. For example, a real + /// number of 1.4 will be returned as "1.4". The boolean true + /// value will be returned as "1" etc. + /// + /// @return Stored value as string. + /// @throw InvalidOperation if the value is null. + std::string getValue() const; + + /// @brief Checks if the value is null. + /// + /// @return true if the value is null, false otherwise. + bool amNull() const { + return (!value_); + } + + /// @brief Returns value as signed integer. + /// + /// @return Stored value as a signed integer. + /// @throw TypeError if the value is not of @c Element::integer + /// type. + int64_t getIntegerValue() const; + + /// @brief Returns value as a boolean. + /// + /// @return Stored value as a boolean. + /// @throw TypeError if the value is not of @c Element::boolean + /// type. + bool getBoolValue() const; + + /// @brief Returns value as a real number. + /// + /// @return Stored value as a real number. + /// @throw TypeError if the value is not of @c Element::real + /// type. + double getDoubleValue() const; + + /// @brief Returns the value as @c Element. + ConstElementPtr getElementValue() const { + return (value_); + } + +private: + + /// @brief Checks if the values passed to the constructors + /// were correct. + /// + /// This is called from the constructors. + /// + /// @throw BadValue if the value is null. + /// @throw TypeError if the value type is neither a string, + /// integer, boolean nor real. + void validateConstruct() const; + + /// @brief Checks if the value is accessed correctly. + /// + /// This is called from the accessors of this class. + /// + /// @param type Type of the value expected by the accessor + /// function. + /// + /// @throw InvalidOperation if the accessed value is null. + /// @throw TypeError if the expected type is not a string + /// and it doesn't match the value type. + void validateAccess(Element::types type) const; + + /// @brief Name of the value. + std::string name_; + + /// @brief Stored value. + ElementPtr value_; +}; + +/// @name Definition of the multi index container for @c StampedValue. +/// +//@{ + +/// @brief Tag for the index for access by value name. +struct StampedValueNameIndexTag { }; + +/// @brief Tag for the index for access by modification time. +struct StampedValueModificationTimeIndexTag { }; + +/// @brief Multi index container for @c StampedValue. +typedef boost::multi_index_container< + StampedValuePtr, + boost::multi_index::indexed_by< + // Index used to access value by name. + boost::multi_index::hashed_non_unique< + boost::multi_index::tag, + boost::multi_index::const_mem_fun< + StampedValue, + std::string, + &StampedValue::getName + > + >, + + // Index used to access value by modification time. + boost::multi_index::ordered_non_unique< + boost::multi_index::tag, + boost::multi_index::const_mem_fun< + BaseStampedElement, + boost::posix_time::ptime, + &BaseStampedElement::getModificationTime + > + > + > +> StampedValueCollection; + +//@} + +} // end of namespace isc::data +} // end of namespace isc + +#endif diff --git a/src/lib/cc/tests/Makefile.am b/src/lib/cc/tests/Makefile.am new file mode 100644 index 0000000..38f6e7a --- /dev/null +++ b/src/lib/cc/tests/Makefile.am @@ -0,0 +1,40 @@ +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +if USE_STATIC_LINK +AM_LDFLAGS = -static +endif + +CLEANFILES = *.gcno *.gcda + +TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +TESTS = +if HAVE_GTEST +TESTS += run_unittests +run_unittests_SOURCES = command_interpreter_unittests.cc +run_unittests_SOURCES += data_unittests.cc +run_unittests_SOURCES += data_file_unittests.cc +run_unittests_SOURCES += element_value_unittests.cc +run_unittests_SOURCES += json_feed_unittests.cc +run_unittests_SOURCES += server_tag_unittest.cc +run_unittests_SOURCES += simple_parser_unittest.cc +run_unittests_SOURCES += stamped_element_unittest.cc +run_unittests_SOURCES += stamped_value_unittest.cc +run_unittests_SOURCES += user_context_unittests.cc +run_unittests_SOURCES += run_unittests.cc +run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) + +run_unittests_LDADD = $(top_builddir)/src/lib/cc/libkea-cc.la +run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +run_unittests_LDADD += $(LOG4CPLUS_LIBS) $(BOOST_LIBS) $(GTEST_LDADD) + +endif + +noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/cc/tests/Makefile.in b/src/lib/cc/tests/Makefile.in new file mode 100644 index 0000000..5fb8c66 --- /dev/null +++ b/src/lib/cc/tests/Makefile.in @@ -0,0 +1,1071 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +TESTS = $(am__EXEEXT_1) +@HAVE_GTEST_TRUE@am__append_1 = run_unittests +noinst_PROGRAMS = $(am__EXEEXT_2) +subdir = src/lib/cc/tests +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp11.m4 \ + $(top_srcdir)/m4macros/ax_cpp20.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_netconf.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +@HAVE_GTEST_TRUE@am__EXEEXT_1 = run_unittests$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +PROGRAMS = $(noinst_PROGRAMS) +am__run_unittests_SOURCES_DIST = command_interpreter_unittests.cc \ + data_unittests.cc data_file_unittests.cc \ + element_value_unittests.cc json_feed_unittests.cc \ + server_tag_unittest.cc simple_parser_unittest.cc \ + stamped_element_unittest.cc stamped_value_unittest.cc \ + user_context_unittests.cc run_unittests.cc +@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = run_unittests-command_interpreter_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-data_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-data_file_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-element_value_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-json_feed_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-server_tag_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-simple_parser_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-stamped_element_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-stamped_value_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-user_context_unittests.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) +run_unittests_OBJECTS = $(am_run_unittests_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = \ +@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/unittests/libutil_unittests.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +run_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(run_unittests_LDFLAGS) $(LDFLAGS) \ + -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = \ + ./$(DEPDIR)/run_unittests-command_interpreter_unittests.Po \ + ./$(DEPDIR)/run_unittests-data_file_unittests.Po \ + ./$(DEPDIR)/run_unittests-data_unittests.Po \ + ./$(DEPDIR)/run_unittests-element_value_unittests.Po \ + ./$(DEPDIR)/run_unittests-json_feed_unittests.Po \ + ./$(DEPDIR)/run_unittests-run_unittests.Po \ + ./$(DEPDIR)/run_unittests-server_tag_unittest.Po \ + ./$(DEPDIR)/run_unittests-simple_parser_unittest.Po \ + ./$(DEPDIR)/run_unittests-stamped_element_unittest.Po \ + ./$(DEPDIR)/run_unittests-stamped_value_unittest.Po \ + ./$(DEPDIR)/run_unittests-user_context_unittests.Po +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(run_unittests_SOURCES) +DIST_SOURCES = $(am__run_unittests_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__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; \ +} +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@ +DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@ +DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_NETCONF = @HAVE_NETCONF@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@ +LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@ +LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@ +LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@ +LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@ +LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@ +LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@ +LIBYANG_LIBS = @LIBYANG_LIBS@ +LIBYANG_PREFIX = @LIBYANG_PREFIX@ +LIBYANG_VERSION = @LIBYANG_VERSION@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_PLUGINS_PATH = @SR_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@ +SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@ +SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@ +SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_PREFIX = @SYSREPO_PREFIX@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \ + $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) +@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static +CLEANFILES = *.gcno *.gcda +TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) +@HAVE_GTEST_TRUE@run_unittests_SOURCES = \ +@HAVE_GTEST_TRUE@ command_interpreter_unittests.cc \ +@HAVE_GTEST_TRUE@ data_unittests.cc data_file_unittests.cc \ +@HAVE_GTEST_TRUE@ element_value_unittests.cc \ +@HAVE_GTEST_TRUE@ json_feed_unittests.cc server_tag_unittest.cc \ +@HAVE_GTEST_TRUE@ simple_parser_unittest.cc \ +@HAVE_GTEST_TRUE@ stamped_element_unittest.cc \ +@HAVE_GTEST_TRUE@ stamped_value_unittest.cc \ +@HAVE_GTEST_TRUE@ user_context_unittests.cc run_unittests.cc +@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) +@HAVE_GTEST_TRUE@run_unittests_LDADD = \ +@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/unittests/libutil_unittests.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(LOG4CPLUS_LIBS) $(BOOST_LIBS) \ +@HAVE_GTEST_TRUE@ $(GTEST_LDADD) +all: all-am + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/cc/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/cc/tests/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +run_unittests$(EXEEXT): $(run_unittests_OBJECTS) $(run_unittests_DEPENDENCIES) $(EXTRA_run_unittests_DEPENDENCIES) + @rm -f run_unittests$(EXEEXT) + $(AM_V_CXXLD)$(run_unittests_LINK) $(run_unittests_OBJECTS) $(run_unittests_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-command_interpreter_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-data_file_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-data_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-element_value_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-json_feed_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-server_tag_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-simple_parser_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-stamped_element_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-stamped_value_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-user_context_unittests.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +run_unittests-command_interpreter_unittests.o: command_interpreter_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_interpreter_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-command_interpreter_unittests.Tpo -c -o run_unittests-command_interpreter_unittests.o `test -f 'command_interpreter_unittests.cc' || echo '$(srcdir)/'`command_interpreter_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_interpreter_unittests.Tpo $(DEPDIR)/run_unittests-command_interpreter_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_interpreter_unittests.cc' object='run_unittests-command_interpreter_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-command_interpreter_unittests.o `test -f 'command_interpreter_unittests.cc' || echo '$(srcdir)/'`command_interpreter_unittests.cc + +run_unittests-command_interpreter_unittests.obj: command_interpreter_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-command_interpreter_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-command_interpreter_unittests.Tpo -c -o run_unittests-command_interpreter_unittests.obj `if test -f 'command_interpreter_unittests.cc'; then $(CYGPATH_W) 'command_interpreter_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/command_interpreter_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-command_interpreter_unittests.Tpo $(DEPDIR)/run_unittests-command_interpreter_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='command_interpreter_unittests.cc' object='run_unittests-command_interpreter_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-command_interpreter_unittests.obj `if test -f 'command_interpreter_unittests.cc'; then $(CYGPATH_W) 'command_interpreter_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/command_interpreter_unittests.cc'; fi` + +run_unittests-data_unittests.o: data_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-data_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-data_unittests.Tpo -c -o run_unittests-data_unittests.o `test -f 'data_unittests.cc' || echo '$(srcdir)/'`data_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-data_unittests.Tpo $(DEPDIR)/run_unittests-data_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='data_unittests.cc' object='run_unittests-data_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-data_unittests.o `test -f 'data_unittests.cc' || echo '$(srcdir)/'`data_unittests.cc + +run_unittests-data_unittests.obj: data_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-data_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-data_unittests.Tpo -c -o run_unittests-data_unittests.obj `if test -f 'data_unittests.cc'; then $(CYGPATH_W) 'data_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/data_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-data_unittests.Tpo $(DEPDIR)/run_unittests-data_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='data_unittests.cc' object='run_unittests-data_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-data_unittests.obj `if test -f 'data_unittests.cc'; then $(CYGPATH_W) 'data_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/data_unittests.cc'; fi` + +run_unittests-data_file_unittests.o: data_file_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-data_file_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-data_file_unittests.Tpo -c -o run_unittests-data_file_unittests.o `test -f 'data_file_unittests.cc' || echo '$(srcdir)/'`data_file_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-data_file_unittests.Tpo $(DEPDIR)/run_unittests-data_file_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='data_file_unittests.cc' object='run_unittests-data_file_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-data_file_unittests.o `test -f 'data_file_unittests.cc' || echo '$(srcdir)/'`data_file_unittests.cc + +run_unittests-data_file_unittests.obj: data_file_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-data_file_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-data_file_unittests.Tpo -c -o run_unittests-data_file_unittests.obj `if test -f 'data_file_unittests.cc'; then $(CYGPATH_W) 'data_file_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/data_file_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-data_file_unittests.Tpo $(DEPDIR)/run_unittests-data_file_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='data_file_unittests.cc' object='run_unittests-data_file_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-data_file_unittests.obj `if test -f 'data_file_unittests.cc'; then $(CYGPATH_W) 'data_file_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/data_file_unittests.cc'; fi` + +run_unittests-element_value_unittests.o: element_value_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-element_value_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-element_value_unittests.Tpo -c -o run_unittests-element_value_unittests.o `test -f 'element_value_unittests.cc' || echo '$(srcdir)/'`element_value_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-element_value_unittests.Tpo $(DEPDIR)/run_unittests-element_value_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='element_value_unittests.cc' object='run_unittests-element_value_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-element_value_unittests.o `test -f 'element_value_unittests.cc' || echo '$(srcdir)/'`element_value_unittests.cc + +run_unittests-element_value_unittests.obj: element_value_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-element_value_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-element_value_unittests.Tpo -c -o run_unittests-element_value_unittests.obj `if test -f 'element_value_unittests.cc'; then $(CYGPATH_W) 'element_value_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/element_value_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-element_value_unittests.Tpo $(DEPDIR)/run_unittests-element_value_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='element_value_unittests.cc' object='run_unittests-element_value_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-element_value_unittests.obj `if test -f 'element_value_unittests.cc'; then $(CYGPATH_W) 'element_value_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/element_value_unittests.cc'; fi` + +run_unittests-json_feed_unittests.o: json_feed_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-json_feed_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-json_feed_unittests.Tpo -c -o run_unittests-json_feed_unittests.o `test -f 'json_feed_unittests.cc' || echo '$(srcdir)/'`json_feed_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-json_feed_unittests.Tpo $(DEPDIR)/run_unittests-json_feed_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='json_feed_unittests.cc' object='run_unittests-json_feed_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-json_feed_unittests.o `test -f 'json_feed_unittests.cc' || echo '$(srcdir)/'`json_feed_unittests.cc + +run_unittests-json_feed_unittests.obj: json_feed_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-json_feed_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-json_feed_unittests.Tpo -c -o run_unittests-json_feed_unittests.obj `if test -f 'json_feed_unittests.cc'; then $(CYGPATH_W) 'json_feed_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/json_feed_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-json_feed_unittests.Tpo $(DEPDIR)/run_unittests-json_feed_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='json_feed_unittests.cc' object='run_unittests-json_feed_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-json_feed_unittests.obj `if test -f 'json_feed_unittests.cc'; then $(CYGPATH_W) 'json_feed_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/json_feed_unittests.cc'; fi` + +run_unittests-server_tag_unittest.o: server_tag_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-server_tag_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-server_tag_unittest.Tpo -c -o run_unittests-server_tag_unittest.o `test -f 'server_tag_unittest.cc' || echo '$(srcdir)/'`server_tag_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-server_tag_unittest.Tpo $(DEPDIR)/run_unittests-server_tag_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_tag_unittest.cc' object='run_unittests-server_tag_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-server_tag_unittest.o `test -f 'server_tag_unittest.cc' || echo '$(srcdir)/'`server_tag_unittest.cc + +run_unittests-server_tag_unittest.obj: server_tag_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-server_tag_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-server_tag_unittest.Tpo -c -o run_unittests-server_tag_unittest.obj `if test -f 'server_tag_unittest.cc'; then $(CYGPATH_W) 'server_tag_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_tag_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-server_tag_unittest.Tpo $(DEPDIR)/run_unittests-server_tag_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='server_tag_unittest.cc' object='run_unittests-server_tag_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-server_tag_unittest.obj `if test -f 'server_tag_unittest.cc'; then $(CYGPATH_W) 'server_tag_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/server_tag_unittest.cc'; fi` + +run_unittests-simple_parser_unittest.o: simple_parser_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-simple_parser_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-simple_parser_unittest.Tpo -c -o run_unittests-simple_parser_unittest.o `test -f 'simple_parser_unittest.cc' || echo '$(srcdir)/'`simple_parser_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-simple_parser_unittest.Tpo $(DEPDIR)/run_unittests-simple_parser_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_parser_unittest.cc' object='run_unittests-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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-simple_parser_unittest.o `test -f 'simple_parser_unittest.cc' || echo '$(srcdir)/'`simple_parser_unittest.cc + +run_unittests-simple_parser_unittest.obj: simple_parser_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-simple_parser_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-simple_parser_unittest.Tpo -c -o run_unittests-simple_parser_unittest.obj `if test -f 'simple_parser_unittest.cc'; then $(CYGPATH_W) 'simple_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/simple_parser_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-simple_parser_unittest.Tpo $(DEPDIR)/run_unittests-simple_parser_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='simple_parser_unittest.cc' object='run_unittests-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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-simple_parser_unittest.obj `if test -f 'simple_parser_unittest.cc'; then $(CYGPATH_W) 'simple_parser_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/simple_parser_unittest.cc'; fi` + +run_unittests-stamped_element_unittest.o: stamped_element_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stamped_element_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-stamped_element_unittest.Tpo -c -o run_unittests-stamped_element_unittest.o `test -f 'stamped_element_unittest.cc' || echo '$(srcdir)/'`stamped_element_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stamped_element_unittest.Tpo $(DEPDIR)/run_unittests-stamped_element_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stamped_element_unittest.cc' object='run_unittests-stamped_element_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-stamped_element_unittest.o `test -f 'stamped_element_unittest.cc' || echo '$(srcdir)/'`stamped_element_unittest.cc + +run_unittests-stamped_element_unittest.obj: stamped_element_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stamped_element_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-stamped_element_unittest.Tpo -c -o run_unittests-stamped_element_unittest.obj `if test -f 'stamped_element_unittest.cc'; then $(CYGPATH_W) 'stamped_element_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stamped_element_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stamped_element_unittest.Tpo $(DEPDIR)/run_unittests-stamped_element_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stamped_element_unittest.cc' object='run_unittests-stamped_element_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-stamped_element_unittest.obj `if test -f 'stamped_element_unittest.cc'; then $(CYGPATH_W) 'stamped_element_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stamped_element_unittest.cc'; fi` + +run_unittests-stamped_value_unittest.o: stamped_value_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stamped_value_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-stamped_value_unittest.Tpo -c -o run_unittests-stamped_value_unittest.o `test -f 'stamped_value_unittest.cc' || echo '$(srcdir)/'`stamped_value_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stamped_value_unittest.Tpo $(DEPDIR)/run_unittests-stamped_value_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stamped_value_unittest.cc' object='run_unittests-stamped_value_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-stamped_value_unittest.o `test -f 'stamped_value_unittest.cc' || echo '$(srcdir)/'`stamped_value_unittest.cc + +run_unittests-stamped_value_unittest.obj: stamped_value_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-stamped_value_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-stamped_value_unittest.Tpo -c -o run_unittests-stamped_value_unittest.obj `if test -f 'stamped_value_unittest.cc'; then $(CYGPATH_W) 'stamped_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stamped_value_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-stamped_value_unittest.Tpo $(DEPDIR)/run_unittests-stamped_value_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='stamped_value_unittest.cc' object='run_unittests-stamped_value_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-stamped_value_unittest.obj `if test -f 'stamped_value_unittest.cc'; then $(CYGPATH_W) 'stamped_value_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/stamped_value_unittest.cc'; fi` + +run_unittests-user_context_unittests.o: user_context_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-user_context_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-user_context_unittests.Tpo -c -o run_unittests-user_context_unittests.o `test -f 'user_context_unittests.cc' || echo '$(srcdir)/'`user_context_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-user_context_unittests.Tpo $(DEPDIR)/run_unittests-user_context_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='user_context_unittests.cc' object='run_unittests-user_context_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-user_context_unittests.o `test -f 'user_context_unittests.cc' || echo '$(srcdir)/'`user_context_unittests.cc + +run_unittests-user_context_unittests.obj: user_context_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-user_context_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-user_context_unittests.Tpo -c -o run_unittests-user_context_unittests.obj `if test -f 'user_context_unittests.cc'; then $(CYGPATH_W) 'user_context_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/user_context_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-user_context_unittests.Tpo $(DEPDIR)/run_unittests-user_context_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='user_context_unittests.cc' object='run_unittests-user_context_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-user_context_unittests.obj `if test -f 'user_context_unittests.cc'; then $(CYGPATH_W) 'user_context_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/user_context_unittests.cc'; fi` + +run_unittests-run_unittests.o: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc + +run_unittests-run_unittests.obj: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +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 +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/run_unittests-command_interpreter_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-data_file_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-data_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-element_value_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-json_feed_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-server_tag_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-simple_parser_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-stamped_element_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-stamped_value_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-user_context_unittests.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/run_unittests-command_interpreter_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-data_file_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-data_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-element_value_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-json_feed_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-server_tag_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-simple_parser_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-stamped_element_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-stamped_value_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-user_context_unittests.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \ + check-am clean clean-generic clean-libtool \ + clean-noinstPROGRAMS cscopelist-am ctags ctags-am distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib/cc/tests/command_interpreter_unittests.cc b/src/lib/cc/tests/command_interpreter_unittests.cc new file mode 100644 index 0000000..8738b92 --- /dev/null +++ b/src/lib/cc/tests/command_interpreter_unittests.cc @@ -0,0 +1,249 @@ +// Copyright (C) 2009-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include + +#include +#include +#include + +#include + +#include + +using namespace isc::data; +using namespace isc::config; +using namespace std; + +namespace { + + +/// @brief Convenience method for creating elements from JSON string +/// +/// @param str string to be converted +/// @return Element structure +ElementPtr +el(const std::string& str) { + return (Element::fromJSON(str)); +} + +// This test verifies that that createAnswer method is able to generate +// various answers. +TEST(CommandInterpreterTest, createAnswer) { + ConstElementPtr answer; + + // By default the answer is a successful one. + answer = createAnswer(); + EXPECT_EQ("{ \"result\": 0 }", answer->str()); + + // Let's check if we can generate an error. + answer = createAnswer(CONTROL_RESULT_ERROR, "error"); + EXPECT_EQ("{ \"result\": 1, \"text\": \"error\" }", answer->str()); + + // This is expected to throw. When status code is non-zero (indicating error), + // textual explanation is mandatory. + EXPECT_THROW(createAnswer(CONTROL_RESULT_ERROR, ElementPtr()), CtrlChannelError); + EXPECT_THROW(createAnswer(CONTROL_RESULT_ERROR, Element::create(1)), CtrlChannelError); + + // Let's check if answer can be generate with some data in it. + ConstElementPtr arg = el("[ \"just\", \"some\", \"data\" ]"); + answer = createAnswer(CONTROL_RESULT_SUCCESS, arg); + EXPECT_EQ("{ \"arguments\": [ \"just\", \"some\", \"data\" ], \"result\": 0 }", + answer->str()); +} + +// This test checks whether parseAnswer is able to handle good and malformed +// answers. +TEST(CommandInterpreterTest, parseAnswer) { + ConstElementPtr answer; + ConstElementPtr arg; + int rcode; + + EXPECT_THROW(parseAnswer(rcode, ElementPtr()), CtrlChannelError); + EXPECT_THROW(parseAnswer(rcode, el("1")), CtrlChannelError); + EXPECT_THROW(parseAnswer(rcode, el("[]")), CtrlChannelError); + EXPECT_THROW(parseAnswer(rcode, el("{ }")), CtrlChannelError); + EXPECT_THROW(parseAnswer(rcode, el("{ \"something\": 1 }")), CtrlChannelError); + EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 0 ] }")), CtrlChannelError); + EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1 ] }")), CtrlChannelError); + EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1, 1 ] }")), CtrlChannelError); + + answer = el("{ \"result\": 0 }"); + arg = parseAnswer(rcode, answer); + EXPECT_EQ(0, rcode); + EXPECT_TRUE(isNull(arg)); + + answer = el("{ \"result\": 3, \"text\": \"error\", \"arguments\": [ \"some\", \"data\" ] }"); + arg = parseAnswer(rcode, answer); + ASSERT_TRUE(arg); + EXPECT_EQ(3, rcode); + EXPECT_EQ("[ \"some\", \"data\" ]", arg->str()); +} + +// Checks if parseAnswerText can return the text +TEST(CommandInterpreterTest, parseAnswerText) { + ConstElementPtr answer; + ConstElementPtr arg; + int rcode; + + answer = el("{ \"result\": 5, \"text\": \"error\", \"arguments\": [ \"some\", \"data\" ] }"); + arg = parseAnswerText(rcode, answer); + ASSERT_TRUE(arg); + EXPECT_EQ(5, rcode); + EXPECT_EQ("error", arg->stringValue()); +} + +// This checks whether we can convert an answer to easily printable form. +TEST(CommandInterpreterTest, answerToText) { + ConstElementPtr answer; + + // Doing jolly good here. + answer = el("{ \"result\": 0 }"); + EXPECT_EQ("success(0)", answerToText(answer)); + + // Sometimes things don't go according to plan. + answer = el("{ \"result\": 1, \"text\": \"ho lee fuk sum ting wong\" }"); + EXPECT_EQ("failure(1), text=ho lee fuk sum ting wong", answerToText(answer)); +} + +// This test checks whether createCommand function is able to create commands +// with and without parameters. +TEST(CommandInterpreterTest, createCommand) { + ConstElementPtr command; + ConstElementPtr arg; + string service; + + command = createCommand("my_command"); + ASSERT_EQ("{ \"command\": \"my_command\" }", command->str()); + + arg = el("1"); + command = createCommand("my_command", arg); + ASSERT_EQ("{ \"arguments\": 1, \"command\": \"my_command\" }", + command->str()); + + arg = el("[ \"a\", \"b\" ]"); + command = createCommand("my_cmd", arg); + ASSERT_EQ("{ \"arguments\": [ \"a\", \"b\" ], \"command\": \"my_cmd\" }", + command->str()); + + arg = el("{ \"a\": \"map\" }"); + command = createCommand("foo", arg); + ASSERT_EQ("{ \"arguments\": { \"a\": \"map\" }, \"command\": \"foo\" }", + command->str()); + + command = createCommand("my_command", "my_service"); + ASSERT_EQ("{ \"command\": \"my_command\", " + "\"service\": [ \"my_service\" ] }", + command->str()); + + arg = el("1"); + command = createCommand("my_command", arg, "my_service"); + ASSERT_EQ("{ \"arguments\": 1, \"command\": \"my_command\", " + "\"service\": [ \"my_service\" ] }", + command->str()); + + arg = el("[ \"a\", \"b\" ]"); + command = createCommand("my_cmd", arg, "my_server"); + ASSERT_EQ("{ \"arguments\": [ \"a\", \"b\" ], " + "\"command\": \"my_cmd\", " + "\"service\": [ \"my_server\" ] }", + command->str()); + + arg = el("{ \"a\": \"map\" }"); + command = createCommand("foo", arg, "bar"); + ASSERT_EQ("{ \"arguments\": { \"a\": \"map\" }, " + "\"command\": \"foo\", " + "\"service\": [ \"bar\" ] }", + command->str()); +} + +// This test checks whether parseCommand function is able to parse various valid +// and malformed commands. +TEST(CommandInterpreterTest, parseCommand) { + ConstElementPtr arg; + std::string cmd; + + // should throw + EXPECT_THROW(parseCommand(arg, ElementPtr()), CtrlChannelError); + EXPECT_THROW(parseCommand(arg, el("1")), CtrlChannelError); + EXPECT_THROW(parseCommand(arg, el("{ }")), CtrlChannelError); + EXPECT_THROW(parseCommand(arg, el("{ \"not a command\": 1 }")), CtrlChannelError); + EXPECT_THROW(parseCommand(arg, el("{ \"command\": 1 }")), CtrlChannelError); + EXPECT_THROW(parseCommand(arg, el("{ \"command\": [] }")), CtrlChannelError); + EXPECT_THROW(parseCommand(arg, el("{ \"command\": [ 1 ] }")), CtrlChannelError); + EXPECT_THROW(parseCommand(arg, el("{ \"command\": \"my_command\", " + "\"unknown\": \"xyz\" }")), CtrlChannelError); + + cmd = parseCommand(arg, el("{ \"command\": \"my_command\" }")); + EXPECT_EQ("my_command", cmd); + EXPECT_FALSE(arg); + + // Include "service" to verify that it is not rejected. + cmd = parseCommand(arg, el("{ \"command\": \"my_command\", \"arguments\": 1, " + " \"service\": [ \"dhcp4\" ] }")); + ASSERT_TRUE(arg); + EXPECT_EQ("my_command", cmd); + EXPECT_EQ("1", arg->str()); + + parseCommand(arg, el("{ \"command\": \"my_command\", \"arguments\": " + "[ \"some\", \"argument\", \"list\" ] }")); + EXPECT_EQ("my_command", cmd); + ASSERT_TRUE(arg); + EXPECT_EQ("[ \"some\", \"argument\", \"list\" ]", arg->str()); + +} + +// This test checks whether parseCommandWithArgs function is able to parse +// various valid and malformed commands. +TEST(CommandInterpreterTest, parseCommandWithArgs) { + ConstElementPtr arg; + std::string cmd; + + // Arguments are required. + EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\" }")), + CtrlChannelError); + + // Arguments must be a map. + EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " + "\"arguments\": [ 1, 2, 3 ] }")), + CtrlChannelError); + + // Arguments must not be empty. + EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " + "\"arguments\": { } }")), + CtrlChannelError); + + // Command with unsupported parameter is rejected. + EXPECT_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " + " \"arguments\": { \"arg1\": \"value1\" }, " + " \"unsupported\": 1 }")), + CtrlChannelError); + + + // Specifying arguments in non empty map should be successful. + EXPECT_NO_THROW( + cmd = parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " + " \"arguments\": { \"arg1\": \"value1\" } }")) + ); + ASSERT_TRUE(arg); + ASSERT_EQ(Element::map, arg->getType()); + auto arg1 = arg->get("arg1"); + ASSERT_TRUE(arg1); + ASSERT_EQ(Element::string, arg1->getType()); + EXPECT_EQ("value1", arg1->stringValue()); + EXPECT_EQ("my_command", cmd); + + // The "service" parameter should be allowed. + EXPECT_NO_THROW(parseCommandWithArgs(arg, el("{ \"command\": \"my_command\", " + " \"service\": [ \"dhcp4\" ], " + " \"arguments\": { \"arg1\": \"value1\" } }")) + ); + +} + +} diff --git a/src/lib/cc/tests/data_file_unittests.cc b/src/lib/cc/tests/data_file_unittests.cc new file mode 100644 index 0000000..a7f1b8d --- /dev/null +++ b/src/lib/cc/tests/data_file_unittests.cc @@ -0,0 +1,98 @@ +// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include + +using namespace isc; +using namespace isc::data; + +namespace { + +/// @brief Test class for testing Daemon class +class DataFileTest : public ::testing::Test { +public: + + /// @brief writes specified text to a file + /// + /// That is an auxiliary function used in fileRead() tests. + /// + /// @param content text to be written to disk + void writeFile(const std::string& content) { + // Write sample content to disk + static_cast(remove(TEMP_FILE)); + std::ofstream write_me(TEMP_FILE); + EXPECT_TRUE(write_me.is_open()); + write_me << content; + write_me.close(); + } + + /// destructor + ~DataFileTest() { + static_cast(remove(TEMP_FILE)); + } + + /// Name of the temporary file + static const char* TEMP_FILE; +}; + +/// Temporary file name used in some tests +const char* DataFileTest::TEMP_FILE="temp-file.json"; + +// Test checks whether a text file can be read from disk. +TEST_F(DataFileTest, readFileMultiline) { + + const char* no_endline = "{ \"abc\": 123 }"; + const char* with_endline = "{\n \"abc\":\n 123\n }\n"; + + // That's what we expect + ElementPtr exp = Element::fromJSON(no_endline); + + // Write sample content to disk + writeFile(no_endline); + + // Check that the read content is correct + EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE))); + + // Write sample content to disk + writeFile(with_endline); + + // Check that the read content is correct + EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE))); +} + +// Test checks whether comments in file are ignored as expected. +TEST_F(DataFileTest, readFileComments) { + const char* commented_content = "# This is a comment\n" + "{ \"abc\":\n" + "# a comment comment\n" + "1 }\n"; + + // That's what we expect + ElementPtr exp = Element::fromJSON("{ \"abc\": 1 }"); + + // Write sample content to disk + writeFile(commented_content); + + // Check that the read will fail (without comment elimination) + EXPECT_THROW(Element::fromJSONFile(TEMP_FILE), JSONError); + + // Check that the read content is correct (with comment elimination) + EXPECT_NO_THROW(Element::fromJSONFile(TEMP_FILE, true)); + EXPECT_TRUE(exp->equals(*Element::fromJSONFile(TEMP_FILE, true))); +} + +// This test checks that missing file will generate an exception. +TEST_F(DataFileTest, readFileError) { + + // Check that the read content is correct + EXPECT_THROW(Element::fromJSONFile("no-such-file.txt"), isc::InvalidOperation); +} + +}; diff --git a/src/lib/cc/tests/data_unittests.cc b/src/lib/cc/tests/data_unittests.cc new file mode 100644 index 0000000..ed23c45 --- /dev/null +++ b/src/lib/cc/tests/data_unittests.cc @@ -0,0 +1,2232 @@ +// Copyright (C) 2009-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace isc::data; + +#include +#include +using std::oct; +#include +using std::setfill; +using std::setw; +using std::string; + +namespace { + +TEST(Position, str) { + Element::Position position("kea.conf", 30, 20); + EXPECT_EQ("kea.conf:30:20", position.str()); + + Element::Position position2("another.conf", 123, 24); + EXPECT_EQ("another.conf:123:24", position2.str()); +} + +TEST(Element, type) { + // this tests checks whether the getType() function returns the + // correct type + IntElement int_el = IntElement(1); + EXPECT_EQ(int_el.getType(), Element::integer); + DoubleElement double_el = DoubleElement(1.0); + EXPECT_EQ(double_el.getType(), Element::real); + BoolElement bool_el = BoolElement(true); + EXPECT_EQ(bool_el.getType(), Element::boolean); + StringElement str_el = StringElement("foo"); + EXPECT_EQ(str_el.getType(), Element::string); + ListElement list_el = ListElement(); + EXPECT_EQ(list_el.getType(), Element::list); + MapElement map_el = MapElement(); + EXPECT_EQ(map_el.getType(), Element::map); + +} + +TEST(Element, TypeNameConversion) { + EXPECT_EQ(Element::integer, Element::nameToType("integer")); + EXPECT_EQ(Element::bigint, Element::nameToType("bigint")); + EXPECT_EQ(Element::real, Element::nameToType("real")); + EXPECT_EQ(Element::boolean, Element::nameToType("boolean")); + EXPECT_EQ(Element::string, Element::nameToType("string")); + EXPECT_EQ(Element::list, Element::nameToType("list")); + EXPECT_EQ(Element::map, Element::nameToType("map")); + EXPECT_EQ(Element::null, Element::nameToType("null")); + EXPECT_EQ(Element::any, Element::nameToType("any")); + EXPECT_THROW(Element::nameToType("somethingunknown"), TypeError); + + EXPECT_EQ("integer", Element::typeToName(Element::integer)); + EXPECT_EQ("bigint", Element::typeToName(Element::bigint)); + EXPECT_EQ("real", Element::typeToName(Element::real)); + EXPECT_EQ("boolean", Element::typeToName(Element::boolean)); + EXPECT_EQ("string", Element::typeToName(Element::string)); + EXPECT_EQ("list", Element::typeToName(Element::list)); + EXPECT_EQ("map", Element::typeToName(Element::map)); + EXPECT_EQ("null", Element::typeToName(Element::null)); + EXPECT_EQ("any", Element::typeToName(Element::any)); + EXPECT_EQ("unknown", Element::typeToName(static_cast(123))); +} + +TEST(Element, from_and_to_json) { + // a set of inputs that are the same when converted to json and + // back to a string (tests for inputs that have equivalent, but + // different string representations when converted back are below) + ConstElementPtr el; + std::vector sv; + + sv.push_back("12"); + sv.push_back("1.1"); + sv.push_back("true"); + sv.push_back("false"); + sv.push_back("\"asdf\""); + sv.push_back("null"); + sv.push_back("[ 1, 2, 3, 4 ]"); + sv.push_back("{ \"name\": \"foo\", \"value\": 56176 }"); + sv.push_back("[ { \"a\": 1, \"b\": \"c\" }, { \"a\": 2, \"b\": \"d\" } ]"); + sv.push_back("8.23"); + sv.push_back("123.456"); + sv.push_back("null"); + sv.push_back("-1"); + sv.push_back("-1.234"); + sv.push_back("-123.456"); + // We should confirm that our string handling is 8-bit clean. + // At one point we were using char-length data and comparing to EOF, + // which means that character '\xFF' would not parse properly. + sv.push_back("\"\\u00ff\""); + + BOOST_FOREACH(const std::string& s, sv) { + // Test two types of fromJSON(): with string and istream. + for (unsigned i = 0; i < 2; ++i) { + // test << operator, which uses Element::str() + if (i == 0) { + el = Element::fromJSON(s); + } else { + std::istringstream iss(s); + el = Element::fromJSON(iss); + } + std::ostringstream stream; + stream << *el; + EXPECT_EQ(s, stream.str()); + + // test toWire(ostream), which should also be the same now + std::ostringstream wire_stream; + el->toWire(wire_stream); + EXPECT_EQ(s, wire_stream.str()); + } + } + + // some parse errors + try { + Element::fromJSON("{1}"); + } catch (const isc::data::JSONError& pe) { + std::string s = std::string(pe.what()); + EXPECT_EQ("String expected in :1:3", s); + } + + sv.clear(); + sv.push_back("{1}"); + //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err"); + //std::cout << ep << std::endl; + sv.push_back("\n\nTrue"); + sv.push_back("\n\ntru"); + sv.push_back("{ \n \"aaa\nbbb\"err:"); + sv.push_back("{ \t\n \"aaa\nbbb\"\t\n\n:\n true, \"\\\""); + sv.push_back("{ \"a\": None}"); + sv.push_back(""); + sv.push_back("NULL"); + sv.push_back("nul"); + sv.push_back("hello\"foobar\""); + sv.push_back("\"foobar\"hello"); + sv.push_back("[]hello"); + sv.push_back("{}hello"); + // String not delimited correctly + sv.push_back("\"hello"); + sv.push_back("hello\""); + // Bad unicode + sv.push_back("\"\\u123\""); + sv.push_back("\"\\u1234\""); + sv.push_back("\"\\u0123\""); + sv.push_back("\"\\u00ag\""); + sv.push_back("\"\\u00BH\""); + + BOOST_FOREACH(std::string s, sv) { + EXPECT_THROW(el = Element::fromJSON(s), isc::data::JSONError); + } + + // some json specific format tests, here the str() output is + // different from the string input + // +100 is incorrect according to the ECMA 404 JSON standard. + // Keeping it as it will be reversed. + // EXPECT_EQ("100", Element::fromJSON("+100")->str()); + EXPECT_EQ("100.0", Element::fromJSON("1e2")->str()); + EXPECT_EQ("100.0", Element::fromJSON("+1e2")->str()); + EXPECT_EQ("-100.0", Element::fromJSON("-1e2")->str()); + + EXPECT_NO_THROW({ + EXPECT_EQ("9223372036854775807", Element::fromJSON("9223372036854775807")->str()); + }); + EXPECT_NO_THROW({ + EXPECT_EQ("-9223372036854775808", Element::fromJSON("-9223372036854775808")->str()); + }); + EXPECT_THROW({ + EXPECT_NE("9223372036854775808", Element::fromJSON("9223372036854775808")->str()); + }, JSONError); + + EXPECT_EQ("0.01", Element::fromJSON("1e-2")->str()); + EXPECT_EQ("0.01", Element::fromJSON(".01")->str()); + EXPECT_EQ("-0.01", Element::fromJSON("-1e-2")->str()); + EXPECT_EQ("1.2", Element::fromJSON("1.2")->str()); + EXPECT_EQ("1.0", Element::fromJSON("1.0")->str()); + EXPECT_EQ("120.0", Element::fromJSON("1.2e2")->str()); + EXPECT_EQ("100.0", Element::fromJSON("1.0e2")->str()); + EXPECT_EQ("100.0", Element::fromJSON("1.0E2")->str()); + EXPECT_EQ("0.01", Element::fromJSON("1.0e-2")->str()); + EXPECT_EQ("0.012", Element::fromJSON("1.2e-2")->str()); + EXPECT_EQ("0.012", Element::fromJSON("1.2E-2")->str()); + EXPECT_EQ("\"\"", Element::fromJSON(" \n \t \r \f \b \"\" \n \f \t \r \b")->str()); + EXPECT_EQ("{ }", Element::fromJSON("{ \n \r \t \b \f }")->str()); + EXPECT_EQ("[ ]", Element::fromJSON("[ \n \r \f \t \b ]")->str()); + + // number overflows + EXPECT_THROW(Element::fromJSON("12345678901234567890")->str(), JSONError); + EXPECT_THROW(Element::fromJSON("1.1e12345678901234567890")->str(), JSONError); + EXPECT_THROW(Element::fromJSON("-1.1e12345678901234567890")->str(), JSONError); + EXPECT_THROW(Element::fromJSON("1e12345678901234567890")->str(), JSONError); + EXPECT_THROW(Element::fromJSON("1e50000")->str(), JSONError); + // number underflow + // EXPECT_THROW(Element::fromJSON("1.1e-12345678901234567890")->str(), JSONError); + +} + +template +void +testGetValueInt() { + T el; + int64_t i; + int32_t i32; + uint32_t ui32; + long l; + long long ll; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::create(1); + EXPECT_NO_THROW({ + EXPECT_EQ(1, el->intValue()); + }); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_TRUE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ(1, i); + + el = Element::create(9223372036854775807LL); + EXPECT_NO_THROW({ + EXPECT_EQ(9223372036854775807LL, el->intValue()); + }); + EXPECT_TRUE(el->getValue(i)); + EXPECT_EQ(9223372036854775807LL, i); + + ll = 9223372036854775807LL; + el = Element::create(ll); + EXPECT_NO_THROW({ + EXPECT_EQ(ll, el->intValue()); + }); + EXPECT_TRUE(el->getValue(i)); + EXPECT_EQ(ll, i); + + i32 = 2147483647L; + el = Element::create(i32); + EXPECT_NO_THROW({ + EXPECT_EQ(i32, el->intValue()); + }); + EXPECT_TRUE(el->getValue(i)); + EXPECT_EQ(i32, i); + + ui32 = 4294967295L; + el = Element::create(ui32); + EXPECT_NO_THROW({ + EXPECT_EQ(ui32, el->intValue()); + }); + EXPECT_TRUE(el->getValue(i)); + EXPECT_EQ(ui32, i); + + l = 2147483647L; + el = Element::create(l); + EXPECT_NO_THROW({ + EXPECT_EQ(l, el->intValue()); + }); + EXPECT_TRUE(el->getValue(i)); + EXPECT_EQ(l, i); +} + +template +void +testGetValueDouble() { + T el; + int64_t i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::create(1.1); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_NO_THROW(el->doubleValue()); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_FALSE(el->getValue(i)); + EXPECT_TRUE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ(1.1, d); +} + +template +void +testGetValueBool() { + T el; + int64_t i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::create(true); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_NO_THROW(el->boolValue()); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_FALSE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_TRUE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ(true, b); +} + +template +void +testGetValueString() { + T el; + int64_t i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::create("foo"); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_NO_THROW(el->stringValue()); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_FALSE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_TRUE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ("foo", s); +} + +template +void +testGetValueList() { + T el; + int64_t i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::createList(); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_NO_THROW(el->listValue()); + EXPECT_THROW(el->mapValue(), TypeError); + EXPECT_FALSE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_TRUE(el->getValue(v)); + EXPECT_FALSE(el->getValue(m)); + EXPECT_EQ("[ ]", el->str()); +} + +template +void +testGetValueMap() { + T el; + int64_t i; + double d; + bool b; + std::string s; + std::vector v; + std::map m; + + el = Element::createMap(); + EXPECT_THROW(el->intValue(), TypeError); + EXPECT_THROW(el->doubleValue(), TypeError); + EXPECT_THROW(el->boolValue(), TypeError); + EXPECT_THROW(el->stringValue(), TypeError); + EXPECT_THROW(el->listValue(), TypeError); + EXPECT_NO_THROW(el->mapValue()); + EXPECT_FALSE(el->getValue(i)); + EXPECT_FALSE(el->getValue(d)); + EXPECT_FALSE(el->getValue(b)); + EXPECT_FALSE(el->getValue(s)); + EXPECT_FALSE(el->getValue(v)); + EXPECT_TRUE(el->getValue(m)); + EXPECT_EQ("{ }", el->str()); +} + +TEST(Element, create_and_value_throws) { + // this test checks whether elements throw exceptions if the + // incorrect type is requested + ElementPtr el; + ConstElementPtr cel; + int64_t i = 0; + double d = 0.0; + bool b = false; + std::string s("asdf"); + std::vector v; + std::map m; + ConstElementPtr tmp; + + testGetValueInt(); + testGetValueInt(); + + el = Element::create(1); + i = 2; + EXPECT_TRUE(el->setValue(i)); + EXPECT_EQ(2, el->intValue()); + EXPECT_FALSE(el->setValue(d)); + EXPECT_FALSE(el->setValue(b)); + EXPECT_FALSE(el->setValue(s)); + EXPECT_FALSE(el->setValue(v)); + EXPECT_FALSE(el->setValue(m)); + EXPECT_THROW(el->get(1), TypeError); + EXPECT_THROW(el->set(1, el), TypeError); + EXPECT_THROW(el->add(el), TypeError); + EXPECT_THROW(el->remove(1), TypeError); + EXPECT_THROW(el->size(), TypeError); + EXPECT_THROW(el->empty(), TypeError); + EXPECT_THROW(el->get("foo"), TypeError); + EXPECT_THROW(el->set("foo", el), TypeError); + EXPECT_THROW(el->remove("foo"), TypeError); + EXPECT_THROW(el->contains("foo"), TypeError); + EXPECT_FALSE(el->find("foo", tmp)); + + testGetValueDouble(); + testGetValueDouble(); + + el = Element::create(1.1); + d = 2.2; + EXPECT_TRUE(el->setValue(d)); + EXPECT_EQ(2.2, el->doubleValue()); + EXPECT_FALSE(el->setValue(i)); + EXPECT_FALSE(el->setValue(b)); + EXPECT_FALSE(el->setValue(s)); + EXPECT_FALSE(el->setValue(v)); + EXPECT_FALSE(el->setValue(m)); + EXPECT_THROW(el->get(1), TypeError); + EXPECT_THROW(el->set(1, el), TypeError); + EXPECT_THROW(el->add(el), TypeError); + EXPECT_THROW(el->remove(1), TypeError); + EXPECT_THROW(el->size(), TypeError); + EXPECT_THROW(el->empty(), TypeError); + EXPECT_THROW(el->get("foo"), TypeError); + EXPECT_THROW(el->set("foo", el), TypeError); + EXPECT_THROW(el->remove("foo"), TypeError); + EXPECT_THROW(el->contains("foo"), TypeError); + EXPECT_FALSE(el->find("foo", tmp)); + + testGetValueBool(); + testGetValueBool(); + + el = Element::create(true); + b = false; + EXPECT_TRUE(el->setValue(b)); + EXPECT_FALSE(el->boolValue()); + EXPECT_FALSE(el->setValue(i)); + EXPECT_FALSE(el->setValue(d)); + EXPECT_FALSE(el->setValue(s)); + EXPECT_FALSE(el->setValue(v)); + EXPECT_FALSE(el->setValue(m)); + EXPECT_THROW(el->get(1), TypeError); + EXPECT_THROW(el->set(1, el), TypeError); + EXPECT_THROW(el->add(el), TypeError); + EXPECT_THROW(el->remove(1), TypeError); + EXPECT_THROW(el->size(), TypeError); + EXPECT_THROW(el->empty(), TypeError); + EXPECT_THROW(el->get("foo"), TypeError); + EXPECT_THROW(el->set("foo", el), TypeError); + EXPECT_THROW(el->remove("foo"), TypeError); + EXPECT_THROW(el->contains("foo"), TypeError); + EXPECT_FALSE(el->find("foo", tmp)); + + testGetValueString(); + testGetValueString(); + + el = Element::create("foo"); + s = "bar"; + EXPECT_TRUE(el->setValue(s)); + EXPECT_EQ("bar", el->stringValue()); + EXPECT_FALSE(el->setValue(i)); + EXPECT_FALSE(el->setValue(b)); + EXPECT_FALSE(el->setValue(d)); + EXPECT_FALSE(el->setValue(v)); + EXPECT_FALSE(el->setValue(m)); + EXPECT_THROW(el->get(1), TypeError); + EXPECT_THROW(el->set(1, el), TypeError); + EXPECT_THROW(el->add(el), TypeError); + EXPECT_THROW(el->remove(1), TypeError); + EXPECT_THROW(el->size(), TypeError); + EXPECT_THROW(el->empty(), TypeError); + EXPECT_THROW(el->get("foo"), TypeError); + EXPECT_THROW(el->set("foo", el), TypeError); + EXPECT_THROW(el->remove("foo"), TypeError); + EXPECT_THROW(el->contains("foo"), TypeError); + EXPECT_FALSE(el->find("foo", tmp)); + + testGetValueList(); + testGetValueList(); + + el = Element::createList(); + EXPECT_TRUE(el->empty()); + v.push_back(Element::create(1)); + EXPECT_TRUE(el->setValue(v)); + EXPECT_FALSE(el->empty()); + EXPECT_EQ("[ 1 ]", el->str()); + + testGetValueMap(); + testGetValueMap(); + + el = Element::createMap(); + EXPECT_NO_THROW(el->set("foo", Element::create("bar"))); + EXPECT_EQ("{ \"foo\": \"bar\" }", el->str()); +} + +// Helper for escape check; it puts the given string in a StringElement, +// then checks for the following conditions: +// stringValue() must be same as input +// toJSON() output must be escaped +// fromJSON() on the previous output must result in original input +void +escapeHelper(const std::string& input, const std::string& expected) { + StringElement str_element = StringElement(input); + EXPECT_EQ(input, str_element.stringValue()); + std::stringstream os; + str_element.toJSON(os); + EXPECT_EQ(expected, os.str()); + ElementPtr str_element2 = Element::fromJSON(os.str()); + EXPECT_EQ(str_element.stringValue(), str_element2->stringValue()); +} + +TEST(Element, escape) { + // Test whether quotes are escaped correctly when creating direct + // String elements. + escapeHelper("foo\"bar", "\"foo\\\"bar\""); + escapeHelper("foo\\bar", "\"foo\\\\bar\""); + escapeHelper("foo\bbar", "\"foo\\bbar\""); + escapeHelper("foo\fbar", "\"foo\\fbar\""); + escapeHelper("foo\nbar", "\"foo\\nbar\""); + escapeHelper("foo\rbar", "\"foo\\rbar\""); + escapeHelper("foo\tbar", "\"foo\\tbar\""); + escapeHelper("foo\u001fbar", "\"foo\\u001fbar\""); + // Bad escapes + EXPECT_THROW(Element::fromJSON("\\a"), JSONError); + EXPECT_THROW(Element::fromJSON("\\"), JSONError); + // Can't have escaped quotes outside strings + EXPECT_THROW(Element::fromJSON("\\\"\\\""), JSONError); + // Unicode use lower u and 4 hexa, only 00 prefix is supported + EXPECT_THROW(Element::fromJSON("\\U0020"), JSONError); + EXPECT_THROW(Element::fromJSON("\\u002"), JSONError); + EXPECT_THROW(Element::fromJSON("\\u0123"), JSONError); + EXPECT_THROW(Element::fromJSON("\\u1023"), JSONError); + EXPECT_THROW(Element::fromJSON("\\u00ag"), JSONError); + EXPECT_THROW(Element::fromJSON("\\u00ga"), JSONError); + EXPECT_THROW(Element::fromJSON("\\u00BH"), JSONError); + EXPECT_THROW(Element::fromJSON("\\u00HB"), JSONError); + // Inside strings is OK + EXPECT_NO_THROW(Element::fromJSON("\"\\\"\\\"\"")); + // A whitespace test + EXPECT_NO_THROW(Element::fromJSON("\" \n \r \t \f \n \n \t\"")); + // Escape for forward slash is optional + ASSERT_NO_THROW(Element::fromJSON("\"foo\\/bar\"")); + EXPECT_EQ("foo/bar", Element::fromJSON("\"foo\\/bar\"")->stringValue()); + // Control characters + StringElement bell("foo\abar"); + EXPECT_EQ("\"foo\\u0007bar\"", bell.str()); + // 8 bit escape + StringElement ab("foo\253bar"); + EXPECT_EQ("\"foo\\u00abbar\"", ab.str()); + ASSERT_NO_THROW(Element::fromJSON("\"foo\\u00abbar\"")); + EXPECT_TRUE(ab.equals(*Element::fromJSON("\"foo\\u00abbar\""))); + ASSERT_NO_THROW(Element::fromJSON("\"foo\\u00ABbar\"")); + EXPECT_TRUE(ab.equals(*Element::fromJSON("\"foo\\u00ABbar\""))); + StringElement f1("foo\361bar"); + EXPECT_EQ("\"foo\\u00f1bar\"", f1.str()); + ASSERT_NO_THROW(Element::fromJSON("\"foo\\u00f1bar\"")); + EXPECT_TRUE(f1.equals(*Element::fromJSON("\"foo\\u00f1bar\""))); + ASSERT_NO_THROW(Element::fromJSON("\"foo\\u00F1bar\"")); + EXPECT_TRUE(f1.equals(*Element::fromJSON("\"foo\\u00F1bar\""))); +} + +// This test verifies that strings are copied. +TEST(Element, stringCopy) { + // StringElement constructor copies its string argument. + std::string foo = "foo"; + ElementPtr elem = ElementPtr(new StringElement(foo)); + EXPECT_EQ(foo, elem->stringValue()); + foo[1] = 'O'; + EXPECT_EQ("fOo", foo); + EXPECT_NE(foo, elem->stringValue()); + + // Map keys are copied too. + ElementPtr map = ElementPtr(new MapElement()); + std::string bar = "bar"; + map->set(bar, ElementPtr(new IntElement(1))); + ConstElementPtr item = map->get("bar"); + ASSERT_TRUE(item); + EXPECT_EQ(1, item->intValue()); + bar[0] = 'B'; + EXPECT_EQ("Bar", bar); + EXPECT_TRUE(map->get("bar")); + EXPECT_FALSE(map->get(bar)); +} + +// This test verifies that a backslash can be used in element content +// when the element is created using constructor. +TEST(Element, backslash1) { + string input = "SMSBoot\\x64";// One slash passed to elem constructor... + string exp = "SMSBoot\\x64"; // ... should result in one slash in the actual option. + + StringElement elem(input); + EXPECT_EQ(exp, elem.stringValue()); +} + +// This test verifies that a backslash can be used in element content +// when the element is created using fromJSON. +TEST(Element, backslash2) { + string input = "\"SMSBoot\\\\x64\""; // Two slashes put in the config file... + string exp = "SMSBoot\\x64"; // ... should result in one slash in the actual option. + + ElementPtr elem = Element::fromJSON(input); + EXPECT_EQ(exp, elem->stringValue()); +} + +TEST(Element, ListElement) { + // this function checks the specific functions for ListElements + ElementPtr el = Element::fromJSON("[ 1, \"bar\", 3 ]"); + EXPECT_EQ(el->get(0)->intValue(), 1); + EXPECT_EQ(el->get(1)->stringValue(), "bar"); + EXPECT_EQ(el->get(2)->intValue(), 3); + + el->set(0, Element::fromJSON("\"foo\"")); + EXPECT_EQ(el->get(0)->stringValue(), "foo"); + + el->add(Element::create(56176)); + EXPECT_EQ(el->get(3)->intValue(), 56176); + + el->remove(1); + el->remove(1); + EXPECT_EQ(el->str(), "[ \"foo\", 56176 ]"); + + // hmm, it errors on EXPECT_THROW(el->get(3), std::out_of_range) + EXPECT_ANY_THROW(el->get(3)); + + el->add(Element::create(32)); + EXPECT_EQ(32, el->get(2)->intValue()); + + // boundary condition tests for set() + el->set(2, Element::create(0)); // update the last entry of the list + EXPECT_EQ(0, el->get(2)->intValue()); + // attempt of set beyond the range of list should trigger an exception. + EXPECT_ANY_THROW(el->set(3, Element::create(0))); +} + +TEST(Element, MapElement) { + // this function checks the specific functions for ListElements + ElementPtr el = Element::fromJSON("{ \"name\": \"foo\", \"value1\": \"bar\", \"value2\": { \"number\": 42 } }"); + ConstElementPtr el2; + + EXPECT_EQ(el->get("name")->stringValue(), "foo"); + EXPECT_EQ(el->get("value2")->getType(), Element::map); + + EXPECT_TRUE(isNull(el->get("value3"))); + + EXPECT_FALSE(el->empty()); + + el->set("value3", Element::create(56176)); + EXPECT_EQ(el->get("value3")->intValue(), 56176); + + el->remove("value3"); + EXPECT_TRUE(isNull(el->get("value3"))); + + EXPECT_EQ(el->find("value2/number")->intValue(), 42); + EXPECT_TRUE(isNull(el->find("value2/nothing/"))); + + EXPECT_EQ(el->find("value1")->stringValue(), "bar"); + EXPECT_EQ(el->find("value1/")->stringValue(), "bar"); + + EXPECT_TRUE(el->find("value1", el2)); + EXPECT_EQ("bar", el2->stringValue()); + EXPECT_FALSE(el->find("name/error", el2)); + + // A map element whose (only) element has the maximum length of tag. + string long_maptag("0123456789abcdef1123456789abcdef2123456789abcdef" + "3123456789abcdef4123456789abcdef5123456789abcdef" + "6123456789abcdef7123456789abcdef8123456789abcdef" + "9123456789abcdefa123456789abcdefb123456789abcdef" + "c123456789abcdefd123456789abcdefe123456789abcdef" + "f123456789abcde"); + + EXPECT_EQ(255, long_maptag.length()); // check prerequisite + el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}"); + EXPECT_EQ("bar", el->find(long_maptag)->stringValue()); + + el = Element::createMap(); + el->set(long_maptag, Element::create("bar")); + EXPECT_EQ("bar", el->find(long_maptag)->stringValue()); + + // A one-byte longer tag should still be allowed + long_maptag.push_back('f'); + el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}"); + el->set(long_maptag, Element::create("bar")); + EXPECT_EQ("bar", el->find(long_maptag)->stringValue()); + + // Null pointer value + el.reset(new MapElement()); + ConstElementPtr null_ptr; + el->set("value", null_ptr); + EXPECT_FALSE(el->get("value")); + EXPECT_EQ("{ \"value\": None }", el->str()); +} + +TEST(Element, to_and_from_wire) { + // Wire format is now plain JSON. + EXPECT_EQ("1", Element::create(1)->toWire()); + EXPECT_EQ("1.1", Element::create(1.1)->toWire()); + EXPECT_EQ("true", Element::create(true)->toWire()); + EXPECT_EQ("false", Element::create(false)->toWire()); + EXPECT_EQ("null", Element::create()->toWire()); + EXPECT_EQ("\"a string\"", Element::create("a string")->toWire()); + EXPECT_EQ("[ \"a\", \"list\" ]", Element::fromJSON("[ \"a\", \"list\" ]")->toWire()); + EXPECT_EQ("{ \"a\": \"map\" }", Element::fromJSON("{ \"a\": \"map\" }")->toWire()); + + EXPECT_EQ("1", Element::fromWire("1")->str()); + + std::stringstream ss; + ss << "1"; + EXPECT_EQ("1", Element::fromWire(ss, 1)->str()); + + // Some malformed JSON input + EXPECT_THROW(Element::fromJSON("{ "), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{ \"a\" "), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{ \"a\": "), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{ \"a\": \"b\""), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{ \"a\": {"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{ \"a\": {}"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{ \"a\": []"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{ \"a\": [ }"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{\":"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("]"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("[ 1, 2, }"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("[ 1, 2, {}"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("[ 1, 2, { ]"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("[ "), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{{}}"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{[]}"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("{ \"a\", \"b\" }"), isc::data::JSONError); + EXPECT_THROW(Element::fromJSON("[ \"a\": \"b\" ]"), isc::data::JSONError); +} + +ConstElementPtr +efs(const std::string& str) { + return (Element::fromJSON(str)); +} + +TEST(Element, equals) { + EXPECT_EQ(*efs("1"), *efs("1")); + EXPECT_NE(*efs("1"), *efs("2")); + EXPECT_NE(*efs("1"), *efs("\"1\"")); + EXPECT_NE(*efs("1"), *efs("[]")); + EXPECT_NE(*efs("1"), *efs("true")); + EXPECT_NE(*efs("1"), *efs("{}")); + EXPECT_EQ(*efs("1.1"), *efs("1.1")); + EXPECT_NE(*efs("1.0"), *efs("1")); + EXPECT_NE(*efs("1.1"), *efs("\"1\"")); + EXPECT_NE(*efs("1.1"), *efs("[]")); + EXPECT_NE(*efs("1.1"), *efs("true")); + EXPECT_NE(*efs("1.1"), *efs("{}")); + + EXPECT_EQ(*efs("true"), *efs("true")); + EXPECT_NE(*efs("true"), *efs("false")); + EXPECT_NE(*efs("true"), *efs("1")); + EXPECT_NE(*efs("true"), *efs("\"1\"")); + EXPECT_NE(*efs("true"), *efs("[]")); + EXPECT_NE(*efs("true"), *efs("{}")); + + EXPECT_EQ(*efs("\"foo\""), *efs("\"foo\"")); + EXPECT_NE(*efs("\"foo\""), *efs("\"bar\"")); + EXPECT_NE(*efs("\"foo\""), *efs("1")); + EXPECT_NE(*efs("\"foo\""), *efs("\"1\"")); + EXPECT_NE(*efs("\"foo\""), *efs("true")); + EXPECT_NE(*efs("\"foo\""), *efs("[]")); + EXPECT_NE(*efs("\"foo\""), *efs("{}")); + + EXPECT_EQ(*efs("[]"), *efs("[]")); + EXPECT_EQ(*efs("[ 1, 2, 3 ]"), *efs("[ 1, 2, 3 ]")); + EXPECT_EQ(*efs("[ \"a\", [ true, 1], 2.2 ]"), *efs("[ \"a\", [ true, 1], 2.2 ]")); + EXPECT_NE(*efs("[ \"a\", [ true, 1], 2.2 ]"), *efs("[ \"a\", [ true, 2], 2.2 ]")); + EXPECT_NE(*efs("[]"), *efs("[1]")); + EXPECT_NE(*efs("[]"), *efs("1")); + EXPECT_NE(*efs("[]"), *efs("\"1\"")); + EXPECT_NE(*efs("[]"), *efs("{}")); + + EXPECT_EQ(*efs("{}"), *efs("{}")); + EXPECT_EQ(*efs("{ \"foo\": \"bar\" }"), *efs("{ \"foo\": \"bar\" }")); + EXPECT_EQ(*efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar\" } }"), *efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar\" } }")); + EXPECT_NE(*efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar\" } }"), *efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar2\" } }")); + EXPECT_NE(*efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\" ], \"item3\": { \"foo\": \"bar\" } }"), *efs("{ \"item1\": 1, \"item2\": [ \"a\", \"list\", 1 ], \"item3\": { \"foo\": \"bar\" } }")); + EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("1")); + EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("\"1\"")); + EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("[]")); + EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("{}")); + EXPECT_NE(*efs("{ \"foo\": \"bar\" }"), *efs("{ \"something\": \"different\" }")); + + EXPECT_EQ(*efs("null"), *Element::create()); +} + +TEST(Element, removeIdentical) { + ElementPtr a = Element::createMap(); + ConstElementPtr b = Element::createMap(); + ConstElementPtr c = Element::createMap(); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": 1 }"); + b = Element::fromJSON("{ \"a\": 1 }"); + c = Element::createMap(); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + b = Element::createMap(); + c = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + b = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + c = Element::createMap(); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + b = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 3 ] }"); + c = Element::fromJSON("{ \"b\": [ 1, 2 ] }"); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::createMap(); + c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + c = Element::createMap(); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }"); + c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }"); + b = Element::fromJSON("{ \"c\": 3, \"b\": 2 }"); + c = Element::fromJSON("{ \"a\": 1 }"); + removeIdentical(a, b); + EXPECT_EQ(*a, *c); + + EXPECT_THROW(removeIdentical(Element::create(1), Element::create(2)), TypeError); +} + +TEST(Element, constRemoveIdentical) { + ConstElementPtr a = Element::createMap(); + ConstElementPtr b = Element::createMap(); + ConstElementPtr c = Element::createMap(); + EXPECT_EQ(*removeIdentical(a, b), *c); + + a = Element::fromJSON("{ \"a\": 1 }"); + b = Element::fromJSON("{ \"a\": 1 }"); + c = Element::createMap(); + EXPECT_EQ(*removeIdentical(a, b), *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + b = Element::createMap(); + c = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + EXPECT_EQ(*removeIdentical(a, b), *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + b = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + c = Element::createMap(); + EXPECT_EQ(*removeIdentical(a, b), *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 2 ] }"); + b = Element::fromJSON("{ \"a\": 1, \"b\": [ 1, 3 ] }"); + c = Element::fromJSON("{ \"b\": [ 1, 2 ] }"); + EXPECT_EQ(*removeIdentical(a, b), *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::createMap(); + c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + EXPECT_EQ(*removeIdentical(a, b), *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + c = Element::createMap(); + EXPECT_EQ(*removeIdentical(a, b), *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }"); + c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + EXPECT_EQ(*removeIdentical(a, b), *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }"); + b = Element::fromJSON("{ \"c\": 3, \"b\": 2 }"); + c = Element::fromJSON("{ \"a\": 1 }"); + EXPECT_EQ(*removeIdentical(a, b), *c); + + // removeIdentical() is overloaded so force the first argument to const + ConstElementPtr bad = Element::create(1); + EXPECT_THROW(removeIdentical(bad, Element::create(2)), TypeError); +} + +TEST(Element, merge) { + ElementPtr a = Element::createMap(); + ElementPtr b = Element::createMap(); + ConstElementPtr c = Element::createMap(); + merge(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("1"); + b = Element::createMap(); + EXPECT_THROW(merge(a, b), TypeError); + + a = Element::createMap(); + b = Element::fromJSON("{ \"a\": 1 }"); + c = Element::fromJSON("{ \"a\": 1 }"); + merge(a, b); + EXPECT_EQ(*a, *c); + + a = Element::createMap(); + b = Element::fromJSON("{ \"a\": 1 }"); + c = Element::fromJSON("{ \"a\": 1 }"); + merge(b, a); + EXPECT_EQ(*b, *c); + + a = Element::fromJSON("{ \"a\": 1 }"); + b = Element::fromJSON("{ \"a\": 2 }"); + c = Element::fromJSON("{ \"a\": 2 }"); + merge(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": 1 }"); + b = Element::fromJSON("{ \"a\": 2 }"); + c = Element::fromJSON("{ \"a\": 1 }"); + merge(b, a); + EXPECT_EQ(*b, *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }"); + c = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }"); + merge(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::fromJSON("{ \"a\": { \"b\": \"d\" } }"); + c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + merge(b, a); + EXPECT_EQ(*b, *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::fromJSON("{ \"a\": null }"); + c = Element::fromJSON("{ }"); + merge(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + b = Element::fromJSON("{ \"a\": null }"); + c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }"); + merge(b, a); + EXPECT_EQ(*b, *c); + + // And some tests with multiple values + a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }"); + b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }"); + c = Element::fromJSON("{ \"a\": 1, \"c\": \"a string\" }"); + merge(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }"); + b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }"); + c = Element::fromJSON("{ \"a\": 1, \"b\": true }"); + merge(b, a); + EXPECT_EQ(*b, *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }"); + b = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }"); + c = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }"); + merge(a, b); + EXPECT_EQ(*a, *c); + + a = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }"); + b = Element::fromJSON("{ \"a\": 3, \"b\": 2, \"c\": 1 }"); + c = Element::fromJSON("{ \"a\": 1, \"b\": 2, \"c\": 3 }"); + merge(b, a); + EXPECT_EQ(*b, *c); + +} + +// This test checks copy. +TEST(Element, copy) { + // Null pointer + ElementPtr elem; + EXPECT_THROW(copy(elem, 0), isc::BadValue); + EXPECT_THROW(copy(elem), isc::BadValue); + EXPECT_THROW(copy(elem, -1), isc::BadValue); + + // Basic types + elem.reset(new IntElement(1)); + EXPECT_TRUE(elem->equals(*Element::fromJSON("1"))); + EXPECT_EQ("1", elem->str()); + ElementPtr copied; + ASSERT_NO_THROW(copied = copy(elem, 0)); + EXPECT_TRUE(elem->equals(*copied)); + + elem.reset(new DoubleElement(1.0)); + EXPECT_TRUE(elem->equals(*Element::fromJSON("1.0"))); + ASSERT_NO_THROW(copied = copy(elem, 0)); + EXPECT_TRUE(elem->equals(*copied)); + + elem.reset(new BoolElement(true)); + EXPECT_TRUE(elem->equals(*Element::fromJSON("true"))); + ASSERT_NO_THROW(copied = copy(elem, 0)); + EXPECT_TRUE(elem->equals(*copied)); + + elem.reset(new NullElement()); + EXPECT_TRUE(elem->equals(*Element::fromJSON("null"))); + ASSERT_NO_THROW(copied = copy(elem, 0)); + EXPECT_TRUE(elem->equals(*copied)); + + elem.reset(new StringElement("foo")); + EXPECT_TRUE(elem->equals(*Element::fromJSON("\"foo\""))); + ASSERT_NO_THROW(copied = copy(elem, 0)); + EXPECT_TRUE(elem->equals(*copied)); + ASSERT_NO_THROW(elem->setValue(std::string("bar"))); + EXPECT_TRUE(elem->equals(*Element::fromJSON("\"bar\""))); + EXPECT_FALSE(elem->equals(*copied)); + + elem.reset(new ListElement()); + ElementPtr item = ElementPtr(new IntElement(1)); + elem->add(item); + EXPECT_TRUE(elem->equals(*Element::fromJSON("[ 1 ]"))); + ASSERT_NO_THROW(copied = copy(elem, 0)); + EXPECT_TRUE(elem->equals(*copied)); + ElementPtr deep; + ASSERT_NO_THROW(deep = copy(elem)); + EXPECT_TRUE(elem->equals(*deep)); + ASSERT_NO_THROW(item = elem->getNonConst(0)); + ASSERT_NO_THROW(item->setValue(2)); + EXPECT_TRUE(elem->equals(*Element::fromJSON("[ 2 ]"))); + EXPECT_TRUE(elem->equals(*copied)); + EXPECT_FALSE(elem->equals(*deep)); + + elem.reset(new MapElement()); + item.reset(new StringElement("bar")); + elem->set("foo", item); + EXPECT_TRUE(elem->equals(*Element::fromJSON("{ \"foo\": \"bar\" }"))); + ASSERT_NO_THROW(copied = copy(elem, 0)); + EXPECT_TRUE(elem->equals(*copied)); + ASSERT_NO_THROW(deep = copy(elem)); + EXPECT_TRUE(elem->equals(*deep)); + ASSERT_NO_THROW(item->setValue(std::string("Bar"))); + EXPECT_TRUE(elem->equals(*Element::fromJSON("{ \"foo\": \"Bar\" }"))); + EXPECT_TRUE(elem->equals(*copied)); + EXPECT_FALSE(elem->equals(*deep)); + + // Complex example + std::string input = "{ \n" + "\"integer\": 1,\n" + "\"double\": 1.0,\n" + "\"boolean\": true,\n" + "\"null\": null,\n" + "\"string\": \"foobar\",\n" + "\"list\": [ 1, 2 ],\n" + "\"map\": { \"foo\": \"bar\" } }\n"; + ConstElementPtr complex; + ASSERT_NO_THROW(complex = Element::fromJSON(input)); + ASSERT_NO_THROW(copied = copy(complex, 0)); + EXPECT_TRUE(copied->equals(*complex)); + ASSERT_NO_THROW(deep = copy(complex)); + EXPECT_TRUE(deep->equals(*complex)); + ElementPtr shallow; + ASSERT_NO_THROW(shallow = copy(complex, 1)); + EXPECT_TRUE(shallow->equals(*complex)); + // Try to modify copies + ASSERT_NO_THROW(item = deep->get("list")->getNonConst(1)); + ASSERT_NO_THROW(item->setValue(3)); + EXPECT_FALSE(deep->equals(*complex)); + EXPECT_TRUE(shallow->equals(*complex)); + ASSERT_NO_THROW(item = boost::const_pointer_cast(shallow->get("string"))); + ASSERT_NO_THROW(item->setValue(std::string("FooBar"))); + EXPECT_FALSE(shallow->equals(*complex)); + EXPECT_TRUE(copied->equals(*complex)); +} + +// This test checks the isEquivalent function. +TEST(Element, isEquivalent) { + // All are different but a is equivalent to b + string texta = "{ \"a\": 1, \"b\": [ ], \"c\": [ 1, 1, 2 ] }"; + string textb = "{ \"b\": [ ], \"a\": 1, \"c\": [ 1, 2, 1 ] }"; + string textc = "{ \"a\": 2, \"b\": [ ], \"c\": [ 1, 1, 2 ] }"; + string textd = "{ \"a\": 1, \"c\": [ ], \"b\": [ 1, 1, 2 ] }"; + string texte = "{ \"a\": 1, \"b\": [ ], \"c\": [ 1, 2, 2 ] }"; + + ElementPtr a = Element::fromJSON(texta); + ElementPtr b = Element::fromJSON(textb); + ElementPtr c = Element::fromJSON(textc); + ElementPtr d = Element::fromJSON(textd); + ElementPtr e = Element::fromJSON(texte); + + EXPECT_TRUE(isEquivalent(a, b)); + EXPECT_NE(a, b); + EXPECT_FALSE(isEquivalent(a, c)); + EXPECT_FALSE(isEquivalent(a, d)); + EXPECT_FALSE(isEquivalent(a, e)); + + // Verifies isEquivalent handles cycles + if (isc::util::unittests::runningOnValgrind()) { + ElementPtr l = Element::createList(); + l->add(l); + EXPECT_THROW(isEquivalent(l, l), isc::BadValue); + } +} + +// This test checks the pretty print function. +TEST(Element, prettyPrint) { + + // default step is 2, order is alphabetic, no \n at the end + string text = "{\n" + " \"boolean\": true,\n" + " \"comment\": \"this is an exception\",\n" + " \"empty-list\": [ ],\n" + " \"empty-map\": { },\n" + " \"integer\": 1,\n" + " \"list\": [ 1, 2, 3 ],\n" + " \"map\": {\n" + " \"item\": null\n" + " },\n" + " \"string\": \"foobar\"\n" + "}"; + ElementPtr json = Element::fromJSON(text); + string pprinted = prettyPrint(json); + EXPECT_EQ(text, pprinted); +} + +// This test checks whether it is possible to ignore comments. It also checks +// that the comments are ignored only when told to. +TEST(Element, preprocessor) { + + string no_comment = "{ \"a\": 1,\n" + " \"b\": 2}"; + + string head_comment = "# this is a comment, ignore me\n" + "{ \"a\": 1,\n" + " \"b\": 2}"; + + string mid_comment = "{ \"a\": 1,\n" + "# this is a comment, ignore me\n" + " \"b\": 2}"; + + string tail_comment = "{ \"a\": 1,\n" + " \"b\": 2}" + "# this is a comment, ignore me\n"; + + string dbl_head_comment = "# this is a comment, ignore me\n" + "# second line, still ignored\n" + "{ \"a\": 1,\n" + " \"b\": 2}"; + + string dbl_mid_comment = "{ \"a\": 1,\n" + "# this is a comment, ignore me\n" + "# second line, still ignored\n" + " \"b\": 2}"; + + string dbl_tail_comment = "{ \"a\": 1,\n" + " \"b\": 2}" + "# this is a comment, ignore me\n" + "# second line, still ignored\n"; + + // This is what we expect in all cases. + ElementPtr exp = Element::fromJSON(no_comment); + + // Let's convert them all and see that the result it the same every time + EXPECT_TRUE(exp->equals(*Element::fromJSON(head_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(mid_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(tail_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_head_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_mid_comment, true))); + EXPECT_TRUE(exp->equals(*Element::fromJSON(dbl_tail_comment, true))); + + // With preprocessing disabled, it should fail all around + EXPECT_THROW(Element::fromJSON(head_comment), JSONError); + EXPECT_THROW(Element::fromJSON(mid_comment), JSONError); + EXPECT_THROW(Element::fromJSON(tail_comment), JSONError); + EXPECT_THROW(Element::fromJSON(dbl_head_comment), JSONError); + EXPECT_THROW(Element::fromJSON(dbl_mid_comment), JSONError); + EXPECT_THROW(Element::fromJSON(dbl_tail_comment), JSONError); + + // For coverage + std::istringstream iss(no_comment); + EXPECT_TRUE(exp->equals(*Element::fromJSON(iss, true))); +} + +TEST(Element, getPosition) { + std::istringstream ss("{\n" + " \"a\": 2,\n" + " \"b\":true,\n" + " \"cy\": \"a string\",\n" + " \"dyz\": {\n" + "\n" + " \"e\": 3,\n" + " \"f\": null\n" + "\n" + " },\n" + " \"g\": [ 5, 6,\n" + " 7 ]\n" + "}\n"); + + // Create a JSON string holding different type of values. Some of the + // values in the config string are not aligned, so as we can check that + // the position is set correctly for the elements. + ElementPtr top = Element::fromJSON(ss, string("kea.conf")); + ASSERT_TRUE(top); + + // Element "a" + ConstElementPtr level1_el = top->get("a"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(2, level1_el->getPosition().line_); + EXPECT_EQ(11, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "b" + level1_el = top->get("b"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(3, level1_el->getPosition().line_); + EXPECT_EQ(9, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "cy" + level1_el = top->get("cy"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(4, level1_el->getPosition().line_); + EXPECT_EQ(11, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "dyz" + level1_el = top->get("dyz"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(5, level1_el->getPosition().line_); + EXPECT_EQ(13, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "e" is a sub element of "dyz". + ConstElementPtr level2_el = level1_el->get("e"); + ASSERT_TRUE(level2_el); + EXPECT_EQ(7, level2_el->getPosition().line_); + EXPECT_EQ(12, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); + + // Element "f" is also a sub element of "dyz" + level2_el = level1_el->get("f"); + ASSERT_TRUE(level2_el); + EXPECT_EQ(8, level2_el->getPosition().line_); + EXPECT_EQ(14, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); + + // Element "g" is a list. + level1_el = top->get("g"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(11, level1_el->getPosition().line_); + // Position indicates where the values start (excluding the "[" character)" + EXPECT_EQ(11, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // First element from the list. + level2_el = level1_el->get(0); + ASSERT_TRUE(level2_el); + EXPECT_EQ(11, level2_el->getPosition().line_); + EXPECT_EQ(12, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); + + // Second element from the list. + level2_el = level1_el->get(1); + ASSERT_TRUE(level2_el); + EXPECT_EQ(11, level2_el->getPosition().line_); + EXPECT_EQ(15, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); + + // Third element from the list. + level2_el = level1_el->get(2); + ASSERT_TRUE(level2_el); + EXPECT_EQ(12, level2_el->getPosition().line_); + EXPECT_EQ(14, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); + +} + +// Tests whether position is returned properly for a commented input JSON text. +TEST(Element, getPositionCommented) { + std::istringstream ss("{\n" + " \"a\": 2,\n" + "# comment\n" + " \"cy\": \"a string\",\n" + " \"dyz\": {\n" + "# another comment\n" + " \"e\": 3,\n" + " \"f\": null\n" + "\n" + " } }\n"); + + // Create a JSON string holding different type of values. Some of the + // values in the config string are not aligned, so as we can check that + // the position is set correctly for the elements. + ElementPtr top = Element::fromJSON(ss, string("kea.conf"), true); + ASSERT_TRUE(top); + + // Element "a" + ConstElementPtr level1_el = top->get("a"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(2, level1_el->getPosition().line_); + EXPECT_EQ(11, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "cy" + level1_el = top->get("cy"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(4, level1_el->getPosition().line_); + EXPECT_EQ(11, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "dyz" + level1_el = top->get("dyz"); + ASSERT_TRUE(level1_el); + EXPECT_EQ(5, level1_el->getPosition().line_); + EXPECT_EQ(13, level1_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level1_el->getPosition().file_); + + // Element "e" is a sub element of "dyz". + ConstElementPtr level2_el = level1_el->get("e"); + ASSERT_TRUE(level2_el); + EXPECT_EQ(7, level2_el->getPosition().line_); + EXPECT_EQ(12, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); + + // Element "f" is also a sub element of "dyz" + level2_el = level1_el->get("f"); + ASSERT_TRUE(level2_el); + EXPECT_EQ(8, level2_el->getPosition().line_); + EXPECT_EQ(14, level2_el->getPosition().pos_); + EXPECT_EQ("kea.conf", level2_el->getPosition().file_); +} + +TEST(Element, empty) { + + // Let's try Map first + ElementPtr m = Element::createMap(); + EXPECT_TRUE(m->empty()); + m->set("something", Element::create(123)); + EXPECT_FALSE(m->empty()); + m->remove("something"); + EXPECT_TRUE(m->empty()); + + // Now do the same with list + ElementPtr l = Element::createList(); + EXPECT_TRUE(l->empty()); + l->add(Element::create(123)); + EXPECT_FALSE(l->empty()); + l->remove(0); + EXPECT_TRUE(l->empty()); +} + +TEST(Element, sortIntegers) { + ElementPtr l(Element::fromJSON("[5, 7, 4, 2, 8, 6, 1, 9, 0, 3]")); + ElementPtr expected(Element::fromJSON("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]")); + boost::dynamic_pointer_cast(l)->sort(); + EXPECT_EQ(*l, *expected); +} + +TEST(Element, sortFloatingPoint) { + ElementPtr l(Element::fromJSON("[2.1, 3.2, 2.1, 2.2, 4.1, 3.2, 1.1, 4.2, 0.1, 1.2]")); + ElementPtr expected(Element::fromJSON("[0.1, 1.1, 1.2, 2.1, 2.1, 2.2, 3.2, 3.2, 4.1, 4.2]")); + boost::dynamic_pointer_cast(l)->sort(); + EXPECT_EQ(*l, *expected); +} + +TEST(Element, sortBooleans) { + ElementPtr l(Element::fromJSON("[false, true, false, true]")); + ElementPtr expected(Element::fromJSON("[false, false, true, true]")); + boost::dynamic_pointer_cast(l)->sort(); + EXPECT_EQ(*l, *expected); +} + +TEST(Element, sortStrings) { + ElementPtr l(Element::fromJSON(R"(["hello", "world", "lorem", "ipsum", "dolor", "sit", "amet"])")); + ElementPtr expected(Element::fromJSON(R"(["amet", "dolor", "hello", "ipsum", "lorem", "sit", "world"])")); + boost::dynamic_pointer_cast(l)->sort(); + EXPECT_EQ(*l, *expected); +} + +TEST(Element, sortMaps) { + ElementPtr e1(Element::fromJSON(R"({"id": 1, "subnet": "10.0.2.0/24"})")); + ElementPtr e2(Element::fromJSON(R"({"id": 2, "subnet": "10.0.1.0/24"})")); + ElementPtr l; + + // Test sorting by "id". Order shouldn't change. + l = Element::createList(); + l->add(e1); + l->add(e2); + boost::dynamic_pointer_cast(l)->sort("id"); + ASSERT_EQ(l->size(), 2); + EXPECT_EQ(*l->get(0), *e1); + EXPECT_EQ(*l->get(1), *e2); + + // Test sorting by "id". Order should change. + l = Element::createList(); + l->add(e2); + l->add(e1); + boost::dynamic_pointer_cast(l)->sort("id"); + ASSERT_EQ(l->size(), 2); + EXPECT_EQ(*l->get(0), *e1); + EXPECT_EQ(*l->get(1), *e2); + + // Test sorting by "subnet". Order should change. + l = Element::createList(); + l->add(e1); + l->add(e2); + boost::dynamic_pointer_cast(l)->sort("subnet"); + ASSERT_EQ(l->size(), 2); + EXPECT_EQ(*l->get(0), *e2); + EXPECT_EQ(*l->get(1), *e1); + + // Test sorting by "subnet". Order shouldn't change. + l = Element::createList(); + l->add(e2); + l->add(e1); + boost::dynamic_pointer_cast(l)->sort("subnet"); + ASSERT_EQ(l->size(), 2); + EXPECT_EQ(*l->get(0), *e2); + EXPECT_EQ(*l->get(1), *e1); +} + +TEST(Element, removeEmptyContainersRecursively) { + ElementPtr e(Element::fromJSON(R"( +{ + "list": [ + { + "nested-list": [ + { + "nestedx2-list": [ + {} + ] + } + ] + } + ], + "map": { + "nested-map": { + "nestedx2-map": {} + } + }, + "simple-list": {}, + "simple-map": {} +} +)")); + e->removeEmptyContainersRecursively(); + EXPECT_EQ(*e, *Element::fromJSON("{}")); + + e = Element::fromJSON(R"( +{ + "list": [ + { + "value": "not empty anymore", + "nested-list": [ + { + "nestedx2-list": [ + {} + ] + } + ] + } + ], + "map": { + "value": "not empty anymore", + "nested-map": { + "nestedx2-map": {} + } + }, + "simple-list": {}, + "simple-map": {} +} +)"); + e->removeEmptyContainersRecursively(); + EXPECT_EQ(*e, *Element::fromJSON(R"( +{ + "list": [ + { + "value": "not empty anymore" + } + ], + "map": { + "value": "not empty anymore" + } +} +)")); +} + +/// @brief Function which creates an imaginary configuration hierarchy used to +/// test mergeDiffAdd, mergeDiffDel and extend. +/// +/// @param any Flag which indicates if traversing the hierarchy should use exact +/// element match or not. +isc::data::HierarchyDescriptor createHierarchy(bool any = false) { + auto const& element_empty = [](ElementPtr& element) { + for (auto const& kv : element->mapValue()) { + auto const& key = kv.first; + if (key != "id") { + return (false); + } + } + return (true); + }; + auto const& element_match = [](ElementPtr& left, ElementPtr& right) -> bool { + return (left->get("id")->intValue() == right->get("id")->intValue()); + }; + auto const& element_any_match = [](ElementPtr&, ElementPtr&) -> bool { + return (true); + }; + auto const& element_is_key = [](const std::string& key) -> bool { + return (key == "id"); + }; + isc::data::HierarchyDescriptor hierarchy = { + { { "root", { element_match, element_empty, element_is_key } } }, + { { "elements", { element_match, element_empty, element_is_key } }, + { "elements-other", { element_match, element_empty, element_is_key } } } + }; + if (any) { + hierarchy = { + { { "root", { element_any_match, element_empty, element_is_key } } }, + { { "elements", { element_any_match, element_empty, element_is_key } }, + { "elements-other", { element_any_match, element_empty, element_is_key } } } + }; + } + return (hierarchy); +} + +/// @brief Test which checks that mergeDiffAdd throws if called with wrong +/// element types. +TEST(Element, mergeDiffAddBadParams) { + { + SCOPED_TRACE("root bad scalars"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(true); + ElementPtr right = Element::create("false"); + ASSERT_THROW(mergeDiffAdd(left, right, hierarchy, ""), TypeError); + } + { + SCOPED_TRACE("map bad elements"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + left->set("elements", Element::createList()); + right->set("elements", Element::createMap()); + ASSERT_THROW(mergeDiffAdd(left, right, hierarchy, "root"), TypeError); + } + { + SCOPED_TRACE("list bad elements"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createList(); + ElementPtr right = Element::createList(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::createMap()); + right_right->set("id", Element::create(0)); + right_right->set("elements", Element::createList()); + left->add(left_left); + right->add(right_right); + ASSERT_THROW(mergeDiffAdd(left, right, hierarchy, "root"), TypeError); + } +} + +/// @brief Test which checks that mergeDiffAdd works as expected. +TEST(Element, mergeDiffAdd) { + { + SCOPED_TRACE("scalar bool"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(true); + ElementPtr right = Element::create(false); + EXPECT_NE(left->boolValue(), right->boolValue()); + mergeDiffAdd(left, right, hierarchy, ""); + EXPECT_EQ(left->boolValue(), right->boolValue()); + std::string expected_str("false"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar int"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(1); + ElementPtr right = Element::create(2); + EXPECT_NE(left->intValue(), right->intValue()); + mergeDiffAdd(left, right, hierarchy, ""); + EXPECT_EQ(left->intValue(), right->intValue()); + std::string expected_str("2"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar double"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(0.1); + ElementPtr right = Element::create(0.2); + EXPECT_NE(left->doubleValue(), right->doubleValue()); + mergeDiffAdd(left, right, hierarchy, ""); + EXPECT_EQ(left->doubleValue(), right->doubleValue()); + std::string expected_str("0.2"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar string"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create("left"); + ElementPtr right = Element::create("right"); + EXPECT_NE(left->stringValue(), right->stringValue()); + mergeDiffAdd(left, right, hierarchy, ""); + EXPECT_EQ(left->stringValue(), right->stringValue()); + std::string expected_str("\"right\""); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar in map"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + left->set("elements", Element::create("left")); + left->set("other-elements", Element::create("other")); + // scalar element which is updated + right->set("elements", Element::create("right")); + // scalar element which is added + right->set("new-elements", Element::create("new")); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + mergeDiffAdd(left, right, hierarchy, ""); + std::string expected_str("{ \"elements\": \"right\", \"new-elements\": \"new\", \"other-elements\": \"other\" }"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar in list"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createList(); + ElementPtr right = Element::createList(); + left->add(Element::create("left")); + left->add(Element::create("other")); + left->add(Element::create("test")); + // scalar element which is added + right->add(Element::create("right")); + // scalar element which is added + right->add(Element::create("new")); + // scalar element which already exists but is still added + right->add(Element::create("test")); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + mergeDiffAdd(left, right, hierarchy, ""); + std::string expected_str("[ \"left\", \"other\", \"test\", \"right\", \"new\", \"test\" ]"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar and list and map in map"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::create("left")); + left_left->set("other-elements", Element::create("other")); + // scalar element used as key + right_right->set("id", Element::create(0)); + // scalar element which is updated + right_right->set("elements", Element::create("right")); + // scalar element which is added + right_right->set("new-elements", Element::create("new")); + ElementPtr left_other_left = Element::createMap(); + ElementPtr right_other_right = Element::createMap(); + left_other_left->set("id", Element::create(1)); + left_other_left->set("elements", Element::create("other-left")); + // scalar element used as key + right_other_right->set("id", Element::create(2)); + // scalar element which is added + right_other_right->set("elements", Element::create("other-right")); + left->set("elements", left_left); + left->set("left-other-elements", left_other_left); + // map element which is added + right->set("right-other-elements", right_other_right); + // map element which is updated + right->set("elements", right_right); + left_other_left = Element::createList(); + right_other_right = Element::createList(); + left_other_left->add(Element::create("left-other-left")); + left_other_left->add(Element::create("left-other-left-other")); + left_other_left->add(Element::create("other-other")); + // scalar element which is added + right_other_right->add(Element::create("right-other-right")); + // scalar element which is added + right_other_right->add(Element::create("right-other-right-other")); + // scalar element which already exists but is still added + right_other_right->add(Element::create("other-other")); + left->set("other", left_other_left); + // list element which is updated + right->set("other", right_other_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + mergeDiffAdd(left, right, hierarchy, "root"); + std::string expected_str("{ \"elements\": { \"elements\": \"right\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" }, " + "\"left-other-elements\": { \"elements\": \"other-left\", \"id\": 1 }, " + "\"other\": [ \"left-other-left\", \"left-other-left-other\", \"other-other\", \"right-other-right\", \"right-other-right-other\", \"other-other\" ], " + "\"right-other-elements\": { \"elements\": \"other-right\", \"id\": 2 } }"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar and list and map in list"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createList(); + ElementPtr right = Element::createList(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::create("left")); + left_left->set("other-elements", Element::create("other")); + // scalar element used as key + right_right->set("id", Element::create(0)); + // scalar element which is updated + right_right->set("elements", Element::create("right")); + // scalar element which is added + right_right->set("new-elements", Element::create("new")); + ElementPtr left_other_left = Element::createMap(); + ElementPtr right_other_right = Element::createMap(); + left_other_left->set("id", Element::create(1)); + left_other_left->set("elements", Element::create("other-left")); + // scalar element used as key + right_other_right->set("id", Element::create(2)); + // scalar element which is added + right_other_right->set("elements", Element::create("other-right")); + left->add(left_left); + left->add(left_other_left); + // map element which is added + right->add(right_other_right); + // map element which is updated + right->add(right_right); + left_other_left = Element::createList(); + right_other_right = Element::createList(); + left_other_left->add(Element::create("left-other-left")); + left_other_left->add(Element::create("left-other-left-other")); + left_other_left->add(Element::create("other-other")); + // scalar element which is added + right_other_right->add(Element::create("right-other-right")); + // scalar element which is added + right_other_right->add(Element::create("right-other-right-other")); + // scalar element which already exists but is still added + right_other_right->add(Element::create("other-other")); + left_left->set("other", left_other_left); + // list element which is updated + right_right->set("other", right_other_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + mergeDiffAdd(left, right, hierarchy, "root"); + std::string expected_str("[ { \"elements\": \"right\", \"id\": 0, \"new-elements\": \"new\", " + "\"other\": [ \"left-other-left\", \"left-other-left-other\", \"other-other\", \"right-other-right\", \"right-other-right-other\", \"other-other\" ], " + "\"other-elements\": \"other\" }, " + "{ \"elements\": \"other-left\", \"id\": 1 }, " + "{ \"elements\": \"other-right\", \"id\": 2 } ]"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } +} + +/// @brief Test which checks that mergeDiffDel throws if called with wrong +/// element types. +TEST(Element, mergeDiffDelBadParams) { + { + SCOPED_TRACE("root bad scalars"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(true); + ElementPtr right = Element::create("false"); + ASSERT_THROW(mergeDiffDel(left, right, hierarchy, ""), TypeError); + } + { + SCOPED_TRACE("map bad elements"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + left->set("elements", Element::createList()); + right->set("elements", Element::createMap()); + ASSERT_THROW(mergeDiffDel(left, right, hierarchy, "root"), TypeError); + } + { + SCOPED_TRACE("list bad elements"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createList(); + ElementPtr right = Element::createList(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::createMap()); + right_right->set("id", Element::create(0)); + right_right->set("elements", Element::createList()); + left->add(left_left); + right->add(right_right); + ASSERT_THROW(mergeDiffDel(left, right, hierarchy, "root"), TypeError); + } +} + +/// @brief Test which checks that mergeDiffDel works as expected. +TEST(Element, mergeDiffDel) { + { + SCOPED_TRACE("scalar bool"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(true); + ElementPtr right = Element::create(false); + EXPECT_NE(left->boolValue(), right->boolValue()); + mergeDiffDel(left, right, hierarchy, ""); + EXPECT_EQ(left->getType(), Element::null); + } + { + SCOPED_TRACE("scalar int"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(1); + ElementPtr right = Element::create(2); + EXPECT_NE(left->intValue(), right->intValue()); + mergeDiffDel(left, right, hierarchy, ""); + EXPECT_EQ(left->getType(), Element::null); + } + { + SCOPED_TRACE("scalar double"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(0.1); + ElementPtr right = Element::create(0.2); + EXPECT_NE(left->doubleValue(), right->doubleValue()); + mergeDiffDel(left, right, hierarchy, ""); + EXPECT_EQ(left->getType(), Element::null); + } + { + SCOPED_TRACE("scalar string"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create("left"); + ElementPtr right = Element::create("right"); + EXPECT_NE(left->stringValue(), right->stringValue()); + mergeDiffDel(left, right, hierarchy, ""); + EXPECT_EQ(left->getType(), Element::null); + } + { + SCOPED_TRACE("scalar in map"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + left->set("elements", Element::create("left")); + left->set("other-elements", Element::create("other")); + // scalar element which is removed + right->set("elements", Element::create("right")); + // scalar element which does not exist and does nothing + right->set("new-elements", Element::create("new")); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + mergeDiffDel(left, right, hierarchy, "root"); + std::string expected_str("{ \"other-elements\": \"other\" }"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar in list"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createList(); + ElementPtr right = Element::createList(); + left->add(Element::create("left")); + left->add(Element::create("other")); + left->add(Element::create("other-left")); + left->add(Element::create("new")); + // scalar element which does not exist and does nothing + right->add(Element::create("right")); + // scalar element which is removed + right->add(Element::create("other")); + // scalar element which does not exist and does nothing + right->add(Element::create("other-right")); + // scalar element which is removed + right->add(Element::create("new")); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + mergeDiffDel(left, right, hierarchy, ""); + std::string expected_str("[ \"left\", \"other-left\" ]"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar and list and map in map"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::create("left")); + left_left->set("other-elements", Element::create("other")); + // scalar element used as key + right_right->set("id", Element::create(0)); + // scalar element which is removed + right_right->set("elements", Element::create("right")); + // scalar element which does not exist and does nothing + right_right->set("new-elements", Element::create("new")); + ElementPtr left_other_left = Element::createMap(); + ElementPtr right_other_right = Element::createMap(); + left_other_left->set("id", Element::create(1)); + left_other_left->set("elements", Element::create("other-left")); + // scalar element used as key + right_other_right->set("id", Element::create(2)); + // scalar element which does not exist and does nothing + right_other_right->set("elements", Element::create("other-right")); + left->set("elements", left_left); + left->set("left-other-elements", left_other_left); + // map element which does not exist and does nothing + right->set("right-other-elements", right_other_right); + // map element which is updated + right->set("elements", right_right); + left_other_left = Element::createList(); + right_other_right = Element::createList(); + left_other_left->add(Element::create("left-other-left")); + left_other_left->add(Element::create("other")); + left_other_left->add(Element::create("left-other-left-other")); + left_other_left->add(Element::create("new")); + // scalar element which does not exist and does nothing + right_other_right->add(Element::create("right-other-right")); + // scalar element which is removed + right_other_right->add(Element::create("other")); + // scalar element which does not exist and does nothing + right_other_right->add(Element::create("right-other-right-other")); + // scalar element which is removed + right_other_right->add(Element::create("new")); + left->set("other", left_other_left); + // list element which is updated + right->set("other", right_other_right); + left_left = Element::createMap(); + right_right = Element::createMap(); + left_left->set("id", Element::create(3)); + left_left->set("elements", Element::create("new-left")); + left_left->set("other-elements", Element::create("new-other")); + left->set("elements-other", left_left); + // scalar element used as key + right_right->set("id", Element::create(3)); + // map element which is not removed because it is contained in a map and + // the key can not be removed + right->set("elements-other", right_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + mergeDiffDel(left, right, hierarchy, "root"); + std::string expected_str("{ \"elements\": { \"id\": 0, \"other-elements\": \"other\" }, " + "\"elements-other\": { \"elements\": \"new-left\", \"id\": 3, \"other-elements\": \"new-other\" }, " + "\"left-other-elements\": { \"elements\": \"other-left\", \"id\": 1 }, " + "\"other\": [ \"left-other-left\", \"left-other-left-other\" ] }"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar and list and map in list"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createList(); + ElementPtr right = Element::createList(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::create("left")); + left_left->set("other-elements", Element::create("other")); + // scalar element used as key + right_right->set("id", Element::create(0)); + // scalar element which is removed + right_right->set("elements", Element::create("right")); + // scalar element which does not exist and does nothing + right_right->set("new-elements", Element::create("new")); + ElementPtr left_other_left = Element::createMap(); + ElementPtr right_other_right = Element::createMap(); + left_other_left->set("id", Element::create(1)); + left_other_left->set("elements", Element::create("other-left")); + // scalar element used as key + right_other_right->set("id", Element::create(2)); + // scalar element which does not exist and does nothing + right_other_right->set("elements", Element::create("other-right")); + left->add(left_left); + left->add(left_other_left); + // map element which does not exist and does nothing + right->add(right_other_right); + // map element which is updated + right->add(right_right); + left_other_left = Element::createList(); + right_other_right = Element::createList(); + left_other_left->add(Element::create("left-other-left")); + left_other_left->add(Element::create("other")); + left_other_left->add(Element::create("left-other-left-other")); + left_other_left->add(Element::create("new")); + // scalar element which does not exist and does nothing + right_other_right->add(Element::create("right-other-right")); + // scalar element which is removed + right_other_right->add(Element::create("other")); + // scalar element which does not exist and does nothing + right_other_right->add(Element::create("right-other-right-other")); + // scalar element which is removed + right_other_right->add(Element::create("new")); + left_left->set("other", left_other_left); + // list element which is updated + right_right->set("other", right_other_right); + left_left = Element::createMap(); + right_right = Element::createMap(); + left_left->set("id", Element::create(3)); + left_left->set("elements", Element::create("new-left")); + left_left->set("other-elements", Element::create("new-other")); + left->add(left_left); + // scalar element used as key + right_right->set("id", Element::create(3)); + // map element which is removed by key + // the key can not be removed + right->add(right_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + mergeDiffDel(left, right, hierarchy, "root"); + std::string expected_str("[ { \"id\": 0, \"other\": [ \"left-other-left\", \"left-other-left-other\" ], \"other-elements\": \"other\" }, " + "{ \"elements\": \"other-left\", \"id\": 1 } ]"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } +} + +/// @brief Test which checks that extend throws if called with wrong element +/// types. +TEST(Element, extendBadParam) { + { + SCOPED_TRACE("root bad scalars"); + isc::data::HierarchyDescriptor hierarchy; + ElementPtr left = Element::create(true); + ElementPtr right = Element::create("false"); + ASSERT_THROW(extend("elements", "", left, right, hierarchy, ""), TypeError); + } + { + SCOPED_TRACE("map bad elements"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + left->set("elements", Element::createList()); + right->set("elements", Element::createMap()); + ASSERT_THROW(extend("elements", "", left, right, hierarchy, "root"), TypeError); + } + { + SCOPED_TRACE("list bad elements"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(); + ElementPtr left = Element::createList(); + ElementPtr right = Element::createList(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::createMap()); + right_right->set("id", Element::create(0)); + right_right->set("elements", Element::createList()); + left->add(left_left); + right->add(right_right); + ASSERT_THROW(extend("elements", "", left, right, hierarchy, "root"), TypeError); + } +} + +/// @brief Test which checks that extend works as expected. +TEST(Element, extend) { + { + SCOPED_TRACE("scalar in map but alter flag is not set"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(true); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + left->set("elements", Element::create("left")); + left->set("other-elements", Element::create("other")); + // scalar element which is not updated + right->set("elements", Element::create("right")); + // scalar element which is extended + right->set("new-elements", Element::create("new")); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + extend("root", "new-elements", left, right, hierarchy, "root", 0, false); + std::string expected_str("{ \"elements\": \"left\", \"other-elements\": \"other\" }"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar in map"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(true); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + left->set("elements", Element::create("left")); + left->set("other-elements", Element::create("other")); + // scalar element which is not updated + right->set("elements", Element::create("right")); + // scalar element which is extended + right->set("new-elements", Element::create("new")); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + extend("root", "new-elements", left, right, hierarchy, "root", 0, true); + std::string expected_str("{ \"elements\": \"left\", \"new-elements\": \"new\", \"other-elements\": \"other\" }"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar in map in map"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(true); + ElementPtr left = Element::createMap(); + ElementPtr right = Element::createMap(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::create("left")); + left_left->set("other-elements", Element::create("other")); + // scalar element used as key + right_right->set("id", Element::create(1)); + // scalar element which is not updated + right_right->set("elements", Element::create("right")); + // scalar element which is extended + right_right->set("new-elements", Element::create("new")); + left->set("elements", left_left); + // map element which is used for extension + right->set("elements", right_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + extend("root", "new-elements", left, right, hierarchy, "root"); + std::string expected_str("{ \"elements\": { \"elements\": \"left\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" } }"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } + { + SCOPED_TRACE("scalar in map in list"); + isc::data::HierarchyDescriptor hierarchy; + hierarchy = createHierarchy(true); + ElementPtr left = Element::createList(); + ElementPtr right = Element::createList(); + ElementPtr left_left = Element::createMap(); + ElementPtr right_right = Element::createMap(); + left_left->set("id", Element::create(0)); + left_left->set("elements", Element::create("left")); + left_left->set("other-elements", Element::create("other")); + // scalar element used as key + right_right->set("id", Element::create(1)); + // scalar element which is not updated + right_right->set("elements", Element::create("right")); + // scalar element which is extended + right_right->set("new-elements", Element::create("new")); + left->add(left_left); + // map element which is used for extension + right->add(right_right); + ASSERT_FALSE(isc::data::isEquivalent(left, right)); + extend("root", "new-elements", left, right, hierarchy, "root"); + std::string expected_str("[ { \"elements\": \"left\", \"id\": 0, \"new-elements\": \"new\", \"other-elements\": \"other\" } ]"); + ElementPtr expected = Element::fromJSON(expected_str); + EXPECT_TRUE(isc::data::isEquivalent(left, expected)) + << "Actual: " << left->str() + << "\nExpected: " << expected->str(); + } +} + +} // namespace diff --git a/src/lib/cc/tests/element_value_unittests.cc b/src/lib/cc/tests/element_value_unittests.cc new file mode 100644 index 0000000..e284440 --- /dev/null +++ b/src/lib/cc/tests/element_value_unittests.cc @@ -0,0 +1,43 @@ +// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include + +using namespace isc::data; + +namespace { + +// This test verifies that integer value can be extracted. +TEST(ElementValue, intValue) { + EXPECT_EQ(5, ElementValue()(Element::create(5))); + EXPECT_THROW(ElementValue()(Element::create("hola!")), + TypeError); +} + +// This test verifies that double value can be extracted. +TEST(ElementValue, doubleValue) { + EXPECT_EQ(1.4, ElementValue()(Element::create(1.4))); + EXPECT_THROW(ElementValue()(Element::create("hola!")), + TypeError); +} + +// This test verifies that boolean value can be extracted. +TEST(ElementValue, boolValue) { + EXPECT_TRUE(ElementValue()(Element::create(true))); + EXPECT_THROW(ElementValue()(Element::create("hola!")), + TypeError); +} + +// This test verifies that string value can be extracted. +TEST(ElementValue, stringValue) { + EXPECT_EQ("hola!", ElementValue()(Element::create("hola!"))); + EXPECT_THROW(ElementValue()(Element::create(false)), + TypeError); +} + +} diff --git a/src/lib/cc/tests/json_feed_unittests.cc b/src/lib/cc/tests/json_feed_unittests.cc new file mode 100644 index 0000000..747d142 --- /dev/null +++ b/src/lib/cc/tests/json_feed_unittests.cc @@ -0,0 +1,453 @@ +// Copyright (C) 2017-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include + +using namespace isc::config; +using namespace isc::data; + +namespace { + +/// @brief Test fixture class for @ref JSONFeed class. +class JSONFeedTest : public ::testing::Test { +public: + + /// @brief Constructor. + /// + /// Initializes @ref json_map_ and @ref json_list_ which hold reference + /// JSON structures. + JSONFeedTest() + : json_map_(), json_list_() { + ElementPtr m = Element::fromJSON(createJSON()); + ElementPtr l = Element::createList(); + l->add(m); + json_map_ = m; + json_list_ = l; + } + + /// @brief Creates a JSON map holding 20 elements. + /// + /// Each map value is a list of 20 elements. + std::string createJSON() const { + // Create a list of 20 elements. + ElementPtr list_element = Element::createList(); + for (unsigned i = 0; i < 20; ++i) { + std::ostringstream s; + s << "list_element" << i; + list_element->add(Element::create(s.str())); + } + + // Create a map of 20 elements. Each map element holds a list + // of 20 elements. + ElementPtr map_element = Element::createMap(); + for (unsigned i = 0; i < 20; ++i) { + std::ostringstream s; + s << "map_element" << i; + map_element->set(s.str(), list_element); + } + + return (prettyPrint(map_element)); + } + + /// @brief Test that the JSONFeed correctly recognizes the beginning + /// and the end of the JSON structure. + /// + /// @param input_json A string holding an input JSON structure. + /// @param expected_output A structure holding expected output from the + /// @ref JSONFeed::toElement. + void testRead(const std::string& input_json, + const ConstElementPtr& expected_output) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + + // Post the data into the feed in 10 bytes long chunks. + size_t chunk = 10; + + for (size_t i = 0; i < input_json.size(); i += chunk) { + bool done = false; + // When we're near the end of the data stream, the chunk length may + // vary. + if (i + chunk >= input_json.size()) { + chunk = input_json.size() - i; + done = true; + } + // Feed the parser with a data chunk and parse it. + feed.postBuffer(&input_json[i], chunk); + feed.poll(); + if (!done) { + ASSERT_TRUE(feed.needData()); + } + } + + // Convert parsed/collected data in the feed into the structure of + // elements. + ConstElementPtr element_from_feed = feed.toElement(); + EXPECT_TRUE(element_from_feed->equals(*expected_output)); + } + + /// @brief Test that the JSONFeed correctly recognizes the beginning + /// and the end of the JSON structure. + /// + /// @param input_json A string holding an input JSON structure. + /// @param expected_output A string holding expected output from the + /// @ref JSONFeed::getProcessedText. + void testRead(const std::string& input_json, + const std::string& expected_output) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + + // Post the data into the feed in 10 bytes long chunks. + size_t chunk = 10; + + for (size_t i = 0; i < input_json.size(); i += chunk) { + bool done = false; + // When we're near the end of the data stream, the chunk length may + // vary. + if (i + chunk >= input_json.size()) { + chunk = input_json.size() - i; + done = true; + } + // Feed the parser with a data chunk and parse it. + feed.postBuffer(&input_json[i], chunk); + feed.poll(); + if (!done) { + ASSERT_TRUE(feed.needData()); + } + } + + EXPECT_EQ(expected_output, feed.getProcessedText()); + } + + /// @brief Test that the @ref JSONFeed signals an error when the input + /// string holds invalid data. + /// + /// @param input_json A string holding an input JSON structure. + /// @param err_msg A string holding an expected error message. + void testInvalidRead(const std::string& input_json, + const std::string& err_msg) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + + ASSERT_NO_THROW(feed.postBuffer(&input_json[0], input_json.size())); + ASSERT_NO_THROW(feed.poll()); + + EXPECT_FALSE(feed.needData()); + EXPECT_FALSE(feed.feedOk()); + + EXPECT_EQ(err_msg, feed.getErrorMessage()); + } + + /// @brief JSON map holding a number of lists. + ConstElementPtr json_map_; + + /// @brief JSON list holding a map of lists. + ConstElementPtr json_list_; + +}; + +// This test verifies that toElement should not be called before +// the feed detects the end of the data stream. +TEST_F(JSONFeedTest, toElementTooSoon) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + std::string json = "{\n"; + feed.postBuffer(&json[0], json.size()); + feed.poll(); + EXPECT_TRUE(feed.needData()); + EXPECT_THROW(feed.toElement(), JSONFeedError); +} + +// This test verifies that toElement checks JSON syntax as a side effect. +TEST_F(JSONFeedTest, badJSON) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + std::string json = "{\n]\n"; + feed.postBuffer(&json[0], json.size()); + feed.poll(); + EXPECT_FALSE(feed.needData()); + EXPECT_THROW(feed.toElement(), JSONFeedError); +} + +// This test verifies that a JSON structure starting with '{' is accepted +// and parsed. +TEST_F(JSONFeedTest, startWithBrace) { + std::string json = createJSON(); + testRead(json, json_map_); +} + +// This test verifies that a JSON structure starting with '[' is accepted +// and parsed. +TEST_F(JSONFeedTest, startWithSquareBracket) { + std::string json = createJSON(); + json = std::string("[") + json + std::string("]"); + testRead(json, json_list_); +} + +// This test verifies that input JSON can be preceded with whitespaces. +TEST_F(JSONFeedTest, startWithWhitespace) { + std::string json = createJSON(); + json = std::string(" \r\r\t ") + json; + testRead(json, json_map_); +} + +// This test verifies that an empty map is accepted and parsed. +TEST_F(JSONFeedTest, emptyMap) { + std::string json = "{}"; + testRead(json, Element::createMap()); +} + +// This test verifies that an empty list is accepted and parsed. +TEST_F(JSONFeedTest, emptyList) { + std::string json = "[ ]"; + testRead(json, Element::createList()); +} + +// This test verifies that an error is signalled when a JSON structure +// is preceded by invalid character. +TEST_F(JSONFeedTest, unexpectedFirstCharacter) { + std::string json = "a {}"; + std::string err_msg = "invalid first character a"; + testInvalidRead(json, err_msg); +} + +// This test verifies that an error is signalled when a JSON structure +// is preceded by white spaces and an invalid character. +TEST_F(JSONFeedTest, unexpectedCharacter) { + std::string json = " a {}"; + std::string err_msg = "invalid character a"; + testInvalidRead(json, err_msg); +} + +// This test verifies that an error is signalled when the JSON structure +// begins by a string. +TEST_F(JSONFeedTest, stringFirst) { + std::string json = "\"foo\""; + std::string err_msg = "invalid first character \""; + testInvalidRead(json, err_msg); +} + +// This test verifies that an error is signalled when the JSON structure +// begins by white spaces followed by a string. +TEST_F(JSONFeedTest, stringBefore) { + std::string json = " \"foo\""; + std::string err_msg = "invalid character \""; + testInvalidRead(json, err_msg); +} + +// This test verifies that an error is signalled when a JSON structure +// lacks an opening brace character. +TEST_F(JSONFeedTest, noOpeningBrace) { + std::string json = "\"x\": \"y\" }"; + std::string err_msg = "invalid first character \""; + testInvalidRead(json, err_msg); +} + +// This test verifies that an error is signalled when a JSON structure +// lacks an opening square bracket. +TEST_F(JSONFeedTest, noOpeningSquareBracket) { + std::string json = "1, 2 ]"; + std::string err_msg = "invalid first character 1"; + testInvalidRead(json, err_msg); +} + +// This test verifies that a string is correctly handled +TEST_F(JSONFeedTest, string) { + std::string json = "{ \"braces\": \"}}}}\" }"; + ElementPtr expected = Element::createMap(); + expected->set("braces", Element::create("}}}}")); + testRead(json, expected); +} + +// This test verifies that a string with escapes is correctly handled +TEST_F(JSONFeedTest, escape) { + std::string json = "{ \"escapes\": \"\\n\\t\\\"\\\\\" }"; + ElementPtr expected = Element::createMap(); + expected->set("escapes", Element::create("\n\t\"\\")); + testRead(json, expected); +} + +// This test verifies that white spaces before JSON are ignored. +TEST_F(JSONFeedTest, whiteSpaceBefore) { + std::string json = " \n [ ]\n"; + std::string expected = "[ ]"; + testRead(json, expected); +} + +// This test verifies that bash style comments before JSON are ignored. +TEST_F(JSONFeedTest, bashCommentBefore) { + std::string json = "# ahah\n # foo\"bar\n{ }\n"; + std::string expected = "{ }"; + testRead(json, expected); +} + +// This test verifies that C++ style comments before JSON are ignored. +TEST_F(JSONFeedTest, cppCommentBefore) { + std::string json = "// ahah\n // foo\"bar\n[ 12 ]\n"; + std::string expected = "[ 12 ]"; + testRead(json, expected); +} + +// This test verifies that multi-line comments before JSON are ignored. +TEST_F(JSONFeedTest, multiLineCommentBefore) { + std::string json = "/* ahah\n \"// foo*bar**/\n { \"foo\": \"bar\" }\n"; + std::string expected = "{ \"foo\": \"bar\" }"; + testRead(json, expected); +} + +// This test verifies that an error is signalled when a slash does not +// begin a C++ or C style comment before JSON. +TEST_F(JSONFeedTest, badCommentBefore) { + std::string json = "/# foo\n [ ]\n"; + std::string err_msg = "invalid characters /#"; + testInvalidRead(json, err_msg); +} + +// This test verifies that bash style comments are ignored. +TEST_F(JSONFeedTest, bashComments) { + std::string json = "{ # ahah\n \"foo\": # value?\n \"bar\" }"; + std::string expected = "{ \n \"foo\": \n \"bar\" }"; + testRead(json, expected); +} + +// This test verifies that C++ style comments are ignored. +TEST_F(JSONFeedTest, cppComments) { + std::string json = "[ // ahah\n \"foo\", /// value?\n \"bar\" ]"; + std::string expected = "[ \n \"foo\", \n \"bar\" ]"; + testRead(json, expected); +} + +// This test verifies that multi-line comments are ignored. +TEST_F(JSONFeedTest, multiLineComments) { + std::string json = "{ /* ahah\n \"// foo*bar**/\n \"foo\": \"bar\" }\n"; + std::string expected = "{ \n\n \"foo\": \"bar\" }"; + testRead(json, expected); +} + +// This test verifies that an error is signalled a slash does not begin +// a C++ or C style comment. +TEST_F(JSONFeedTest, badComment) { + std::string json = "[ /# foo\n ]\n"; + std::string err_msg = "invalid characters /#"; + testInvalidRead(json, err_msg); +} + +// This test verifies that trailing garbage is ignored. +TEST_F(JSONFeedTest, trailing) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + std::string json = "[ 1, 2] 3, 4]"; + feed.postBuffer(&json[0], json.size()); + feed.poll(); + EXPECT_FALSE(feed.needData()); + EXPECT_TRUE(feed.feedOk()); + std::string expected = "[ 1, 2]"; + EXPECT_EQ(expected, feed.getProcessedText()); +} + +// Example from DHCPv4 unit tests. +TEST_F(JSONFeedTest, bashComment4) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + std::string json = "{ \"Dhcp4\": { \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "},\n" + "# this is a comment\n" + "\"rebind-timer\": 2000, \n" + "# lots of comments here\n" + "# and here\n" + "\"renew-timer\": 1000, \n" + "\"subnet4\": [ { " + " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ]," + " \"subnet\": \"192.0.2.0/24\", " + " \"id\": 1, " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 } }"; + feed.postBuffer(&json[0], json.size()); + feed.poll(); + EXPECT_FALSE(feed.needData()); + EXPECT_TRUE(feed.feedOk()); + EXPECT_NO_THROW(feed.toElement()); +} + +// Example from DHCPv4 unit tests. +TEST_F(JSONFeedTest, bashCommentsInline4) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + std::string json = "{ \"Dhcp4\": { \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "},\n" + "\"rebind-timer\": 2000, # everything after # is ignored\n" + "\"renew-timer\": 1000, # this will be ignored, too\n" + "\"subnet4\": [ { " + " \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ]," + " \"subnet\": \"192.0.2.0/24\", " + " \"id\": 1, " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 } }"; + feed.postBuffer(&json[0], json.size()); + feed.poll(); + EXPECT_FALSE(feed.needData()); + EXPECT_TRUE(feed.feedOk()); + EXPECT_NO_THROW(feed.toElement()); +} + +// Example from DHCPv6 unit tests. +TEST_F(JSONFeedTest, cppComments6) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + std::string json = "{ \"Dhcp6\": { \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "},\n" + "\"preferred-lifetime\": 3000, // this is a comment \n" + "\"rebind-timer\": 2000, // everything after // is ignored\n" + "\"renew-timer\": 1000, // this will be ignored, too\n" + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"id\": 1, " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 } }"; + feed.postBuffer(&json[0], json.size()); + feed.poll(); + EXPECT_FALSE(feed.needData()); + EXPECT_TRUE(feed.feedOk()); + EXPECT_NO_THROW(feed.toElement()); +} + +// Example from DHCPv6 unit tests. +TEST_F(JSONFeedTest, multilineComments6) { + JSONFeed feed; + ASSERT_NO_THROW(feed.initModel()); + std::string json = "{ \"Dhcp6\": { \"interfaces-config\": {" + " \"interfaces\": [ \"*\" ]" + "},\n" + "\"preferred-lifetime\": 3000, /* this is a C style comment\n" + "that\n can \n span \n multiple \n lines */ \n" + "\"rebind-timer\": 2000,\n" + "\"renew-timer\": 1000, \n" + "\"subnet6\": [ { " + " \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ]," + " \"subnet\": \"2001:db8:1::/48\", " + " \"id\": 1, " + " \"interface\": \"eth0\"" + " } ]," + "\"valid-lifetime\": 4000 } }"; + feed.postBuffer(&json[0], json.size()); + feed.poll(); + EXPECT_FALSE(feed.needData()); + EXPECT_TRUE(feed.feedOk()); + EXPECT_NO_THROW(feed.toElement()); +} + +} // end of anonymous namespace. diff --git a/src/lib/cc/tests/run_unittests.cc b/src/lib/cc/tests/run_unittests.cc new file mode 100644 index 0000000..40c051f --- /dev/null +++ b/src/lib/cc/tests/run_unittests.cc @@ -0,0 +1,20 @@ +// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + + isc::log::initLogger(); + + return (isc::util::unittests::run_all()); +} diff --git a/src/lib/cc/tests/server_tag_unittest.cc b/src/lib/cc/tests/server_tag_unittest.cc new file mode 100644 index 0000000..523be74 --- /dev/null +++ b/src/lib/cc/tests/server_tag_unittest.cc @@ -0,0 +1,97 @@ +// Copyright (C) 2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include + +using namespace isc; +using namespace isc::data; + +namespace { + +// This test verifies that the constructors of the ServerTag class +// work properly. +TEST(ServerTagTest, constructors) { + boost::scoped_ptr tag; + + { + SCOPED_TRACE("default constructor for all servers"); + ASSERT_NO_THROW(tag.reset(new ServerTag())); + EXPECT_EQ(ServerTag::ALL, tag->get()); + EXPECT_TRUE(tag->amAll()); + } + + { + SCOPED_TRACE("all servers"); + ASSERT_NO_THROW(tag.reset(new ServerTag(ServerTag::ALL))); + EXPECT_EQ(ServerTag::ALL, tag->get()); + EXPECT_TRUE(tag->amAll()); + } + + { + SCOPED_TRACE("no whitespace"); + ASSERT_NO_THROW(tag.reset(new ServerTag("xyz"))); + EXPECT_EQ("xyz", tag->get()); + EXPECT_FALSE(tag->amAll()); + } + + { + SCOPED_TRACE("leading whitespace"); + ASSERT_NO_THROW(tag.reset(new ServerTag(" left"))); + EXPECT_EQ("left", tag->get()); + EXPECT_FALSE(tag->amAll()); + } + + { + SCOPED_TRACE("terminating whitespace"); + ASSERT_NO_THROW(tag.reset(new ServerTag("right "))); + EXPECT_EQ("right", tag->get()); + EXPECT_FALSE(tag->amAll()); + } + + { + SCOPED_TRACE("leading and terminating whitespace"); + ASSERT_NO_THROW(tag.reset(new ServerTag(" both left-right "))); + EXPECT_EQ("both left-right", tag->get()); + EXPECT_FALSE(tag->amAll()); + } + + { + SCOPED_TRACE("upper to lower case"); + ASSERT_NO_THROW(tag.reset(new ServerTag("UPPER CASE TAG"))); + EXPECT_EQ("upper case tag", tag->get()); + EXPECT_FALSE(tag->amAll()); + } +} + +// This test verifies that malformed server tags are rejected. +TEST(ServerTagTest, malformed) { + { + SCOPED_TRACE("empty tag"); + EXPECT_THROW(ServerTag(""), BadValue); + } + + { + SCOPED_TRACE("only whitespaces"); + EXPECT_THROW(ServerTag(" "), BadValue); + } + + { + SCOPED_TRACE("too long tag, max is 256"); + EXPECT_THROW(ServerTag(std::string(257, 'c')), BadValue); + } + + { + SCOPED_TRACE("use reserved keyword any as a tag"); + EXPECT_THROW(ServerTag("any"), BadValue); + } +} + +} diff --git a/src/lib/cc/tests/simple_parser_unittest.cc b/src/lib/cc/tests/simple_parser_unittest.cc new file mode 100644 index 0000000..76e5ffc --- /dev/null +++ b/src/lib/cc/tests/simple_parser_unittest.cc @@ -0,0 +1,364 @@ +// Copyright (C) 2016-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include + +using namespace isc; +using namespace isc::data; +using namespace isc::asiolink; +using isc::dhcp::DhcpConfigError; + +/// This list defines required keywords. +const SimpleRequiredKeywords REQUIRED_KEYWORDS = { "foobar" }; + +/// This table defines keywords and types. +const SimpleKeywords KEYWORDS = { + { "id", Element::integer }, + { "prefix", Element::string }, + { "map", Element::map }, + { "any", Element::any } +}; + +/// This table defines sample default values. Although these are DHCPv6 +/// specific, the mechanism is generic and can be used by any other component. +const SimpleDefaults SAMPLE_DEFAULTS = { + { "renew-timer", Element::integer, "900" }, + { "rebind-timer", Element::integer, "1800" }, + { "preferred-lifetime", Element::integer, "3600" }, + { "valid-lifetime", Element::integer, "7200" } +}; + +/// This list defines parameters that can be inherited from one scope +/// to another. Although these are DHCPv6 specific, the mechanism is generic and +/// can be used by any other component. +const ParamsList SAMPLE_INHERITS = { + "renew-timer", + "rebind-timer", + "preferred-lifetime", + "valid-lifetime" +}; + +/// @brief Simple Parser test fixture class +class SimpleParserTest : public ::testing::Test { +public: + /// @brief Checks if specified map has an integer parameter with expected value + /// + /// @param map map to be checked + /// @param param_name name of the parameter to be checked + /// @param exp_value expected value of the parameter. + void checkIntegerValue(const ConstElementPtr& map, const std::string& param_name, + int64_t exp_value) { + + // First check if the passed element is a map. + ASSERT_EQ(Element::map, map->getType()); + + // Now try to get the element being checked + ConstElementPtr elem = map->get(param_name); + ASSERT_TRUE(elem); + + // Now check if it's indeed integer + ASSERT_EQ(Element::integer, elem->getType()); + + // Finally, check if its value meets expectation. + EXPECT_EQ(exp_value, elem->intValue()); + } +}; + +class SimpleParserClassTest : public SimpleParser { +public: + /// @brief Instantiation of getAndConvert + /// + /// @param scope specified parameter will be extracted from this scope + /// @param name name of the parameter for error report + /// @return a bool value + bool getAsBool(ConstElementPtr scope, const std::string& name) { + return (getAndConvert(scope, name, "boolean")); + } + + /// @brief Convert to boolean + /// + /// @param str the string "false" or "true" + /// @return false for "false" and true for "true" + /// @thrown isc::OutOfRange if not "false" or "true' + static bool toBool(const std::string& str) { + if (str == "false") { + return (false); + } else if (str == "true") { + return (true); + } else { + isc_throw(TypeError, "not a boolean: " << str); + } + } +}; + +// This test checks if the checkRequired method works as expected. +TEST_F(SimpleParserTest, checkRequired) { + ConstElementPtr empty = Element::fromJSON("{ }"); + EXPECT_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, empty), + DhcpConfigError); + ConstElementPtr other = Element::fromJSON("{ \"foo\": 1, \"bar\": 2 }"); + EXPECT_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, other), + DhcpConfigError); + ConstElementPtr good = Element::fromJSON("{ \"foobar\": 2 }"); + EXPECT_NO_THROW(SimpleParser::checkRequired(REQUIRED_KEYWORDS, good)); +} + +// This test checks if the checkKeywords method works as expected. +TEST_F(SimpleParserTest, checkKeywords) { + ConstElementPtr empty = Element::fromJSON("{ }"); + EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, empty)); + ConstElementPtr id = Element::fromJSON("{ \"id\": 1 }"); + EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, id)); + ConstElementPtr any = Element::fromJSON("{ \"any\": 1 }"); + EXPECT_NO_THROW(SimpleParser::checkKeywords(KEYWORDS, any)); + ConstElementPtr bad_id = Element::fromJSON("{ \"id\": true }"); + EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_id), + DhcpConfigError); + ConstElementPtr bad_prefix = Element::fromJSON("{ \"prefix\": 12 }"); + EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_prefix), + DhcpConfigError); + ConstElementPtr bad_map = Element::fromJSON("{ \"map\": [ ] }"); + EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, bad_map), + DhcpConfigError); + ConstElementPtr spurious = Element::fromJSON("{ \"spurious\": 1 }"); + EXPECT_THROW(SimpleParser::checkKeywords(KEYWORDS, spurious), + DhcpConfigError); + + // Bad type has precedence. + ConstElementPtr bad = Element::fromJSON("{ \"spurious\": 1, \"id\": true }"); + try { + SimpleParser::checkKeywords(KEYWORDS, bad); + ADD_FAILURE() << "expect exception"; + } catch (const DhcpConfigError& ex) { + EXPECT_EQ("'id' parameter is not an integer", std::string(ex.what())); + } catch (...) { + ADD_FAILURE() << "expect DhcpConfigError"; + } +} + +// This test checks if the parameters can be inherited from the global +// scope to the subnet scope. +TEST_F(SimpleParserTest, deriveParams) { + ElementPtr global = Element::fromJSON("{ \"renew-timer\": 1," + " \"rebind-timer\": 2," + " \"preferred-lifetime\": 3," + " \"valid-lifetime\": 4" + "}"); + ElementPtr subnet = Element::fromJSON("{ \"renew-timer\": 100 }"); + + // we should inherit 3 parameters. Renew-timer should remain intact, + // as it was already defined in the subnet scope. + size_t num; + EXPECT_NO_THROW(num = SimpleParser::deriveParams(global, subnet, + SAMPLE_INHERITS)); + EXPECT_EQ(3, num); + + // Check the values. 3 of them are inherited, while the fourth one + // was already defined in the subnet, so should not be inherited. + checkIntegerValue(subnet, "renew-timer", 100); + checkIntegerValue(subnet, "rebind-timer", 2); + checkIntegerValue(subnet, "preferred-lifetime", 3); + checkIntegerValue(subnet, "valid-lifetime", 4); +} + +// This test checks if global defaults are properly set for DHCPv6. +TEST_F(SimpleParserTest, setDefaults) { + + ElementPtr empty = Element::fromJSON("{ }"); + size_t num = 0; + + EXPECT_NO_THROW(num = SimpleParser::setDefaults(empty, SAMPLE_DEFAULTS)); + + // We expect at least 4 parameters to be inserted. + EXPECT_GE(num, 3); + + checkIntegerValue(empty, "valid-lifetime", 7200); + checkIntegerValue(empty, "preferred-lifetime", 3600); + checkIntegerValue(empty, "rebind-timer", 1800); + checkIntegerValue(empty, "renew-timer", 900); +} + +// This test checks if global defaults are properly set for DHCPv6. +TEST_F(SimpleParserTest, setListDefaults) { + + ElementPtr empty = Element::fromJSON("[{}, {}, {}]"); + size_t num; + + EXPECT_NO_THROW(num = SimpleParser::setListDefaults(empty, SAMPLE_DEFAULTS)); + + // We expect at least 12 parameters to be inserted (3 entries, with + // 4 parameters inserted in each) + EXPECT_EQ(12, num); + + ASSERT_EQ(Element::list, empty->getType()); + ASSERT_EQ(3, empty->size()); + + ConstElementPtr first = empty->get(0); + ConstElementPtr second = empty->get(1); + ConstElementPtr third = empty->get(2); + + checkIntegerValue(first, "valid-lifetime", 7200); + checkIntegerValue(first, "preferred-lifetime", 3600); + checkIntegerValue(first, "rebind-timer", 1800); + checkIntegerValue(first, "renew-timer", 900); + + checkIntegerValue(second, "valid-lifetime", 7200); + checkIntegerValue(second, "preferred-lifetime", 3600); + checkIntegerValue(second, "rebind-timer", 1800); + checkIntegerValue(second, "renew-timer", 900); + + checkIntegerValue(third, "valid-lifetime", 7200); + checkIntegerValue(third, "preferred-lifetime", 3600); + checkIntegerValue(third, "rebind-timer", 1800); + checkIntegerValue(third, "renew-timer", 900); +} + +// This test exercises the getIntType template +TEST_F(SimpleParserTest, getIntType) { + + SimpleParserClassTest parser; + + // getIntType checks it can be found + ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }"); + EXPECT_THROW(parser.getUint8(not_found, "foo"), DhcpConfigError); + + // getIntType checks if it is an integer + ElementPtr not_int = Element::fromJSON("{ \"foo\": \"xyz\" }"); + EXPECT_THROW(parser.getUint8(not_int, "foo"), DhcpConfigError); + + // getIntType checks bounds + ElementPtr negative = Element::fromJSON("{ \"foo\": -1 }"); + EXPECT_THROW(parser.getUint8(negative, "foo"), DhcpConfigError); + ElementPtr too_large = Element::fromJSON("{ \"foo\": 1024 }"); + EXPECT_THROW(parser.getUint8(too_large, "foo"), DhcpConfigError); + + // checks if getIntType can return the expected value + ElementPtr hundred = Element::fromJSON("{ \"foo\": 100 }"); + uint8_t val = 0; + EXPECT_NO_THROW(val = parser.getUint8(hundred, "foo")); + EXPECT_EQ(100, val); +} + +// This test exercises the getInteger with range checking +TEST_F(SimpleParserTest, getInteger) { + + // The value specified is 100. + ElementPtr json = Element::fromJSON("{ \"bar\": 100 }"); + int64_t x = -1; + + // Positive case: we expect value in range 0..200. All ok. + EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 0, 200)); + EXPECT_EQ(100, x); + + // Border checks: 100 for 100..200 range is still ok. + EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 100, 200)); + // Border checks: 100 for 1..100 range is still ok. + EXPECT_NO_THROW(x = SimpleParser::getInteger(json, "bar", 1, 100)); + + // Out of expected range. Should throw. + EXPECT_THROW(x = SimpleParser::getInteger(json, "bar", 101, 200), OutOfRange); + EXPECT_THROW(x = SimpleParser::getInteger(json, "bar", 1, 99), OutOfRange); +} + +// This test exercises the getAndConvert template +TEST_F(SimpleParserTest, getAndConvert) { + + SimpleParserClassTest parser; + + // getAndConvert checks it can be found + ElementPtr not_found = Element::fromJSON("{ \"bar\": \"true\" }"); + EXPECT_THROW(parser.getAsBool(not_found, "foo"), DhcpConfigError); + + // getAndConvert checks if it is a string + ElementPtr not_bool = Element::fromJSON("{ \"foo\": 1 }"); + EXPECT_THROW(parser.getAsBool(not_bool, "foo"), DhcpConfigError); + + // checks if getAndConvert can return the expected value + ElementPtr a_bool = Element::fromJSON("{ \"foo\": \"false\" }"); + bool val = true; + EXPECT_NO_THROW(val = parser.getAsBool(a_bool, "foo")); + EXPECT_FALSE(val); + + // getAndConvert checks conversion + ElementPtr bad_bool = Element::fromJSON("{ \"foo\": \"bar\" }"); + EXPECT_THROW(parser.getAsBool(bad_bool, "bar"), DhcpConfigError); +} + +// This test exercises the getIOAddress +TEST_F(SimpleParserTest, getIOAddress) { + + SimpleParserClassTest parser; + + // getAddress checks it can be found + ElementPtr not_found = Element::fromJSON("{ \"bar\": 1 }"); + EXPECT_THROW(parser.getAddress(not_found, "foo"), DhcpConfigError); + + // getAddress checks if it is a string + ElementPtr not_addr = Element::fromJSON("{ \"foo\": 1234 }"); + EXPECT_THROW(parser.getAddress(not_addr, "foo"), DhcpConfigError); + + // checks if getAddress can return the expected value of v4 address + ElementPtr v4 = Element::fromJSON("{ \"foo\": \"192.0.2.1\" }"); + IOAddress val("::"); + EXPECT_NO_THROW(val = parser.getAddress(v4, "foo")); + EXPECT_EQ("192.0.2.1" , val.toText()); + + // checks if getAddress can return the expected value of v4 address + ElementPtr v6 = Element::fromJSON("{ \"foo\": \"2001:db8::1\" }"); + EXPECT_NO_THROW(val = parser.getAddress(v6, "foo")); + EXPECT_EQ("2001:db8::1" , val.toText()); +} + +// This test exercises getDouble() +TEST_F(SimpleParserTest, getDouble) { + + SimpleParserClassTest parser; + std::string json = + "{\n" + " \"string\" : \"12.3\",\n" + " \"bool\" : true, \n" + " \"int\" : 777, \n" + " \"map\" : {}, \n" + " \"list\" : [], \n" + " \"zero\" : 0.0, \n" + " \"fraction\" : .75, \n" + " \"negative\" : -1.45, \n" + " \"positive\" : 346.7 \n" + "}\n"; + + // Create our test set of parameters. + ElementPtr elems; + ASSERT_NO_THROW(elems = Element::fromJSON(json)) << " invalid JSON, test is broken"; + + // Verify that a non-existant element is caught. + EXPECT_THROW(parser.getDouble(elems, "not-there"), DhcpConfigError); + + // Verify that wrong element types are caught. + EXPECT_THROW(parser.getDouble(elems, "string"), DhcpConfigError); + EXPECT_THROW(parser.getDouble(elems, "int"), DhcpConfigError); + EXPECT_THROW(parser.getDouble(elems, "bool"), DhcpConfigError); + EXPECT_THROW(parser.getDouble(elems, "map"), DhcpConfigError); + EXPECT_THROW(parser.getDouble(elems, "list"), DhcpConfigError); + + // Verify valid values are correct. + double value; + + EXPECT_NO_THROW(value = parser.getDouble(elems, "zero")); + EXPECT_EQ(0.0, value); + + EXPECT_NO_THROW(value = parser.getDouble(elems, "fraction")); + EXPECT_EQ(.75, value); + + EXPECT_NO_THROW(value = parser.getDouble(elems, "negative")); + EXPECT_EQ(-1.45, value); + + EXPECT_NO_THROW(value = parser.getDouble(elems, "positive")); + EXPECT_EQ(346.7, value); +} diff --git a/src/lib/cc/tests/stamped_element_unittest.cc b/src/lib/cc/tests/stamped_element_unittest.cc new file mode 100644 index 0000000..1ffcd84 --- /dev/null +++ b/src/lib/cc/tests/stamped_element_unittest.cc @@ -0,0 +1,132 @@ +// Copyright (C) 2018-2019 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include + +using namespace isc::data; + +namespace { + +// Tests that the modification timestamp is by default set to current +// time and the identifier is set to 0. +TEST(StampedElementTest, create) { + StampedElement element; + + // Default identifier is 0. + EXPECT_EQ(0, element.getId()); + + // By default there is no server tag. + EXPECT_TRUE(element.getServerTags().empty()); + + // Checking that the delta between now and the timestamp is within + // 5s range should be sufficient. + boost::posix_time::time_duration delta = + boost::posix_time::second_clock::local_time() - + element.getModificationTime(); + EXPECT_LT(delta.seconds(), 5); +} + +// Tests that default id can be overridden by a new value. +TEST(StampedElementTest, setId) { + StampedElement element; + element.setId(123); + EXPECT_EQ(123, element.getId()); +} + +// Tests that the modification timestamp can be set to an arbitrary +// value. +TEST(StampedElementTest, setModificationTime) { + boost::posix_time::ptime + modification_time(boost::gregorian::date(2002, boost::date_time::Jan, 10), + boost::posix_time::time_duration(1,2,3)); + StampedElement element; + element.setModificationTime(modification_time); + EXPECT_TRUE(element.getModificationTime() == modification_time); +} + +// Tests that updating modification timestamp sets it to the current +// time. +TEST(StampedElementTest, update) { + boost::posix_time::ptime + modification_time(boost::gregorian::date(2002, boost::date_time::Jan, 10), + boost::posix_time::time_duration(1,2,3)); + StampedElement element; + element.setModificationTime(modification_time); + element.updateModificationTime(); + + // Checking that the delta between now and the timestamp is within + // 5s range should be sufficient. + boost::posix_time::time_duration delta = + boost::posix_time::second_clock::local_time() - + element.getModificationTime(); + EXPECT_LT(delta.seconds(), 5); +} + +// Tests that one or more server tag can be specified. +TEST(StampedElementTest, setServerTag) { + StampedElement element; + element.setServerTag("foo"); + EXPECT_EQ(1, element.getServerTags().size()); + EXPECT_EQ("foo", element.getServerTags().begin()->get()); + + element.setServerTag("bar"); + EXPECT_EQ(2, element.getServerTags().size()); + + EXPECT_TRUE(element.hasServerTag(ServerTag("foo"))); + EXPECT_TRUE(element.hasServerTag(ServerTag("bar"))); + EXPECT_FALSE(element.hasServerTag(ServerTag("xyz"))); + EXPECT_FALSE(element.hasAllServerTag()); + + element.setServerTag(ServerTag::ALL); + EXPECT_TRUE(element.hasAllServerTag()); +} + +// Tests that a server tag can be deleted. +TEST(StampedElementTest, delServerTag) { + StampedElement element; + EXPECT_THROW(element.delServerTag("foo"), isc::NotFound); + element.setServerTag("foo"); + element.setServerTag("bar"); + + ASSERT_EQ(2, element.getServerTags().size()); + EXPECT_TRUE(element.hasServerTag(ServerTag("foo"))); + EXPECT_TRUE(element.hasServerTag(ServerTag("bar"))); + + EXPECT_NO_THROW(element.delServerTag("foo")); + ASSERT_EQ(1, element.getServerTags().size()); + EXPECT_TRUE(element.hasServerTag(ServerTag("bar"))); + + EXPECT_NO_THROW(element.delServerTag("bar")); + EXPECT_EQ(0, element.getServerTags().size()); + EXPECT_THROW(element.delServerTag("bar"), isc::NotFound); +} + + +// Test that metadata can be created from the StampedElement. +TEST(StampedElementTest, getMetadata) { + StampedElement element; + element.setServerTag("world"); + auto metadata = element.getMetadata(); + ASSERT_TRUE(metadata); + ASSERT_EQ(Element::map, metadata->getType()); + + auto server_tags_element = metadata->get("server-tags"); + ASSERT_TRUE(server_tags_element); + EXPECT_EQ(Element::list, server_tags_element->getType()); + EXPECT_EQ(1, server_tags_element->size()); + + auto server_tag_element = server_tags_element->get(0); + ASSERT_TRUE(server_tag_element); + EXPECT_EQ(Element::string, server_tag_element->getType()); + EXPECT_EQ("world", server_tag_element->stringValue()); +} + +} diff --git a/src/lib/cc/tests/stamped_value_unittest.cc b/src/lib/cc/tests/stamped_value_unittest.cc new file mode 100644 index 0000000..d305a34 --- /dev/null +++ b/src/lib/cc/tests/stamped_value_unittest.cc @@ -0,0 +1,175 @@ +// Copyright (C) 2018-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include +#include +#include + +using namespace isc; +using namespace isc::data; + +namespace { + +// Tests that the stamped value can be created with a NULL value. +TEST(StampedValueTest, createNull) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar")); + + EXPECT_TRUE(value->amNull()); + + EXPECT_THROW(value->getType(), InvalidOperation); + EXPECT_THROW(value->getValue(), InvalidOperation); + EXPECT_THROW(value->getIntegerValue(), InvalidOperation); + EXPECT_THROW(value->getBoolValue(), InvalidOperation); + EXPECT_THROW(value->getDoubleValue(), InvalidOperation); +} + +// Tests that stamped value from string can be created. +TEST(StampedValueTest, createFromString) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create("foo"))); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::string, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_EQ("foo", value->getValue()); + + EXPECT_THROW(value->getIntegerValue(), TypeError); + EXPECT_THROW(value->getBoolValue(), TypeError); + EXPECT_THROW(value->getDoubleValue(), TypeError); +} + +// Tests that the stamped value can be created from string using the +// factory function variant that takes parameter type as an argument. +TEST(StampedValueTest, convertStringToString) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", "foo", Element::string)); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::string, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_EQ("foo", value->getValue()); +} + +// Tests that stamped value from integer can be created. +TEST(StampedValueTest, createFromInteger) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create(static_cast(5)))); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::integer, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_EQ("5", value->getValue()); + int64_t signed_integer = 0; + ASSERT_NO_THROW(signed_integer = value->getIntegerValue()); + EXPECT_EQ(5, signed_integer); + + EXPECT_THROW(value->getBoolValue(), TypeError); + EXPECT_THROW(value->getDoubleValue(), TypeError); +} + +// Tests that stamped value can be converted from string to integer. +TEST(StampedValueTest, convertStringToInteger) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", "123", Element::integer)); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::integer, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_EQ(123, value->getIntegerValue()); + + EXPECT_THROW(StampedValue::create("bar", "hoho", Element::integer), BadValue); +} + +// Tests that stamped value from bool can be created. +TEST(StampedValueTest, createFromBool) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create(static_cast(true)))); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::boolean, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_EQ("1", value->getValue()); + bool bool_value = false; + ASSERT_NO_THROW(bool_value = value->getBoolValue()); + EXPECT_TRUE(bool_value); + + EXPECT_THROW(value->getIntegerValue(), TypeError); + EXPECT_THROW(value->getDoubleValue(), TypeError); +} + +// Tests that stamped value can be converted from string to boolean. +TEST(StampedValueTest, convertStringToBoolean) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", "1", Element::boolean)); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::boolean, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_TRUE(value->getBoolValue()); + + ASSERT_NO_THROW(value = StampedValue::create("foo", "0", Element::boolean)); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::boolean, value->getType()); + EXPECT_EQ("foo", value->getName()); + EXPECT_FALSE(value->getBoolValue()); + + EXPECT_THROW(StampedValue::create("bar", "888", Element::boolean), BadValue); +} + +// Tests that stamped value from real can be created. +TEST(StampedValueTest, createFromDouble) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create(static_cast(1.45)))); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::real, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_EQ("1.45", value->getValue()); + double double_value = 0; + ASSERT_NO_THROW(double_value = value->getDoubleValue()); + EXPECT_EQ(1.45, double_value); + + EXPECT_THROW(value->getIntegerValue(), TypeError); + EXPECT_THROW(value->getBoolValue(), TypeError); +} + +// Tests that stamped value from real can handle a round value. +TEST(StampedValueTest, createFromDoubleRound) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", Element::create(static_cast(7.0)))); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::real, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_EQ("7.0", value->getValue()); + double double_value = 0; + ASSERT_NO_THROW(double_value = value->getDoubleValue()); + EXPECT_EQ(7.0, double_value); + + EXPECT_THROW(value->getIntegerValue(), TypeError); + EXPECT_THROW(value->getBoolValue(), TypeError); +} + +// Tests that stamped value can be converted from string to real. +TEST(StampedValueTest, convertStringToDouble) { + StampedValuePtr value; + ASSERT_NO_THROW(value = StampedValue::create("bar", "1.67", Element::real)); + EXPECT_FALSE(value->amNull()); + EXPECT_EQ(Element::real, value->getType()); + EXPECT_EQ("bar", value->getName()); + EXPECT_EQ(1.67, value->getDoubleValue()); + + EXPECT_THROW(StampedValue::create("bar", "hoho", Element::real), BadValue); +} + +// Tests that the value must have an allowed type. +TEST(StampedValueTest, createFailures) { + EXPECT_THROW(StampedValue::create("bar", ElementPtr()), BadValue); + EXPECT_THROW(StampedValue::create("bar", Element::createMap()), TypeError); + EXPECT_THROW(StampedValue::create("bar", Element::createList()), TypeError); + + EXPECT_THROW(StampedValue::create("bar", "1", Element::map), TypeError); + EXPECT_THROW(StampedValue::create("bar", "1", Element::list), TypeError); + EXPECT_THROW(StampedValue::create("bar", "1", Element::null), TypeError); +} + +} diff --git a/src/lib/cc/tests/user_context_unittests.cc b/src/lib/cc/tests/user_context_unittests.cc new file mode 100644 index 0000000..e88b421 --- /dev/null +++ b/src/lib/cc/tests/user_context_unittests.cc @@ -0,0 +1,132 @@ +// Copyright (C) 2018-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include + +using namespace isc::data; + +namespace { + +ElementPtr gen() { + std::string content = "{ \"foo\": 1, \"bar\": \"xyz\" }"; + return (Element::fromJSON(content)); +} + +TEST(UserContext, setget) { + UserContext parent; + EXPECT_FALSE(parent.getContext()); + ConstElementPtr map = gen(); + parent.setContext(map); + ConstElementPtr ctx = parent.getContext(); + EXPECT_EQ(*ctx, *map); +} + +TEST(UserContext, null) { + UserContext parent; + ElementPtr map = gen(); + parent.contextToElement(map); + ElementPtr expected = gen(); + EXPECT_EQ(*expected, *map); +} + +TEST(UserContext, notMap) { + UserContext parent; + ConstElementPtr ctx = Element::create("foo"); + parent.setContext(ctx); + ElementPtr map = gen(); + parent.contextToElement(map); + ElementPtr expected = gen(); + expected->set("user-context", ctx); + EXPECT_EQ(*expected, *map); +} + +TEST(UserContext, empty) { + UserContext parent; + ConstElementPtr ctx = Element::createMap(); + parent.setContext(ctx); + ElementPtr map = gen(); + parent.contextToElement(map); + ElementPtr expected = gen(); + expected->set("user-context", ctx); + EXPECT_EQ(*expected, *map); +} + +TEST(UserContext, noComment) { + UserContext parent; + ConstElementPtr ctx = Element::fromJSON("{ \"version\": 1 }"); + parent.setContext(ctx); + ElementPtr map = gen(); + parent.contextToElement(map); + ElementPtr expected = gen(); + expected->set("user-context", ctx); + EXPECT_EQ(*expected, *map); +}; + +TEST(UserContext, onlyComment) { + UserContext parent; + ConstElementPtr ctx = Element::fromJSON("{ \"comment\": \"foobar\" }"); + parent.setContext(ctx); + ElementPtr map = gen(); + parent.contextToElement(map); + ElementPtr expected = gen(); + expected->set("user-context", ctx); + EXPECT_EQ(*expected, *map); +} + +TEST(UserContext, both) { + UserContext parent; + ConstElementPtr ctx = + Element::fromJSON("{ \"comment\": \"foobar\", \"version\": 1 }"); + parent.setContext(ctx); + ElementPtr map = gen(); + parent.contextToElement(map); + ElementPtr expected = gen(); + expected->set("user-context", ctx); + EXPECT_EQ(*expected, *map); +} + +TEST(toElement, notMap) { + ConstElementPtr arg = Element::create("foo"); + ConstElementPtr result = UserContext::toElement(arg); + EXPECT_EQ(*result, *arg); +} + +TEST(toElement, empty) { + ElementPtr map = gen(); + ConstElementPtr ctx = Element::createMap(); + map->set("user-context", ctx); + ConstElementPtr result = UserContext::toElement(map); + EXPECT_EQ(*result, *map); +} + +TEST(toElement, noComment) { + ElementPtr map = gen(); + ConstElementPtr ctx = Element::fromJSON("{ \"version\": 1 }"); + map->set("user-context", ctx); + ConstElementPtr result = UserContext::toElement(map); + EXPECT_EQ(*result, *map); +} + +TEST(toElement, onlyComment) { + ElementPtr map = gen(); + ConstElementPtr ctx = Element::fromJSON("{ \"comment\": \"foobar\" }"); + map->set("user-context", ctx); + ConstElementPtr result = UserContext::toElement(map); + EXPECT_EQ(*result, *map); +} + +TEST(toElement, both) { + ElementPtr map = gen(); + ConstElementPtr ctx = + Element::fromJSON("{ \"comment\": \"foobar\", \"version\": 1 }"); + map->set("user-context", ctx); + ConstElementPtr result = UserContext::toElement(map); + EXPECT_EQ(*result, *map); +} + +} diff --git a/src/lib/cc/user_context.cc b/src/lib/cc/user_context.cc new file mode 100644 index 0000000..2ff1984 --- /dev/null +++ b/src/lib/cc/user_context.cc @@ -0,0 +1,30 @@ +// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include + + +namespace isc { +namespace data { + +void +UserContext::contextToElement(ElementPtr map) const { + // Set user-context extracting comment + ConstElementPtr context = getContext(); + if (context) { + map->set("user-context", context); + } +} + +ElementPtr +UserContext::toElement(ConstElementPtr map) { + ElementPtr result = isc::data::copy(map); + return (result); +} + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/cc/user_context.h b/src/lib/cc/user_context.h new file mode 100644 index 0000000..2804c7e --- /dev/null +++ b/src/lib/cc/user_context.h @@ -0,0 +1,59 @@ +// 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 USER_CONTEXT_H +#define USER_CONTEXT_H + +#include +#include + +namespace isc { +namespace data { + +/// @brief Base class for user context +/// +/// Many configuration structures allow attaching and storing arbitrary +/// user data that we call user context. Each of those configuration +/// structures need to derive from this class or include this class +/// to handle user context. +struct UserContext { + /// @brief Returns const pointer to the user context. + data::ConstElementPtr getContext() const { + return (user_context_); + } + + /// @brief Sets user context. + /// @param ctx user context to be stored. + void setContext(const data::ConstElementPtr& ctx) { + user_context_ = ctx; + } + + /// @brief Merge unparse a user_context object. + /// + /// Add user-context to map, but only if defined. Omit if it was not. + /// + /// @param map A pointer to map where the user context will be unparsed. + void contextToElement(data::ElementPtr map) const; + + /// @brief Copy an Element map + /// + /// A previous version of this extracted comments. + /// + /// @param map A pointer to map. + /// @return a copy of map + static data::ElementPtr toElement(data::ConstElementPtr map); + +protected: + + /// @brief Pointer to the user context (may be NULL) + data::ConstElementPtr user_context_; +}; + +} // end of isc::dhcp namespace +} // end of isc namespace + + +#endif // USER_CONTEXT_H -- cgit v1.2.3