diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:15:43 +0000 |
commit | f5f56e1a1c4d9e9496fcb9d81131066a964ccd23 (patch) | |
tree | 49e44c6f87febed37efb953ab5485aa49f6481a7 /src/lib/eval | |
parent | Initial commit. (diff) | |
download | isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.tar.xz isc-kea-f5f56e1a1c4d9e9496fcb9d81131066a964ccd23.zip |
Adding upstream version 2.4.1.upstream/2.4.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/eval')
31 files changed, 21872 insertions, 0 deletions
diff --git a/src/lib/eval/Makefile.am b/src/lib/eval/Makefile.am new file mode 100644 index 0000000..ded89f2 --- /dev/null +++ b/src/lib/eval/Makefile.am @@ -0,0 +1,139 @@ +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +# GCC 4.4 emits warning about breaking strict aliasing rule. +# This warning is a result of a GCC bug: +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41874 +# The warning is raised in the generated code in parser.h. +# Disabling the strict aliasing rule suppresses this warning. +AM_CXXFLAGS += $(WARNING_GCC_44_STRICT_ALIASING_CFLAG) + +lib_LTLIBRARIES = libkea-eval.la +libkea_eval_la_SOURCES = +libkea_eval_la_SOURCES += dependency.cc dependency.h +libkea_eval_la_SOURCES += eval_log.cc eval_log.h +libkea_eval_la_SOURCES += evaluate.cc evaluate.h +libkea_eval_la_SOURCES += token.cc token.h + +libkea_eval_la_SOURCES += parser.cc parser.h +libkea_eval_la_SOURCES += lexer.cc +libkea_eval_la_SOURCES += location.hh +libkea_eval_la_SOURCES += eval_context.cc eval_context.h eval_context_decl.h +libkea_eval_la_SOURCES += eval_messages.h eval_messages.cc + +libkea_eval_la_CXXFLAGS = $(AM_CXXFLAGS) +libkea_eval_la_CPPFLAGS = $(AM_CPPFLAGS) +libkea_eval_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +libkea_eval_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +libkea_eval_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libkea_eval_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +libkea_eval_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +libkea_eval_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la +libkea_eval_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la +libkea_eval_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libkea_eval_la_LIBADD += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS) + +libkea_eval_la_LDFLAGS = -no-undefined -version-info 52:0:0 +libkea_eval_la_LDFLAGS += $(CRYPTO_LDFLAGS) + +EXTRA_DIST = eval.dox +EXTRA_DIST += eval_messages.mes +EXTRA_DIST += lexer.ll parser.yy + +CLEANFILES = *.gcno *.gcda + +# To regenerate messages files, one can do: +# +# make messages-clean +# make messages +# +# This is needed only when a .mes file is modified. +messages-clean: + rm -f eval_messages.h eval_messages.cc + +if GENERATE_MESSAGES + +# Define rule to build logging source files from message file +messages: eval_messages.h eval_messages.cc + @echo Message files regenerated + +eval_messages.h eval_messages.cc: eval_messages.mes + $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/eval/eval_messages.mes + +else + +messages eval_messages.h eval_messages.cc: + @echo Messages generation disabled. Configure with --enable-generate-messages to enable it. + +endif + +# If we want to get rid of all generated messages files, we need to use +# make maintainer-clean. The proper way to introduce custom commands for +# that operation is to define maintainer-clean-local target. However, +# make maintainer-clean also removes Makefile, so running configure script +# is required. To make it easy to rebuild messages without going through +# reconfigure, a new target messages-clean has been added. +# +# If we want to get rid of all flex/bison generated files, we need to use +# make maintainer-clean. The proper way to introduce custom commands for +# that operation is to define maintainer-clean-local target. However, +# make maintainer-clean also removes Makefile, so running configure script +# is required. To make it easy to rebuild flex/bison without going through +# reconfigure, a new target parser-clean has been added. +maintainer-clean-local: + rm -f eval_messages.h eval_messages.cc + rm -f location.hh lexer.cc parser.cc parser.h + +# To regenerate flex/bison files, one can do: +# +# make parser-clean +# make parser +# +# This is needed only when the lexer.ll or parser.yy files are modified. +# Make sure you have both flex and bison installed. +parser-clean: + rm -f location.hh lexer.cc parser.cc parser.h + +if GENERATE_PARSER + +# Generate parser first. +all-recursive: lexer.cc location.hh parser.cc parser.h + +parser: lexer.cc location.hh parser.cc parser.h + @echo "Flex/bison files regenerated" + +# --- Flex/Bison stuff below -------------------------------------------------- +# When debugging grammar issues, it's useful to add -v to bison parameters. +# bison will generate parser.output file that explains the whole grammar. +# It can be used to manually follow what's going on in the parser. +# This is especially useful if yydebug_ is set to 1 as that variable +# will cause parser to print out its internal state. +location.hh parser.cc parser.h: parser.yy + $(YACC) -Wno-yacc --defines=parser.h -o parser.cc parser.yy + +lexer.cc: lexer.ll + $(LEX) --prefix eval -o lexer.cc lexer.ll + +else + +parser location.hh parser.cc parser.h lexer.cc: + @echo Parser generation disabled. Configure with --enable-generate-parser to enable it. + +endif + + +# Specify the headers for copying into the installation directory tree. +libkea_eval_includedir = $(pkgincludedir)/eval +libkea_eval_include_HEADERS = \ + dependency.h \ + eval_context.h \ + eval_context_decl.h \ + eval_log.h \ + eval_messages.h \ + evaluate.h \ + parser.h \ + token.h +# does not include *.hh generated headers as they come with lexer and parser. diff --git a/src/lib/eval/Makefile.in b/src/lib/eval/Makefile.in new file mode 100644 index 0000000..6b8edb5 --- /dev/null +++ b/src/lib/eval/Makefile.in @@ -0,0 +1,1134 @@ +# 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/eval +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_eval_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_eval_includedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libkea_eval_la_DEPENDENCIES = \ + $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ + $(top_builddir)/src/lib/hooks/libkea-hooks.la \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/dns/libkea-dns++.la \ + $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ + $(top_builddir)/src/lib/log/libkea-log.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +am_libkea_eval_la_OBJECTS = libkea_eval_la-dependency.lo \ + libkea_eval_la-eval_log.lo libkea_eval_la-evaluate.lo \ + libkea_eval_la-token.lo libkea_eval_la-parser.lo \ + libkea_eval_la-lexer.lo libkea_eval_la-eval_context.lo \ + libkea_eval_la-eval_messages.lo +libkea_eval_la_OBJECTS = $(am_libkea_eval_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_eval_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) \ + $(libkea_eval_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)/libkea_eval_la-dependency.Plo \ + ./$(DEPDIR)/libkea_eval_la-eval_context.Plo \ + ./$(DEPDIR)/libkea_eval_la-eval_log.Plo \ + ./$(DEPDIR)/libkea_eval_la-eval_messages.Plo \ + ./$(DEPDIR)/libkea_eval_la-evaluate.Plo \ + ./$(DEPDIR)/libkea_eval_la-lexer.Plo \ + ./$(DEPDIR)/libkea_eval_la-parser.Plo \ + ./$(DEPDIR)/libkea_eval_la-token.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_eval_la_SOURCES) +DIST_SOURCES = $(libkea_eval_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_eval_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_builddir)/src/lib -I$(top_srcdir)/src/lib \ + $(BOOST_INCLUDES) + +# GCC 4.4 emits warning about breaking strict aliasing rule. +# This warning is a result of a GCC bug: +# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=41874 +# The warning is raised in the generated code in parser.h. +# Disabling the strict aliasing rule suppresses this warning. +AM_CXXFLAGS = $(KEA_CXXFLAGS) $(WARNING_GCC_44_STRICT_ALIASING_CFLAG) +lib_LTLIBRARIES = libkea-eval.la +libkea_eval_la_SOURCES = dependency.cc dependency.h eval_log.cc \ + eval_log.h evaluate.cc evaluate.h token.cc token.h parser.cc \ + parser.h lexer.cc location.hh eval_context.cc eval_context.h \ + eval_context_decl.h eval_messages.h eval_messages.cc +libkea_eval_la_CXXFLAGS = $(AM_CXXFLAGS) +libkea_eval_la_CPPFLAGS = $(AM_CPPFLAGS) +libkea_eval_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ + $(top_builddir)/src/lib/hooks/libkea-hooks.la \ + $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ + $(top_builddir)/src/lib/dns/libkea-dns++.la \ + $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ + $(top_builddir)/src/lib/log/libkea-log.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS) $(BOOST_LIBS) +libkea_eval_la_LDFLAGS = -no-undefined -version-info 52:0:0 \ + $(CRYPTO_LDFLAGS) +EXTRA_DIST = eval.dox eval_messages.mes lexer.ll parser.yy +CLEANFILES = *.gcno *.gcda + +# Specify the headers for copying into the installation directory tree. +libkea_eval_includedir = $(pkgincludedir)/eval +libkea_eval_include_HEADERS = \ + dependency.h \ + eval_context.h \ + eval_context_decl.h \ + eval_log.h \ + eval_messages.h \ + evaluate.h \ + parser.h \ + token.h + +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/eval/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/eval/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-eval.la: $(libkea_eval_la_OBJECTS) $(libkea_eval_la_DEPENDENCIES) $(EXTRA_libkea_eval_la_DEPENDENCIES) + $(AM_V_CXXLD)$(libkea_eval_la_LINK) -rpath $(libdir) $(libkea_eval_la_OBJECTS) $(libkea_eval_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-dependency.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-eval_context.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-eval_log.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-eval_messages.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-evaluate.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-lexer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-parser.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_eval_la-token.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 $@ $< + +libkea_eval_la-dependency.lo: dependency.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-dependency.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-dependency.Tpo -c -o libkea_eval_la-dependency.lo `test -f 'dependency.cc' || echo '$(srcdir)/'`dependency.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-dependency.Tpo $(DEPDIR)/libkea_eval_la-dependency.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dependency.cc' object='libkea_eval_la-dependency.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-dependency.lo `test -f 'dependency.cc' || echo '$(srcdir)/'`dependency.cc + +libkea_eval_la-eval_log.lo: eval_log.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-eval_log.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-eval_log.Tpo -c -o libkea_eval_la-eval_log.lo `test -f 'eval_log.cc' || echo '$(srcdir)/'`eval_log.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-eval_log.Tpo $(DEPDIR)/libkea_eval_la-eval_log.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='eval_log.cc' object='libkea_eval_la-eval_log.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-eval_log.lo `test -f 'eval_log.cc' || echo '$(srcdir)/'`eval_log.cc + +libkea_eval_la-evaluate.lo: evaluate.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-evaluate.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-evaluate.Tpo -c -o libkea_eval_la-evaluate.lo `test -f 'evaluate.cc' || echo '$(srcdir)/'`evaluate.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-evaluate.Tpo $(DEPDIR)/libkea_eval_la-evaluate.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='evaluate.cc' object='libkea_eval_la-evaluate.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-evaluate.lo `test -f 'evaluate.cc' || echo '$(srcdir)/'`evaluate.cc + +libkea_eval_la-token.lo: token.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-token.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-token.Tpo -c -o libkea_eval_la-token.lo `test -f 'token.cc' || echo '$(srcdir)/'`token.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-token.Tpo $(DEPDIR)/libkea_eval_la-token.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='token.cc' object='libkea_eval_la-token.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-token.lo `test -f 'token.cc' || echo '$(srcdir)/'`token.cc + +libkea_eval_la-parser.lo: parser.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-parser.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-parser.Tpo -c -o libkea_eval_la-parser.lo `test -f 'parser.cc' || echo '$(srcdir)/'`parser.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-parser.Tpo $(DEPDIR)/libkea_eval_la-parser.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='parser.cc' object='libkea_eval_la-parser.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-parser.lo `test -f 'parser.cc' || echo '$(srcdir)/'`parser.cc + +libkea_eval_la-lexer.lo: lexer.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-lexer.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-lexer.Tpo -c -o libkea_eval_la-lexer.lo `test -f 'lexer.cc' || echo '$(srcdir)/'`lexer.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-lexer.Tpo $(DEPDIR)/libkea_eval_la-lexer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='lexer.cc' object='libkea_eval_la-lexer.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-lexer.lo `test -f 'lexer.cc' || echo '$(srcdir)/'`lexer.cc + +libkea_eval_la-eval_context.lo: eval_context.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-eval_context.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-eval_context.Tpo -c -o libkea_eval_la-eval_context.lo `test -f 'eval_context.cc' || echo '$(srcdir)/'`eval_context.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-eval_context.Tpo $(DEPDIR)/libkea_eval_la-eval_context.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='eval_context.cc' object='libkea_eval_la-eval_context.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-eval_context.lo `test -f 'eval_context.cc' || echo '$(srcdir)/'`eval_context.cc + +libkea_eval_la-eval_messages.lo: eval_messages.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -MT libkea_eval_la-eval_messages.lo -MD -MP -MF $(DEPDIR)/libkea_eval_la-eval_messages.Tpo -c -o libkea_eval_la-eval_messages.lo `test -f 'eval_messages.cc' || echo '$(srcdir)/'`eval_messages.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_eval_la-eval_messages.Tpo $(DEPDIR)/libkea_eval_la-eval_messages.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='eval_messages.cc' object='libkea_eval_la-eval_messages.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_eval_la_CPPFLAGS) $(CPPFLAGS) $(libkea_eval_la_CXXFLAGS) $(CXXFLAGS) -c -o libkea_eval_la-eval_messages.lo `test -f 'eval_messages.cc' || echo '$(srcdir)/'`eval_messages.cc + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-libkea_eval_includeHEADERS: $(libkea_eval_include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libkea_eval_include_HEADERS)'; test -n "$(libkea_eval_includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libkea_eval_includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libkea_eval_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_eval_includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libkea_eval_includedir)" || exit $$?; \ + done + +uninstall-libkea_eval_includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libkea_eval_include_HEADERS)'; test -n "$(libkea_eval_includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libkea_eval_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_eval_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)/libkea_eval_la-dependency.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-eval_context.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-eval_log.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-eval_messages.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-evaluate.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-lexer.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-parser.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-token.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_eval_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)/libkea_eval_la-dependency.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-eval_context.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-eval_log.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-eval_messages.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-evaluate.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-lexer.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-parser.Plo + -rm -f ./$(DEPDIR)/libkea_eval_la-token.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic \ + maintainer-clean-local + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES \ + uninstall-libkea_eval_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_eval_includeHEADERS install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic \ + maintainer-clean-local mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-libLTLIBRARIES \ + uninstall-libkea_eval_includeHEADERS + +.PRECIOUS: Makefile + + +# To regenerate messages files, one can do: +# +# make messages-clean +# make messages +# +# This is needed only when a .mes file is modified. +messages-clean: + rm -f eval_messages.h eval_messages.cc + +# Define rule to build logging source files from message file +@GENERATE_MESSAGES_TRUE@messages: eval_messages.h eval_messages.cc +@GENERATE_MESSAGES_TRUE@ @echo Message files regenerated + +@GENERATE_MESSAGES_TRUE@eval_messages.h eval_messages.cc: eval_messages.mes +@GENERATE_MESSAGES_TRUE@ $(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/lib/eval/eval_messages.mes + +@GENERATE_MESSAGES_FALSE@messages eval_messages.h eval_messages.cc: +@GENERATE_MESSAGES_FALSE@ @echo Messages generation disabled. Configure with --enable-generate-messages to enable it. + +# If we want to get rid of all generated messages files, we need to use +# make maintainer-clean. The proper way to introduce custom commands for +# that operation is to define maintainer-clean-local target. However, +# make maintainer-clean also removes Makefile, so running configure script +# is required. To make it easy to rebuild messages without going through +# reconfigure, a new target messages-clean has been added. +# +# If we want to get rid of all flex/bison generated files, we need to use +# make maintainer-clean. The proper way to introduce custom commands for +# that operation is to define maintainer-clean-local target. However, +# make maintainer-clean also removes Makefile, so running configure script +# is required. To make it easy to rebuild flex/bison without going through +# reconfigure, a new target parser-clean has been added. +maintainer-clean-local: + rm -f eval_messages.h eval_messages.cc + rm -f location.hh lexer.cc parser.cc parser.h + +# To regenerate flex/bison files, one can do: +# +# make parser-clean +# make parser +# +# This is needed only when the lexer.ll or parser.yy files are modified. +# Make sure you have both flex and bison installed. +parser-clean: + rm -f location.hh lexer.cc parser.cc parser.h + +# Generate parser first. +@GENERATE_PARSER_TRUE@all-recursive: lexer.cc location.hh parser.cc parser.h + +@GENERATE_PARSER_TRUE@parser: lexer.cc location.hh parser.cc parser.h +@GENERATE_PARSER_TRUE@ @echo "Flex/bison files regenerated" + +# --- Flex/Bison stuff below -------------------------------------------------- +# When debugging grammar issues, it's useful to add -v to bison parameters. +# bison will generate parser.output file that explains the whole grammar. +# It can be used to manually follow what's going on in the parser. +# This is especially useful if yydebug_ is set to 1 as that variable +# will cause parser to print out its internal state. +@GENERATE_PARSER_TRUE@location.hh parser.cc parser.h: parser.yy +@GENERATE_PARSER_TRUE@ $(YACC) -Wno-yacc --defines=parser.h -o parser.cc parser.yy + +@GENERATE_PARSER_TRUE@lexer.cc: lexer.ll +@GENERATE_PARSER_TRUE@ $(LEX) --prefix eval -o lexer.cc lexer.ll + +@GENERATE_PARSER_FALSE@parser location.hh parser.cc parser.h lexer.cc: +@GENERATE_PARSER_FALSE@ @echo Parser generation disabled. Configure with --enable-generate-parser to enable it. +# does not include *.hh generated headers as they come with lexer and parser. + +# 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/eval/dependency.cc b/src/lib/eval/dependency.cc new file mode 100644 index 0000000..66e3600 --- /dev/null +++ b/src/lib/eval/dependency.cc @@ -0,0 +1,37 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <eval/dependency.h> +#include <boost/pointer_cast.hpp> + +namespace isc { +namespace dhcp { + +bool dependOnClass(const TokenPtr& token, const std::string& name) { + boost::shared_ptr<TokenMember> member; + member = boost::dynamic_pointer_cast<TokenMember>(token); + if (!member) { + return (false); + } + return (member->getClientClass() == name); +} + +bool dependOnClass(const ExpressionPtr& expr, const std::string& name) { + if (!expr) { + return (false); + } + for (auto it = expr->cbegin(); it != expr->cend(); ++it) { + if (dependOnClass(*it, name)) { + return (true); + } + } + return (false); +} + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/eval/dependency.h b/src/lib/eval/dependency.h new file mode 100644 index 0000000..001bed7 --- /dev/null +++ b/src/lib/eval/dependency.h @@ -0,0 +1,37 @@ +// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef DEPENDENCY_H +#define DEPENDENCY_H + +#include <eval/token.h> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief Checks dependency on a token. +/// +/// It checks if the token is a TokenMember for the given class. +/// +/// @param token A pointer to the token. +/// @param name The client class name. +/// @return true if token points to a TokenMember of name, false if not. +bool dependOnClass(const TokenPtr& token, const std::string& name); + +/// @brief Checks dependency on an expression. +/// +/// It checks if a member of the expression depends on the given class. +/// +/// @param expr An expression. +/// @param name The client class name. +/// @return true if a member of expr depends on name, false if not. +bool dependOnClass(const ExpressionPtr& expr, const std::string& name); + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif diff --git a/src/lib/eval/eval.dox b/src/lib/eval/eval.dox new file mode 100644 index 0000000..e728006 --- /dev/null +++ b/src/lib/eval/eval.dox @@ -0,0 +1,198 @@ +// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/** + @page libeval libkea-eval - Expression Evaluation and Client Classification Library + + @section dhcpEvalIntroduction Introduction + + The core of the libeval library is a parser that is able to parse an + expression (e.g. option[123].text == 'APC'). This is currently used for + client classification, but in the future may be also used for other + applications. + + The external interface to the library is the @ref isc::eval::EvalContext + class. Once instantiated, it offers a major method: + @ref isc::eval::EvalContext::parseString, which parses the specified + string. Once the expression is parsed, it is converted to a collection of + tokens that are stored in Reverse Polish Notation in + EvalContext::expression. + + Parameters to the @ref isc::eval::EvalContext class constructor are + the universe to choose between DHCPv4 and DHCPv6 for DHCP version + dependent expressions, and a function used + by the parser to accept only already defined or built-in client + class names in client class membership expressions. This function defaults + to accept all client class names. + + Internally, the parser code is generated by flex and bison. These two + tools convert lexer.ll and parser.yy files into a number of .cc and .hh files. + To avoid a build of Kea depending on the presence of flex and bison, the + result of the generation is checked into the github repository and is + distributed in the tarballs. + + @section dhcpEvalLexer Lexer generation using flex + + Flex is used to generate the lexer, a piece of code that converts input + data into a series of tokens. It contains a small number of directives, + but the majority of the code consists of the definitions of tokens. These + definitions are regular expressions that define various tokens, e.g. strings, + numbers, parentheses, etc. Once the expression is matched, the associated + action is executed. In the majority of the cases a generator method from + @ref isc::eval::EvalParser is called, which returns returns a newly created + bison token. The purpose of the lexer is to generate a stream + of tokens that are consumed by the parser. + + lexer.cc and lexer.hh must not be edited. If there is a need + to introduce changes, lexer.ll must be updated and the .cc and .hh files + regenerated. + + @section dhcpEvalParser Parser generation using bison + + Bison is used to generate the parser, a piece of code that consumes a + stream of tokens and attempts to match it against a defined grammar. + The bison parser is created from parser.yy. It contains + a number of directives, but the two most important sections are: + a list of tokens (for each token defined here, bison will generate the + make_NAMEOFTOKEN method in the @ref isc::eval::EvalParser class) and + the grammar. The Grammar is a tree like structure with possible loops. + + Here is an over-simplified version of the grammar: + +@code +01. %start expression; +02. +03. expression : token EQUAL token +04. | token +05. ; +06. +07. token : STRING +08. { +09. TokenPtr str(new TokenString($1)); +10. ctx.expression.push_back(str); +11. } +12. | HEXSTRING +13. { +14. TokenPtr hex(new TokenHexString($1)); +15. ctx.expression.push_back(hex); +16. } +17. | OPTION '[' INTEGER ']' DOT TEXT +18. { +19. TokenPtr opt(new TokenOption($3, TokenOption::TEXTUAL)); +20. ctx.expression.push_back(opt); +21. } +22. | OPTION '[' INTEGER ']' DOT HEX +23. { +24. TokenPtr opt(new TokenOption($3, TokenOption::HEXADECIMAL)); +25. ctx.expression.push_back(opt); +26. } +27. ; +@endcode + +This code determines that the grammar starts from expression (line 1). +The actual definition of expression (lines 3-5) may either be a +single token or an expression "token == token" (EQUAL has been defined as +"==" elsewhere). Token is further +defined in lines 7-22: it may either be a string (lines 7-11), +a hex string (lines 12-16), option in the textual format (lines 17-21) +or option in a hexadecimal format (lines 22-26). +When the actual case is determined, the respective C++ action +is executed. For example, if the token is a string, the TokenString class is +instantiated with the appropriate value and put onto the expression vector. + +@section dhcpEvalMakefile Generating parser files + + In the general case, we want to avoid generating parser files, so an + average user interested in just compiling Kea would not need flex or + bison. Therefore the generated files are already included in the + git repository and will be included in the tarball releases. + + However, there will be cases when one of the developers would want + to tweak the lexer.ll and parser.yy files and then regenerate + the code. For this purpose, two makefile targets are defined: + @code + make parser + @endcode + will generate the parsers and + @code + make parser-clean + @endcode + will remove the files. Generated files removal was also hooked + into the maintainer-clean target. + +@section dhcpEvalConfigure Configure options + + Since the flex/bison tools are not necessary for a regular compilation, + checks are conducted during the configure script, but the lack of flex or + bison tools does not stop the process. There is a flag + (--enable-generate-parser) that tells configure script that the + parser will be generated. With this flag, the checks for flex/bison + are mandatory. If either tool is missing or at too early a version, the + configure process will terminate with an error. + +@section dhcpEvalToken Supported tokens + + There are a number of tokens implemented. Each token is derived from + isc::eval::Token class and represents a certain expression primitive. + Currently supported tokens are: + + - isc::dhcp::TokenString -- represents a constant string, e.g. "MSFT". + - isc::dhcp::TokenHexString -- represents a constant string, encoded as + hex string, e.g. 0x666f6f which is actually "foo". + - isc::dhcp::TokenIpAddress -- represents a constant IP address, encoded as + a 4 or 16 byte binary string, e.g., 10.0.0.1 is 0x10000001. + - isc::dhcp::TokenIpAddressToText -- represents an IP address in text format. + - isc::dhcp::TokenOption -- represents an option in a packet, e.g. + option[123].text. + - isc::dhcp::TokenRelay4Option -- represents a sub-option inserted by the + DHCPv4 relay, e.g. relay[123].text or relay[123].hex + - isc::dhcp::TokenRelay6Option -- represents a sub-option inserted by + a DHCPv6 relay + - isc::dhcp::TokenPkt -- represents a DHCP packet meta data (incoming + interface name, source/remote or destination/local IP address, length). + - isc::dhcp::TokenPkt4 -- represents a DHCPv4 packet field. + - isc::dhcp::TokenPkt6 -- represents a DHCPv6 packet field (message type + or transaction id). + - isc::dhcp::TokenRelay6Field -- represents a DHCPv6 relay information field. + - isc::dhcp::TokenEqual -- represents the equal (==) operator. + - isc::dhcp::TokenSubstring -- represents the substring(text, start, length) operator. + - isc::dhcp::TokenConcat -- represents the concat operator which + concatenate two other tokens. + - isc::dhcp::TokenIfElse -- represents the ifelse(cond, iftrue, ifelse) operator. + - isc::dhcp::TokenToHexString -- represents the hexstring operator which + converts a binary value to its hexadecimal string representation. + - isc::dhcp::TokenInt8ToText -- represents the signed 8 bit integer in string + representation. + - isc::dhcp::TokenInt16ToText -- represents the signed 16 bit integer in string + representation. + - isc::dhcp::TokenInt32ToText -- represents the signed 32 bit integer in string + representation. + - isc::dhcp::TokenUInt8ToText -- represents the unsigned 8 bit integer in string + representation. + - isc::dhcp::TokenUInt16ToText -- represents the unsigned 16 bit integer in string + representation. + - isc::dhcp::TokenUInt32ToText -- represents the unsigned 32 bit integer in string + representation. + - isc::dhcp::TokenNot -- the logical not operator. + - isc::dhcp::TokenAnd -- the logical and (strict) operator. + - isc::dhcp::TokenOr -- the logical or (strict) operator (strict means + it always evaluates its operands). + - isc::dhcp::TokenVendor -- represents vendor information option's existence, + enterprise-id field and possible sub-options. (e.g. vendor[1234].exists, + vendor[*].enterprise-id, vendor[1234].option[1].exists, vendor[1234].option[1].hex) + - isc::dhcp::TokenVendorClass -- represents vendor information option's existence, + enterprise-id and included data chunks. (e.g. vendor-class[1234].exists, + vendor-class[*].enterprise-id, vendor-class[*].data[3]) + +More operators are expected to be implemented in upcoming releases. + +@section dhcpEvalMTConsiderations Multi-Threading Consideration for Expression Evaluation Library + +This library is not thread safe, for instance @ref isc::dhcp::evaluateBool +or @ref isc::dhcp::evaluateString must not be called in different threads +on the same packet. + +*/ diff --git a/src/lib/eval/eval_context.cc b/src/lib/eval/eval_context.cc new file mode 100644 index 0000000..d91fbb0 --- /dev/null +++ b/src/lib/eval/eval_context.cc @@ -0,0 +1,260 @@ +// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dhcp/dhcp6.h> +#include <dhcp/option.h> +#include <dhcp/option_definition.h> +#include <dhcp/libdhcp++.h> +#include <dhcp/option_space.h> +#include <eval/eval_context.h> +#include <eval/parser.h> +#include <exceptions/exceptions.h> +#include <boost/lexical_cast.hpp> +#include <fstream> +#include <limits> + +EvalContext::EvalContext(const Option::Universe& option_universe, + CheckDefined check_defined) + : trace_scanning_(false), trace_parsing_(false), + option_universe_(option_universe), check_defined_(check_defined) +{ +} + +EvalContext::~EvalContext() { +} + +bool +EvalContext::acceptAll(const ClientClass&) { + return (true); +} + +bool +EvalContext::parseString(const std::string& str, ParserType type) { + file_ = "<string>"; + string_ = str; + scanStringBegin(type); + int res = -1; + try { + isc::eval::EvalParser parser(*this); + parser.set_debug_level(trace_parsing_); + res = parser.parse(); + } catch (...) { + scanStringEnd(); + throw; + } + scanStringEnd(); + return (res == 0); +} + +void +EvalContext::error(const isc::eval::location& loc, const std::string& what) { + isc_throw(EvalParseError, loc << ": " << what); +} + +void +EvalContext::error (const std::string& what) { + isc_throw(EvalParseError, what); +} + +uint16_t +EvalContext::convertOptionCode(const std::string& option_code, + const isc::eval::location& loc) { + int n = 0; + try { + n = boost::lexical_cast<int>(option_code); + } catch (const boost::bad_lexical_cast &) { + // This can't happen... + error(loc, "Option code has invalid value in " + option_code); + } + if (option_universe_ == Option::V6) { + if (n < 0 || n > 65535) { + error(loc, "Option code has invalid value in " + + option_code + ". Allowed range: 0..65535"); + } + } else { + if (n < 0 || n > 255) { + error(loc, "Option code has invalid value in " + + option_code + ". Allowed range: 0..255"); + } + } + return (static_cast<uint16_t>(n)); +} + +uint16_t +EvalContext::convertOptionName(const std::string& option_name, + const isc::eval::location& loc) { + const std::string global_space = (option_universe_ == Option::V4) ? + DHCP4_OPTION_SPACE : DHCP6_OPTION_SPACE; + + OptionDefinitionPtr option_def = LibDHCP::getOptionDef(global_space, + option_name); + if (!option_def) { + option_def = LibDHCP::getRuntimeOptionDef(global_space, option_name); + } + + if (!option_def) { + option_def = LibDHCP::getLastResortOptionDef(global_space, option_name); + } + + if (!option_def) { + error(loc, "option '" + option_name + "' is not defined"); + } + + return (option_def->getCode()); +} + +int8_t +EvalContext::convertNestLevelNumber(const std::string& nest_level, + const isc::eval::location& loc) { + int8_t n = convertInt8(nest_level, loc); + if (option_universe_ == Option::V6) { + if ((n < - HOP_COUNT_LIMIT) || (n >= HOP_COUNT_LIMIT)) { + error(loc, "Nest level has invalid value in " + + nest_level + ". Allowed range: -32..31"); + } + } else { + error(loc, "Nest level invalid for DHCPv4 packets"); + } + + return (n); +} + +uint8_t +EvalContext::convertUint8(const std::string& number, + const isc::eval::location& loc) { + int64_t n = 0; + try { + n = boost::lexical_cast<int64_t>(number); + } catch (const boost::bad_lexical_cast &) { + error(loc, "Invalid integer value in " + number); + } + if (n < 0 || n > std::numeric_limits<uint8_t>::max()) { + error(loc, "Invalid value in " + + number + ". Allowed range: 0..255"); + } + + return (static_cast<uint8_t>(n)); +} + +int8_t +EvalContext::convertInt8(const std::string& number, + const isc::eval::location& loc) { + int64_t n = 0; + try { + n = boost::lexical_cast<int64_t>(number); + } catch (const boost::bad_lexical_cast &) { + error(loc, "Invalid integer value in " + number); + } + if (n < std::numeric_limits<int8_t>::min() || + n > std::numeric_limits<int8_t>::max()) { + error(loc, "Invalid value in " + + number + ". Allowed range: -128..127"); + } + + return (static_cast<int8_t>(n)); +} + +uint16_t +EvalContext::convertUint16(const std::string& number, + const isc::eval::location& loc) { + int64_t n = 0; + try { + n = boost::lexical_cast<int64_t>(number); + } catch (const boost::bad_lexical_cast &) { + error(loc, "Invalid value in " + number); + } + if (n < 0 || n > std::numeric_limits<uint16_t>::max()) { + error(loc, "Invalid value in " + + number + ". Allowed range: 0..65535"); + } + + return (static_cast<uint16_t>(n)); +} + +int16_t +EvalContext::convertInt16(const std::string& number, + const isc::eval::location& loc) { + uint64_t n = 0; + try { + n = boost::lexical_cast<int64_t>(number); + } catch (const boost::bad_lexical_cast &) { + error(loc, "Invalid value in " + number); + } + if (n > std::numeric_limits<int16_t>::max() || + n < std::numeric_limits<int16_t>::max()) { + error(loc, "Invalid value in " + + number + ". Allowed range: -32768..32767"); + } + + return (static_cast<int16_t>(n)); +} + +uint32_t +EvalContext::convertUint32(const std::string& number, + const isc::eval::location& loc) { + int64_t n = 0; + try { + n = boost::lexical_cast<int64_t>(number); + } catch (const boost::bad_lexical_cast &) { + error(loc, "Invalid value in " + number); + } + if (n < 0 || n > std::numeric_limits<uint32_t>::max()) { + error(loc, "Invalid value in " + + number + ". Allowed range: 0..4294967295"); + } + + return (static_cast<uint32_t>(n)); +} + +int32_t +EvalContext::convertInt32(const std::string& number, + const isc::eval::location& loc) { + int64_t n = 0; + try { + n = boost::lexical_cast<int64_t>(number); + } catch (const boost::bad_lexical_cast &) { + error(loc, "Invalid value in " + number); + } + if (n > std::numeric_limits<int32_t>::max() || + n < std::numeric_limits<int32_t>::max()) { + error(loc, "Invalid value in " + + number + ". Allowed range: -2147483648..2147483647"); + } + + return (static_cast<int32_t>(n)); +} + +std::string +EvalContext::fromUint32(const uint32_t integer) { + std::string tmp(4, 0); + tmp[0] = (integer >> 24) & 0xff; + tmp[1] = (integer >> 16) & 0xff; + tmp[2] = (integer >> 8) & 0xff; + tmp[3] = integer & 0xff; + + return (tmp); +} + +std::string +EvalContext::fromUint16(const uint16_t integer) { + std::string tmp(2, 0); + tmp[0] = (integer >> 8) & 0xff; + tmp[1] = integer & 0xff; + + return (tmp); +} + +bool +EvalContext::isClientClassDefined(const ClientClass& client_class) { + return (check_defined_(client_class)); +} + +void +EvalContext::fatal(const std::string& what) { + isc_throw(Unexpected, what); +} diff --git a/src/lib/eval/eval_context.h b/src/lib/eval/eval_context.h new file mode 100644 index 0000000..8050925 --- /dev/null +++ b/src/lib/eval/eval_context.h @@ -0,0 +1,249 @@ +// Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef EVAL_CONTEXT_H +#define EVAL_CONTEXT_H +#include <string> +#include <map> +#include <eval/parser.h> +#include <eval/eval_context_decl.h> +#include <exceptions/exceptions.h> + +// Tell Flex the lexer's prototype ... +#define YY_DECL \ + isc::eval::EvalParser::symbol_type evallex (EvalContext& driver) + +// ... and declare it for the parser's sake. +YY_DECL; + +namespace isc { +namespace eval { + +/// @brief Evaluation error exception raised when trying to parse an exceptions. +class EvalParseError : public isc::Exception { +public: + EvalParseError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + + +/// @brief Evaluation context, an interface to the expression evaluation. +class EvalContext { +public: + + /// @brief Specifies what type of expression the parser is expected to see + typedef enum { + PARSER_BOOL, ///< expression is expected to evaluate to bool + PARSER_STRING ///< expression is expected to evaluate to string + } ParserType; + + /// @brief Type of the check defined function. + typedef std::function<bool(const ClientClass&)> CheckDefined; + + /// @brief Default constructor. + /// + /// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used + /// by the parser to determine which option definitions set should be used + /// to map option names to option codes. + /// @param check_defined A function called to check if a client class + /// used for membership is already defined. If it is not the parser + /// will fail: only backward or built-in references are accepted. + EvalContext(const Option::Universe& option_universe, + CheckDefined check_defined = acceptAll); + + /// @brief destructor + virtual ~EvalContext(); + + /// @brief Accept all client class names + /// + /// @param client_class (unused) + /// @return true + static bool acceptAll(const ClientClass& client_class); + + /// @brief Parsed expression (output tokens are stored here) + isc::dhcp::Expression expression; + + /// @brief Method called before scanning starts on a string. + /// + /// @param type specifies type of the expression to be parsed + void scanStringBegin(ParserType type); + + /// @brief Method called after the last tokens are scanned from a string. + void scanStringEnd(); + + /// @brief Run the parser on the string specified. + /// + /// @param str string to be parsed + /// @param type type of the expression expected/parser type to be created + /// @return true on success. + bool parseString(const std::string& str, ParserType type = PARSER_BOOL); + + /// @brief The name of the file being parsed. + /// Used later to pass the file name to the location tracker. + std::string file_; + + /// @brief The string being parsed. + std::string string_; + + /// @brief Error handler + /// + /// @param loc location within the parsed file where the problem was experienced. + /// @param what string explaining the nature of the error. + static void error(const isc::eval::location& loc, const std::string& what); + + /// @brief Error handler + /// + /// This is a simplified error reporting tool for possible future + /// cases when the EvalParser is not able to handle the packet. + static void error(const std::string& what); + + /// @brief Fatal error handler + /// + /// This is for should not happen but fatal errors + static void fatal(const std::string& what); + + /// @brief Option code conversion + /// + /// @param option_code a string representing the integer code + /// @param loc the location of the token + /// @result the option code + /// @throw calls the syntax error function if the value is not in + /// the range 0..255 or 0..65535 + uint16_t convertOptionCode(const std::string& option_code, + const isc::eval::location& loc); + + /// @brief Option name conversion + /// + /// @param option_name the option name + /// @param loc the location of the token + /// @return the option code + /// @throw calls the syntax error function if the name cannot be resolved + uint16_t convertOptionName(const std::string& option_name, + const isc::eval::location& loc); + + /// @brief Attempts to convert string to unsigned 32bit integer + /// + /// For reverse conversion, see @ref fromUint32 + /// + /// @param number string to be converted + /// @param loc the location of the token + /// @return the integer value + /// @throw EvalParseError if conversion fails or the value is out of range. + static uint32_t convertUint32(const std::string& number, + const isc::eval::location& loc); + + /// @brief Attempts to convert string to signed 32bit integer + /// + /// @param number string to be converted + /// @param loc the location of the token + /// @return the integer value + /// @throw EvalParseError if conversion fails or the value is out of range. + static int32_t convertInt32(const std::string& number, + const isc::eval::location& loc); + + /// @brief Attempts to convert string to unsigned 16bit integer + /// + /// For reverse conversion, see @ref fromUint16 + /// + /// @param number string to be converted + /// @param loc the location of the token + /// @return the integer value + /// @throw EvalParseError if conversion fails or the value is out of range. + static uint16_t convertUint16(const std::string& number, + const isc::eval::location& loc); + + /// @brief Attempts to convert string to signed 16bit integer + /// + /// @param number string to be converted + /// @param loc the location of the token + /// @return the integer value + /// @throw EvalParseError if conversion fails or the value is out of range. + static int16_t convertInt16(const std::string& number, + const isc::eval::location& loc); + + /// @brief Attempts to convert string to unsigned 8bit integer + /// + /// @param number string to be converted + /// @param loc the location of the token + /// @return the integer value + /// @throw EvalParseError if conversion fails or the value is out of range. + static uint8_t convertUint8(const std::string& number, + const isc::eval::location& loc); + + /// @brief Attempts to convert string to signed 8bit integer + /// + /// @param number string to be converted + /// @param loc the location of the token + /// @return the integer value + /// @throw EvalParseError if conversion fails or the value is out of range. + static int8_t convertInt8(const std::string& number, + const isc::eval::location& loc); + + /// @brief Nest level conversion + /// + /// @param nest_level a string representing the integer nesting level + /// @param loc the location of the token + /// @return the nesting level + /// @throw calls the syntax error function if the value is not in + /// the range -32..31 + int8_t convertNestLevelNumber(const std::string& nest_level, + const isc::eval::location& loc); + + /// @brief Converts unsigned 32bit integer to string representation + /// + /// The integer is coded as a 4 byte long string in network order, e.g. + /// 6 is represented as 00000006. For reverse conversion, see + /// @ref convertUint32. + /// + /// @param integer value to be converted + /// @return 4 byte long string that encodes the value. + static std::string fromUint32(const uint32_t integer); + + /// @brief Converts unsigned 16bit integer to string representation + /// + /// The integer is coded as a 2 byte long string in network order, e.g. + /// 6 is represented as 0006. For reverse conversion, see + /// @ref convertUint16. + /// + /// @param integer value to be converted + /// @return 2 byte long string that encodes the value. + static std::string fromUint16(const uint16_t integer); + + /// @brief Returns the universe (v4 or v6) + /// + /// @return universe + Option::Universe getUniverse() { + return (option_universe_); + } + + /// @brief Check if a client class is already defined + /// + /// @param client_class the client class name to check + /// @return true if the client class is defined, false if not + bool isClientClassDefined(const ClientClass& client_class); + +private: + /// @brief Flag determining scanner debugging. + bool trace_scanning_; + + /// @brief Flag determining parser debugging. + bool trace_parsing_; + + /// @brief Option universe: DHCPv4 or DHCPv6. + /// + /// This is used by the parser to determine which option definitions + /// set should be used to map option name to option code. + Option::Universe option_universe_; + + /// @brief Function to check if a client class is already defined. + CheckDefined check_defined_; + +}; + +} // end of isc::eval namespace +} // end of isc namespace + +#endif diff --git a/src/lib/eval/eval_context_decl.h b/src/lib/eval/eval_context_decl.h new file mode 100644 index 0000000..c838011 --- /dev/null +++ b/src/lib/eval/eval_context_decl.h @@ -0,0 +1,20 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef EVAL_CONTEXT_DECL_H +#define EVAL_CONTEXT_DECL_H + +/// @file eval_context_decl.h Forward declaration of the EvalContext class + +namespace isc { +namespace eval { + +class EvalContext; + +}; // end of isc::eval namespace +}; // end of isc namespace + +#endif diff --git a/src/lib/eval/eval_log.cc b/src/lib/eval/eval_log.cc new file mode 100644 index 0000000..d9aeffa --- /dev/null +++ b/src/lib/eval/eval_log.cc @@ -0,0 +1,23 @@ +// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/// Defines the logger used by the Eval (classification) code + +#include <config.h> + +#include <eval/eval_log.h> + +namespace isc { +namespace dhcp { + +extern const int EVAL_DBG_TRACE = isc::log::DBGLVL_TRACE_BASIC; +extern const int EVAL_DBG_STACK = isc::log::DBGLVL_TRACE_DETAIL_DATA; + +isc::log::Logger eval_logger("eval"); + +} // namespace dhcp +} // namespace isc + diff --git a/src/lib/eval/eval_log.h b/src/lib/eval/eval_log.h new file mode 100644 index 0000000..5667025 --- /dev/null +++ b/src/lib/eval/eval_log.h @@ -0,0 +1,38 @@ +// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef EVAL_LOG_H +#define EVAL_LOG_H + +#include <log/macros.h> +#include <eval/eval_messages.h> + +namespace isc { +namespace dhcp { + +/// @brief Eval debug Logging levels +/// +/// Defines the levels used to output debug messages in the eval (classification) code. +/// Note that higher numbers equate to more verbose (and detailed) output. + +// The first level traces normal operations, +extern const int EVAL_DBG_TRACE; + +// Additional information on the calls. Report the values that were +// popped from or pushed to the value stack. +extern const int EVAL_DBG_STACK; + +/// @brief Eval Logger +/// +/// Define the logger used to log messages. We could define it in multiple +/// modules, but defining in a single module and linking to it saves time and +/// space. +extern isc::log::Logger eval_logger; + +} // namespace dhcp +} // namespace isc + +#endif // EVAL_LOG_H diff --git a/src/lib/eval/eval_messages.cc b/src/lib/eval/eval_messages.cc new file mode 100644 index 0000000..7dfeba1 --- /dev/null +++ b/src/lib/eval/eval_messages.cc @@ -0,0 +1,113 @@ +// File created from ../../../src/lib/eval/eval_messages.mes + +#include <cstddef> +#include <log/message_types.h> +#include <log/message_initializer.h> + +namespace isc { +namespace dhcp { + +extern const isc::log::MessageID EVAL_DEBUG_AND = "EVAL_DEBUG_AND"; +extern const isc::log::MessageID EVAL_DEBUG_CONCAT = "EVAL_DEBUG_CONCAT"; +extern const isc::log::MessageID EVAL_DEBUG_EQUAL = "EVAL_DEBUG_EQUAL"; +extern const isc::log::MessageID EVAL_DEBUG_HEXSTRING = "EVAL_DEBUG_HEXSTRING"; +extern const isc::log::MessageID EVAL_DEBUG_IFELSE_FALSE = "EVAL_DEBUG_IFELSE_FALSE"; +extern const isc::log::MessageID EVAL_DEBUG_IFELSE_TRUE = "EVAL_DEBUG_IFELSE_TRUE"; +extern const isc::log::MessageID EVAL_DEBUG_INT16TOTEXT = "EVAL_DEBUG_INT16TOTEXT"; +extern const isc::log::MessageID EVAL_DEBUG_INT32TOTEXT = "EVAL_DEBUG_INT32TOTEXT"; +extern const isc::log::MessageID EVAL_DEBUG_INT8TOTEXT = "EVAL_DEBUG_INT8TOTEXT"; +extern const isc::log::MessageID EVAL_DEBUG_IPADDRESS = "EVAL_DEBUG_IPADDRESS"; +extern const isc::log::MessageID EVAL_DEBUG_IPADDRESSTOTEXT = "EVAL_DEBUG_IPADDRESSTOTEXT"; +extern const isc::log::MessageID EVAL_DEBUG_MEMBER = "EVAL_DEBUG_MEMBER"; +extern const isc::log::MessageID EVAL_DEBUG_NOT = "EVAL_DEBUG_NOT"; +extern const isc::log::MessageID EVAL_DEBUG_OPTION = "EVAL_DEBUG_OPTION"; +extern const isc::log::MessageID EVAL_DEBUG_OR = "EVAL_DEBUG_OR"; +extern const isc::log::MessageID EVAL_DEBUG_PKT = "EVAL_DEBUG_PKT"; +extern const isc::log::MessageID EVAL_DEBUG_PKT4 = "EVAL_DEBUG_PKT4"; +extern const isc::log::MessageID EVAL_DEBUG_PKT6 = "EVAL_DEBUG_PKT6"; +extern const isc::log::MessageID EVAL_DEBUG_RELAY6 = "EVAL_DEBUG_RELAY6"; +extern const isc::log::MessageID EVAL_DEBUG_RELAY6_RANGE = "EVAL_DEBUG_RELAY6_RANGE"; +extern const isc::log::MessageID EVAL_DEBUG_SPLIT = "EVAL_DEBUG_SPLIT"; +extern const isc::log::MessageID EVAL_DEBUG_SPLIT_DELIM_EMPTY = "EVAL_DEBUG_SPLIT_DELIM_EMPTY"; +extern const isc::log::MessageID EVAL_DEBUG_SPLIT_EMPTY = "EVAL_DEBUG_SPLIT_EMPTY"; +extern const isc::log::MessageID EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE = "EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE"; +extern const isc::log::MessageID EVAL_DEBUG_STRING = "EVAL_DEBUG_STRING"; +extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING = "EVAL_DEBUG_SUBSTRING"; +extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING_EMPTY = "EVAL_DEBUG_SUBSTRING_EMPTY"; +extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING_RANGE = "EVAL_DEBUG_SUBSTRING_RANGE"; +extern const isc::log::MessageID EVAL_DEBUG_SUB_OPTION = "EVAL_DEBUG_SUB_OPTION"; +extern const isc::log::MessageID EVAL_DEBUG_SUB_OPTION_NO_OPTION = "EVAL_DEBUG_SUB_OPTION_NO_OPTION"; +extern const isc::log::MessageID EVAL_DEBUG_TOHEXSTRING = "EVAL_DEBUG_TOHEXSTRING"; +extern const isc::log::MessageID EVAL_DEBUG_UINT16TOTEXT = "EVAL_DEBUG_UINT16TOTEXT"; +extern const isc::log::MessageID EVAL_DEBUG_UINT32TOTEXT = "EVAL_DEBUG_UINT32TOTEXT"; +extern const isc::log::MessageID EVAL_DEBUG_UINT8TOTEXT = "EVAL_DEBUG_UINT8TOTEXT"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_DATA = "EVAL_DEBUG_VENDOR_CLASS_DATA"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND = "EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID = "EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH = "EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_EXISTS = "EVAL_DEBUG_VENDOR_CLASS_EXISTS"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_NO_OPTION = "EVAL_DEBUG_VENDOR_CLASS_NO_OPTION"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_ENTERPRISE_ID = "EVAL_DEBUG_VENDOR_ENTERPRISE_ID"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH = "EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_EXISTS = "EVAL_DEBUG_VENDOR_EXISTS"; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_NO_OPTION = "EVAL_DEBUG_VENDOR_NO_OPTION"; +extern const isc::log::MessageID EVAL_RESULT = "EVAL_RESULT"; + +} // namespace dhcp +} // namespace isc + +namespace { + +const char* values[] = { + "EVAL_DEBUG_AND", "Popping %1 and %2 pushing %3", + "EVAL_DEBUG_CONCAT", "Popping %1 and %2 pushing %3", + "EVAL_DEBUG_EQUAL", "Popping %1 and %2 pushing result %3", + "EVAL_DEBUG_HEXSTRING", "Pushing hex string %1", + "EVAL_DEBUG_IFELSE_FALSE", "Popping %1 (false) and %2, leaving %3", + "EVAL_DEBUG_IFELSE_TRUE", "Popping %1 (true) and %2, leaving %3", + "EVAL_DEBUG_INT16TOTEXT", "Pushing Int16 %1", + "EVAL_DEBUG_INT32TOTEXT", "Pushing Int32 %1", + "EVAL_DEBUG_INT8TOTEXT", "Pushing Int8 %1", + "EVAL_DEBUG_IPADDRESS", "Pushing IPAddress %1", + "EVAL_DEBUG_IPADDRESSTOTEXT", "Pushing IPAddress %1", + "EVAL_DEBUG_MEMBER", "Checking membership of '%1', pushing result %2", + "EVAL_DEBUG_NOT", "Popping %1 pushing %2", + "EVAL_DEBUG_OPTION", "Pushing option %1 with value %2", + "EVAL_DEBUG_OR", "Popping %1 and %2 pushing %3", + "EVAL_DEBUG_PKT", "Pushing PKT meta data %1 with value %2", + "EVAL_DEBUG_PKT4", "Pushing PKT4 field %1 with value %2", + "EVAL_DEBUG_PKT6", "Pushing PKT6 field %1 with value %2", + "EVAL_DEBUG_RELAY6", "Pushing PKT6 relay field %1 nest %2 with value %3", + "EVAL_DEBUG_RELAY6_RANGE", "Pushing PKT6 relay field %1 nest %2 with value %3", + "EVAL_DEBUG_SPLIT", "Popping field %1, delimiters %2, string %3, pushing result %4", + "EVAL_DEBUG_SPLIT_DELIM_EMPTY", "Popping field %1, delimiters %2, string %3, pushing result %4", + "EVAL_DEBUG_SPLIT_EMPTY", "Popping field %1, delimiters %2, string %3, pushing result %4", + "EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE", "Popping field %1, delimiters %2, string %3, pushing result %4", + "EVAL_DEBUG_STRING", "Pushing text string %1", + "EVAL_DEBUG_SUBSTRING", "Popping length %1, start %2, string %3 pushing result %4", + "EVAL_DEBUG_SUBSTRING_EMPTY", "Popping length %1, start %2, string %3 pushing result %4", + "EVAL_DEBUG_SUBSTRING_RANGE", "Popping length %1, start %2, string %3 pushing result %4", + "EVAL_DEBUG_SUB_OPTION", "Pushing option %1 sub-option %2 with value %3", + "EVAL_DEBUG_SUB_OPTION_NO_OPTION", "Requested option %1 sub-option %2, but the parent option is not present, pushing result %3", + "EVAL_DEBUG_TOHEXSTRING", "Popping binary value %1 and separator %2, pushing result %3", + "EVAL_DEBUG_UINT16TOTEXT", "Pushing UInt16 %1", + "EVAL_DEBUG_UINT32TOTEXT", "Pushing UInt32 %1", + "EVAL_DEBUG_UINT8TOTEXT", "Pushing UInt8 %1", + "EVAL_DEBUG_VENDOR_CLASS_DATA", "Data %1 (out of %2 received) in vendor class found, pushing result '%3'", + "EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND", "Requested data index %1, but option with enterprise-id %2 has only %3 data tuple(s), pushing result '%4'", + "EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID", "Pushing enterprise-id %1 as result 0x%2", + "EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH", "Was looking for %1, option had %2, pushing result '%3'", + "EVAL_DEBUG_VENDOR_CLASS_EXISTS", "Option with enterprise-id %1 found, pushing result '%2'", + "EVAL_DEBUG_VENDOR_CLASS_NO_OPTION", "Option with code %1 missing, pushing result '%2'", + "EVAL_DEBUG_VENDOR_ENTERPRISE_ID", "Pushing enterprise-id %1 as result 0x%2", + "EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH", "Was looking for %1, option had %2, pushing result '%3'", + "EVAL_DEBUG_VENDOR_EXISTS", "Option with enterprise-id %1 found, pushing result '%2'", + "EVAL_DEBUG_VENDOR_NO_OPTION", "Option with code %1 missing, pushing result '%2'", + "EVAL_RESULT", "Expression %1 evaluated to %2", + NULL +}; + +const isc::log::MessageInitializer initializer(values); + +} // Anonymous namespace + diff --git a/src/lib/eval/eval_messages.h b/src/lib/eval/eval_messages.h new file mode 100644 index 0000000..6353ab2 --- /dev/null +++ b/src/lib/eval/eval_messages.h @@ -0,0 +1,60 @@ +// File created from ../../../src/lib/eval/eval_messages.mes + +#ifndef EVAL_MESSAGES_H +#define EVAL_MESSAGES_H + +#include <log/message_types.h> + +namespace isc { +namespace dhcp { + +extern const isc::log::MessageID EVAL_DEBUG_AND; +extern const isc::log::MessageID EVAL_DEBUG_CONCAT; +extern const isc::log::MessageID EVAL_DEBUG_EQUAL; +extern const isc::log::MessageID EVAL_DEBUG_HEXSTRING; +extern const isc::log::MessageID EVAL_DEBUG_IFELSE_FALSE; +extern const isc::log::MessageID EVAL_DEBUG_IFELSE_TRUE; +extern const isc::log::MessageID EVAL_DEBUG_INT16TOTEXT; +extern const isc::log::MessageID EVAL_DEBUG_INT32TOTEXT; +extern const isc::log::MessageID EVAL_DEBUG_INT8TOTEXT; +extern const isc::log::MessageID EVAL_DEBUG_IPADDRESS; +extern const isc::log::MessageID EVAL_DEBUG_IPADDRESSTOTEXT; +extern const isc::log::MessageID EVAL_DEBUG_MEMBER; +extern const isc::log::MessageID EVAL_DEBUG_NOT; +extern const isc::log::MessageID EVAL_DEBUG_OPTION; +extern const isc::log::MessageID EVAL_DEBUG_OR; +extern const isc::log::MessageID EVAL_DEBUG_PKT; +extern const isc::log::MessageID EVAL_DEBUG_PKT4; +extern const isc::log::MessageID EVAL_DEBUG_PKT6; +extern const isc::log::MessageID EVAL_DEBUG_RELAY6; +extern const isc::log::MessageID EVAL_DEBUG_RELAY6_RANGE; +extern const isc::log::MessageID EVAL_DEBUG_SPLIT; +extern const isc::log::MessageID EVAL_DEBUG_SPLIT_DELIM_EMPTY; +extern const isc::log::MessageID EVAL_DEBUG_SPLIT_EMPTY; +extern const isc::log::MessageID EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE; +extern const isc::log::MessageID EVAL_DEBUG_STRING; +extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING; +extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING_EMPTY; +extern const isc::log::MessageID EVAL_DEBUG_SUBSTRING_RANGE; +extern const isc::log::MessageID EVAL_DEBUG_SUB_OPTION; +extern const isc::log::MessageID EVAL_DEBUG_SUB_OPTION_NO_OPTION; +extern const isc::log::MessageID EVAL_DEBUG_TOHEXSTRING; +extern const isc::log::MessageID EVAL_DEBUG_UINT16TOTEXT; +extern const isc::log::MessageID EVAL_DEBUG_UINT32TOTEXT; +extern const isc::log::MessageID EVAL_DEBUG_UINT8TOTEXT; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_DATA; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_EXISTS; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_CLASS_NO_OPTION; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_ENTERPRISE_ID; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_EXISTS; +extern const isc::log::MessageID EVAL_DEBUG_VENDOR_NO_OPTION; +extern const isc::log::MessageID EVAL_RESULT; + +} // namespace dhcp +} // namespace isc + +#endif // EVAL_MESSAGES_H diff --git a/src/lib/eval/eval_messages.mes b/src/lib/eval/eval_messages.mes new file mode 100644 index 0000000..1cace9a --- /dev/null +++ b/src/lib/eval/eval_messages.mes @@ -0,0 +1,285 @@ +# Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +$NAMESPACE isc::dhcp + + +# For use with TokenAnd +% EVAL_DEBUG_AND Popping %1 and %2 pushing %3 +This debug message indicates that two values are popped from +the value stack. Then are then combined via logical and and +the result is pushed onto the value stack. + +# For use with TokenConcat + +% EVAL_DEBUG_CONCAT Popping %1 and %2 pushing %3 +This debug message indicates that the two strings are being popped off +of the stack. They are then concatenated and the resulting string is +pushed onto the stack. The strings are displayed in hex. + +# For use with TokenEqual +# Start with binary for the inputs for now, we may add text in the future. + +% EVAL_DEBUG_EQUAL Popping %1 and %2 pushing result %3 +This debug message indicates that the two strings are being popped off +of the value stack and the result of comparing them is being pushed onto +the value stack. The strings are displayed in hex. + +# For use with TokenHexString + +% EVAL_DEBUG_HEXSTRING Pushing hex string %1 +This debug message indicates that the given binary string is being pushed +onto the value stack. The string is displayed in hex. + +# For use with TokenIfElse + +% EVAL_DEBUG_IFELSE_FALSE Popping %1 (false) and %2, leaving %3 +This debug message indicates that the condition is false so +the iftrue branch value is removed and the ifelse branch value +is left on the value stack. + +% EVAL_DEBUG_IFELSE_TRUE Popping %1 (true) and %2, leaving %3 +This debug message indicates that the condition is true so +the ifelse branch value is removed and the iftrue branch value +is left on the value stack. + +# For use with TokenIpAddress + +% EVAL_DEBUG_INT16TOTEXT Pushing Int16 %1 +This debug message indicates that the given address string representation is +being pushed onto the value stack. This represents a 16 bit integer. + +# For use with TokenInt32ToText + +% EVAL_DEBUG_INT32TOTEXT Pushing Int32 %1 +This debug message indicates that the given address string representation is +being pushed onto the value stack. This represents a 32 bit integer. + +# For use with TokenUInt8ToText + +% EVAL_DEBUG_INT8TOTEXT Pushing Int8 %1 +This debug message indicates that the given address string representation is +being pushed onto the value stack. This represents an 8 bit integer. + +# For use with TokenInt16ToText + +% EVAL_DEBUG_IPADDRESS Pushing IPAddress %1 +This debug message indicates that the given binary string is being pushed +onto the value stack. This represents either an IPv4 or IPv6 address. +The string is displayed in hex. + +# For use with TokenIpAddressToText + +% EVAL_DEBUG_IPADDRESSTOTEXT Pushing IPAddress %1 +This debug message indicates that the given address string representation is +being pushed onto the value stack. This represents either an IPv4 or IPv6 +address. + +# For use with TokenInt8ToText + +% EVAL_DEBUG_MEMBER Checking membership of '%1', pushing result %2 +This debug message indicates that the membership of the packet for +the client class was checked. + +# For use with TokenNot + +% EVAL_DEBUG_NOT Popping %1 pushing %2 +This debug message indicates that the first value is popped from +the value stack, negated and then pushed onto the value stack. +The string is displayed in text. + +# For use with TokenOption based classes. These include TokenOption, +# TokenRelay4Option and TokenRelay6Option. + +% EVAL_DEBUG_OPTION Pushing option %1 with value %2 +This debug message indicates that the given string representing the +value of the requested option is being pushed onto the value stack. +The string may be the text or binary value of the string based on the +representation type requested (.text or .hex) or "true" or "false" if +the requested type is .exists. The option code may be for either an +option or a sub-option as requested in the classification statement. + +# For use with TokenOr + +% EVAL_DEBUG_OR Popping %1 and %2 pushing %3 +This debug message indicates that two values are popped from +the value stack. Then are then combined via logical or and +the result is pushed onto the value stack. The string is displayed +in text. + +# For use with TokenPkt + +% EVAL_DEBUG_PKT Pushing PKT meta data %1 with value %2 +This debug message indicates that the given binary string representing +the value of the requested meta data is being pushed onto the value stack. +The string is displayed in hex at the exception of interface name. + +# For use with TokenPkt4 + +% EVAL_DEBUG_PKT4 Pushing PKT4 field %1 with value %2 +This debug message indicates that the given binary string representing +the value of the requested field is being pushed onto the value stack. +The string is displayed in hex. + +# For use with TokenPkt6 + +% EVAL_DEBUG_PKT6 Pushing PKT6 field %1 with value %2 +This debug message indicates that the given binary string representing +the value of the requested field is being pushed onto the value stack. +The string is displayed in hex. + +# For use with TokenRelay6Field + +% EVAL_DEBUG_RELAY6 Pushing PKT6 relay field %1 nest %2 with value %3 +This debug message indicates that the given binary string representing +the value of the requested field is being pushed onto the value stack. +The string is displayed in hex. + +% EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field %1 nest %2 with value %3 +This debug message is generated if the nest field is out of range. The +empty string will always be the value pushed onto the stack. + +# For use with TokenString + +% EVAL_DEBUG_SPLIT Popping field %1, delimiters %2, string %3, pushing result %4 +This debug message indicates that three values are being popped from the stack +and a result is being pushed onto the stack. The values being popped are the +field, delimiter and string. The result is the extracted field which is pushed +onto the stack. The strings are displayed in hex. + +% EVAL_DEBUG_SPLIT_DELIM_EMPTY Popping field %1, delimiters %2, string %3, pushing result %4 +This debug message indicates that the delimiter popped from the stack was empty +and so the result will be the entire string. The field, delimiter and string +are still popped from the stack and the result is still pushed. + +% EVAL_DEBUG_SPLIT_EMPTY Popping field %1, delimiters %2, string %3, pushing result %4 +This debug message indicates that the string popped from the stack was empty +and so the result will also be empty. The field, delimiter and string are +still popped from the stack and the result is still pushed. + +% EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field %1, delimiters %2, string %3, pushing result %4 +This debug message indicates that the field is either less than one or larger +than the number of fields in the string popped from the stack. The result will +be empty. The field, delimiter and string are still popped from the stack and +the result is still pushed. + +% EVAL_DEBUG_STRING Pushing text string %1 +This debug message indicates that the given text string is being pushed +onto the value stack. The string is displayed in text. + +# For use with TokenSubstring +# Start with binary for the strings for now, we may add text in the future. + +% EVAL_DEBUG_SUBSTRING Popping length %1, start %2, string %3 pushing result %4 +This debug message indicates that three values are being popped from +the value stack and a result is being pushed onto the value stack. The +values being popped are the starting point and length of a substring to +extract from the given string. The resulting string is pushed onto +the stack. The strings are displayed in hex. + +% EVAL_DEBUG_SUBSTRING_EMPTY Popping length %1, start %2, string %3 pushing result %4 +This debug message indicates that the string popped from the stack was empty +and so the result will also be empty. The start, length and string are +still popped from the stack and the result is still pushed. + +% EVAL_DEBUG_SUBSTRING_RANGE Popping length %1, start %2, string %3 pushing result %4 +This debug message indicates that the value of start is outside of the +string and an empty result will be pushed onto the stack. The start, +length and string are still popped from the stack and the result is +still pushed. The strings are displayed in hex. + +# For use with TokenSubOption + +% EVAL_DEBUG_SUB_OPTION Pushing option %1 sub-option %2 with value %3 +This debug message indicates that the given string representing the +value of the requested sub-option of the requested parent option is +being pushed onto the value stack. The string may be the text or +binary value of the string based on the representation type requested +(.text or .hex) or "true" or "false" if the requested type is .exists. +The codes are the parent option and the sub-option codes as requested +in the classification statement. + +% EVAL_DEBUG_SUB_OPTION_NO_OPTION Requested option %1 sub-option %2, but the parent option is not present, pushing result %3 +This debug message indicates that the parent option was not found. +The codes are the parent option and the sub-option codes as requested +in the classification statement. + +# For use with TokenToHexString + +% EVAL_DEBUG_TOHEXSTRING Popping binary value %1 and separator %2, pushing result %3 +This debug message indicates that two values are being popped from +the value stack and a result is being pushed onto the value stack. +The values being popped are the binary value to convert and the separator. +The binary value is converted to its hexadecimal string representation +and pushed onto the stack. The binary value is displayed in hex. + +% EVAL_DEBUG_UINT16TOTEXT Pushing UInt16 %1 +This debug message indicates that the given address string representation is +being pushed onto the value stack. This represents a 16 bit unsigned integer. + +# For use with TokenUInt32ToText + +% EVAL_DEBUG_UINT32TOTEXT Pushing UInt32 %1 +This debug message indicates that the given address string representation is +being pushed onto the value stack. This represents a 32 bit unsigned integer. + +# For use with TokenMember + +% EVAL_DEBUG_UINT8TOTEXT Pushing UInt8 %1 +This debug message indicates that the given address string representation is +being pushed onto the value stack. This represents an 8 bit unsigned integer. + +# For use with TokenUInt16ToText + +% EVAL_DEBUG_VENDOR_CLASS_DATA Data %1 (out of %2 received) in vendor class found, pushing result '%3' +This debug message indicates that vendor class option was found and passed +enterprise-id checks and has sufficient number of data chunks. The total number +of chunks and value pushed are reported as debugging aid. + +% EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index %1, but option with enterprise-id %2 has only %3 data tuple(s), pushing result '%4' +This debug message indicates that vendor class option was found and passed +enterprise-id checks, but does not have sufficient number of data chunks. +Note that the index starts at 0, so there has to be at least (index + 1) +data chunks. + +% EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id %1 as result 0x%2 +This debug message indicates that the expression has been evaluated and vendor +class option was found and its enterprise-id is being reported. + +% EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for %1, option had %2, pushing result '%3' +This debug message indicates that the expression has been evaluated +and vendor class option was found, but has different enterprise-id than specified +in the expression. + +% EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id %1 found, pushing result '%2' +This debug message indicates that the expression has been evaluated and vendor +class option was found. + +% EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code %1 missing, pushing result '%2' +This debug message indicates that the expression has been evaluated +and vendor class option was not found. + +% EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id %1 as result 0x%2 +This debug message indicates that the expression has been evaluated and vendor +option was found and its enterprise-id is being reported. + +% EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for %1, option had %2, pushing result '%3' +This debug message indicates that the expression has been evaluated +and vendor option was found, but has different enterprise-id than specified +in the expression. + +% EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id %1 found, pushing result '%2' +This debug message indicates that the expression has been evaluated and vendor +option was found. + +% EVAL_DEBUG_VENDOR_NO_OPTION Option with code %1 missing, pushing result '%2' +This debug message indicates that the expression has been evaluated +and vendor option was not found. + +% EVAL_RESULT Expression %1 evaluated to %2 +This debug message indicates that the expression has been evaluated +to said value. This message is mostly useful during debugging of the +client classification expressions. diff --git a/src/lib/eval/evaluate.cc b/src/lib/eval/evaluate.cc new file mode 100644 index 0000000..f2cadce --- /dev/null +++ b/src/lib/eval/evaluate.cc @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <eval/evaluate.h> + +namespace isc { +namespace dhcp { + +bool evaluateBool(const Expression& expr, Pkt& pkt) { + ValueStack values; + for (Expression::const_iterator it = expr.begin(); + it != expr.end(); ++it) { + (*it)->evaluate(pkt, values); + } + if (values.size() != 1) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly " + "1 value at the end of evaluation, got " << values.size()); + } + return (Token::toBool(values.top())); +} + +std::string +evaluateString(const Expression& expr, Pkt& pkt) { + ValueStack values; + for (auto it = expr.begin(); it != expr.end(); ++it) { + (*it)->evaluate(pkt, values); + } + if (values.size() != 1) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected exactly " + "1 value at the end of evaluation, got " << values.size()); + } + return (values.top()); +} + + +}; // end of isc::dhcp namespace +}; // end of isc namespace diff --git a/src/lib/eval/evaluate.h b/src/lib/eval/evaluate.h new file mode 100644 index 0000000..b2f6eb2 --- /dev/null +++ b/src/lib/eval/evaluate.h @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef EVALUATE_H +#define EVALUATE_H + +#include <eval/token.h> +#include <string> + +namespace isc { +namespace dhcp { + +/// @brief Evaluate a RPN expression for a v4 or v6 packet and return +/// a true or false decision +/// +/// @param expr the RPN expression, i.e., a vector of parsed tokens +/// @param pkt The v4 or v6 packet +/// @return the boolean decision +/// @throw EvalStackError if there is not exactly one element on the value +/// stack at the end of the evaluation +/// @throw EvalTypeError if the value at the top of the stack at the +/// end of the evaluation is not "false" or "true" +bool evaluateBool(const Expression& expr, Pkt& pkt); + + +std::string evaluateString(const Expression& expr, Pkt& pkt); + +}; // end of isc::dhcp namespace +}; // end of isc namespace + +#endif diff --git a/src/lib/eval/lexer.cc b/src/lib/eval/lexer.cc new file mode 100644 index 0000000..5855ede --- /dev/null +++ b/src/lib/eval/lexer.cc @@ -0,0 +1,2996 @@ +#line 1 "lexer.cc" + +#line 3 "lexer.cc" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +/* %not-for-header */ +/* %if-c-only */ +/* %if-not-reentrant */ +#define yy_create_buffer eval_create_buffer +#define yy_delete_buffer eval_delete_buffer +#define yy_scan_buffer eval_scan_buffer +#define yy_scan_string eval_scan_string +#define yy_scan_bytes eval_scan_bytes +#define yy_init_buffer eval_init_buffer +#define yy_flush_buffer eval_flush_buffer +#define yy_load_buffer_state eval_load_buffer_state +#define yy_switch_to_buffer eval_switch_to_buffer +#define yypush_buffer_state evalpush_buffer_state +#define yypop_buffer_state evalpop_buffer_state +#define yyensure_buffer_stack evalensure_buffer_stack +#define yy_flex_debug eval_flex_debug +#define yyin evalin +#define yyleng evalleng +#define yylex evallex +#define yylineno evallineno +#define yyout evalout +#define yyrestart evalrestart +#define yytext evaltext +#define yywrap evalwrap +#define yyalloc evalalloc +#define yyrealloc evalrealloc +#define yyfree evalfree + +/* %endif */ +/* %endif */ +/* %ok-for-header */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* %if-c++-only */ +/* %endif */ + +/* %if-c-only */ +#ifdef yy_create_buffer +#define eval_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer eval_create_buffer +#endif + +#ifdef yy_delete_buffer +#define eval_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer eval_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define eval_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer eval_scan_buffer +#endif + +#ifdef yy_scan_string +#define eval_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string eval_scan_string +#endif + +#ifdef yy_scan_bytes +#define eval_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes eval_scan_bytes +#endif + +#ifdef yy_init_buffer +#define eval_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer eval_init_buffer +#endif + +#ifdef yy_flush_buffer +#define eval_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer eval_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define eval_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state eval_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define eval_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer eval_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define evalpush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state evalpush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define evalpop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state evalpop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define evalensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack evalensure_buffer_stack +#endif + +#ifdef yylex +#define evallex_ALREADY_DEFINED +#else +#define yylex evallex +#endif + +#ifdef yyrestart +#define evalrestart_ALREADY_DEFINED +#else +#define yyrestart evalrestart +#endif + +#ifdef yylex_init +#define evallex_init_ALREADY_DEFINED +#else +#define yylex_init evallex_init +#endif + +#ifdef yylex_init_extra +#define evallex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra evallex_init_extra +#endif + +#ifdef yylex_destroy +#define evallex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy evallex_destroy +#endif + +#ifdef yyget_debug +#define evalget_debug_ALREADY_DEFINED +#else +#define yyget_debug evalget_debug +#endif + +#ifdef yyset_debug +#define evalset_debug_ALREADY_DEFINED +#else +#define yyset_debug evalset_debug +#endif + +#ifdef yyget_extra +#define evalget_extra_ALREADY_DEFINED +#else +#define yyget_extra evalget_extra +#endif + +#ifdef yyset_extra +#define evalset_extra_ALREADY_DEFINED +#else +#define yyset_extra evalset_extra +#endif + +#ifdef yyget_in +#define evalget_in_ALREADY_DEFINED +#else +#define yyget_in evalget_in +#endif + +#ifdef yyset_in +#define evalset_in_ALREADY_DEFINED +#else +#define yyset_in evalset_in +#endif + +#ifdef yyget_out +#define evalget_out_ALREADY_DEFINED +#else +#define yyget_out evalget_out +#endif + +#ifdef yyset_out +#define evalset_out_ALREADY_DEFINED +#else +#define yyset_out evalset_out +#endif + +#ifdef yyget_leng +#define evalget_leng_ALREADY_DEFINED +#else +#define yyget_leng evalget_leng +#endif + +#ifdef yyget_text +#define evalget_text_ALREADY_DEFINED +#else +#define yyget_text evalget_text +#endif + +#ifdef yyget_lineno +#define evalget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno evalget_lineno +#endif + +#ifdef yyset_lineno +#define evalset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno evalset_lineno +#endif + +#ifdef yywrap +#define evalwrap_ALREADY_DEFINED +#else +#define yywrap evalwrap +#endif + +/* %endif */ + +#ifdef yyalloc +#define evalalloc_ALREADY_DEFINED +#else +#define yyalloc evalalloc +#endif + +#ifdef yyrealloc +#define evalrealloc_ALREADY_DEFINED +#else +#define yyrealloc evalrealloc +#endif + +#ifdef yyfree +#define evalfree_ALREADY_DEFINED +#else +#define yyfree evalfree +#endif + +/* %if-c-only */ + +#ifdef yytext +#define evaltext_ALREADY_DEFINED +#else +#define yytext evaltext +#endif + +#ifdef yyleng +#define evalleng_ALREADY_DEFINED +#else +#define yyleng evalleng +#endif + +#ifdef yyin +#define evalin_ALREADY_DEFINED +#else +#define yyin evalin +#endif + +#ifdef yyout +#define evalout_ALREADY_DEFINED +#else +#define yyout evalout +#endif + +#ifdef yy_flex_debug +#define eval_flex_debug_ALREADY_DEFINED +#else +#define yy_flex_debug eval_flex_debug +#endif + +#ifdef yylineno +#define evallineno_ALREADY_DEFINED +#else +#define yylineno evallineno +#endif + +/* %endif */ + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +/* %if-c-only */ +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +/* %endif */ + +/* %if-tables-serialization */ +/* %endif */ +/* end standard C headers. */ + +/* %if-c-or-c++ */ +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include <inttypes.h> +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* %endif */ + +/* begin standard C++ headers. */ +/* %if-c++-only */ +/* %endif */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* %not-for-header */ +/* Returned upon end-of-file. */ +#define YY_NULL 0 +/* %ok-for-header */ + +/* %not-for-header */ +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) +/* %ok-for-header */ + +/* %if-reentrant */ +/* %endif */ + +/* %if-not-reentrant */ + +/* %endif */ + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +/* %if-not-reentrant */ +extern int yyleng; +/* %endif */ + +/* %if-c-only */ +/* %if-not-reentrant */ +extern FILE *yyin, *yyout; +/* %endif */ +/* %endif */ + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires + * access to the local variable yy_act. Since yyless() is a macro, it would break + * existing scanners that call yyless() from OUTSIDE yylex. + * One obvious solution it to make yy_act a global. I tried that, and saw + * a 5% performance hit in a non-yylineno scanner, because yy_act is + * normally declared as a register variable-- so it is not worth it. + */ + #define YY_LESS_LINENO(n) \ + do { \ + int yyl;\ + for ( yyl = n; yyl < yyleng; ++yyl )\ + if ( yytext[yyl] == '\n' )\ + --yylineno;\ + }while(0) + #define YY_LINENO_REWIND_TO(dst) \ + do {\ + const char *p;\ + for ( p = yy_cp-1; p >= (dst); --p)\ + if ( *p == '\n' )\ + --yylineno;\ + }while(0) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, (yytext_ptr) ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { +/* %if-c-only */ + FILE *yy_input_file; +/* %endif */ + +/* %if-c++-only */ +/* %endif */ + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* %if-c-only Standard (non-C++) definition */ +/* %not-for-header */ +/* %if-not-reentrant */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */ +/* %endif */ +/* %ok-for-header */ + +/* %endif */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* %if-c-only Standard (non-C++) definition */ + +/* %if-not-reentrant */ +/* %not-for-header */ +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = NULL; +static int yy_init = 0; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; +/* %ok-for-header */ + +/* %endif */ + +void yyrestart ( FILE *input_file ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size ); +void yy_delete_buffer ( YY_BUFFER_STATE b ); +void yy_flush_buffer ( YY_BUFFER_STATE b ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer ); +void yypop_buffer_state ( void ); + +static void yyensure_buffer_stack ( void ); +static void yy_load_buffer_state ( void ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len ); + +/* %endif */ + +void *yyalloc ( yy_size_t ); +void *yyrealloc ( void *, yy_size_t ); +void yyfree ( void * ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* %% [1.0] yytext/yyin/yyout/yy_state_type/yylineno etc. def's & init go here */ +/* Begin user sect3 */ + +#define evalwrap() (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP + +#define FLEX_DEBUG +typedef flex_uint8_t YY_CHAR; + +FILE *yyin = NULL, *yyout = NULL; + +typedef int yy_state_type; + +extern int yylineno; +int yylineno = 1; + +extern char *yytext; +#ifdef yytext_ptr +#undef yytext_ptr +#endif +#define yytext_ptr yytext + +/* %% [1.5] DFA */ + +/* %if-c-only Standard (non-C++) definition */ + +static yy_state_type yy_get_previous_state ( void ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state ); +static int yy_get_next_buffer ( void ); +static void yynoreturn yy_fatal_error ( const char* msg ); + +/* %endif */ + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ +/* %% [2.0] code to fiddle yytext and yyleng for yymore() goes here \ */\ + yyleng = (int) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ +/* %% [3.0] code to copy yytext_ptr to yytext[] goes here, if %array \ */\ + (yy_c_buf_p) = yy_cp; +/* %% [4.0] data tables for the DFA and the user's section 1 definitions go here */ +#define YY_NUM_RULES 63 +#define YY_END_OF_BUFFER 64 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[279] = + { 0, + 0, 0, 64, 62, 1, 2, 62, 55, 56, 60, + 61, 59, 62, 54, 5, 5, 62, 62, 62, 62, + 57, 58, 62, 62, 62, 62, 62, 62, 62, 62, + 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, + 1, 2, 0, 3, 5, 0, 5, 0, 0, 0, + 0, 7, 8, 0, 0, 0, 0, 6, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 4, 7, 0, 39, 51, 0, + + 0, 0, 20, 0, 0, 0, 15, 0, 0, 0, + 0, 0, 21, 0, 23, 0, 0, 50, 0, 0, + 17, 0, 0, 0, 19, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 35, 0, 0, 0, 0, + 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 22, 30, 0, 0, 0, 0, 14, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 25, 18, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 26, 40, 0, 16, 27, 0, + + 41, 0, 0, 0, 0, 53, 0, 9, 0, 10, + 11, 29, 0, 0, 0, 0, 0, 33, 28, 7, + 0, 0, 0, 0, 0, 0, 0, 31, 0, 0, + 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 13, 12, 0, 0, 0, 0, 0, 0, 0, + 42, 0, 0, 0, 37, 0, 0, 0, 0, 43, + 36, 0, 0, 44, 0, 0, 0, 0, 45, 46, + 0, 0, 47, 0, 48, 49, 34, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 4, 5, + 6, 7, 8, 9, 10, 11, 1, 12, 13, 14, + 15, 16, 17, 18, 17, 19, 17, 20, 1, 1, + 21, 1, 1, 1, 22, 22, 22, 22, 22, 22, + 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, + 23, 23, 23, 23, 23, 23, 23, 24, 23, 23, + 25, 1, 26, 1, 27, 1, 28, 29, 30, 31, + + 32, 33, 34, 35, 36, 23, 37, 38, 39, 40, + 41, 42, 23, 43, 44, 45, 46, 47, 23, 48, + 49, 23, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[50] = + { 0, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, + 1, 6, 1, 1, 1, 1, 1, 6, 6, 6, + 6, 6, 6, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1 + } ; + +static const flex_int16_t yy_base[288] = + { 0, + 0, 0, 383, 384, 380, 378, 376, 384, 384, 384, + 384, 384, 0, 384, 39, 36, 359, 357, 86, 124, + 384, 384, 35, 38, 34, 37, 341, 48, 43, 58, + 63, 335, 22, 60, 343, 122, 116, 338, 341, 336, + 369, 367, 365, 384, 0, 0, 121, 348, 347, 0, + 346, 0, 384, 147, 158, 0, 0, 384, 334, 326, + 332, 334, 321, 315, 314, 313, 321, 328, 307, 322, + 304, 119, 307, 311, 310, 319, 309, 313, 301, 300, + 0, 312, 298, 304, 313, 302, 309, 309, 289, 308, + 295, 294, 305, 321, 0, 0, 288, 0, 0, 299, + + 299, 300, 0, 295, 282, 294, 280, 283, 280, 291, + 282, 157, 0, 282, 0, 289, 272, 0, 280, 272, + 159, 286, 282, 276, 0, 267, 265, 269, 263, 276, + 275, 0, 260, 273, 275, 0, 259, 256, 269, 254, + 0, 266, 265, 252, 277, 280, 248, 264, 259, 241, + 248, 260, 0, 0, 238, 255, 240, 239, 0, 239, + 166, 241, 250, 269, 238, 235, 232, 234, 231, 231, + 230, 0, 0, 240, 226, 225, 228, 237, 224, 224, + 225, 233, 162, 220, 0, 219, 225, 242, 245, 213, + 214, 213, 0, 210, 0, 0, 211, 0, 0, 217, + + 0, 211, 210, 205, 218, 0, 216, 0, 216, 0, + 0, 0, 210, 214, 199, 198, 201, 231, 0, 0, + 208, 203, 198, 192, 191, 203, 191, 0, 190, 192, + 0, 190, 189, 184, 160, 180, 183, 192, 193, 192, + 175, 0, 0, 188, 176, 175, 187, 180, 172, 184, + 0, 167, 166, 168, 0, 180, 179, 162, 176, 0, + 0, 147, 144, 0, 140, 139, 138, 138, 0, 0, + 129, 126, 0, 123, 0, 0, 0, 384, 190, 162, + 193, 107, 196, 199, 203, 78, 77 + } ; + +static const flex_int16_t yy_def[288] = + { 0, + 278, 1, 278, 278, 278, 278, 279, 278, 278, 278, + 278, 278, 280, 278, 278, 15, 281, 278, 278, 19, + 278, 278, 19, 19, 19, 19, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 278, 278, 279, 278, 280, 282, 15, 281, 283, 284, + 281, 285, 278, 278, 20, 19, 20, 278, 19, 20, + 20, 20, 20, 19, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 282, 284, 285, 19, 20, 20, 20, + + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 286, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 286, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 287, 20, 20, 20, 20, 20, 20, 20, + + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 287, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 20, 20, 20, 0, 278, 278, + 278, 278, 278, 278, 278, 278, 278 + } ; + +static const flex_int16_t yy_nxt[434] = + { 0, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 16, 16, 16, 16, 16, 16, 17, + 18, 19, 20, 20, 21, 22, 4, 23, 19, 24, + 25, 26, 19, 27, 28, 29, 20, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 20, 40, 46, + 47, 47, 47, 47, 47, 47, 47, 47, 48, 278, + 49, 64, 50, 80, 81, 59, 49, 49, 49, 49, + 49, 49, 60, 62, 61, 72, 66, 65, 63, 69, + 220, 164, 73, 278, 67, 70, 50, 54, 54, 74, + 76, 82, 71, 75, 77, 55, 83, 56, 56, 56, + + 56, 56, 56, 56, 56, 48, 78, 56, 57, 57, + 94, 58, 55, 56, 56, 56, 56, 56, 56, 57, + 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, + 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, + 57, 57, 57, 278, 278, 57, 110, 89, 54, 54, + 111, 57, 57, 57, 57, 57, 57, 85, 90, 278, + 278, 278, 278, 86, 87, 45, 277, 88, 278, 145, + 276, 146, 58, 275, 153, 147, 154, 210, 188, 211, + 189, 274, 273, 278, 190, 278, 272, 271, 270, 248, + 43, 269, 43, 43, 43, 43, 51, 51, 51, 49, + + 49, 49, 95, 268, 95, 96, 96, 96, 96, 267, + 266, 265, 264, 263, 262, 261, 260, 259, 258, 257, + 256, 255, 254, 253, 252, 251, 250, 249, 247, 246, + 245, 244, 243, 242, 241, 240, 239, 238, 237, 236, + 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, + 225, 224, 223, 222, 221, 219, 218, 217, 216, 215, + 214, 213, 212, 209, 208, 207, 206, 205, 204, 203, + 202, 201, 200, 199, 198, 197, 196, 195, 194, 193, + 192, 191, 187, 186, 185, 184, 183, 182, 181, 180, + 179, 178, 177, 176, 175, 174, 173, 172, 171, 170, + + 169, 168, 167, 166, 165, 163, 162, 161, 160, 159, + 158, 157, 156, 155, 152, 151, 150, 149, 148, 144, + 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, + 133, 132, 131, 130, 129, 128, 127, 126, 125, 124, + 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, + 113, 112, 109, 108, 107, 106, 105, 104, 103, 102, + 101, 100, 99, 98, 97, 52, 48, 52, 44, 42, + 41, 93, 92, 91, 84, 79, 68, 53, 52, 44, + 42, 41, 278, 3, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + + 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278 + } ; + +static const flex_int16_t yy_chk[434] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, + 15, 25, 15, 33, 33, 23, 15, 15, 15, 15, + 15, 15, 23, 24, 23, 29, 26, 25, 24, 28, + 287, 286, 29, 16, 26, 28, 15, 19, 19, 30, + 31, 34, 28, 30, 31, 19, 34, 19, 19, 19, + + 19, 19, 19, 19, 19, 19, 31, 19, 19, 19, + 282, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, + 20, 20, 20, 20, 47, 20, 72, 37, 54, 54, + 72, 20, 20, 20, 20, 20, 20, 36, 37, 55, + 55, 235, 235, 36, 36, 280, 274, 36, 47, 112, + 272, 112, 54, 271, 121, 112, 121, 183, 161, 183, + 161, 268, 267, 55, 161, 235, 266, 265, 263, 235, + 279, 262, 279, 279, 279, 279, 281, 281, 281, 283, + + 283, 283, 284, 259, 284, 285, 285, 285, 285, 258, + 257, 256, 254, 253, 252, 250, 249, 248, 247, 246, + 245, 244, 241, 240, 239, 238, 237, 236, 234, 233, + 232, 230, 229, 227, 226, 225, 224, 223, 222, 221, + 218, 217, 216, 215, 214, 213, 209, 207, 205, 204, + 203, 202, 200, 197, 194, 192, 191, 190, 189, 188, + 187, 186, 184, 182, 181, 180, 179, 178, 177, 176, + 175, 174, 171, 170, 169, 168, 167, 166, 165, 164, + 163, 162, 160, 158, 157, 156, 155, 152, 151, 150, + 149, 148, 147, 146, 145, 144, 143, 142, 140, 139, + + 138, 137, 135, 134, 133, 131, 130, 129, 128, 127, + 126, 124, 123, 122, 120, 119, 117, 116, 114, 111, + 110, 109, 108, 107, 106, 105, 104, 102, 101, 100, + 97, 94, 93, 92, 91, 90, 89, 88, 87, 86, + 85, 84, 83, 82, 80, 79, 78, 77, 76, 75, + 74, 73, 71, 70, 69, 68, 67, 66, 65, 64, + 63, 62, 61, 60, 59, 51, 49, 48, 43, 42, + 41, 40, 39, 38, 35, 32, 27, 18, 17, 7, + 6, 5, 3, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + + 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 278, 278, 278 + } ; + +/* Table of booleans, true if rule could match eol. */ +static const flex_int32_t yy_rule_can_match_eol[64] = + { 0, +0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, }; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +extern int yy_flex_debug; +int yy_flex_debug = 1; + +static const flex_int16_t yy_rule_linenum[63] = + { 0, + 106, 111, 117, 127, 133, 151, 175, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, + 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, + 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, + 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243 + } ; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *yytext; +#line 1 "lexer.ll" +/* Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC") + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#line 8 "lexer.ll" + +/* Generated files do not make clang static analyser so happy */ +#ifndef __clang_analyzer__ + +#include <cerrno> +#include <climits> +#include <cstdlib> +#include <string> +#include <eval/eval_context.h> +#include <eval/parser.h> +#include <asiolink/io_address.h> +#include <boost/lexical_cast.hpp> + +/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */ + +/* Work around an incompatibility in flex (at least versions + 2.5.31 through 2.5.33): it generates code that does + not conform to C89. See Debian bug 333231 + <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */ +# undef yywrap +# define yywrap() 1 + +/* The location of the current token. The lexer will keep updating it. This + variable will be useful for logging errors. */ +static isc::eval::location loc; + +namespace { + bool start_token_flag = false; + isc::eval::EvalContext::ParserType start_token_value; +}; + +/* To avoid the call to exit... oops! */ +#define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg) +#line 1044 "lexer.cc" +/* noyywrap disables automatic rewinding for the next file to parse. Since we + always parse only a single string, there's no need to do any wraps. And + using yywrap requires linking with -lfl, which provides the default yywrap + implementation that always returns 1 anyway. */ +/* nounput simplifies the lexer, by removing support for putting a character + back into the input stream. We never use such capability anyway. */ +/* batch means that we'll never use the generated lexer interactively. */ +/* Enables debug mode. To see the debug messages, one needs to also set + eval_flex_debug to 1, then the debug messages will be printed on stderr. */ +/* I have no idea what this option does, except it was specified in the bison + examples and Postgres folks added it to remove gcc 4.3 warnings. Let's + be on the safe side and keep it. */ +#define YY_NO_INPUT 1 +/* This line tells flex to track the line numbers. It's not really that + useful for client classes, which typically are one-liners, but it may be + useful in more complex cases. */ +/* These are not token expressions yet, just convenience expressions that + can be used during actual token definitions. Note some can match + incorrect inputs (e.g., IP addresses) which must be checked. */ +#line 80 "lexer.ll" +/* This code run each time a pattern is matched. It updates the location + by moving it ahead by yyleng bytes. yyleng specifies the length of the + currently matched token. */ +#define YY_USER_ACTION loc.columns(evalleng); +#line 1069 "lexer.cc" +#line 1070 "lexer.cc" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +/* %if-c-only */ +#include <unistd.h> +/* %endif */ +/* %if-c++-only */ +/* %endif */ +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* %if-c-only Reentrant structure and macros (non-C++). */ +/* %if-reentrant */ +/* %if-c-only */ + +static int yy_init_globals ( void ); + +/* %endif */ +/* %if-reentrant */ +/* %endif */ +/* %endif End reentrant structures and macros. */ + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( void ); + +int yyget_debug ( void ); + +void yyset_debug ( int debug_flag ); + +YY_EXTRA_TYPE yyget_extra ( void ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined ); + +FILE *yyget_in ( void ); + +void yyset_in ( FILE * _in_str ); + +FILE *yyget_out ( void ); + +void yyset_out ( FILE * _out_str ); + + int yyget_leng ( void ); + +char *yyget_text ( void ); + +int yyget_lineno ( void ); + +void yyset_lineno ( int _line_number ); + +/* %if-bison-bridge */ +/* %endif */ + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( void ); +#else +extern int yywrap ( void ); +#endif +#endif + +/* %not-for-header */ +#ifndef YY_NO_UNPUT + +#endif +/* %ok-for-header */ + +/* %endif */ + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * ); +#endif + +#ifndef YY_NO_INPUT +/* %if-c-only Standard (non-C++) definition */ +/* %not-for-header */ +#ifdef __cplusplus +static int yyinput ( void ); +#else +static int input ( void ); +#endif +/* %ok-for-header */ + +/* %endif */ +#endif + +/* %if-c-only */ + +/* %endif */ + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* %if-c-only Standard (non-C++) definition */ +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +/* %endif */ +/* %if-c++-only C++ definition */ +/* %endif */ +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ +/* %% [5.0] fread()/read() definition of YY_INPUT goes here unless we're doing C++ \ */\ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ +/* %if-c++-only C++ definition \ */\ +/* %endif */ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +/* %if-c-only */ +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +#endif + +/* %if-tables-serialization structures and prototypes */ +/* %not-for-header */ +/* %ok-for-header */ + +/* %not-for-header */ +/* %tables-yydmap generated elements */ +/* %endif */ +/* end tables serialization structures and prototypes */ + +/* %ok-for-header */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 +/* %if-c-only Standard (non-C++) definition */ + +extern int yylex (void); + +#define YY_DECL int yylex (void) +/* %endif */ +/* %if-c++-only C++ definition */ +/* %endif */ +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +/* %% [6.0] YY_RULE_SETUP definition goes here */ +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/* %not-for-header */ +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) +/* %if-c-only */ + yyin = stdin; +/* %endif */ +/* %if-c++-only */ +/* %endif */ + + if ( ! yyout ) +/* %if-c-only */ + yyout = stdout; +/* %endif */ +/* %if-c++-only */ +/* %endif */ + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + { +/* %% [7.0] user's declarations go here */ +#line 86 "lexer.ll" + + + +#line 90 "lexer.ll" + /* Code run each time evallex is called. */ + loc.step(); + + if (start_token_flag) { + start_token_flag = false; + switch (start_token_value) { + case EvalContext::PARSER_BOOL: + return isc::eval::EvalParser::make_TOPLEVEL_BOOL(loc); + default: + case EvalContext::PARSER_STRING: + return isc::eval::EvalParser::make_TOPLEVEL_STRING(loc); + } + } + + + +#line 1369 "lexer.cc" + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { +/* %% [8.0] yymore()-related code goes here */ + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + +/* %% [9.0] code to set up and find next match goes here */ + yy_current_state = (yy_start); +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 279 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_current_state != 278 ); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + +yy_find_action: +/* %% [10.0] code to find the action number goes here */ + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +/* %% [11.0] code for yylineno update goes here */ + + if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] ) + { + int yyl; + for ( yyl = 0; yyl < yyleng; ++yyl ) + if ( yytext[yyl] == '\n' ) + + yylineno++; +; + } + +do_action: /* This label is used only to access EOF actions. */ + +/* %% [12.0] debug code goes here */ + if ( yy_flex_debug ) + { + if ( yy_act == 0 ) + fprintf( stderr, "--scanner backing up\n" ); + else if ( yy_act < 63 ) + fprintf( stderr, "--accepting rule at line %ld (\"%s\")\n", + (long)yy_rule_linenum[yy_act], yytext ); + else if ( yy_act == 63 ) + fprintf( stderr, "--accepting default rule (\"%s\")\n", + yytext ); + else if ( yy_act == 64 ) + fprintf( stderr, "--(end of buffer or a NUL)\n" ); + else + fprintf( stderr, "--EOF (start condition %d)\n", YY_START ); + } + + switch ( yy_act ) + { /* beginning of action switch */ +/* %% [13.0] actions go here */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +YY_RULE_SETUP +#line 106 "lexer.ll" +{ + /* Ok, we found a with space. Let's ignore it and update loc variable. */ + loc.step(); +} + YY_BREAK +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +#line 111 "lexer.ll" +{ + /* Newline found. Let's update the location and continue. */ + loc.lines(evalleng); + loc.step(); +} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 117 "lexer.ll" +{ + /* A string has been matched. It contains the actual string and single quotes. + We need to get those quotes out of the way and just use its content, e.g. + for 'foo' we should get foo */ + std::string tmp(evaltext+1); + tmp.resize(tmp.size() - 1); + + return isc::eval::EvalParser::make_STRING(tmp, loc); +} + YY_BREAK +case 4: +YY_RULE_SETUP +#line 127 "lexer.ll" +{ + /* A hex string has been matched. It contains the '0x' or '0X' header + followed by at least one hexadecimal digit. */ + return isc::eval::EvalParser::make_HEXSTRING(evaltext, loc); +} + YY_BREAK +case 5: +YY_RULE_SETUP +#line 133 "lexer.ll" +{ + /* An integer was found. */ + std::string tmp(evaltext); + + try { + /* In substring we want to use negative values (e.g. -1). + In enterprise-id we need to use values up to 0xffffffff. + To cover both of those use cases, we need at least + int64_t. */ + static_cast<void>(boost::lexical_cast<int64_t>(tmp)); + } catch (const boost::bad_lexical_cast &) { + driver.error(loc, "Failed to convert " + tmp + " to an integer."); + } + + /* The parser needs the string form as double conversion is no lossless */ + return isc::eval::EvalParser::make_INTEGER(tmp, loc); +} + YY_BREAK +case 6: +/* rule 6 can match eol */ +*yy_cp = (yy_hold_char); /* undo effects of setting up yytext */ +YY_LINENO_REWIND_TO(yy_cp - 1); +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up yytext again */ +YY_RULE_SETUP +#line 151 "lexer.ll" +{ + /* This string specifies option name starting with a letter + and further containing letters, digits, hyphens and + underscores and finishing by letters or digits. */ + /* Moved from a variable trailing context to C++ code as it was too slow */ + std::string tmp(evaltext); + /* remove possible trailing blanks or newlines */ + while (tmp.size() > 1) { + char last = tmp[tmp.size() - 1]; + if ((last != ' ') && (last != '\t') && (last != '\n')) { + break; + } + if (last == '\n') { + /* Take embedded newlines into account */ + /* Can make it more complex to handle spaces after the last + newline but currently keep it simple... */ + loc.lines(); + loc.step(); + } + tmp.resize(tmp.size() - 1); + } + return isc::eval::EvalParser::make_OPTION_NAME(tmp, loc); +} + YY_BREAK +case 7: +YY_RULE_SETUP +#line 175 "lexer.ll" +{ + /* IPv4 or IPv6 address */ + std::string tmp(evaltext); + + /* Some incorrect addresses can match so we have to check. */ + try { + isc::asiolink::IOAddress ip(tmp); + } catch (...) { + driver.error(loc, "Failed to convert " + tmp + " to an IP address."); + } + + return isc::eval::EvalParser::make_IP_ADDRESS(evaltext, loc); +} + YY_BREAK +case 8: +YY_RULE_SETUP +#line 189 "lexer.ll" +return isc::eval::EvalParser::make_EQUAL(loc); + YY_BREAK +case 9: +YY_RULE_SETUP +#line 190 "lexer.ll" +return isc::eval::EvalParser::make_OPTION(loc); + YY_BREAK +case 10: +YY_RULE_SETUP +#line 191 "lexer.ll" +return isc::eval::EvalParser::make_RELAY4(loc); + YY_BREAK +case 11: +YY_RULE_SETUP +#line 192 "lexer.ll" +return isc::eval::EvalParser::make_RELAY6(loc); + YY_BREAK +case 12: +YY_RULE_SETUP +#line 193 "lexer.ll" +return isc::eval::EvalParser::make_PEERADDR(loc); + YY_BREAK +case 13: +YY_RULE_SETUP +#line 194 "lexer.ll" +return isc::eval::EvalParser::make_LINKADDR(loc); + YY_BREAK +case 14: +YY_RULE_SETUP +#line 195 "lexer.ll" +return isc::eval::EvalParser::make_TEXT(loc); + YY_BREAK +case 15: +YY_RULE_SETUP +#line 196 "lexer.ll" +return isc::eval::EvalParser::make_HEX(loc); + YY_BREAK +case 16: +YY_RULE_SETUP +#line 197 "lexer.ll" +return isc::eval::EvalParser::make_EXISTS(loc); + YY_BREAK +case 17: +YY_RULE_SETUP +#line 198 "lexer.ll" +return isc::eval::EvalParser::make_PKT(loc); + YY_BREAK +case 18: +YY_RULE_SETUP +#line 199 "lexer.ll" +return isc::eval::EvalParser::make_IFACE(loc); + YY_BREAK +case 19: +YY_RULE_SETUP +#line 200 "lexer.ll" +return isc::eval::EvalParser::make_SRC(loc); + YY_BREAK +case 20: +YY_RULE_SETUP +#line 201 "lexer.ll" +return isc::eval::EvalParser::make_DST(loc); + YY_BREAK +case 21: +YY_RULE_SETUP +#line 202 "lexer.ll" +return isc::eval::EvalParser::make_LEN(loc); + YY_BREAK +case 22: +YY_RULE_SETUP +#line 203 "lexer.ll" +return isc::eval::EvalParser::make_PKT4(loc); + YY_BREAK +case 23: +YY_RULE_SETUP +#line 204 "lexer.ll" +return isc::eval::EvalParser::make_CHADDR(loc); + YY_BREAK +case 24: +YY_RULE_SETUP +#line 205 "lexer.ll" +return isc::eval::EvalParser::make_HLEN(loc); + YY_BREAK +case 25: +YY_RULE_SETUP +#line 206 "lexer.ll" +return isc::eval::EvalParser::make_HTYPE(loc); + YY_BREAK +case 26: +YY_RULE_SETUP +#line 207 "lexer.ll" +return isc::eval::EvalParser::make_CIADDR(loc); + YY_BREAK +case 27: +YY_RULE_SETUP +#line 208 "lexer.ll" +return isc::eval::EvalParser::make_GIADDR(loc); + YY_BREAK +case 28: +YY_RULE_SETUP +#line 209 "lexer.ll" +return isc::eval::EvalParser::make_YIADDR(loc); + YY_BREAK +case 29: +YY_RULE_SETUP +#line 210 "lexer.ll" +return isc::eval::EvalParser::make_SIADDR(loc); + YY_BREAK +case 30: +YY_RULE_SETUP +#line 211 "lexer.ll" +return isc::eval::EvalParser::make_PKT6(loc); + YY_BREAK +case 31: +YY_RULE_SETUP +#line 212 "lexer.ll" +return isc::eval::EvalParser::make_MSGTYPE(loc); + YY_BREAK +case 32: +YY_RULE_SETUP +#line 213 "lexer.ll" +return isc::eval::EvalParser::make_TRANSID(loc); + YY_BREAK +case 33: +YY_RULE_SETUP +#line 214 "lexer.ll" +return isc::eval::EvalParser::make_VENDOR(loc); + YY_BREAK +case 34: +YY_RULE_SETUP +#line 215 "lexer.ll" +return isc::eval::EvalParser::make_VENDOR_CLASS(loc); + YY_BREAK +case 35: +YY_RULE_SETUP +#line 216 "lexer.ll" +return isc::eval::EvalParser::make_DATA(loc); + YY_BREAK +case 36: +YY_RULE_SETUP +#line 217 "lexer.ll" +return isc::eval::EvalParser::make_ENTERPRISE(loc); + YY_BREAK +case 37: +YY_RULE_SETUP +#line 218 "lexer.ll" +return isc::eval::EvalParser::make_SUBSTRING(loc); + YY_BREAK +case 38: +YY_RULE_SETUP +#line 219 "lexer.ll" +return isc::eval::EvalParser::make_SPLIT(loc); + YY_BREAK +case 39: +YY_RULE_SETUP +#line 220 "lexer.ll" +return isc::eval::EvalParser::make_ALL(loc); + YY_BREAK +case 40: +YY_RULE_SETUP +#line 221 "lexer.ll" +return isc::eval::EvalParser::make_CONCAT(loc); + YY_BREAK +case 41: +YY_RULE_SETUP +#line 222 "lexer.ll" +return isc::eval::EvalParser::make_IFELSE(loc); + YY_BREAK +case 42: +YY_RULE_SETUP +#line 223 "lexer.ll" +return isc::eval::EvalParser::make_TOHEXSTRING(loc); + YY_BREAK +case 43: +YY_RULE_SETUP +#line 224 "lexer.ll" +return isc::eval::EvalParser::make_ADDRTOTEXT(loc); + YY_BREAK +case 44: +YY_RULE_SETUP +#line 225 "lexer.ll" +return isc::eval::EvalParser::make_INT8TOTEXT(loc); + YY_BREAK +case 45: +YY_RULE_SETUP +#line 226 "lexer.ll" +return isc::eval::EvalParser::make_INT16TOTEXT(loc); + YY_BREAK +case 46: +YY_RULE_SETUP +#line 227 "lexer.ll" +return isc::eval::EvalParser::make_INT32TOTEXT(loc); + YY_BREAK +case 47: +YY_RULE_SETUP +#line 228 "lexer.ll" +return isc::eval::EvalParser::make_UINT8TOTEXT(loc); + YY_BREAK +case 48: +YY_RULE_SETUP +#line 229 "lexer.ll" +return isc::eval::EvalParser::make_UINT16TOTEXT(loc); + YY_BREAK +case 49: +YY_RULE_SETUP +#line 230 "lexer.ll" +return isc::eval::EvalParser::make_UINT32TOTEXT(loc); + YY_BREAK +case 50: +YY_RULE_SETUP +#line 231 "lexer.ll" +return isc::eval::EvalParser::make_NOT(loc); + YY_BREAK +case 51: +YY_RULE_SETUP +#line 232 "lexer.ll" +return isc::eval::EvalParser::make_AND(loc); + YY_BREAK +case 52: +YY_RULE_SETUP +#line 233 "lexer.ll" +return isc::eval::EvalParser::make_OR(loc); + YY_BREAK +case 53: +YY_RULE_SETUP +#line 234 "lexer.ll" +return isc::eval::EvalParser::make_MEMBER(loc); + YY_BREAK +case 54: +YY_RULE_SETUP +#line 235 "lexer.ll" +return isc::eval::EvalParser::make_DOT(loc); + YY_BREAK +case 55: +YY_RULE_SETUP +#line 236 "lexer.ll" +return isc::eval::EvalParser::make_LPAREN(loc); + YY_BREAK +case 56: +YY_RULE_SETUP +#line 237 "lexer.ll" +return isc::eval::EvalParser::make_RPAREN(loc); + YY_BREAK +case 57: +YY_RULE_SETUP +#line 238 "lexer.ll" +return isc::eval::EvalParser::make_LBRACKET(loc); + YY_BREAK +case 58: +YY_RULE_SETUP +#line 239 "lexer.ll" +return isc::eval::EvalParser::make_RBRACKET(loc); + YY_BREAK +case 59: +YY_RULE_SETUP +#line 240 "lexer.ll" +return isc::eval::EvalParser::make_COMA(loc); + YY_BREAK +case 60: +YY_RULE_SETUP +#line 241 "lexer.ll" +return isc::eval::EvalParser::make_ANY(loc); + YY_BREAK +case 61: +YY_RULE_SETUP +#line 242 "lexer.ll" +return isc::eval::EvalParser::make_PLUS(loc); + YY_BREAK +case 62: +YY_RULE_SETUP +#line 243 "lexer.ll" +driver.error (loc, "Invalid character: " + std::string(evaltext)); + YY_BREAK +case YY_STATE_EOF(INITIAL): +#line 244 "lexer.ll" +return isc::eval::EvalParser::make_END(loc); + YY_BREAK +case 63: +YY_RULE_SETUP +#line 245 "lexer.ll" +ECHO; + YY_BREAK +#line 1849 "lexer.cc" + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; +/* %if-c-only */ + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; +/* %endif */ +/* %if-c++-only */ +/* %endif */ + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { +/* %% [14.0] code to do back-up for compressed tables and set up yy_cp goes here */ + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ +/* %ok-for-header */ + +/* %if-c++-only */ +/* %not-for-header */ +/* %ok-for-header */ + +/* %endif */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +/* %if-c-only */ +static int yy_get_next_buffer (void) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = (yytext_ptr); + int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + +/* %if-c-only */ +/* %not-for-header */ + static yy_state_type yy_get_previous_state (void) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + yy_state_type yy_current_state; + char *yy_cp; + +/* %% [15.0] code to get the start state into yy_current_state goes here */ + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { +/* %% [16.0] code to find the next state goes here */ + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 279 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ +/* %if-c-only */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + int yy_is_jam; + /* %% [17.0] code to find the next state, and perhaps do backing up, goes here */ + char *yy_cp = (yy_c_buf_p); + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 279 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 278); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT +/* %if-c-only */ + +/* %endif */ +#endif + +/* %if-c-only */ +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (int) ((yy_c_buf_p) - (yytext_ptr)); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return 0; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + +/* %% [19.0] update BOL and yylineno */ + if ( c == '\n' ) + + yylineno++; +; + + return c; +} +/* %if-c-only */ +#endif /* ifndef YY_NO_INPUT */ +/* %endif */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ +/* %if-c-only */ + void yyrestart (FILE * input_file ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file ); + yy_load_buffer_state( ); +} + +/* %if-c++-only */ +/* %endif */ + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ +/* %if-c-only */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +/* %if-c-only */ +static void yy_load_buffer_state (void) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; +/* %if-c-only */ + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; +/* %endif */ +/* %if-c++-only */ +/* %endif */ + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ +/* %if-c-only */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; +} + +/* %if-c++-only */ +/* %endif */ + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ +/* %if-c-only */ + void yy_delete_buffer (YY_BUFFER_STATE b ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf ); + + yyfree( (void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ +/* %if-c-only */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ + +{ + int oerrno = errno; + + yy_flush_buffer( b ); + +/* %if-c-only */ + b->yy_input_file = file; +/* %endif */ +/* %if-c++-only */ +/* %endif */ + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + +/* %if-c-only */ + + b->yy_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0; + +/* %endif */ +/* %if-c++-only */ +/* %endif */ + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ +/* %if-c-only */ + void yy_flush_buffer (YY_BUFFER_STATE b ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( ); +} + +/* %if-c-or-c++ */ +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +/* %if-c-only */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} +/* %endif */ + +/* %if-c-or-c++ */ +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +/* %if-c-only */ +void yypop_buffer_state (void) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} +/* %endif */ + +/* %if-c-or-c++ */ +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +/* %if-c-only */ +static void yyensure_buffer_stack (void) +/* %endif */ +/* %if-c++-only */ +/* %endif */ +{ + yy_size_t num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} +/* %endif */ + +/* %if-c-only */ +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b ); + + return b; +} +/* %endif */ + +/* %if-c-only */ +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr ) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) ); +} +/* %endif */ + +/* %if-c-only */ +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} +/* %endif */ + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +/* %if-c-only */ +static void yynoreturn yy_fatal_error (const char* msg ) +{ + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} +/* %endif */ +/* %if-c++-only */ +/* %endif */ + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/* %if-c-only */ +/* %if-reentrant */ +/* %endif */ + +/** Get the current line number. + * + */ +int yyget_lineno (void) +{ + + return yylineno; +} + +/** Get the input stream. + * + */ +FILE *yyget_in (void) +{ + return yyin; +} + +/** Get the output stream. + * + */ +FILE *yyget_out (void) +{ + return yyout; +} + +/** Get the length of the current token. + * + */ +int yyget_leng (void) +{ + return yyleng; +} + +/** Get the current token. + * + */ + +char *yyget_text (void) +{ + return yytext; +} + +/* %if-reentrant */ +/* %endif */ + +/** Set the current line number. + * @param _line_number line number + * + */ +void yyset_lineno (int _line_number ) +{ + + yylineno = _line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str ) +{ + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str ) +{ + yyout = _out_str ; +} + +int yyget_debug (void) +{ + return yy_flex_debug; +} + +void yyset_debug (int _bdebug ) +{ + yy_flex_debug = _bdebug ; +} + +/* %endif */ + +/* %if-reentrant */ +/* %if-bison-bridge */ +/* %endif */ +/* %endif if-c-only */ + +/* %if-c-only */ +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + /* We do not touch yylineno unless the option is enabled. */ + yylineno = 1; + + (yy_buffer_stack) = NULL; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = NULL; + (yy_init) = 0; + (yy_start) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} +/* %endif */ + +/* %if-c-only SNIP! this currently causes conflicts with the c++ scanner */ +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(); + } + + /* Destroy the stack itself. */ + yyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( ); + +/* %if-reentrant */ +/* %endif */ + return 0; +} +/* %endif */ + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n ) +{ + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s ) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *yyalloc (yy_size_t size ) +{ + return malloc(size); +} + +void *yyrealloc (void * ptr, yy_size_t size ) +{ + + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return realloc(ptr, size); +} + +void yyfree (void * ptr ) +{ + free( (char *) ptr ); /* see yyrealloc() for (char *) cast */ +} + +/* %if-tables-serialization definitions */ +/* %define-yytables The name for this specific scanner's tables. */ +#define YYTABLES_NAME "yytables" +/* %endif */ + +/* %ok-for-header */ + +#line 245 "lexer.ll" + + +using namespace isc::eval; + +void +EvalContext::scanStringBegin(ParserType type) +{ + start_token_flag = true; + start_token_value = type; + + loc.initialize(&file_); + eval_flex_debug = trace_scanning_; + YY_BUFFER_STATE buffer; + buffer = eval_scan_bytes(string_.c_str(), string_.size()); + if (!buffer) { + fatal("cannot scan string"); + /* fatal() throws an exception so this can't be reached */ + } +} + +void +EvalContext::scanStringEnd() +{ + eval_delete_buffer(YY_CURRENT_BUFFER); +} + +namespace { +/** To avoid unused function error */ +class Dummy { + /* cppcheck-suppress unusedPrivateFunction */ + void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); } +}; +} +#endif /* !__clang_analyzer__ */ + diff --git a/src/lib/eval/lexer.ll b/src/lib/eval/lexer.ll new file mode 100644 index 0000000..3c376c2 --- /dev/null +++ b/src/lib/eval/lexer.ll @@ -0,0 +1,277 @@ +/* Copyright (C) 2015-2022 Internet Systems Consortium, Inc. ("ISC") + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +%{ /* -*- C++ -*- */ + +/* Generated files do not make clang static analyser so happy */ +#ifndef __clang_analyzer__ + +#include <cerrno> +#include <climits> +#include <cstdlib> +#include <string> +#include <eval/eval_context.h> +#include <eval/parser.h> +#include <asiolink/io_address.h> +#include <boost/lexical_cast.hpp> + +/* Please avoid C++ style comments (// ... eol) as they break flex 2.6.2 */ + +/* Work around an incompatibility in flex (at least versions + 2.5.31 through 2.5.33): it generates code that does + not conform to C89. See Debian bug 333231 + <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>. */ +# undef yywrap +# define yywrap() 1 + +/* The location of the current token. The lexer will keep updating it. This + variable will be useful for logging errors. */ +static isc::eval::location loc; + +namespace { + bool start_token_flag = false; + isc::eval::EvalContext::ParserType start_token_value; +}; + +/* To avoid the call to exit... oops! */ +#define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg) +%} + +/* noyywrap disables automatic rewinding for the next file to parse. Since we + always parse only a single string, there's no need to do any wraps. And + using yywrap requires linking with -lfl, which provides the default yywrap + implementation that always returns 1 anyway. */ +%option noyywrap + +/* nounput simplifies the lexer, by removing support for putting a character + back into the input stream. We never use such capability anyway. */ +%option nounput + +/* batch means that we'll never use the generated lexer interactively. */ +%option batch + +/* Enables debug mode. To see the debug messages, one needs to also set + eval_flex_debug to 1, then the debug messages will be printed on stderr. */ +%option debug + +/* I have no idea what this option does, except it was specified in the bison + examples and Postgres folks added it to remove gcc 4.3 warnings. Let's + be on the safe side and keep it. */ +%option noinput + +/* This line tells flex to track the line numbers. It's not really that + useful for client classes, which typically are one-liners, but it may be + useful in more complex cases. */ +%option yylineno + +/* These are not token expressions yet, just convenience expressions that + can be used during actual token definitions. Note some can match + incorrect inputs (e.g., IP addresses) which must be checked. */ +int \-?[0-9]+ +hex [0-9a-fA-F]+ +blank [ \t] +addr4 [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ +addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]* + +%{ +/* This code run each time a pattern is matched. It updates the location + by moving it ahead by yyleng bytes. yyleng specifies the length of the + currently matched token. */ +#define YY_USER_ACTION loc.columns(evalleng); +%} + +%% + +%{ + /* Code run each time evallex is called. */ + loc.step(); + + if (start_token_flag) { + start_token_flag = false; + switch (start_token_value) { + case EvalContext::PARSER_BOOL: + return isc::eval::EvalParser::make_TOPLEVEL_BOOL(loc); + default: + case EvalContext::PARSER_STRING: + return isc::eval::EvalParser::make_TOPLEVEL_STRING(loc); + } + } + +%} + +{blank}+ { + /* Ok, we found a with space. Let's ignore it and update loc variable. */ + loc.step(); +} + +[\n]+ { + /* Newline found. Let's update the location and continue. */ + loc.lines(evalleng); + loc.step(); +} + +\'[^\'\n]*\' { + /* A string has been matched. It contains the actual string and single quotes. + We need to get those quotes out of the way and just use its content, e.g. + for 'foo' we should get foo */ + std::string tmp(evaltext+1); + tmp.resize(tmp.size() - 1); + + return isc::eval::EvalParser::make_STRING(tmp, loc); +} + +0[xX]{hex} { + /* A hex string has been matched. It contains the '0x' or '0X' header + followed by at least one hexadecimal digit. */ + return isc::eval::EvalParser::make_HEXSTRING(evaltext, loc); +} + +{int} { + /* An integer was found. */ + std::string tmp(evaltext); + + try { + /* In substring we want to use negative values (e.g. -1). + In enterprise-id we need to use values up to 0xffffffff. + To cover both of those use cases, we need at least + int64_t. */ + static_cast<void>(boost::lexical_cast<int64_t>(tmp)); + } catch (const boost::bad_lexical_cast &) { + driver.error(loc, "Failed to convert " + tmp + " to an integer."); + } + + /* The parser needs the string form as double conversion is no lossless */ + return isc::eval::EvalParser::make_INTEGER(tmp, loc); +} + +[A-Za-z]([-_A-Za-z0-9]*[A-Za-z0-9])?({blank}|\n)*/] { + /* This string specifies option name starting with a letter + and further containing letters, digits, hyphens and + underscores and finishing by letters or digits. */ + /* Moved from a variable trailing context to C++ code as it was too slow */ + std::string tmp(evaltext); + /* remove possible trailing blanks or newlines */ + while (tmp.size() > 1) { + char last = tmp[tmp.size() - 1]; + if ((last != ' ') && (last != '\t') && (last != '\n')) { + break; + } + if (last == '\n') { + /* Take embedded newlines into account */ + /* Can make it more complex to handle spaces after the last + newline but currently keep it simple... */ + loc.lines(); + loc.step(); + } + tmp.resize(tmp.size() - 1); + } + return isc::eval::EvalParser::make_OPTION_NAME(tmp, loc); +} + +{addr4}|{addr6} { + /* IPv4 or IPv6 address */ + std::string tmp(evaltext); + + /* Some incorrect addresses can match so we have to check. */ + try { + isc::asiolink::IOAddress ip(tmp); + } catch (...) { + driver.error(loc, "Failed to convert " + tmp + " to an IP address."); + } + + return isc::eval::EvalParser::make_IP_ADDRESS(evaltext, loc); +} + +"==" return isc::eval::EvalParser::make_EQUAL(loc); +"option" return isc::eval::EvalParser::make_OPTION(loc); +"relay4" return isc::eval::EvalParser::make_RELAY4(loc); +"relay6" return isc::eval::EvalParser::make_RELAY6(loc); +"peeraddr" return isc::eval::EvalParser::make_PEERADDR(loc); +"linkaddr" return isc::eval::EvalParser::make_LINKADDR(loc); +"text" return isc::eval::EvalParser::make_TEXT(loc); +"hex" return isc::eval::EvalParser::make_HEX(loc); +"exists" return isc::eval::EvalParser::make_EXISTS(loc); +"pkt" return isc::eval::EvalParser::make_PKT(loc); +"iface" return isc::eval::EvalParser::make_IFACE(loc); +"src" return isc::eval::EvalParser::make_SRC(loc); +"dst" return isc::eval::EvalParser::make_DST(loc); +"len" return isc::eval::EvalParser::make_LEN(loc); +"pkt4" return isc::eval::EvalParser::make_PKT4(loc); +"mac" return isc::eval::EvalParser::make_CHADDR(loc); +"hlen" return isc::eval::EvalParser::make_HLEN(loc); +"htype" return isc::eval::EvalParser::make_HTYPE(loc); +"ciaddr" return isc::eval::EvalParser::make_CIADDR(loc); +"giaddr" return isc::eval::EvalParser::make_GIADDR(loc); +"yiaddr" return isc::eval::EvalParser::make_YIADDR(loc); +"siaddr" return isc::eval::EvalParser::make_SIADDR(loc); +"pkt6" return isc::eval::EvalParser::make_PKT6(loc); +"msgtype" return isc::eval::EvalParser::make_MSGTYPE(loc); +"transid" return isc::eval::EvalParser::make_TRANSID(loc); +"vendor" return isc::eval::EvalParser::make_VENDOR(loc); +"vendor-class" return isc::eval::EvalParser::make_VENDOR_CLASS(loc); +"data" return isc::eval::EvalParser::make_DATA(loc); +"enterprise" return isc::eval::EvalParser::make_ENTERPRISE(loc); +"substring" return isc::eval::EvalParser::make_SUBSTRING(loc); +"split" return isc::eval::EvalParser::make_SPLIT(loc); +"all" return isc::eval::EvalParser::make_ALL(loc); +"concat" return isc::eval::EvalParser::make_CONCAT(loc); +"ifelse" return isc::eval::EvalParser::make_IFELSE(loc); +"hexstring" return isc::eval::EvalParser::make_TOHEXSTRING(loc); +"addrtotext" return isc::eval::EvalParser::make_ADDRTOTEXT(loc); +"int8totext" return isc::eval::EvalParser::make_INT8TOTEXT(loc); +"int16totext" return isc::eval::EvalParser::make_INT16TOTEXT(loc); +"int32totext" return isc::eval::EvalParser::make_INT32TOTEXT(loc); +"uint8totext" return isc::eval::EvalParser::make_UINT8TOTEXT(loc); +"uint16totext" return isc::eval::EvalParser::make_UINT16TOTEXT(loc); +"uint32totext" return isc::eval::EvalParser::make_UINT32TOTEXT(loc); +"not" return isc::eval::EvalParser::make_NOT(loc); +"and" return isc::eval::EvalParser::make_AND(loc); +"or" return isc::eval::EvalParser::make_OR(loc); +"member" return isc::eval::EvalParser::make_MEMBER(loc); +"." return isc::eval::EvalParser::make_DOT(loc); +"(" return isc::eval::EvalParser::make_LPAREN(loc); +")" return isc::eval::EvalParser::make_RPAREN(loc); +"[" return isc::eval::EvalParser::make_LBRACKET(loc); +"]" return isc::eval::EvalParser::make_RBRACKET(loc); +"," return isc::eval::EvalParser::make_COMA(loc); +"*" return isc::eval::EvalParser::make_ANY(loc); +"+" return isc::eval::EvalParser::make_PLUS(loc); +. driver.error (loc, "Invalid character: " + std::string(evaltext)); +<<EOF>> return isc::eval::EvalParser::make_END(loc); +%% + +using namespace isc::eval; + +void +EvalContext::scanStringBegin(ParserType type) +{ + start_token_flag = true; + start_token_value = type; + + loc.initialize(&file_); + eval_flex_debug = trace_scanning_; + YY_BUFFER_STATE buffer; + buffer = eval_scan_bytes(string_.c_str(), string_.size()); + if (!buffer) { + fatal("cannot scan string"); + /* fatal() throws an exception so this can't be reached */ + } +} + +void +EvalContext::scanStringEnd() +{ + eval_delete_buffer(YY_CURRENT_BUFFER); +} + +namespace { +/** To avoid unused function error */ +class Dummy { + /* cppcheck-suppress unusedPrivateFunction */ + void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); } +}; +} +#endif /* !__clang_analyzer__ */ diff --git a/src/lib/eval/location.hh b/src/lib/eval/location.hh new file mode 100644 index 0000000..5bde479 --- /dev/null +++ b/src/lib/eval/location.hh @@ -0,0 +1,306 @@ +// A Bison parser, made by GNU Bison 3.8.2. + +// Locations for Bison parsers in C++ + +// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +// As a special exception, you may create a larger work that contains +// part or all of the Bison parser skeleton and distribute that work +// under terms of your choice, so long as that work isn't itself a +// parser generator using the skeleton or a modified version thereof +// as a parser skeleton. Alternatively, if you modify or redistribute +// the parser skeleton itself, you may (at your option) remove this +// special exception, which will cause the skeleton and the resulting +// Bison output files to be licensed under the GNU General Public +// License without this special exception. + +// This special exception was added by the Free Software Foundation in +// version 2.2 of Bison. + +/** + ** \file location.hh + ** Define the isc::eval::location class. + */ + +#ifndef YY_EVAL_LOCATION_HH_INCLUDED +# define YY_EVAL_LOCATION_HH_INCLUDED + +# include <iostream> +# include <string> + +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif + +#line 14 "parser.yy" +namespace isc { namespace eval { +#line 59 "location.hh" + + /// A point in a source file. + class position + { + public: + /// Type for file name. + typedef const std::string filename_type; + /// Type for line and column numbers. + typedef int counter_type; + + /// Construct a position. + explicit position (filename_type* f = YY_NULLPTR, + counter_type l = 1, + counter_type c = 1) + : filename (f) + , line (l) + , column (c) + {} + + + /// Initialization. + void initialize (filename_type* fn = YY_NULLPTR, + counter_type l = 1, + counter_type c = 1) + { + filename = fn; + line = l; + column = c; + } + + /** \name Line and Column related manipulators + ** \{ */ + /// (line related) Advance to the COUNT next lines. + void lines (counter_type count = 1) + { + if (count) + { + column = 1; + line = add_ (line, count, 1); + } + } + + /// (column related) Advance to the COUNT next columns. + void columns (counter_type count = 1) + { + column = add_ (column, count, 1); + } + /** \} */ + + /// File name to which this position refers. + filename_type* filename; + /// Current line number. + counter_type line; + /// Current column number. + counter_type column; + + private: + /// Compute max (min, lhs+rhs). + static counter_type add_ (counter_type lhs, counter_type rhs, counter_type min) + { + return lhs + rhs < min ? min : lhs + rhs; + } + }; + + /// Add \a width columns, in place. + inline position& + operator+= (position& res, position::counter_type width) + { + res.columns (width); + return res; + } + + /// Add \a width columns. + inline position + operator+ (position res, position::counter_type width) + { + return res += width; + } + + /// Subtract \a width columns, in place. + inline position& + operator-= (position& res, position::counter_type width) + { + return res += -width; + } + + /// Subtract \a width columns. + inline position + operator- (position res, position::counter_type width) + { + return res -= width; + } + + /** \brief Intercept output stream redirection. + ** \param ostr the destination output stream + ** \param pos a reference to the position to redirect + */ + template <typename YYChar> + std::basic_ostream<YYChar>& + operator<< (std::basic_ostream<YYChar>& ostr, const position& pos) + { + if (pos.filename) + ostr << *pos.filename << ':'; + return ostr << pos.line << '.' << pos.column; + } + + /// Two points in a source file. + class location + { + public: + /// Type for file name. + typedef position::filename_type filename_type; + /// Type for line and column numbers. + typedef position::counter_type counter_type; + + /// Construct a location from \a b to \a e. + location (const position& b, const position& e) + : begin (b) + , end (e) + {} + + /// Construct a 0-width location in \a p. + explicit location (const position& p = position ()) + : begin (p) + , end (p) + {} + + /// Construct a 0-width location in \a f, \a l, \a c. + explicit location (filename_type* f, + counter_type l = 1, + counter_type c = 1) + : begin (f, l, c) + , end (f, l, c) + {} + + + /// Initialization. + void initialize (filename_type* f = YY_NULLPTR, + counter_type l = 1, + counter_type c = 1) + { + begin.initialize (f, l, c); + end = begin; + } + + /** \name Line and Column related manipulators + ** \{ */ + public: + /// Reset initial location to final location. + void step () + { + begin = end; + } + + /// Extend the current location to the COUNT next columns. + void columns (counter_type count = 1) + { + end += count; + } + + /// Extend the current location to the COUNT next lines. + void lines (counter_type count = 1) + { + end.lines (count); + } + /** \} */ + + + public: + /// Beginning of the located region. + position begin; + /// End of the located region. + position end; + }; + + /// Join two locations, in place. + inline location& + operator+= (location& res, const location& end) + { + res.end = end.end; + return res; + } + + /// Join two locations. + inline location + operator+ (location res, const location& end) + { + return res += end; + } + + /// Add \a width columns to the end position, in place. + inline location& + operator+= (location& res, location::counter_type width) + { + res.columns (width); + return res; + } + + /// Add \a width columns to the end position. + inline location + operator+ (location res, location::counter_type width) + { + return res += width; + } + + /// Subtract \a width columns to the end position, in place. + inline location& + operator-= (location& res, location::counter_type width) + { + return res += -width; + } + + /// Subtract \a width columns to the end position. + inline location + operator- (location res, location::counter_type width) + { + return res -= width; + } + + /** \brief Intercept output stream redirection. + ** \param ostr the destination output stream + ** \param loc a reference to the location to redirect + ** + ** Avoid duplicate information. + */ + template <typename YYChar> + std::basic_ostream<YYChar>& + operator<< (std::basic_ostream<YYChar>& ostr, const location& loc) + { + location::counter_type end_col + = 0 < loc.end.column ? loc.end.column - 1 : 0; + ostr << loc.begin; + if (loc.end.filename + && (!loc.begin.filename + || *loc.begin.filename != *loc.end.filename)) + ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col; + else if (loc.begin.line < loc.end.line) + ostr << '-' << loc.end.line << '.' << end_col; + else if (loc.begin.column < end_col) + ostr << '-' << end_col; + return ostr; + } + +#line 14 "parser.yy" +} } // isc::eval +#line 305 "location.hh" + +#endif // !YY_EVAL_LOCATION_HH_INCLUDED diff --git a/src/lib/eval/parser.cc b/src/lib/eval/parser.cc new file mode 100644 index 0000000..e73d694 --- /dev/null +++ b/src/lib/eval/parser.cc @@ -0,0 +1,2227 @@ +// A Bison parser, made by GNU Bison 3.8.2. + +// Skeleton implementation for Bison LALR(1) parsers in C++ + +// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +// As a special exception, you may create a larger work that contains +// part or all of the Bison parser skeleton and distribute that work +// under terms of your choice, so long as that work isn't itself a +// parser generator using the skeleton or a modified version thereof +// as a parser skeleton. Alternatively, if you modify or redistribute +// the parser skeleton itself, you may (at your option) remove this +// special exception, which will cause the skeleton and the resulting +// Bison output files to be licensed under the GNU General Public +// License without this special exception. + +// This special exception was added by the Free Software Foundation in +// version 2.2 of Bison. + +// DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, +// especially those whose name start with YY_ or yy_. They are +// private implementation details that can be changed or removed. + + +// Take the name prefix into account. +#define yylex evallex + + + +#include "parser.h" + + +// Unqualified %code blocks. +#line 33 "parser.yy" + +# include "eval_context.h" + +// Avoid warnings with the error counter. +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + +#line 57 "parser.cc" + + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include <libintl.h> // FIXME: INFRINGES ON USER NAME SPACE. +# define YY_(msgid) dgettext ("bison-runtime", msgid) +# endif +# endif +# ifndef YY_ +# define YY_(msgid) msgid +# endif +#endif + + +// Whether we are compiled with exception support. +#ifndef YY_EXCEPTIONS +# if defined __GNUC__ && !defined __EXCEPTIONS +# define YY_EXCEPTIONS 0 +# else +# define YY_EXCEPTIONS 1 +# endif +#endif + +#define YYRHSLOC(Rhs, K) ((Rhs)[K].location) +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +# ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (N) \ + { \ + (Current).begin = YYRHSLOC (Rhs, 1).begin; \ + (Current).end = YYRHSLOC (Rhs, N).end; \ + } \ + else \ + { \ + (Current).begin = (Current).end = YYRHSLOC (Rhs, 0).end; \ + } \ + while (false) +# endif + + +// Enable debugging if requested. +#if EVALDEBUG + +// A pseudo ostream that takes yydebug_ into account. +# define YYCDEBUG if (yydebug_) (*yycdebug_) + +# define YY_SYMBOL_PRINT(Title, Symbol) \ + do { \ + if (yydebug_) \ + { \ + *yycdebug_ << Title << ' '; \ + yy_print_ (*yycdebug_, Symbol); \ + *yycdebug_ << '\n'; \ + } \ + } while (false) + +# define YY_REDUCE_PRINT(Rule) \ + do { \ + if (yydebug_) \ + yy_reduce_print_ (Rule); \ + } while (false) + +# define YY_STACK_PRINT() \ + do { \ + if (yydebug_) \ + yy_stack_print_ (); \ + } while (false) + +#else // !EVALDEBUG + +# define YYCDEBUG if (false) std::cerr +# define YY_SYMBOL_PRINT(Title, Symbol) YY_USE (Symbol) +# define YY_REDUCE_PRINT(Rule) static_cast<void> (0) +# define YY_STACK_PRINT() static_cast<void> (0) + +#endif // !EVALDEBUG + +#define yyerrok (yyerrstatus_ = 0) +#define yyclearin (yyla.clear ()) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab +#define YYRECOVERING() (!!yyerrstatus_) + +#line 14 "parser.yy" +namespace isc { namespace eval { +#line 150 "parser.cc" + + /// Build a parser object. + EvalParser::EvalParser (EvalContext& ctx_yyarg) +#if EVALDEBUG + : yydebug_ (false), + yycdebug_ (&std::cerr), +#else + : +#endif + ctx (ctx_yyarg) + {} + + EvalParser::~EvalParser () + {} + + EvalParser::syntax_error::~syntax_error () YY_NOEXCEPT YY_NOTHROW + {} + + /*---------. + | symbol. | + `---------*/ + + + + // by_state. + EvalParser::by_state::by_state () YY_NOEXCEPT + : state (empty_state) + {} + + EvalParser::by_state::by_state (const by_state& that) YY_NOEXCEPT + : state (that.state) + {} + + void + EvalParser::by_state::clear () YY_NOEXCEPT + { + state = empty_state; + } + + void + EvalParser::by_state::move (by_state& that) + { + state = that.state; + that.clear (); + } + + EvalParser::by_state::by_state (state_type s) YY_NOEXCEPT + : state (s) + {} + + EvalParser::symbol_kind_type + EvalParser::by_state::kind () const YY_NOEXCEPT + { + if (state == empty_state) + return symbol_kind::S_YYEMPTY; + else + return YY_CAST (symbol_kind_type, yystos_[+state]); + } + + EvalParser::stack_symbol_type::stack_symbol_type () + {} + + EvalParser::stack_symbol_type::stack_symbol_type (YY_RVREF (stack_symbol_type) that) + : super_type (YY_MOVE (that.state), YY_MOVE (that.location)) + { + switch (that.kind ()) + { + case symbol_kind::S_option_repr_type: // option_repr_type + value.YY_MOVE_OR_COPY< TokenOption::RepresentationType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + value.YY_MOVE_OR_COPY< TokenPkt4::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + value.YY_MOVE_OR_COPY< TokenPkt6::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + value.YY_MOVE_OR_COPY< TokenPkt::MetadataType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_relay6_field: // relay6_field + value.YY_MOVE_OR_COPY< TokenRelay6Field::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_nest_level: // nest_level + value.YY_MOVE_OR_COPY< int8_t > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + value.YY_MOVE_OR_COPY< std::string > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + value.YY_MOVE_OR_COPY< uint16_t > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + value.YY_MOVE_OR_COPY< uint32_t > (YY_MOVE (that.value)); + break; + + default: + break; + } + +#if 201103L <= YY_CPLUSPLUS + // that is emptied. + that.state = empty_state; +#endif + } + + EvalParser::stack_symbol_type::stack_symbol_type (state_type s, YY_MOVE_REF (symbol_type) that) + : super_type (s, YY_MOVE (that.location)) + { + switch (that.kind ()) + { + case symbol_kind::S_option_repr_type: // option_repr_type + value.move< TokenOption::RepresentationType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + value.move< TokenPkt4::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + value.move< TokenPkt6::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + value.move< TokenPkt::MetadataType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_relay6_field: // relay6_field + value.move< TokenRelay6Field::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_nest_level: // nest_level + value.move< int8_t > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + value.move< std::string > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + value.move< uint16_t > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + value.move< uint32_t > (YY_MOVE (that.value)); + break; + + default: + break; + } + + // that is emptied. + that.kind_ = symbol_kind::S_YYEMPTY; + } + +#if YY_CPLUSPLUS < 201103L + EvalParser::stack_symbol_type& + EvalParser::stack_symbol_type::operator= (const stack_symbol_type& that) + { + state = that.state; + switch (that.kind ()) + { + case symbol_kind::S_option_repr_type: // option_repr_type + value.copy< TokenOption::RepresentationType > (that.value); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + value.copy< TokenPkt4::FieldType > (that.value); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + value.copy< TokenPkt6::FieldType > (that.value); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + value.copy< TokenPkt::MetadataType > (that.value); + break; + + case symbol_kind::S_relay6_field: // relay6_field + value.copy< TokenRelay6Field::FieldType > (that.value); + break; + + case symbol_kind::S_nest_level: // nest_level + value.copy< int8_t > (that.value); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + value.copy< std::string > (that.value); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + value.copy< uint16_t > (that.value); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + value.copy< uint32_t > (that.value); + break; + + default: + break; + } + + location = that.location; + return *this; + } + + EvalParser::stack_symbol_type& + EvalParser::stack_symbol_type::operator= (stack_symbol_type& that) + { + state = that.state; + switch (that.kind ()) + { + case symbol_kind::S_option_repr_type: // option_repr_type + value.move< TokenOption::RepresentationType > (that.value); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + value.move< TokenPkt4::FieldType > (that.value); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + value.move< TokenPkt6::FieldType > (that.value); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + value.move< TokenPkt::MetadataType > (that.value); + break; + + case symbol_kind::S_relay6_field: // relay6_field + value.move< TokenRelay6Field::FieldType > (that.value); + break; + + case symbol_kind::S_nest_level: // nest_level + value.move< int8_t > (that.value); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + value.move< std::string > (that.value); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + value.move< uint16_t > (that.value); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + value.move< uint32_t > (that.value); + break; + + default: + break; + } + + location = that.location; + // that is emptied. + that.state = empty_state; + return *this; + } +#endif + + template <typename Base> + void + EvalParser::yy_destroy_ (const char* yymsg, basic_symbol<Base>& yysym) const + { + if (yymsg) + YY_SYMBOL_PRINT (yymsg, yysym); + } + +#if EVALDEBUG + template <typename Base> + void + EvalParser::yy_print_ (std::ostream& yyo, const basic_symbol<Base>& yysym) const + { + std::ostream& yyoutput = yyo; + YY_USE (yyoutput); + if (yysym.empty ()) + yyo << "empty symbol"; + else + { + symbol_kind_type yykind = yysym.kind (); + yyo << (yykind < YYNTOKENS ? "token" : "nterm") + << ' ' << yysym.name () << " (" + << yysym.location << ": "; + switch (yykind) + { + case symbol_kind::S_STRING: // "constant string" +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < std::string > (); } +#line 469 "parser.cc" + break; + + case symbol_kind::S_INTEGER: // "integer" +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < std::string > (); } +#line 475 "parser.cc" + break; + + case symbol_kind::S_HEXSTRING: // "constant hexstring" +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < std::string > (); } +#line 481 "parser.cc" + break; + + case symbol_kind::S_OPTION_NAME: // "option name" +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < std::string > (); } +#line 487 "parser.cc" + break; + + case symbol_kind::S_IP_ADDRESS: // "ip address" +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < std::string > (); } +#line 493 "parser.cc" + break; + + case symbol_kind::S_integer_expr: // integer_expr +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < uint32_t > (); } +#line 499 "parser.cc" + break; + + case symbol_kind::S_option_code: // option_code +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < uint16_t > (); } +#line 505 "parser.cc" + break; + + case symbol_kind::S_sub_option_code: // sub_option_code +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < uint16_t > (); } +#line 511 "parser.cc" + break; + + case symbol_kind::S_option_repr_type: // option_repr_type +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < TokenOption::RepresentationType > (); } +#line 517 "parser.cc" + break; + + case symbol_kind::S_nest_level: // nest_level +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < int8_t > (); } +#line 523 "parser.cc" + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < TokenPkt::MetadataType > (); } +#line 529 "parser.cc" + break; + + case symbol_kind::S_enterprise_id: // enterprise_id +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < uint32_t > (); } +#line 535 "parser.cc" + break; + + case symbol_kind::S_pkt4_field: // pkt4_field +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < TokenPkt4::FieldType > (); } +#line 541 "parser.cc" + break; + + case symbol_kind::S_pkt6_field: // pkt6_field +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < TokenPkt6::FieldType > (); } +#line 547 "parser.cc" + break; + + case symbol_kind::S_relay6_field: // relay6_field +#line 127 "parser.yy" + { yyoutput << yysym.value.template as < TokenRelay6Field::FieldType > (); } +#line 553 "parser.cc" + break; + + default: + break; + } + yyo << ')'; + } + } +#endif + + void + EvalParser::yypush_ (const char* m, YY_MOVE_REF (stack_symbol_type) sym) + { + if (m) + YY_SYMBOL_PRINT (m, sym); + yystack_.push (YY_MOVE (sym)); + } + + void + EvalParser::yypush_ (const char* m, state_type s, YY_MOVE_REF (symbol_type) sym) + { +#if 201103L <= YY_CPLUSPLUS + yypush_ (m, stack_symbol_type (s, std::move (sym))); +#else + stack_symbol_type ss (s, sym); + yypush_ (m, ss); +#endif + } + + void + EvalParser::yypop_ (int n) YY_NOEXCEPT + { + yystack_.pop (n); + } + +#if EVALDEBUG + std::ostream& + EvalParser::debug_stream () const + { + return *yycdebug_; + } + + void + EvalParser::set_debug_stream (std::ostream& o) + { + yycdebug_ = &o; + } + + + EvalParser::debug_level_type + EvalParser::debug_level () const + { + return yydebug_; + } + + void + EvalParser::set_debug_level (debug_level_type l) + { + yydebug_ = l; + } +#endif // EVALDEBUG + + EvalParser::state_type + EvalParser::yy_lr_goto_state_ (state_type yystate, int yysym) + { + int yyr = yypgoto_[yysym - YYNTOKENS] + yystate; + if (0 <= yyr && yyr <= yylast_ && yycheck_[yyr] == yystate) + return yytable_[yyr]; + else + return yydefgoto_[yysym - YYNTOKENS]; + } + + bool + EvalParser::yy_pact_value_is_default_ (int yyvalue) YY_NOEXCEPT + { + return yyvalue == yypact_ninf_; + } + + bool + EvalParser::yy_table_value_is_error_ (int yyvalue) YY_NOEXCEPT + { + return yyvalue == yytable_ninf_; + } + + int + EvalParser::operator() () + { + return parse (); + } + + int + EvalParser::parse () + { + int yyn; + /// Length of the RHS of the rule being reduced. + int yylen = 0; + + // Error handling. + int yynerrs_ = 0; + int yyerrstatus_ = 0; + + /// The lookahead symbol. + symbol_type yyla; + + /// The locations where the error started and ended. + stack_symbol_type yyerror_range[3]; + + /// The return value of parse (). + int yyresult; + +#if YY_EXCEPTIONS + try +#endif // YY_EXCEPTIONS + { + YYCDEBUG << "Starting parse\n"; + + + /* Initialize the stack. The initial state will be set in + yynewstate, since the latter expects the semantical and the + location values to have been already stored, initialize these + stacks with a primary value. */ + yystack_.clear (); + yypush_ (YY_NULLPTR, 0, YY_MOVE (yyla)); + + /*-----------------------------------------------. + | yynewstate -- push a new symbol on the stack. | + `-----------------------------------------------*/ + yynewstate: + YYCDEBUG << "Entering state " << int (yystack_[0].state) << '\n'; + YY_STACK_PRINT (); + + // Accept? + if (yystack_[0].state == yyfinal_) + YYACCEPT; + + goto yybackup; + + + /*-----------. + | yybackup. | + `-----------*/ + yybackup: + // Try to take a decision without lookahead. + yyn = yypact_[+yystack_[0].state]; + if (yy_pact_value_is_default_ (yyn)) + goto yydefault; + + // Read a lookahead token. + if (yyla.empty ()) + { + YYCDEBUG << "Reading a token\n"; +#if YY_EXCEPTIONS + try +#endif // YY_EXCEPTIONS + { + symbol_type yylookahead (yylex (ctx)); + yyla.move (yylookahead); + } +#if YY_EXCEPTIONS + catch (const syntax_error& yyexc) + { + YYCDEBUG << "Caught exception: " << yyexc.what() << '\n'; + error (yyexc); + goto yyerrlab1; + } +#endif // YY_EXCEPTIONS + } + YY_SYMBOL_PRINT ("Next token is", yyla); + + if (yyla.kind () == symbol_kind::S_YYerror) + { + // The scanner already issued an error message, process directly + // to error recovery. But do not keep the error token as + // lookahead, it is too special and may lead us to an endless + // loop in error recovery. */ + yyla.kind_ = symbol_kind::S_YYUNDEF; + goto yyerrlab1; + } + + /* If the proper action on seeing token YYLA.TYPE is to reduce or + to detect an error, take that action. */ + yyn += yyla.kind (); + if (yyn < 0 || yylast_ < yyn || yycheck_[yyn] != yyla.kind ()) + { + goto yydefault; + } + + // Reduce or error. + yyn = yytable_[yyn]; + if (yyn <= 0) + { + if (yy_table_value_is_error_ (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + // Count tokens shifted since error; after three, turn off error status. + if (yyerrstatus_) + --yyerrstatus_; + + // Shift the lookahead token. + yypush_ ("Shifting", state_type (yyn), YY_MOVE (yyla)); + goto yynewstate; + + + /*-----------------------------------------------------------. + | yydefault -- do the default action for the current state. | + `-----------------------------------------------------------*/ + yydefault: + yyn = yydefact_[+yystack_[0].state]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + + /*-----------------------------. + | yyreduce -- do a reduction. | + `-----------------------------*/ + yyreduce: + yylen = yyr2_[yyn]; + { + stack_symbol_type yylhs; + yylhs.state = yy_lr_goto_state_ (yystack_[yylen].state, yyr1_[yyn]); + /* Variants are always initialized to an empty instance of the + correct type. The default '$$ = $1' action is NOT applied + when using variants. */ + switch (yyr1_[yyn]) + { + case symbol_kind::S_option_repr_type: // option_repr_type + yylhs.value.emplace< TokenOption::RepresentationType > (); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + yylhs.value.emplace< TokenPkt4::FieldType > (); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + yylhs.value.emplace< TokenPkt6::FieldType > (); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + yylhs.value.emplace< TokenPkt::MetadataType > (); + break; + + case symbol_kind::S_relay6_field: // relay6_field + yylhs.value.emplace< TokenRelay6Field::FieldType > (); + break; + + case symbol_kind::S_nest_level: // nest_level + yylhs.value.emplace< int8_t > (); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + yylhs.value.emplace< std::string > (); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + yylhs.value.emplace< uint16_t > (); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + yylhs.value.emplace< uint32_t > (); + break; + + default: + break; + } + + + // Default location. + { + stack_type::slice range (yystack_, yylen); + YYLLOC_DEFAULT (yylhs.location, range, yylen); + yyerror_range[1].location = yylhs.location; + } + + // Perform the reduction. + YY_REDUCE_PRINT (yyn); +#if YY_EXCEPTIONS + try +#endif // YY_EXCEPTIONS + { + switch (yyn) + { + case 6: // bool_expr: "not" bool_expr +#line 147 "parser.yy" + { + TokenPtr neg(new TokenNot()); + ctx.expression.push_back(neg); + } +#line 851 "parser.cc" + break; + + case 7: // bool_expr: bool_expr "and" bool_expr +#line 152 "parser.yy" + { + TokenPtr neg(new TokenAnd()); + ctx.expression.push_back(neg); + } +#line 860 "parser.cc" + break; + + case 8: // bool_expr: bool_expr "or" bool_expr +#line 157 "parser.yy" + { + TokenPtr neg(new TokenOr()); + ctx.expression.push_back(neg); + } +#line 869 "parser.cc" + break; + + case 9: // bool_expr: string_expr "==" string_expr +#line 162 "parser.yy" + { + TokenPtr eq(new TokenEqual()); + ctx.expression.push_back(eq); + } +#line 878 "parser.cc" + break; + + case 10: // bool_expr: "option" "[" option_code "]" "." "exists" +#line 167 "parser.yy" + { + TokenPtr opt(new TokenOption(yystack_[3].value.as < uint16_t > (), TokenOption::EXISTS)); + ctx.expression.push_back(opt); + } +#line 887 "parser.cc" + break; + + case 11: // bool_expr: "option" "[" option_code "]" "." "option" "[" sub_option_code "]" "." "exists" +#line 172 "parser.yy" + { + TokenPtr opt(new TokenSubOption(yystack_[8].value.as < uint16_t > (), yystack_[3].value.as < uint16_t > (), TokenOption::EXISTS)); + ctx.expression.push_back(opt); + } +#line 896 "parser.cc" + break; + + case 12: // bool_expr: "relay4" "[" sub_option_code "]" "." "exists" +#line 177 "parser.yy" + { + switch (ctx.getUniverse()) { + case Option::V4: + { + TokenPtr opt(new TokenRelay4Option(yystack_[3].value.as < uint16_t > (), TokenOption::EXISTS)); + ctx.expression.push_back(opt); + break; + } + case Option::V6: + // We will have relay6[123] for the DHCPv6. + // In a very distant future we'll possibly be able + // to mix both if we have DHCPv4-over-DHCPv6, so it + // has some sense to make it explicit whether we + // talk about DHCPv4 relay or DHCPv6 relay. However, + // for the time being relay4 can be used in DHCPv4 + // only. + error(yystack_[5].location, "relay4 can only be used in DHCPv4."); + } + } +#line 920 "parser.cc" + break; + + case 13: // bool_expr: "relay6" "[" nest_level "]" "." "option" "[" sub_option_code "]" "." "exists" +#line 197 "parser.yy" + { + switch (ctx.getUniverse()) { + case Option::V6: + { + TokenPtr opt(new TokenRelay6Option(yystack_[8].value.as < int8_t > (), yystack_[3].value.as < uint16_t > (), TokenOption::EXISTS)); + ctx.expression.push_back(opt); + break; + } + case Option::V4: + // For now we only use relay6 in DHCPv6. + error(yystack_[10].location, "relay6 can only be used in DHCPv6."); + } + } +#line 938 "parser.cc" + break; + + case 14: // bool_expr: "vendor-class" "[" enterprise_id "]" "." "exists" +#line 211 "parser.yy" + { + // Expression: vendor-class[1234].exists + // + // This token will find option 124 (DHCPv4) or 16 (DHCPv6), + // and will check if enterprise-id equals specified value. + TokenPtr exist(new TokenVendorClass(ctx.getUniverse(), yystack_[3].value.as < uint32_t > (), TokenOption::EXISTS)); + ctx.expression.push_back(exist); + } +#line 951 "parser.cc" + break; + + case 15: // bool_expr: "vendor" "[" enterprise_id "]" "." "exists" +#line 220 "parser.yy" + { + // Expression: vendor[1234].exists + // + // This token will find option 125 (DHCPv4) or 17 (DHCPv6), + // and will check if enterprise-id equals specified value. + TokenPtr exist(new TokenVendor(ctx.getUniverse(), yystack_[3].value.as < uint32_t > (), TokenOption::EXISTS)); + ctx.expression.push_back(exist); + } +#line 964 "parser.cc" + break; + + case 16: // bool_expr: "vendor" "[" enterprise_id "]" "." "option" "[" sub_option_code "]" "." "exists" +#line 229 "parser.yy" + { + // Expression vendor[1234].option[123].exists + // + // This token will check if specified vendor option + // exists, has specified enterprise-id and if has + // specified suboption. + TokenPtr exist(new TokenVendor(ctx.getUniverse(), yystack_[8].value.as < uint32_t > (), TokenOption::EXISTS, yystack_[3].value.as < uint16_t > ())); + ctx.expression.push_back(exist); + } +#line 978 "parser.cc" + break; + + case 17: // bool_expr: "member" "(" "constant string" ")" +#line 239 "parser.yy" + { + // Expression member('foo') + // + // This token will check if the packet is a member of + // the specified client class. + // To avoid loops at evaluation only already defined and + // built-in classes are allowed. + std::string cc = yystack_[1].value.as < std::string > (); + if (!ctx.isClientClassDefined(cc)) { + error(yystack_[1].location, "Not defined client class '" + cc + "'"); + } + TokenPtr member(new TokenMember(cc)); + ctx.expression.push_back(member); + } +#line 997 "parser.cc" + break; + + case 18: // string_expr: "constant string" +#line 256 "parser.yy" + { + TokenPtr str(new TokenString(yystack_[0].value.as < std::string > ())); + ctx.expression.push_back(str); + } +#line 1006 "parser.cc" + break; + + case 19: // string_expr: "constant hexstring" +#line 261 "parser.yy" + { + TokenPtr hex(new TokenHexString(yystack_[0].value.as < std::string > ())); + ctx.expression.push_back(hex); + } +#line 1015 "parser.cc" + break; + + case 20: // string_expr: "ip address" +#line 266 "parser.yy" + { + TokenPtr ip(new TokenIpAddress(yystack_[0].value.as < std::string > ())); + ctx.expression.push_back(ip); + } +#line 1024 "parser.cc" + break; + + case 21: // string_expr: "option" "[" option_code "]" "." option_repr_type +#line 271 "parser.yy" + { + TokenPtr opt(new TokenOption(yystack_[3].value.as < uint16_t > (), yystack_[0].value.as < TokenOption::RepresentationType > ())); + ctx.expression.push_back(opt); + } +#line 1033 "parser.cc" + break; + + case 22: // string_expr: "option" "[" option_code "]" "." "option" "[" sub_option_code "]" "." option_repr_type +#line 276 "parser.yy" + { + TokenPtr opt(new TokenSubOption(yystack_[8].value.as < uint16_t > (), yystack_[3].value.as < uint16_t > (), yystack_[0].value.as < TokenOption::RepresentationType > ())); + ctx.expression.push_back(opt); + } +#line 1042 "parser.cc" + break; + + case 23: // string_expr: "relay4" "[" sub_option_code "]" "." option_repr_type +#line 281 "parser.yy" + { + switch (ctx.getUniverse()) { + case Option::V4: + { + TokenPtr opt(new TokenRelay4Option(yystack_[3].value.as < uint16_t > (), yystack_[0].value.as < TokenOption::RepresentationType > ())); + ctx.expression.push_back(opt); + break; + } + case Option::V6: + // We will have relay6[123] for the DHCPv6. + // In a very distant future we'll possibly be able + // to mix both if we have DHCPv4-over-DHCPv6, so it + // has some sense to make it explicit whether we + // talk about DHCPv4 relay or DHCPv6 relay. However, + // for the time being relay4 can be used in DHCPv4 + // only. + error(yystack_[5].location, "relay4 can only be used in DHCPv4."); + } + } +#line 1066 "parser.cc" + break; + + case 24: // string_expr: "relay6" "[" nest_level "]" "." "option" "[" sub_option_code "]" "." option_repr_type +#line 302 "parser.yy" + { + switch (ctx.getUniverse()) { + case Option::V6: + { + TokenPtr opt(new TokenRelay6Option(yystack_[8].value.as < int8_t > (), yystack_[3].value.as < uint16_t > (), yystack_[0].value.as < TokenOption::RepresentationType > ())); + ctx.expression.push_back(opt); + break; + } + case Option::V4: + // For now we only use relay6 in DHCPv6. + error(yystack_[10].location, "relay6 can only be used in DHCPv6."); + } + } +#line 1084 "parser.cc" + break; + + case 25: // string_expr: "pkt" "." pkt_metadata +#line 317 "parser.yy" + { + TokenPtr pkt_metadata(new TokenPkt(yystack_[0].value.as < TokenPkt::MetadataType > ())); + ctx.expression.push_back(pkt_metadata); + } +#line 1093 "parser.cc" + break; + + case 26: // string_expr: "pkt4" "." pkt4_field +#line 322 "parser.yy" + { + switch (ctx.getUniverse()) { + case Option::V4: + { + TokenPtr pkt4_field(new TokenPkt4(yystack_[0].value.as < TokenPkt4::FieldType > ())); + ctx.expression.push_back(pkt4_field); + break; + } + case Option::V6: + // For now we only use pkt4 in DHCPv4. + error(yystack_[2].location, "pkt4 can only be used in DHCPv4."); + } + } +#line 1111 "parser.cc" + break; + + case 27: // string_expr: "pkt6" "." pkt6_field +#line 336 "parser.yy" + { + switch (ctx.getUniverse()) { + case Option::V6: + { + TokenPtr pkt6_field(new TokenPkt6(yystack_[0].value.as < TokenPkt6::FieldType > ())); + ctx.expression.push_back(pkt6_field); + break; + } + case Option::V4: + // For now we only use pkt6 in DHCPv6. + error(yystack_[2].location, "pkt6 can only be used in DHCPv6."); + } + } +#line 1129 "parser.cc" + break; + + case 28: // string_expr: "relay6" "[" nest_level "]" "." relay6_field +#line 350 "parser.yy" + { + switch (ctx.getUniverse()) { + case Option::V6: + { + TokenPtr relay6field(new TokenRelay6Field(yystack_[3].value.as < int8_t > (), yystack_[0].value.as < TokenRelay6Field::FieldType > ())); + ctx.expression.push_back(relay6field); + break; + } + case Option::V4: + // For now we only use relay6 in DHCPv6. + error(yystack_[5].location, "relay6 can only be used in DHCPv6."); + } + } +#line 1147 "parser.cc" + break; + + case 29: // string_expr: "substring" "(" string_expr "," start_expr "," length_expr ")" +#line 365 "parser.yy" + { + TokenPtr sub(new TokenSubstring()); + ctx.expression.push_back(sub); + } +#line 1156 "parser.cc" + break; + + case 30: // string_expr: "split" "(" string_expr "," string_expr "," int_expr ")" +#line 370 "parser.yy" + { + TokenPtr split(new TokenSplit()); + ctx.expression.push_back(split); + } +#line 1165 "parser.cc" + break; + + case 31: // string_expr: "concat" "(" string_expr "," string_expr ")" +#line 375 "parser.yy" + { + TokenPtr conc(new TokenConcat()); + ctx.expression.push_back(conc); + } +#line 1174 "parser.cc" + break; + + case 32: // string_expr: string_expr "+" string_expr +#line 380 "parser.yy" + { + TokenPtr conc(new TokenConcat()); + ctx.expression.push_back(conc); + } +#line 1183 "parser.cc" + break; + + case 33: // string_expr: "ifelse" "(" bool_expr "," string_expr "," string_expr ")" +#line 385 "parser.yy" + { + TokenPtr cond(new TokenIfElse()); + ctx.expression.push_back(cond); + } +#line 1192 "parser.cc" + break; + + case 34: // string_expr: "hexstring" "(" string_expr "," string_expr ")" +#line 390 "parser.yy" + { + TokenPtr tohex(new TokenToHexString()); + ctx.expression.push_back(tohex); + } +#line 1201 "parser.cc" + break; + + case 35: // string_expr: "addrtotext" "(" string_expr ")" +#line 395 "parser.yy" + { + TokenPtr addrtotext(new TokenIpAddressToText()); + ctx.expression.push_back(addrtotext); + } +#line 1210 "parser.cc" + break; + + case 36: // string_expr: "int8totext" "(" string_expr ")" +#line 400 "parser.yy" + { + TokenPtr int8totext(new TokenInt8ToText()); + ctx.expression.push_back(int8totext); + } +#line 1219 "parser.cc" + break; + + case 37: // string_expr: "int16totext" "(" string_expr ")" +#line 405 "parser.yy" + { + TokenPtr int16totext(new TokenInt16ToText()); + ctx.expression.push_back(int16totext); + } +#line 1228 "parser.cc" + break; + + case 38: // string_expr: "int32totext" "(" string_expr ")" +#line 410 "parser.yy" + { + TokenPtr int32totext(new TokenInt32ToText()); + ctx.expression.push_back(int32totext); + } +#line 1237 "parser.cc" + break; + + case 39: // string_expr: "uint8totext" "(" string_expr ")" +#line 415 "parser.yy" + { + TokenPtr uint8totext(new TokenUInt8ToText()); + ctx.expression.push_back(uint8totext); + } +#line 1246 "parser.cc" + break; + + case 40: // string_expr: "uint16totext" "(" string_expr ")" +#line 420 "parser.yy" + { + TokenPtr uint16totext(new TokenUInt16ToText()); + ctx.expression.push_back(uint16totext); + } +#line 1255 "parser.cc" + break; + + case 41: // string_expr: "uint32totext" "(" string_expr ")" +#line 425 "parser.yy" + { + TokenPtr uint32totext(new TokenUInt32ToText()); + ctx.expression.push_back(uint32totext); + } +#line 1264 "parser.cc" + break; + + case 42: // string_expr: "vendor" "." "enterprise" +#line 430 "parser.yy" + { + // expression: vendor.enterprise + // + // This token will return enterprise-id number of + // received vendor option. + TokenPtr vendor(new TokenVendor(ctx.getUniverse(), 0, TokenVendor::ENTERPRISE_ID)); + ctx.expression.push_back(vendor); + } +#line 1277 "parser.cc" + break; + + case 43: // string_expr: "vendor-class" "." "enterprise" +#line 439 "parser.yy" + { + // expression: vendor-class.enterprise + // + // This token will return enterprise-id number of + // received vendor class option. + TokenPtr vendor(new TokenVendorClass(ctx.getUniverse(), 0, + TokenVendor::ENTERPRISE_ID)); + ctx.expression.push_back(vendor); + } +#line 1291 "parser.cc" + break; + + case 44: // string_expr: "vendor" "[" enterprise_id "]" "." "option" "[" sub_option_code "]" "." option_repr_type +#line 449 "parser.yy" + { + // This token will search for vendor option with + // specified enterprise-id. If found, will search + // for specified suboption and finally will return + // its content. + TokenPtr opt(new TokenVendor(ctx.getUniverse(), yystack_[8].value.as < uint32_t > (), yystack_[0].value.as < TokenOption::RepresentationType > (), yystack_[3].value.as < uint16_t > ())); + ctx.expression.push_back(opt); + } +#line 1304 "parser.cc" + break; + + case 45: // string_expr: "vendor-class" "[" enterprise_id "]" "." "data" +#line 458 "parser.yy" + { + // expression: vendor-class[1234].data + // + // Vendor class option does not have suboptions, + // but chunks of data (typically 1, but the option + // structure allows multiple of them). If chunk + // offset is not specified, we assume the first (0th) + // is requested. + TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), yystack_[3].value.as < uint32_t > (), + TokenVendor::DATA, 0)); + ctx.expression.push_back(vendor_class); + } +#line 1321 "parser.cc" + break; + + case 46: // string_expr: "vendor-class" "[" enterprise_id "]" "." "data" "[" "integer" "]" +#line 471 "parser.yy" + { + // expression: vendor-class[1234].data[5] + // + // Vendor class option does not have suboptions, + // but chunks of data (typically 1, but the option + // structure allows multiple of them). This syntax + // specifies which data chunk (tuple) we want. + uint8_t index = ctx.convertUint8(yystack_[1].value.as < std::string > (), yystack_[1].location); + TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), yystack_[6].value.as < uint32_t > (), + TokenVendor::DATA, index)); + ctx.expression.push_back(vendor_class); + } +#line 1338 "parser.cc" + break; + + case 47: // string_expr: integer_expr +#line 484 "parser.yy" + { + TokenPtr integer(new TokenInteger(yystack_[0].value.as < uint32_t > ())); + ctx.expression.push_back(integer); + } +#line 1347 "parser.cc" + break; + + case 49: // integer_expr: "integer" +#line 492 "parser.yy" + { + yylhs.value.as < uint32_t > () = ctx.convertUint32(yystack_[0].value.as < std::string > (), yystack_[0].location); + } +#line 1355 "parser.cc" + break; + + case 50: // option_code: "integer" +#line 498 "parser.yy" + { + yylhs.value.as < uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as < std::string > (), yystack_[0].location); + } +#line 1363 "parser.cc" + break; + + case 51: // option_code: "option name" +#line 502 "parser.yy" + { + yylhs.value.as < uint16_t > () = ctx.convertOptionName(yystack_[0].value.as < std::string > (), yystack_[0].location); + } +#line 1371 "parser.cc" + break; + + case 52: // sub_option_code: "integer" +#line 508 "parser.yy" + { + yylhs.value.as < uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as < std::string > (), yystack_[0].location); + } +#line 1379 "parser.cc" + break; + + case 53: // option_repr_type: "text" +#line 514 "parser.yy" + { + yylhs.value.as < TokenOption::RepresentationType > () = TokenOption::TEXTUAL; + } +#line 1387 "parser.cc" + break; + + case 54: // option_repr_type: "hex" +#line 518 "parser.yy" + { + yylhs.value.as < TokenOption::RepresentationType > () = TokenOption::HEXADECIMAL; + } +#line 1395 "parser.cc" + break; + + case 55: // nest_level: "integer" +#line 524 "parser.yy" + { + yylhs.value.as < int8_t > () = ctx.convertNestLevelNumber(yystack_[0].value.as < std::string > (), yystack_[0].location); + } +#line 1403 "parser.cc" + break; + + case 56: // pkt_metadata: "iface" +#line 533 "parser.yy" + { + yylhs.value.as < TokenPkt::MetadataType > () = TokenPkt::IFACE; + } +#line 1411 "parser.cc" + break; + + case 57: // pkt_metadata: "src" +#line 537 "parser.yy" + { + yylhs.value.as < TokenPkt::MetadataType > () = TokenPkt::SRC; + } +#line 1419 "parser.cc" + break; + + case 58: // pkt_metadata: "dst" +#line 541 "parser.yy" + { + yylhs.value.as < TokenPkt::MetadataType > () = TokenPkt::DST; + } +#line 1427 "parser.cc" + break; + + case 59: // pkt_metadata: "len" +#line 545 "parser.yy" + { + yylhs.value.as < TokenPkt::MetadataType > () = TokenPkt::LEN; + } +#line 1435 "parser.cc" + break; + + case 60: // enterprise_id: "integer" +#line 551 "parser.yy" + { + yylhs.value.as < uint32_t > () = ctx.convertUint32(yystack_[0].value.as < std::string > (), yystack_[0].location); + } +#line 1443 "parser.cc" + break; + + case 61: // enterprise_id: "*" +#line 555 "parser.yy" + { + yylhs.value.as < uint32_t > () = 0; + } +#line 1451 "parser.cc" + break; + + case 62: // pkt4_field: "mac" +#line 561 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::CHADDR; + } +#line 1459 "parser.cc" + break; + + case 63: // pkt4_field: "hlen" +#line 565 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::HLEN; + } +#line 1467 "parser.cc" + break; + + case 64: // pkt4_field: "htype" +#line 569 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::HTYPE; + } +#line 1475 "parser.cc" + break; + + case 65: // pkt4_field: "ciaddr" +#line 573 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::CIADDR; + } +#line 1483 "parser.cc" + break; + + case 66: // pkt4_field: "giaddr" +#line 577 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::GIADDR; + } +#line 1491 "parser.cc" + break; + + case 67: // pkt4_field: "yiaddr" +#line 581 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::YIADDR; + } +#line 1499 "parser.cc" + break; + + case 68: // pkt4_field: "siaddr" +#line 585 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::SIADDR; + } +#line 1507 "parser.cc" + break; + + case 69: // pkt4_field: "msgtype" +#line 589 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::MSGTYPE; + } +#line 1515 "parser.cc" + break; + + case 70: // pkt4_field: "transid" +#line 593 "parser.yy" + { + yylhs.value.as < TokenPkt4::FieldType > () = TokenPkt4::TRANSID; + } +#line 1523 "parser.cc" + break; + + case 71: // pkt6_field: "msgtype" +#line 599 "parser.yy" + { + yylhs.value.as < TokenPkt6::FieldType > () = TokenPkt6::MSGTYPE; + } +#line 1531 "parser.cc" + break; + + case 72: // pkt6_field: "transid" +#line 603 "parser.yy" + { + yylhs.value.as < TokenPkt6::FieldType > () = TokenPkt6::TRANSID; + } +#line 1539 "parser.cc" + break; + + case 73: // relay6_field: "peeraddr" +#line 609 "parser.yy" + { + yylhs.value.as < TokenRelay6Field::FieldType > () = TokenRelay6Field::PEERADDR; + } +#line 1547 "parser.cc" + break; + + case 74: // relay6_field: "linkaddr" +#line 613 "parser.yy" + { + yylhs.value.as < TokenRelay6Field::FieldType > () = TokenRelay6Field::LINKADDR; + } +#line 1555 "parser.cc" + break; + + case 75: // start_expr: "integer" +#line 619 "parser.yy" + { + TokenPtr str(new TokenString(yystack_[0].value.as < std::string > ())); + ctx.expression.push_back(str); + } +#line 1564 "parser.cc" + break; + + case 76: // length_expr: "integer" +#line 626 "parser.yy" + { + TokenPtr str(new TokenString(yystack_[0].value.as < std::string > ())); + ctx.expression.push_back(str); + } +#line 1573 "parser.cc" + break; + + case 77: // length_expr: "all" +#line 631 "parser.yy" + { + TokenPtr str(new TokenString("all")); + ctx.expression.push_back(str); + } +#line 1582 "parser.cc" + break; + + case 78: // int_expr: "integer" +#line 637 "parser.yy" + { + TokenPtr str(new TokenString(yystack_[0].value.as < std::string > ())); + ctx.expression.push_back(str); + } +#line 1591 "parser.cc" + break; + + +#line 1595 "parser.cc" + + default: + break; + } + } +#if YY_EXCEPTIONS + catch (const syntax_error& yyexc) + { + YYCDEBUG << "Caught exception: " << yyexc.what() << '\n'; + error (yyexc); + YYERROR; + } +#endif // YY_EXCEPTIONS + YY_SYMBOL_PRINT ("-> $$ =", yylhs); + yypop_ (yylen); + yylen = 0; + + // Shift the result of the reduction. + yypush_ (YY_NULLPTR, YY_MOVE (yylhs)); + } + goto yynewstate; + + + /*--------------------------------------. + | yyerrlab -- here on detecting error. | + `--------------------------------------*/ + yyerrlab: + // If not already recovering from an error, report this error. + if (!yyerrstatus_) + { + ++yynerrs_; + context yyctx (*this, yyla); + std::string msg = yysyntax_error_ (yyctx); + error (yyla.location, YY_MOVE (msg)); + } + + + yyerror_range[1].location = yyla.location; + if (yyerrstatus_ == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + // Return failure if at end of input. + if (yyla.kind () == symbol_kind::S_YYEOF) + YYABORT; + else if (!yyla.empty ()) + { + yy_destroy_ ("Error: discarding", yyla); + yyla.clear (); + } + } + + // Else will try to reuse lookahead token after shifting the error token. + goto yyerrlab1; + + + /*---------------------------------------------------. + | yyerrorlab -- error raised explicitly by YYERROR. | + `---------------------------------------------------*/ + yyerrorlab: + /* Pacify compilers when the user code never invokes YYERROR and + the label yyerrorlab therefore never appears in user code. */ + if (false) + YYERROR; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + yypop_ (yylen); + yylen = 0; + YY_STACK_PRINT (); + goto yyerrlab1; + + + /*-------------------------------------------------------------. + | yyerrlab1 -- common code for both syntax error and YYERROR. | + `-------------------------------------------------------------*/ + yyerrlab1: + yyerrstatus_ = 3; // Each real token shifted decrements this. + // Pop stack until we find a state that shifts the error token. + for (;;) + { + yyn = yypact_[+yystack_[0].state]; + if (!yy_pact_value_is_default_ (yyn)) + { + yyn += symbol_kind::S_YYerror; + if (0 <= yyn && yyn <= yylast_ + && yycheck_[yyn] == symbol_kind::S_YYerror) + { + yyn = yytable_[yyn]; + if (0 < yyn) + break; + } + } + + // Pop the current state because it cannot handle the error token. + if (yystack_.size () == 1) + YYABORT; + + yyerror_range[1].location = yystack_[0].location; + yy_destroy_ ("Error: popping", yystack_[0]); + yypop_ (); + YY_STACK_PRINT (); + } + { + stack_symbol_type error_token; + + yyerror_range[2].location = yyla.location; + YYLLOC_DEFAULT (error_token.location, yyerror_range, 2); + + // Shift the error token. + error_token.state = state_type (yyn); + yypush_ ("Shifting", YY_MOVE (error_token)); + } + goto yynewstate; + + + /*-------------------------------------. + | yyacceptlab -- YYACCEPT comes here. | + `-------------------------------------*/ + yyacceptlab: + yyresult = 0; + goto yyreturn; + + + /*-----------------------------------. + | yyabortlab -- YYABORT comes here. | + `-----------------------------------*/ + yyabortlab: + yyresult = 1; + goto yyreturn; + + + /*-----------------------------------------------------. + | yyreturn -- parsing is finished, return the result. | + `-----------------------------------------------------*/ + yyreturn: + if (!yyla.empty ()) + yy_destroy_ ("Cleanup: discarding lookahead", yyla); + + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + yypop_ (yylen); + YY_STACK_PRINT (); + while (1 < yystack_.size ()) + { + yy_destroy_ ("Cleanup: popping", yystack_[0]); + yypop_ (); + } + + return yyresult; + } +#if YY_EXCEPTIONS + catch (...) + { + YYCDEBUG << "Exception caught: cleaning lookahead and stack\n"; + // Do not try to display the values of the reclaimed symbols, + // as their printers might throw an exception. + if (!yyla.empty ()) + yy_destroy_ (YY_NULLPTR, yyla); + + while (1 < yystack_.size ()) + { + yy_destroy_ (YY_NULLPTR, yystack_[0]); + yypop_ (); + } + throw; + } +#endif // YY_EXCEPTIONS + } + + void + EvalParser::error (const syntax_error& yyexc) + { + error (yyexc.location, yyexc.what ()); + } + + /* Return YYSTR after stripping away unnecessary quotes and + backslashes, so that it's suitable for yyerror. The heuristic is + that double-quoting is unnecessary unless the string contains an + apostrophe, a comma, or backslash (other than backslash-backslash). + YYSTR is taken from yytname. */ + std::string + EvalParser::yytnamerr_ (const char *yystr) + { + if (*yystr == '"') + { + std::string yyr; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + else + goto append; + + append: + default: + yyr += *yyp; + break; + + case '"': + return yyr; + } + do_not_strip_quotes: ; + } + + return yystr; + } + + std::string + EvalParser::symbol_name (symbol_kind_type yysymbol) + { + return yytnamerr_ (yytname_[yysymbol]); + } + + + + // EvalParser::context. + EvalParser::context::context (const EvalParser& yyparser, const symbol_type& yyla) + : yyparser_ (yyparser) + , yyla_ (yyla) + {} + + int + EvalParser::context::expected_tokens (symbol_kind_type yyarg[], int yyargn) const + { + // Actual number of expected tokens + int yycount = 0; + + const int yyn = yypact_[+yyparser_.yystack_[0].state]; + if (!yy_pact_value_is_default_ (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + const int yyxbegin = yyn < 0 ? -yyn : 0; + // Stay within bounds of both yycheck and yytname. + const int yychecklim = yylast_ - yyn + 1; + const int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + for (int yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck_[yyx + yyn] == yyx && yyx != symbol_kind::S_YYerror + && !yy_table_value_is_error_ (yytable_[yyx + yyn])) + { + if (!yyarg) + ++yycount; + else if (yycount == yyargn) + return 0; + else + yyarg[yycount++] = YY_CAST (symbol_kind_type, yyx); + } + } + + if (yyarg && yycount == 0 && 0 < yyargn) + yyarg[0] = symbol_kind::S_YYEMPTY; + return yycount; + } + + + + + + + int + EvalParser::yy_syntax_error_arguments_ (const context& yyctx, + symbol_kind_type yyarg[], int yyargn) const + { + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yyla) is + if this state is a consistent state with a default action. + Thus, detecting the absence of a lookahead is sufficient to + determine that there is no unexpected or expected token to + report. In that case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is + a consistent state with a default action. There might have + been a previous inconsistent state, consistent state with a + non-default action, or user semantic action that manipulated + yyla. (However, yyla is currently not documented for users.) + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + + if (!yyctx.lookahead ().empty ()) + { + if (yyarg) + yyarg[0] = yyctx.token (); + int yyn = yyctx.expected_tokens (yyarg ? yyarg + 1 : yyarg, yyargn - 1); + return yyn + 1; + } + return 0; + } + + // Generate an error message. + std::string + EvalParser::yysyntax_error_ (const context& yyctx) const + { + // Its maximum. + enum { YYARGS_MAX = 5 }; + // Arguments of yyformat. + symbol_kind_type yyarg[YYARGS_MAX]; + int yycount = yy_syntax_error_arguments_ (yyctx, yyarg, YYARGS_MAX); + + char const* yyformat = YY_NULLPTR; + switch (yycount) + { +#define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + default: // Avoid compiler warnings. + YYCASE_ (0, YY_("syntax error")); + YYCASE_ (1, YY_("syntax error, unexpected %s")); + YYCASE_ (2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_ (3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_ (4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_ (5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +#undef YYCASE_ + } + + std::string yyres; + // Argument number. + std::ptrdiff_t yyi = 0; + for (char const* yyp = yyformat; *yyp; ++yyp) + if (yyp[0] == '%' && yyp[1] == 's' && yyi < yycount) + { + yyres += symbol_name (yyarg[yyi++]); + ++yyp; + } + else + yyres += *yyp; + return yyres; + } + + + const short EvalParser::yypact_ninf_ = -156; + + const signed char EvalParser::yytable_ninf_ = -1; + + const short + EvalParser::yypact_[] = + { + 43, 109, 156, 20, 109, 109, 73, 94, 98, 90, + 79, 111, 133, 142, 157, 168, 177, 180, 185, 189, + 190, 192, 211, 219, 207, 112, 131, -156, -156, -156, + -156, -156, 135, 29, -156, 156, 210, 212, 213, 161, + 164, 187, -156, 104, 0, -156, -34, 169, 170, 172, + 82, 52, 156, 156, 156, 109, 156, 156, 156, 156, + 156, 156, 156, 156, 113, -39, 176, -39, 178, 109, + 109, 156, 156, 1, -34, 169, 170, -39, -39, -156, + -156, -156, -156, 217, -156, 220, -156, 221, 231, -156, + -156, -156, -156, -156, -156, -156, -156, -156, -156, -156, + -156, -156, -156, -156, 147, 150, 173, 10, 174, 2, + 3, 5, 6, 7, 14, 23, -156, -156, -156, -156, + -156, 222, -156, 223, -156, -156, 234, 187, -156, 225, + 226, 227, 228, 229, 230, 232, 233, -156, 186, 156, + 156, 156, 156, -156, -156, -156, -156, -156, -156, -156, + 235, 236, 237, 238, 239, 240, 241, 16, 4, 78, + -156, 214, 181, 25, 184, 26, 11, 77, 80, 188, + 81, 193, 250, 245, -156, -156, -156, -156, -156, -156, + 246, -156, -156, -156, -17, 202, -156, 156, -156, -156, + 248, 249, -156, 251, 252, 253, 169, 169, -156, -156, + 261, -156, 265, 28, 215, 169, 169, 169, 169, 254, + 255, -156, -156, -156, 256, 257, 258, 260, 262, 263, + 264, -156, 266, 267, 268, 269, 97, 106, 155, 188, + 188, 188, -156, -156, -156, -156, -156, -156 + }; + + const signed char + EvalParser::yydefact_[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 18, 49, 19, + 20, 2, 4, 0, 47, 0, 0, 0, 0, 0, + 0, 3, 1, 0, 0, 6, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, + 48, 50, 51, 0, 52, 0, 55, 0, 0, 56, + 57, 58, 59, 25, 62, 63, 64, 65, 66, 67, + 68, 69, 70, 26, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 71, 72, 27, 61, + 60, 0, 43, 0, 42, 7, 8, 9, 32, 0, + 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, + 0, 0, 0, 35, 36, 37, 38, 39, 40, 41, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 75, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 53, 54, 10, 21, 12, 23, + 0, 73, 74, 28, 0, 0, 31, 0, 34, 14, + 45, 0, 15, 0, 0, 0, 0, 0, 77, 76, + 0, 78, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 29, 30, 33, 0, 0, 0, 0, 0, 0, + 0, 46, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 11, 22, 13, 24, 16, 44 + }; + + const short + EvalParser::yypgoto_[] = + { + -156, -156, -156, 8, -2, -156, 203, -74, -155, 206, + -156, -29, -156, -156, -156, -156, -156, -156 + }; + + const unsigned char + EvalParser::yydefgoto_[] = + { + 0, 3, 31, 32, 33, 34, 83, 85, 177, 87, + 93, 121, 103, 118, 183, 161, 200, 202 + }; + + const unsigned char + EvalParser::yytable_[] = + { + 41, 130, 44, 179, 80, 80, 143, 144, 71, 145, + 146, 147, 43, 45, 179, 119, 69, 70, 148, 198, + 42, 120, 174, 175, 178, 173, 81, 149, 82, 186, + 188, 189, 213, 73, 174, 175, 176, 71, 123, 72, + 72, 72, 72, 199, 72, 72, 72, 141, 132, 133, + 104, 105, 106, 72, 108, 109, 110, 111, 112, 113, + 114, 115, 72, 107, 72, 72, 190, 72, 72, 127, + 128, 233, 235, 237, 233, 235, 237, 125, 126, 94, + 95, 96, 97, 98, 99, 100, 191, 180, 46, 193, + 194, 181, 182, 49, 181, 182, 50, 192, 174, 175, + 1, 2, 101, 102, 89, 90, 91, 92, 79, 47, + 69, 70, 4, 48, 5, 174, 175, 232, 6, 7, + 8, 9, 209, 210, 174, 175, 234, 65, 51, 66, + 10, 215, 216, 217, 218, 11, 52, 162, 163, 164, + 165, 69, 70, 12, 13, 53, 67, 14, 68, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 35, + 54, 25, 26, 116, 117, 36, 37, 38, 27, 28, + 29, 55, 30, 174, 175, 236, 77, 10, 66, 78, + 56, 68, 11, 57, 138, 203, 72, 139, 58, 72, + 12, 13, 59, 60, 14, 61, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 174, 175, 39, 40, + 140, 142, 72, 72, 62, 27, 28, 29, 185, 30, + 72, 187, 63, 72, 64, 74, 72, 75, 76, 84, + 86, 88, 122, 134, 124, 137, 135, 136, 150, 151, + 69, 152, 153, 154, 155, 156, 160, 157, 190, 158, + 159, 184, 166, 167, 168, 169, 170, 171, 172, 195, + 196, 197, 201, 204, 205, 211, 206, 207, 208, 212, + 219, 220, 221, 222, 223, 214, 224, 129, 225, 0, + 226, 227, 131, 228, 229, 230, 231 + }; + + const short + EvalParser::yycheck_[] = + { + 2, 75, 4, 158, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 5, 169, 54, 6, 7, 4, 36, + 0, 60, 18, 19, 20, 9, 60, 4, 62, 4, + 4, 20, 4, 35, 18, 19, 20, 8, 67, 39, + 39, 39, 39, 60, 39, 39, 39, 37, 77, 78, + 52, 53, 54, 39, 56, 57, 58, 59, 60, 61, + 62, 63, 39, 55, 39, 39, 55, 39, 39, 71, + 72, 226, 227, 228, 229, 230, 231, 69, 70, 27, + 28, 29, 30, 31, 32, 33, 9, 9, 15, 9, + 9, 13, 14, 3, 13, 14, 17, 20, 18, 19, + 57, 58, 50, 51, 22, 23, 24, 25, 4, 15, + 6, 7, 3, 15, 5, 18, 19, 20, 9, 10, + 11, 12, 196, 197, 18, 19, 20, 15, 17, 17, + 21, 205, 206, 207, 208, 26, 3, 139, 140, 141, + 142, 6, 7, 34, 35, 3, 15, 38, 17, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 3, + 3, 52, 53, 50, 51, 9, 10, 11, 59, 60, + 61, 3, 63, 18, 19, 20, 15, 21, 17, 15, + 3, 17, 26, 3, 37, 187, 39, 37, 3, 39, + 34, 35, 3, 3, 38, 3, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 18, 19, 52, 53, + 37, 37, 39, 39, 3, 59, 60, 61, 37, 63, + 39, 37, 3, 39, 17, 15, 39, 15, 15, 60, + 60, 59, 56, 16, 56, 4, 16, 16, 16, 16, + 6, 16, 16, 16, 16, 16, 60, 17, 55, 17, + 17, 37, 17, 17, 17, 17, 17, 17, 17, 9, + 15, 15, 60, 15, 15, 4, 15, 15, 15, 4, + 16, 16, 16, 16, 16, 60, 16, 74, 16, -1, + 17, 17, 76, 17, 17, 17, 17 + }; + + const signed char + EvalParser::yystos_[] = + { + 0, 57, 58, 65, 3, 5, 9, 10, 11, 12, + 21, 26, 34, 35, 38, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 52, 53, 59, 60, 61, + 63, 66, 67, 68, 69, 3, 9, 10, 11, 52, + 53, 68, 0, 67, 68, 67, 15, 15, 15, 3, + 17, 17, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 17, 15, 17, 15, 17, 6, + 7, 8, 39, 68, 15, 15, 15, 15, 15, 4, + 4, 60, 62, 70, 60, 71, 60, 73, 59, 22, + 23, 24, 25, 74, 27, 28, 29, 30, 31, 32, + 33, 50, 51, 76, 68, 68, 68, 67, 68, 68, + 68, 68, 68, 68, 68, 68, 50, 51, 77, 54, + 60, 75, 56, 75, 56, 67, 67, 68, 68, 70, + 71, 73, 75, 75, 16, 16, 16, 4, 37, 37, + 37, 37, 37, 4, 4, 4, 4, 4, 4, 4, + 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, + 60, 79, 68, 68, 68, 68, 17, 17, 17, 17, + 17, 17, 17, 9, 18, 19, 20, 72, 20, 72, + 9, 13, 14, 78, 37, 37, 4, 37, 4, 20, + 55, 9, 20, 9, 9, 9, 15, 15, 36, 60, + 80, 60, 81, 68, 15, 15, 15, 15, 15, 71, + 71, 4, 4, 4, 60, 71, 71, 71, 71, 16, + 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, + 17, 17, 20, 72, 20, 72, 20, 72 + }; + + const signed char + EvalParser::yyr1_[] = + { + 0, 64, 65, 65, 66, 67, 67, 67, 67, 67, + 67, 67, 67, 67, 67, 67, 67, 67, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, 68, 69, + 70, 70, 71, 72, 72, 73, 74, 74, 74, 74, + 75, 75, 76, 76, 76, 76, 76, 76, 76, 76, + 76, 77, 77, 78, 78, 79, 80, 80, 81 + }; + + const signed char + EvalParser::yyr2_[] = + { + 0, 2, 2, 2, 1, 3, 2, 3, 3, 3, + 6, 11, 6, 11, 6, 6, 11, 4, 1, 1, + 1, 6, 11, 6, 11, 3, 3, 3, 6, 8, + 8, 6, 3, 8, 6, 4, 4, 4, 4, 4, + 4, 4, 3, 3, 11, 6, 9, 1, 3, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + +#if EVALDEBUG || 1 + // YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + // First, the terminals, then, starting at \a YYNTOKENS, nonterminals. + const char* + const EvalParser::yytname_[] = + { + "\"end of file\"", "error", "\"invalid token\"", "\"(\"", "\")\"", + "\"not\"", "\"and\"", "\"or\"", "\"==\"", "\"option\"", "\"relay4\"", + "\"relay6\"", "\"member\"", "\"peeraddr\"", "\"linkaddr\"", "\"[\"", + "\"]\"", "\".\"", "\"text\"", "\"hex\"", "\"exists\"", "\"pkt\"", + "\"iface\"", "\"src\"", "\"dst\"", "\"len\"", "\"pkt4\"", "\"mac\"", + "\"hlen\"", "\"htype\"", "\"ciaddr\"", "\"giaddr\"", "\"yiaddr\"", + "\"siaddr\"", "\"substring\"", "\"split\"", "\"all\"", "\",\"", + "\"concat\"", "\"+\"", "\"ifelse\"", "\"hexstring\"", "\"addrtotext\"", + "\"int8totext\"", "\"int16totext\"", "\"int32totext\"", + "\"uint8totext\"", "\"uint16totext\"", "\"uint32totext\"", "\"pkt6\"", + "\"msgtype\"", "\"transid\"", "\"vendor-class\"", "\"vendor\"", "\"*\"", + "\"data\"", "\"enterprise\"", "\"top-level bool\"", + "\"top-level string\"", "\"constant string\"", "\"integer\"", + "\"constant hexstring\"", "\"option name\"", "\"ip address\"", "$accept", + "start", "expression", "bool_expr", "string_expr", "integer_expr", + "option_code", "sub_option_code", "option_repr_type", "nest_level", + "pkt_metadata", "enterprise_id", "pkt4_field", "pkt6_field", + "relay6_field", "start_expr", "length_expr", "int_expr", YY_NULLPTR + }; +#endif + + +#if EVALDEBUG + const short + EvalParser::yyrline_[] = + { + 0, 136, 136, 137, 142, 145, 146, 151, 156, 161, + 166, 171, 176, 196, 210, 219, 228, 238, 255, 260, + 265, 270, 275, 280, 301, 316, 321, 335, 349, 364, + 369, 374, 379, 384, 389, 394, 399, 404, 409, 414, + 419, 424, 429, 438, 448, 457, 470, 483, 488, 491, + 497, 501, 507, 513, 517, 523, 532, 536, 540, 544, + 550, 554, 560, 564, 568, 572, 576, 580, 584, 588, + 592, 598, 602, 608, 612, 618, 625, 630, 636 + }; + + void + EvalParser::yy_stack_print_ () const + { + *yycdebug_ << "Stack now"; + for (stack_type::const_iterator + i = yystack_.begin (), + i_end = yystack_.end (); + i != i_end; ++i) + *yycdebug_ << ' ' << int (i->state); + *yycdebug_ << '\n'; + } + + void + EvalParser::yy_reduce_print_ (int yyrule) const + { + int yylno = yyrline_[yyrule]; + int yynrhs = yyr2_[yyrule]; + // Print the symbols being reduced, and their result. + *yycdebug_ << "Reducing stack by rule " << yyrule - 1 + << " (line " << yylno << "):\n"; + // The symbols being reduced. + for (int yyi = 0; yyi < yynrhs; yyi++) + YY_SYMBOL_PRINT (" $" << yyi + 1 << " =", + yystack_[(yynrhs) - (yyi + 1)]); + } +#endif // EVALDEBUG + + +#line 14 "parser.yy" +} } // isc::eval +#line 2219 "parser.cc" + +#line 643 "parser.yy" + +void +isc::eval::EvalParser::error(const location_type& loc, + const std::string& what) +{ + ctx.error(loc, what); +} diff --git a/src/lib/eval/parser.h b/src/lib/eval/parser.h new file mode 100644 index 0000000..597cf62 --- /dev/null +++ b/src/lib/eval/parser.h @@ -0,0 +1,2677 @@ +// A Bison parser, made by GNU Bison 3.8.2. + +// Skeleton interface for Bison LALR(1) parsers in C++ + +// Copyright (C) 2002-2015, 2018-2021 Free Software Foundation, Inc. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +// As a special exception, you may create a larger work that contains +// part or all of the Bison parser skeleton and distribute that work +// under terms of your choice, so long as that work isn't itself a +// parser generator using the skeleton or a modified version thereof +// as a parser skeleton. Alternatively, if you modify or redistribute +// the parser skeleton itself, you may (at your option) remove this +// special exception, which will cause the skeleton and the resulting +// Bison output files to be licensed under the GNU General Public +// License without this special exception. + +// This special exception was added by the Free Software Foundation in +// version 2.2 of Bison. + + +/** + ** \file parser.h + ** Define the isc::eval::parser class. + */ + +// C++ LALR(1) parser skeleton written by Akim Demaille. + +// DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, +// especially those whose name start with YY_ or yy_. They are +// private implementation details that can be changed or removed. + +#ifndef YY_EVAL_PARSER_H_INCLUDED +# define YY_EVAL_PARSER_H_INCLUDED +// "%code requires" blocks. +#line 17 "parser.yy" + +#include <string> +#include <eval/token.h> +#include <eval/eval_context_decl.h> +#include <dhcp/option.h> +#include <boost/lexical_cast.hpp> + +using namespace isc::dhcp; +using namespace isc::eval; + +#line 60 "parser.h" + +# include <cassert> +# include <cstdlib> // std::abort +# include <iostream> +# include <stdexcept> +# include <string> +# include <vector> + +#if defined __cplusplus +# define YY_CPLUSPLUS __cplusplus +#else +# define YY_CPLUSPLUS 199711L +#endif + +// Support move semantics when possible. +#if 201103L <= YY_CPLUSPLUS +# define YY_MOVE std::move +# define YY_MOVE_OR_COPY move +# define YY_MOVE_REF(Type) Type&& +# define YY_RVREF(Type) Type&& +# define YY_COPY(Type) Type +#else +# define YY_MOVE +# define YY_MOVE_OR_COPY copy +# define YY_MOVE_REF(Type) Type& +# define YY_RVREF(Type) const Type& +# define YY_COPY(Type) const Type& +#endif + +// Support noexcept when possible. +#if 201103L <= YY_CPLUSPLUS +# define YY_NOEXCEPT noexcept +# define YY_NOTHROW +#else +# define YY_NOEXCEPT +# define YY_NOTHROW throw () +#endif + +// Support constexpr when possible. +#if 201703 <= YY_CPLUSPLUS +# define YY_CONSTEXPR constexpr +#else +# define YY_CONSTEXPR +#endif +# include "location.hh" +#include <typeinfo> +#ifndef EVAL_ASSERT +# include <cassert> +# define EVAL_ASSERT assert +#endif + + +#ifndef YY_ATTRIBUTE_PURE +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# else +# define YY_ATTRIBUTE_UNUSED +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YY_USE(E) ((void) (E)) +#else +# define YY_USE(E) /* empty */ +#endif + +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__ +# if __GNUC__ * 100 + __GNUC_MINOR__ < 407 +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") +# else +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# endif +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast<Type> (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif + +/* Debug traces. */ +#ifndef EVALDEBUG +# if defined YYDEBUG +#if YYDEBUG +# define EVALDEBUG 1 +# else +# define EVALDEBUG 0 +# endif +# else /* ! defined YYDEBUG */ +# define EVALDEBUG 1 +# endif /* ! defined YYDEBUG */ +#endif /* ! defined EVALDEBUG */ + +#line 14 "parser.yy" +namespace isc { namespace eval { +#line 209 "parser.h" + + + + + /// A Bison parser. + class EvalParser + { + public: +#ifdef EVALSTYPE +# ifdef __GNUC__ +# pragma GCC message "bison: do not #define EVALSTYPE in C++, use %define api.value.type" +# endif + typedef EVALSTYPE value_type; +#else + /// A buffer to store and retrieve objects. + /// + /// Sort of a variant, but does not keep track of the nature + /// of the stored data, since that knowledge is available + /// via the current parser state. + class value_type + { + public: + /// Type of *this. + typedef value_type self_type; + + /// Empty construction. + value_type () YY_NOEXCEPT + : yyraw_ () + , yytypeid_ (YY_NULLPTR) + {} + + /// Construct and fill. + template <typename T> + value_type (YY_RVREF (T) t) + : yytypeid_ (&typeid (T)) + { + EVAL_ASSERT (sizeof (T) <= size); + new (yyas_<T> ()) T (YY_MOVE (t)); + } + +#if 201103L <= YY_CPLUSPLUS + /// Non copyable. + value_type (const self_type&) = delete; + /// Non copyable. + self_type& operator= (const self_type&) = delete; +#endif + + /// Destruction, allowed only if empty. + ~value_type () YY_NOEXCEPT + { + EVAL_ASSERT (!yytypeid_); + } + +# if 201103L <= YY_CPLUSPLUS + /// Instantiate a \a T in here from \a t. + template <typename T, typename... U> + T& + emplace (U&&... u) + { + EVAL_ASSERT (!yytypeid_); + EVAL_ASSERT (sizeof (T) <= size); + yytypeid_ = & typeid (T); + return *new (yyas_<T> ()) T (std::forward <U>(u)...); + } +# else + /// Instantiate an empty \a T in here. + template <typename T> + T& + emplace () + { + EVAL_ASSERT (!yytypeid_); + EVAL_ASSERT (sizeof (T) <= size); + yytypeid_ = & typeid (T); + return *new (yyas_<T> ()) T (); + } + + /// Instantiate a \a T in here from \a t. + template <typename T> + T& + emplace (const T& t) + { + EVAL_ASSERT (!yytypeid_); + EVAL_ASSERT (sizeof (T) <= size); + yytypeid_ = & typeid (T); + return *new (yyas_<T> ()) T (t); + } +# endif + + /// Instantiate an empty \a T in here. + /// Obsolete, use emplace. + template <typename T> + T& + build () + { + return emplace<T> (); + } + + /// Instantiate a \a T in here from \a t. + /// Obsolete, use emplace. + template <typename T> + T& + build (const T& t) + { + return emplace<T> (t); + } + + /// Accessor to a built \a T. + template <typename T> + T& + as () YY_NOEXCEPT + { + EVAL_ASSERT (yytypeid_); + EVAL_ASSERT (*yytypeid_ == typeid (T)); + EVAL_ASSERT (sizeof (T) <= size); + return *yyas_<T> (); + } + + /// Const accessor to a built \a T (for %printer). + template <typename T> + const T& + as () const YY_NOEXCEPT + { + EVAL_ASSERT (yytypeid_); + EVAL_ASSERT (*yytypeid_ == typeid (T)); + EVAL_ASSERT (sizeof (T) <= size); + return *yyas_<T> (); + } + + /// Swap the content with \a that, of same type. + /// + /// Both variants must be built beforehand, because swapping the actual + /// data requires reading it (with as()), and this is not possible on + /// unconstructed variants: it would require some dynamic testing, which + /// should not be the variant's responsibility. + /// Swapping between built and (possibly) non-built is done with + /// self_type::move (). + template <typename T> + void + swap (self_type& that) YY_NOEXCEPT + { + EVAL_ASSERT (yytypeid_); + EVAL_ASSERT (*yytypeid_ == *that.yytypeid_); + std::swap (as<T> (), that.as<T> ()); + } + + /// Move the content of \a that to this. + /// + /// Destroys \a that. + template <typename T> + void + move (self_type& that) + { +# if 201103L <= YY_CPLUSPLUS + emplace<T> (std::move (that.as<T> ())); +# else + emplace<T> (); + swap<T> (that); +# endif + that.destroy<T> (); + } + +# if 201103L <= YY_CPLUSPLUS + /// Move the content of \a that to this. + template <typename T> + void + move (self_type&& that) + { + emplace<T> (std::move (that.as<T> ())); + that.destroy<T> (); + } +#endif + + /// Copy the content of \a that to this. + template <typename T> + void + copy (const self_type& that) + { + emplace<T> (that.as<T> ()); + } + + /// Destroy the stored \a T. + template <typename T> + void + destroy () + { + as<T> ().~T (); + yytypeid_ = YY_NULLPTR; + } + + private: +#if YY_CPLUSPLUS < 201103L + /// Non copyable. + value_type (const self_type&); + /// Non copyable. + self_type& operator= (const self_type&); +#endif + + /// Accessor to raw memory as \a T. + template <typename T> + T* + yyas_ () YY_NOEXCEPT + { + void *yyp = yyraw_; + return static_cast<T*> (yyp); + } + + /// Const accessor to raw memory as \a T. + template <typename T> + const T* + yyas_ () const YY_NOEXCEPT + { + const void *yyp = yyraw_; + return static_cast<const T*> (yyp); + } + + /// An auxiliary type to compute the largest semantic type. + union union_type + { + // option_repr_type + char dummy1[sizeof (TokenOption::RepresentationType)]; + + // pkt4_field + char dummy2[sizeof (TokenPkt4::FieldType)]; + + // pkt6_field + char dummy3[sizeof (TokenPkt6::FieldType)]; + + // pkt_metadata + char dummy4[sizeof (TokenPkt::MetadataType)]; + + // relay6_field + char dummy5[sizeof (TokenRelay6Field::FieldType)]; + + // nest_level + char dummy6[sizeof (int8_t)]; + + // "constant string" + // "integer" + // "constant hexstring" + // "option name" + // "ip address" + char dummy7[sizeof (std::string)]; + + // option_code + // sub_option_code + char dummy8[sizeof (uint16_t)]; + + // integer_expr + // enterprise_id + char dummy9[sizeof (uint32_t)]; + }; + + /// The size of the largest semantic type. + enum { size = sizeof (union_type) }; + + /// A buffer to store semantic values. + union + { + /// Strongest alignment constraints. + long double yyalign_me_; + /// A buffer large enough to store any of the semantic values. + char yyraw_[size]; + }; + + /// Whether the content is built: if defined, the name of the stored type. + const std::type_info *yytypeid_; + }; + +#endif + /// Backward compatibility (Bison 3.8). + typedef value_type semantic_type; + + /// Symbol locations. + typedef location location_type; + + /// Syntax errors thrown from user actions. + struct syntax_error : std::runtime_error + { + syntax_error (const location_type& l, const std::string& m) + : std::runtime_error (m) + , location (l) + {} + + syntax_error (const syntax_error& s) + : std::runtime_error (s.what ()) + , location (s.location) + {} + + ~syntax_error () YY_NOEXCEPT YY_NOTHROW; + + location_type location; + }; + + /// Token kinds. + struct token + { + enum token_kind_type + { + TOKEN_EVALEMPTY = -2, + TOKEN_END = 0, // "end of file" + TOKEN_EVALerror = 256, // error + TOKEN_EVALUNDEF = 257, // "invalid token" + TOKEN_LPAREN = 258, // "(" + TOKEN_RPAREN = 259, // ")" + TOKEN_NOT = 260, // "not" + TOKEN_AND = 261, // "and" + TOKEN_OR = 262, // "or" + TOKEN_EQUAL = 263, // "==" + TOKEN_OPTION = 264, // "option" + TOKEN_RELAY4 = 265, // "relay4" + TOKEN_RELAY6 = 266, // "relay6" + TOKEN_MEMBER = 267, // "member" + TOKEN_PEERADDR = 268, // "peeraddr" + TOKEN_LINKADDR = 269, // "linkaddr" + TOKEN_LBRACKET = 270, // "[" + TOKEN_RBRACKET = 271, // "]" + TOKEN_DOT = 272, // "." + TOKEN_TEXT = 273, // "text" + TOKEN_HEX = 274, // "hex" + TOKEN_EXISTS = 275, // "exists" + TOKEN_PKT = 276, // "pkt" + TOKEN_IFACE = 277, // "iface" + TOKEN_SRC = 278, // "src" + TOKEN_DST = 279, // "dst" + TOKEN_LEN = 280, // "len" + TOKEN_PKT4 = 281, // "pkt4" + TOKEN_CHADDR = 282, // "mac" + TOKEN_HLEN = 283, // "hlen" + TOKEN_HTYPE = 284, // "htype" + TOKEN_CIADDR = 285, // "ciaddr" + TOKEN_GIADDR = 286, // "giaddr" + TOKEN_YIADDR = 287, // "yiaddr" + TOKEN_SIADDR = 288, // "siaddr" + TOKEN_SUBSTRING = 289, // "substring" + TOKEN_SPLIT = 290, // "split" + TOKEN_ALL = 291, // "all" + TOKEN_COMA = 292, // "," + TOKEN_CONCAT = 293, // "concat" + TOKEN_PLUS = 294, // "+" + TOKEN_IFELSE = 295, // "ifelse" + TOKEN_TOHEXSTRING = 296, // "hexstring" + TOKEN_ADDRTOTEXT = 297, // "addrtotext" + TOKEN_INT8TOTEXT = 298, // "int8totext" + TOKEN_INT16TOTEXT = 299, // "int16totext" + TOKEN_INT32TOTEXT = 300, // "int32totext" + TOKEN_UINT8TOTEXT = 301, // "uint8totext" + TOKEN_UINT16TOTEXT = 302, // "uint16totext" + TOKEN_UINT32TOTEXT = 303, // "uint32totext" + TOKEN_PKT6 = 304, // "pkt6" + TOKEN_MSGTYPE = 305, // "msgtype" + TOKEN_TRANSID = 306, // "transid" + TOKEN_VENDOR_CLASS = 307, // "vendor-class" + TOKEN_VENDOR = 308, // "vendor" + TOKEN_ANY = 309, // "*" + TOKEN_DATA = 310, // "data" + TOKEN_ENTERPRISE = 311, // "enterprise" + TOKEN_TOPLEVEL_BOOL = 312, // "top-level bool" + TOKEN_TOPLEVEL_STRING = 313, // "top-level string" + TOKEN_STRING = 314, // "constant string" + TOKEN_INTEGER = 315, // "integer" + TOKEN_HEXSTRING = 316, // "constant hexstring" + TOKEN_OPTION_NAME = 317, // "option name" + TOKEN_IP_ADDRESS = 318 // "ip address" + }; + /// Backward compatibility alias (Bison 3.6). + typedef token_kind_type yytokentype; + }; + + /// Token kind, as returned by yylex. + typedef token::token_kind_type token_kind_type; + + /// Backward compatibility alias (Bison 3.6). + typedef token_kind_type token_type; + + /// Symbol kinds. + struct symbol_kind + { + enum symbol_kind_type + { + YYNTOKENS = 64, ///< Number of tokens. + S_YYEMPTY = -2, + S_YYEOF = 0, // "end of file" + S_YYerror = 1, // error + S_YYUNDEF = 2, // "invalid token" + S_LPAREN = 3, // "(" + S_RPAREN = 4, // ")" + S_NOT = 5, // "not" + S_AND = 6, // "and" + S_OR = 7, // "or" + S_EQUAL = 8, // "==" + S_OPTION = 9, // "option" + S_RELAY4 = 10, // "relay4" + S_RELAY6 = 11, // "relay6" + S_MEMBER = 12, // "member" + S_PEERADDR = 13, // "peeraddr" + S_LINKADDR = 14, // "linkaddr" + S_LBRACKET = 15, // "[" + S_RBRACKET = 16, // "]" + S_DOT = 17, // "." + S_TEXT = 18, // "text" + S_HEX = 19, // "hex" + S_EXISTS = 20, // "exists" + S_PKT = 21, // "pkt" + S_IFACE = 22, // "iface" + S_SRC = 23, // "src" + S_DST = 24, // "dst" + S_LEN = 25, // "len" + S_PKT4 = 26, // "pkt4" + S_CHADDR = 27, // "mac" + S_HLEN = 28, // "hlen" + S_HTYPE = 29, // "htype" + S_CIADDR = 30, // "ciaddr" + S_GIADDR = 31, // "giaddr" + S_YIADDR = 32, // "yiaddr" + S_SIADDR = 33, // "siaddr" + S_SUBSTRING = 34, // "substring" + S_SPLIT = 35, // "split" + S_ALL = 36, // "all" + S_COMA = 37, // "," + S_CONCAT = 38, // "concat" + S_PLUS = 39, // "+" + S_IFELSE = 40, // "ifelse" + S_TOHEXSTRING = 41, // "hexstring" + S_ADDRTOTEXT = 42, // "addrtotext" + S_INT8TOTEXT = 43, // "int8totext" + S_INT16TOTEXT = 44, // "int16totext" + S_INT32TOTEXT = 45, // "int32totext" + S_UINT8TOTEXT = 46, // "uint8totext" + S_UINT16TOTEXT = 47, // "uint16totext" + S_UINT32TOTEXT = 48, // "uint32totext" + S_PKT6 = 49, // "pkt6" + S_MSGTYPE = 50, // "msgtype" + S_TRANSID = 51, // "transid" + S_VENDOR_CLASS = 52, // "vendor-class" + S_VENDOR = 53, // "vendor" + S_ANY = 54, // "*" + S_DATA = 55, // "data" + S_ENTERPRISE = 56, // "enterprise" + S_TOPLEVEL_BOOL = 57, // "top-level bool" + S_TOPLEVEL_STRING = 58, // "top-level string" + S_STRING = 59, // "constant string" + S_INTEGER = 60, // "integer" + S_HEXSTRING = 61, // "constant hexstring" + S_OPTION_NAME = 62, // "option name" + S_IP_ADDRESS = 63, // "ip address" + S_YYACCEPT = 64, // $accept + S_start = 65, // start + S_expression = 66, // expression + S_bool_expr = 67, // bool_expr + S_string_expr = 68, // string_expr + S_integer_expr = 69, // integer_expr + S_option_code = 70, // option_code + S_sub_option_code = 71, // sub_option_code + S_option_repr_type = 72, // option_repr_type + S_nest_level = 73, // nest_level + S_pkt_metadata = 74, // pkt_metadata + S_enterprise_id = 75, // enterprise_id + S_pkt4_field = 76, // pkt4_field + S_pkt6_field = 77, // pkt6_field + S_relay6_field = 78, // relay6_field + S_start_expr = 79, // start_expr + S_length_expr = 80, // length_expr + S_int_expr = 81 // int_expr + }; + }; + + /// (Internal) symbol kind. + typedef symbol_kind::symbol_kind_type symbol_kind_type; + + /// The number of tokens. + static const symbol_kind_type YYNTOKENS = symbol_kind::YYNTOKENS; + + /// A complete symbol. + /// + /// Expects its Base type to provide access to the symbol kind + /// via kind (). + /// + /// Provide access to semantic value and location. + template <typename Base> + struct basic_symbol : Base + { + /// Alias to Base. + typedef Base super_type; + + /// Default constructor. + basic_symbol () YY_NOEXCEPT + : value () + , location () + {} + +#if 201103L <= YY_CPLUSPLUS + /// Move constructor. + basic_symbol (basic_symbol&& that) + : Base (std::move (that)) + , value () + , location (std::move (that.location)) + { + switch (this->kind ()) + { + case symbol_kind::S_option_repr_type: // option_repr_type + value.move< TokenOption::RepresentationType > (std::move (that.value)); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + value.move< TokenPkt4::FieldType > (std::move (that.value)); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + value.move< TokenPkt6::FieldType > (std::move (that.value)); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + value.move< TokenPkt::MetadataType > (std::move (that.value)); + break; + + case symbol_kind::S_relay6_field: // relay6_field + value.move< TokenRelay6Field::FieldType > (std::move (that.value)); + break; + + case symbol_kind::S_nest_level: // nest_level + value.move< int8_t > (std::move (that.value)); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + value.move< std::string > (std::move (that.value)); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + value.move< uint16_t > (std::move (that.value)); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + value.move< uint32_t > (std::move (that.value)); + break; + + default: + break; + } + + } +#endif + + /// Copy constructor. + basic_symbol (const basic_symbol& that); + + /// Constructors for typed symbols. +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, location_type&& l) + : Base (t) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const location_type& l) + : Base (t) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, TokenOption::RepresentationType&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const TokenOption::RepresentationType& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, TokenPkt4::FieldType&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const TokenPkt4::FieldType& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, TokenPkt6::FieldType&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const TokenPkt6::FieldType& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, TokenPkt::MetadataType&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const TokenPkt::MetadataType& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, TokenRelay6Field::FieldType&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const TokenRelay6Field::FieldType& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, int8_t&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const int8_t& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, std::string&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const std::string& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, uint16_t&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const uint16_t& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + +#if 201103L <= YY_CPLUSPLUS + basic_symbol (typename Base::kind_type t, uint32_t&& v, location_type&& l) + : Base (t) + , value (std::move (v)) + , location (std::move (l)) + {} +#else + basic_symbol (typename Base::kind_type t, const uint32_t& v, const location_type& l) + : Base (t) + , value (v) + , location (l) + {} +#endif + + /// Destroy the symbol. + ~basic_symbol () + { + clear (); + } + + + + /// Destroy contents, and record that is empty. + void clear () YY_NOEXCEPT + { + // User destructor. + symbol_kind_type yykind = this->kind (); + basic_symbol<Base>& yysym = *this; + (void) yysym; + switch (yykind) + { + default: + break; + } + + // Value type destructor. +switch (yykind) + { + case symbol_kind::S_option_repr_type: // option_repr_type + value.template destroy< TokenOption::RepresentationType > (); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + value.template destroy< TokenPkt4::FieldType > (); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + value.template destroy< TokenPkt6::FieldType > (); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + value.template destroy< TokenPkt::MetadataType > (); + break; + + case symbol_kind::S_relay6_field: // relay6_field + value.template destroy< TokenRelay6Field::FieldType > (); + break; + + case symbol_kind::S_nest_level: // nest_level + value.template destroy< int8_t > (); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + value.template destroy< std::string > (); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + value.template destroy< uint16_t > (); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + value.template destroy< uint32_t > (); + break; + + default: + break; + } + + Base::clear (); + } + + /// The user-facing name of this symbol. + std::string name () const YY_NOEXCEPT + { + return EvalParser::symbol_name (this->kind ()); + } + + /// Backward compatibility (Bison 3.6). + symbol_kind_type type_get () const YY_NOEXCEPT; + + /// Whether empty. + bool empty () const YY_NOEXCEPT; + + /// Destructive move, \a s is emptied into this. + void move (basic_symbol& s); + + /// The semantic value. + value_type value; + + /// The location. + location_type location; + + private: +#if YY_CPLUSPLUS < 201103L + /// Assignment operator. + basic_symbol& operator= (const basic_symbol& that); +#endif + }; + + /// Type access provider for token (enum) based symbols. + struct by_kind + { + /// The symbol kind as needed by the constructor. + typedef token_kind_type kind_type; + + /// Default constructor. + by_kind () YY_NOEXCEPT; + +#if 201103L <= YY_CPLUSPLUS + /// Move constructor. + by_kind (by_kind&& that) YY_NOEXCEPT; +#endif + + /// Copy constructor. + by_kind (const by_kind& that) YY_NOEXCEPT; + + /// Constructor from (external) token numbers. + by_kind (kind_type t) YY_NOEXCEPT; + + + + /// Record that this symbol is empty. + void clear () YY_NOEXCEPT; + + /// Steal the symbol kind from \a that. + void move (by_kind& that); + + /// The (internal) type number (corresponding to \a type). + /// \a empty when empty. + symbol_kind_type kind () const YY_NOEXCEPT; + + /// Backward compatibility (Bison 3.6). + symbol_kind_type type_get () const YY_NOEXCEPT; + + /// The symbol kind. + /// \a S_YYEMPTY when empty. + symbol_kind_type kind_; + }; + + /// Backward compatibility for a private implementation detail (Bison 3.6). + typedef by_kind by_type; + + /// "External" symbols: returned by the scanner. + struct symbol_type : basic_symbol<by_kind> + { + /// Superclass. + typedef basic_symbol<by_kind> super_type; + + /// Empty symbol. + symbol_type () YY_NOEXCEPT {} + + /// Constructor for valueless symbols, and symbols from each type. +#if 201103L <= YY_CPLUSPLUS + symbol_type (int tok, location_type l) + : super_type (token_kind_type (tok), std::move (l)) +#else + symbol_type (int tok, const location_type& l) + : super_type (token_kind_type (tok), l) +#endif + { +#if !defined _MSC_VER || defined __clang__ + EVAL_ASSERT (tok == token::TOKEN_END + || (token::TOKEN_EVALerror <= tok && tok <= token::TOKEN_TOPLEVEL_STRING)); +#endif + } +#if 201103L <= YY_CPLUSPLUS + symbol_type (int tok, std::string v, location_type l) + : super_type (token_kind_type (tok), std::move (v), std::move (l)) +#else + symbol_type (int tok, const std::string& v, const location_type& l) + : super_type (token_kind_type (tok), v, l) +#endif + { +#if !defined _MSC_VER || defined __clang__ + EVAL_ASSERT ((token::TOKEN_STRING <= tok && tok <= token::TOKEN_IP_ADDRESS)); +#endif + } + }; + + /// Build a parser object. + EvalParser (EvalContext& ctx_yyarg); + virtual ~EvalParser (); + +#if 201103L <= YY_CPLUSPLUS + /// Non copyable. + EvalParser (const EvalParser&) = delete; + /// Non copyable. + EvalParser& operator= (const EvalParser&) = delete; +#endif + + /// Parse. An alias for parse (). + /// \returns 0 iff parsing succeeded. + int operator() (); + + /// Parse. + /// \returns 0 iff parsing succeeded. + virtual int parse (); + +#if EVALDEBUG + /// The current debugging stream. + std::ostream& debug_stream () const YY_ATTRIBUTE_PURE; + /// Set the current debugging stream. + void set_debug_stream (std::ostream &); + + /// Type for debugging levels. + typedef int debug_level_type; + /// The current debugging level. + debug_level_type debug_level () const YY_ATTRIBUTE_PURE; + /// Set the current debugging level. + void set_debug_level (debug_level_type l); +#endif + + /// Report a syntax error. + /// \param loc where the syntax error is found. + /// \param msg a description of the syntax error. + virtual void error (const location_type& loc, const std::string& msg); + + /// Report a syntax error. + void error (const syntax_error& err); + + /// The user-facing name of the symbol whose (internal) number is + /// YYSYMBOL. No bounds checking. + static std::string symbol_name (symbol_kind_type yysymbol); + + // Implementation of make_symbol for each token kind. +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_END (location_type l) + { + return symbol_type (token::TOKEN_END, std::move (l)); + } +#else + static + symbol_type + make_END (const location_type& l) + { + return symbol_type (token::TOKEN_END, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_EVALerror (location_type l) + { + return symbol_type (token::TOKEN_EVALerror, std::move (l)); + } +#else + static + symbol_type + make_EVALerror (const location_type& l) + { + return symbol_type (token::TOKEN_EVALerror, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_EVALUNDEF (location_type l) + { + return symbol_type (token::TOKEN_EVALUNDEF, std::move (l)); + } +#else + static + symbol_type + make_EVALUNDEF (const location_type& l) + { + return symbol_type (token::TOKEN_EVALUNDEF, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_LPAREN (location_type l) + { + return symbol_type (token::TOKEN_LPAREN, std::move (l)); + } +#else + static + symbol_type + make_LPAREN (const location_type& l) + { + return symbol_type (token::TOKEN_LPAREN, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_RPAREN (location_type l) + { + return symbol_type (token::TOKEN_RPAREN, std::move (l)); + } +#else + static + symbol_type + make_RPAREN (const location_type& l) + { + return symbol_type (token::TOKEN_RPAREN, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_NOT (location_type l) + { + return symbol_type (token::TOKEN_NOT, std::move (l)); + } +#else + static + symbol_type + make_NOT (const location_type& l) + { + return symbol_type (token::TOKEN_NOT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_AND (location_type l) + { + return symbol_type (token::TOKEN_AND, std::move (l)); + } +#else + static + symbol_type + make_AND (const location_type& l) + { + return symbol_type (token::TOKEN_AND, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_OR (location_type l) + { + return symbol_type (token::TOKEN_OR, std::move (l)); + } +#else + static + symbol_type + make_OR (const location_type& l) + { + return symbol_type (token::TOKEN_OR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_EQUAL (location_type l) + { + return symbol_type (token::TOKEN_EQUAL, std::move (l)); + } +#else + static + symbol_type + make_EQUAL (const location_type& l) + { + return symbol_type (token::TOKEN_EQUAL, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_OPTION (location_type l) + { + return symbol_type (token::TOKEN_OPTION, std::move (l)); + } +#else + static + symbol_type + make_OPTION (const location_type& l) + { + return symbol_type (token::TOKEN_OPTION, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_RELAY4 (location_type l) + { + return symbol_type (token::TOKEN_RELAY4, std::move (l)); + } +#else + static + symbol_type + make_RELAY4 (const location_type& l) + { + return symbol_type (token::TOKEN_RELAY4, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_RELAY6 (location_type l) + { + return symbol_type (token::TOKEN_RELAY6, std::move (l)); + } +#else + static + symbol_type + make_RELAY6 (const location_type& l) + { + return symbol_type (token::TOKEN_RELAY6, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_MEMBER (location_type l) + { + return symbol_type (token::TOKEN_MEMBER, std::move (l)); + } +#else + static + symbol_type + make_MEMBER (const location_type& l) + { + return symbol_type (token::TOKEN_MEMBER, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_PEERADDR (location_type l) + { + return symbol_type (token::TOKEN_PEERADDR, std::move (l)); + } +#else + static + symbol_type + make_PEERADDR (const location_type& l) + { + return symbol_type (token::TOKEN_PEERADDR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_LINKADDR (location_type l) + { + return symbol_type (token::TOKEN_LINKADDR, std::move (l)); + } +#else + static + symbol_type + make_LINKADDR (const location_type& l) + { + return symbol_type (token::TOKEN_LINKADDR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_LBRACKET (location_type l) + { + return symbol_type (token::TOKEN_LBRACKET, std::move (l)); + } +#else + static + symbol_type + make_LBRACKET (const location_type& l) + { + return symbol_type (token::TOKEN_LBRACKET, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_RBRACKET (location_type l) + { + return symbol_type (token::TOKEN_RBRACKET, std::move (l)); + } +#else + static + symbol_type + make_RBRACKET (const location_type& l) + { + return symbol_type (token::TOKEN_RBRACKET, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_DOT (location_type l) + { + return symbol_type (token::TOKEN_DOT, std::move (l)); + } +#else + static + symbol_type + make_DOT (const location_type& l) + { + return symbol_type (token::TOKEN_DOT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_TEXT (location_type l) + { + return symbol_type (token::TOKEN_TEXT, std::move (l)); + } +#else + static + symbol_type + make_TEXT (const location_type& l) + { + return symbol_type (token::TOKEN_TEXT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_HEX (location_type l) + { + return symbol_type (token::TOKEN_HEX, std::move (l)); + } +#else + static + symbol_type + make_HEX (const location_type& l) + { + return symbol_type (token::TOKEN_HEX, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_EXISTS (location_type l) + { + return symbol_type (token::TOKEN_EXISTS, std::move (l)); + } +#else + static + symbol_type + make_EXISTS (const location_type& l) + { + return symbol_type (token::TOKEN_EXISTS, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_PKT (location_type l) + { + return symbol_type (token::TOKEN_PKT, std::move (l)); + } +#else + static + symbol_type + make_PKT (const location_type& l) + { + return symbol_type (token::TOKEN_PKT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_IFACE (location_type l) + { + return symbol_type (token::TOKEN_IFACE, std::move (l)); + } +#else + static + symbol_type + make_IFACE (const location_type& l) + { + return symbol_type (token::TOKEN_IFACE, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_SRC (location_type l) + { + return symbol_type (token::TOKEN_SRC, std::move (l)); + } +#else + static + symbol_type + make_SRC (const location_type& l) + { + return symbol_type (token::TOKEN_SRC, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_DST (location_type l) + { + return symbol_type (token::TOKEN_DST, std::move (l)); + } +#else + static + symbol_type + make_DST (const location_type& l) + { + return symbol_type (token::TOKEN_DST, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_LEN (location_type l) + { + return symbol_type (token::TOKEN_LEN, std::move (l)); + } +#else + static + symbol_type + make_LEN (const location_type& l) + { + return symbol_type (token::TOKEN_LEN, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_PKT4 (location_type l) + { + return symbol_type (token::TOKEN_PKT4, std::move (l)); + } +#else + static + symbol_type + make_PKT4 (const location_type& l) + { + return symbol_type (token::TOKEN_PKT4, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_CHADDR (location_type l) + { + return symbol_type (token::TOKEN_CHADDR, std::move (l)); + } +#else + static + symbol_type + make_CHADDR (const location_type& l) + { + return symbol_type (token::TOKEN_CHADDR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_HLEN (location_type l) + { + return symbol_type (token::TOKEN_HLEN, std::move (l)); + } +#else + static + symbol_type + make_HLEN (const location_type& l) + { + return symbol_type (token::TOKEN_HLEN, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_HTYPE (location_type l) + { + return symbol_type (token::TOKEN_HTYPE, std::move (l)); + } +#else + static + symbol_type + make_HTYPE (const location_type& l) + { + return symbol_type (token::TOKEN_HTYPE, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_CIADDR (location_type l) + { + return symbol_type (token::TOKEN_CIADDR, std::move (l)); + } +#else + static + symbol_type + make_CIADDR (const location_type& l) + { + return symbol_type (token::TOKEN_CIADDR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_GIADDR (location_type l) + { + return symbol_type (token::TOKEN_GIADDR, std::move (l)); + } +#else + static + symbol_type + make_GIADDR (const location_type& l) + { + return symbol_type (token::TOKEN_GIADDR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_YIADDR (location_type l) + { + return symbol_type (token::TOKEN_YIADDR, std::move (l)); + } +#else + static + symbol_type + make_YIADDR (const location_type& l) + { + return symbol_type (token::TOKEN_YIADDR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_SIADDR (location_type l) + { + return symbol_type (token::TOKEN_SIADDR, std::move (l)); + } +#else + static + symbol_type + make_SIADDR (const location_type& l) + { + return symbol_type (token::TOKEN_SIADDR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_SUBSTRING (location_type l) + { + return symbol_type (token::TOKEN_SUBSTRING, std::move (l)); + } +#else + static + symbol_type + make_SUBSTRING (const location_type& l) + { + return symbol_type (token::TOKEN_SUBSTRING, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_SPLIT (location_type l) + { + return symbol_type (token::TOKEN_SPLIT, std::move (l)); + } +#else + static + symbol_type + make_SPLIT (const location_type& l) + { + return symbol_type (token::TOKEN_SPLIT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_ALL (location_type l) + { + return symbol_type (token::TOKEN_ALL, std::move (l)); + } +#else + static + symbol_type + make_ALL (const location_type& l) + { + return symbol_type (token::TOKEN_ALL, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_COMA (location_type l) + { + return symbol_type (token::TOKEN_COMA, std::move (l)); + } +#else + static + symbol_type + make_COMA (const location_type& l) + { + return symbol_type (token::TOKEN_COMA, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_CONCAT (location_type l) + { + return symbol_type (token::TOKEN_CONCAT, std::move (l)); + } +#else + static + symbol_type + make_CONCAT (const location_type& l) + { + return symbol_type (token::TOKEN_CONCAT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_PLUS (location_type l) + { + return symbol_type (token::TOKEN_PLUS, std::move (l)); + } +#else + static + symbol_type + make_PLUS (const location_type& l) + { + return symbol_type (token::TOKEN_PLUS, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_IFELSE (location_type l) + { + return symbol_type (token::TOKEN_IFELSE, std::move (l)); + } +#else + static + symbol_type + make_IFELSE (const location_type& l) + { + return symbol_type (token::TOKEN_IFELSE, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_TOHEXSTRING (location_type l) + { + return symbol_type (token::TOKEN_TOHEXSTRING, std::move (l)); + } +#else + static + symbol_type + make_TOHEXSTRING (const location_type& l) + { + return symbol_type (token::TOKEN_TOHEXSTRING, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_ADDRTOTEXT (location_type l) + { + return symbol_type (token::TOKEN_ADDRTOTEXT, std::move (l)); + } +#else + static + symbol_type + make_ADDRTOTEXT (const location_type& l) + { + return symbol_type (token::TOKEN_ADDRTOTEXT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_INT8TOTEXT (location_type l) + { + return symbol_type (token::TOKEN_INT8TOTEXT, std::move (l)); + } +#else + static + symbol_type + make_INT8TOTEXT (const location_type& l) + { + return symbol_type (token::TOKEN_INT8TOTEXT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_INT16TOTEXT (location_type l) + { + return symbol_type (token::TOKEN_INT16TOTEXT, std::move (l)); + } +#else + static + symbol_type + make_INT16TOTEXT (const location_type& l) + { + return symbol_type (token::TOKEN_INT16TOTEXT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_INT32TOTEXT (location_type l) + { + return symbol_type (token::TOKEN_INT32TOTEXT, std::move (l)); + } +#else + static + symbol_type + make_INT32TOTEXT (const location_type& l) + { + return symbol_type (token::TOKEN_INT32TOTEXT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_UINT8TOTEXT (location_type l) + { + return symbol_type (token::TOKEN_UINT8TOTEXT, std::move (l)); + } +#else + static + symbol_type + make_UINT8TOTEXT (const location_type& l) + { + return symbol_type (token::TOKEN_UINT8TOTEXT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_UINT16TOTEXT (location_type l) + { + return symbol_type (token::TOKEN_UINT16TOTEXT, std::move (l)); + } +#else + static + symbol_type + make_UINT16TOTEXT (const location_type& l) + { + return symbol_type (token::TOKEN_UINT16TOTEXT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_UINT32TOTEXT (location_type l) + { + return symbol_type (token::TOKEN_UINT32TOTEXT, std::move (l)); + } +#else + static + symbol_type + make_UINT32TOTEXT (const location_type& l) + { + return symbol_type (token::TOKEN_UINT32TOTEXT, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_PKT6 (location_type l) + { + return symbol_type (token::TOKEN_PKT6, std::move (l)); + } +#else + static + symbol_type + make_PKT6 (const location_type& l) + { + return symbol_type (token::TOKEN_PKT6, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_MSGTYPE (location_type l) + { + return symbol_type (token::TOKEN_MSGTYPE, std::move (l)); + } +#else + static + symbol_type + make_MSGTYPE (const location_type& l) + { + return symbol_type (token::TOKEN_MSGTYPE, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_TRANSID (location_type l) + { + return symbol_type (token::TOKEN_TRANSID, std::move (l)); + } +#else + static + symbol_type + make_TRANSID (const location_type& l) + { + return symbol_type (token::TOKEN_TRANSID, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_VENDOR_CLASS (location_type l) + { + return symbol_type (token::TOKEN_VENDOR_CLASS, std::move (l)); + } +#else + static + symbol_type + make_VENDOR_CLASS (const location_type& l) + { + return symbol_type (token::TOKEN_VENDOR_CLASS, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_VENDOR (location_type l) + { + return symbol_type (token::TOKEN_VENDOR, std::move (l)); + } +#else + static + symbol_type + make_VENDOR (const location_type& l) + { + return symbol_type (token::TOKEN_VENDOR, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_ANY (location_type l) + { + return symbol_type (token::TOKEN_ANY, std::move (l)); + } +#else + static + symbol_type + make_ANY (const location_type& l) + { + return symbol_type (token::TOKEN_ANY, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_DATA (location_type l) + { + return symbol_type (token::TOKEN_DATA, std::move (l)); + } +#else + static + symbol_type + make_DATA (const location_type& l) + { + return symbol_type (token::TOKEN_DATA, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_ENTERPRISE (location_type l) + { + return symbol_type (token::TOKEN_ENTERPRISE, std::move (l)); + } +#else + static + symbol_type + make_ENTERPRISE (const location_type& l) + { + return symbol_type (token::TOKEN_ENTERPRISE, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_TOPLEVEL_BOOL (location_type l) + { + return symbol_type (token::TOKEN_TOPLEVEL_BOOL, std::move (l)); + } +#else + static + symbol_type + make_TOPLEVEL_BOOL (const location_type& l) + { + return symbol_type (token::TOKEN_TOPLEVEL_BOOL, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_TOPLEVEL_STRING (location_type l) + { + return symbol_type (token::TOKEN_TOPLEVEL_STRING, std::move (l)); + } +#else + static + symbol_type + make_TOPLEVEL_STRING (const location_type& l) + { + return symbol_type (token::TOKEN_TOPLEVEL_STRING, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_STRING (std::string v, location_type l) + { + return symbol_type (token::TOKEN_STRING, std::move (v), std::move (l)); + } +#else + static + symbol_type + make_STRING (const std::string& v, const location_type& l) + { + return symbol_type (token::TOKEN_STRING, v, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_INTEGER (std::string v, location_type l) + { + return symbol_type (token::TOKEN_INTEGER, std::move (v), std::move (l)); + } +#else + static + symbol_type + make_INTEGER (const std::string& v, const location_type& l) + { + return symbol_type (token::TOKEN_INTEGER, v, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_HEXSTRING (std::string v, location_type l) + { + return symbol_type (token::TOKEN_HEXSTRING, std::move (v), std::move (l)); + } +#else + static + symbol_type + make_HEXSTRING (const std::string& v, const location_type& l) + { + return symbol_type (token::TOKEN_HEXSTRING, v, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_OPTION_NAME (std::string v, location_type l) + { + return symbol_type (token::TOKEN_OPTION_NAME, std::move (v), std::move (l)); + } +#else + static + symbol_type + make_OPTION_NAME (const std::string& v, const location_type& l) + { + return symbol_type (token::TOKEN_OPTION_NAME, v, l); + } +#endif +#if 201103L <= YY_CPLUSPLUS + static + symbol_type + make_IP_ADDRESS (std::string v, location_type l) + { + return symbol_type (token::TOKEN_IP_ADDRESS, std::move (v), std::move (l)); + } +#else + static + symbol_type + make_IP_ADDRESS (const std::string& v, const location_type& l) + { + return symbol_type (token::TOKEN_IP_ADDRESS, v, l); + } +#endif + + + class context + { + public: + context (const EvalParser& yyparser, const symbol_type& yyla); + const symbol_type& lookahead () const YY_NOEXCEPT { return yyla_; } + symbol_kind_type token () const YY_NOEXCEPT { return yyla_.kind (); } + const location_type& location () const YY_NOEXCEPT { return yyla_.location; } + + /// Put in YYARG at most YYARGN of the expected tokens, and return the + /// number of tokens stored in YYARG. If YYARG is null, return the + /// number of expected tokens (guaranteed to be less than YYNTOKENS). + int expected_tokens (symbol_kind_type yyarg[], int yyargn) const; + + private: + const EvalParser& yyparser_; + const symbol_type& yyla_; + }; + + private: +#if YY_CPLUSPLUS < 201103L + /// Non copyable. + EvalParser (const EvalParser&); + /// Non copyable. + EvalParser& operator= (const EvalParser&); +#endif + + + /// Stored state numbers (used for stacks). + typedef unsigned char state_type; + + /// The arguments of the error message. + int yy_syntax_error_arguments_ (const context& yyctx, + symbol_kind_type yyarg[], int yyargn) const; + + /// Generate an error message. + /// \param yyctx the context in which the error occurred. + virtual std::string yysyntax_error_ (const context& yyctx) const; + /// Compute post-reduction state. + /// \param yystate the current state + /// \param yysym the nonterminal to push on the stack + static state_type yy_lr_goto_state_ (state_type yystate, int yysym); + + /// Whether the given \c yypact_ value indicates a defaulted state. + /// \param yyvalue the value to check + static bool yy_pact_value_is_default_ (int yyvalue) YY_NOEXCEPT; + + /// Whether the given \c yytable_ value indicates a syntax error. + /// \param yyvalue the value to check + static bool yy_table_value_is_error_ (int yyvalue) YY_NOEXCEPT; + + static const short yypact_ninf_; + static const signed char yytable_ninf_; + + /// Convert a scanner token kind \a t to a symbol kind. + /// In theory \a t should be a token_kind_type, but character literals + /// are valid, yet not members of the token_kind_type enum. + static symbol_kind_type yytranslate_ (int t) YY_NOEXCEPT; + + /// Convert the symbol name \a n to a form suitable for a diagnostic. + static std::string yytnamerr_ (const char *yystr); + + /// For a symbol, its name in clear. + static const char* const yytname_[]; + + + // Tables. + // YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + // STATE-NUM. + static const short yypact_[]; + + // YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + // Performed when YYTABLE does not specify something else to do. Zero + // means the default is an error. + static const signed char yydefact_[]; + + // YYPGOTO[NTERM-NUM]. + static const short yypgoto_[]; + + // YYDEFGOTO[NTERM-NUM]. + static const unsigned char yydefgoto_[]; + + // YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + // positive, shift that token. If negative, reduce the rule whose + // number is the opposite. If YYTABLE_NINF, syntax error. + static const unsigned char yytable_[]; + + static const short yycheck_[]; + + // YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of + // state STATE-NUM. + static const signed char yystos_[]; + + // YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. + static const signed char yyr1_[]; + + // YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. + static const signed char yyr2_[]; + + +#if EVALDEBUG + // YYRLINE[YYN] -- Source line where rule number YYN was defined. + static const short yyrline_[]; + /// Report on the debug stream that the rule \a r is going to be reduced. + virtual void yy_reduce_print_ (int r) const; + /// Print the state stack on the debug stream. + virtual void yy_stack_print_ () const; + + /// Debugging level. + int yydebug_; + /// Debug stream. + std::ostream* yycdebug_; + + /// \brief Display a symbol kind, value and location. + /// \param yyo The output stream. + /// \param yysym The symbol. + template <typename Base> + void yy_print_ (std::ostream& yyo, const basic_symbol<Base>& yysym) const; +#endif + + /// \brief Reclaim the memory associated to a symbol. + /// \param yymsg Why this token is reclaimed. + /// If null, print nothing. + /// \param yysym The symbol. + template <typename Base> + void yy_destroy_ (const char* yymsg, basic_symbol<Base>& yysym) const; + + private: + /// Type access provider for state based symbols. + struct by_state + { + /// Default constructor. + by_state () YY_NOEXCEPT; + + /// The symbol kind as needed by the constructor. + typedef state_type kind_type; + + /// Constructor. + by_state (kind_type s) YY_NOEXCEPT; + + /// Copy constructor. + by_state (const by_state& that) YY_NOEXCEPT; + + /// Record that this symbol is empty. + void clear () YY_NOEXCEPT; + + /// Steal the symbol kind from \a that. + void move (by_state& that); + + /// The symbol kind (corresponding to \a state). + /// \a symbol_kind::S_YYEMPTY when empty. + symbol_kind_type kind () const YY_NOEXCEPT; + + /// The state number used to denote an empty symbol. + /// We use the initial state, as it does not have a value. + enum { empty_state = 0 }; + + /// The state. + /// \a empty when empty. + state_type state; + }; + + /// "Internal" symbol: element of the stack. + struct stack_symbol_type : basic_symbol<by_state> + { + /// Superclass. + typedef basic_symbol<by_state> super_type; + /// Construct an empty symbol. + stack_symbol_type (); + /// Move or copy construction. + stack_symbol_type (YY_RVREF (stack_symbol_type) that); + /// Steal the contents from \a sym to build this. + stack_symbol_type (state_type s, YY_MOVE_REF (symbol_type) sym); +#if YY_CPLUSPLUS < 201103L + /// Assignment, needed by push_back by some old implementations. + /// Moves the contents of that. + stack_symbol_type& operator= (stack_symbol_type& that); + + /// Assignment, needed by push_back by other implementations. + /// Needed by some other old implementations. + stack_symbol_type& operator= (const stack_symbol_type& that); +#endif + }; + + /// A stack with random access from its top. + template <typename T, typename S = std::vector<T> > + class stack + { + public: + // Hide our reversed order. + typedef typename S::iterator iterator; + typedef typename S::const_iterator const_iterator; + typedef typename S::size_type size_type; + typedef typename std::ptrdiff_t index_type; + + stack (size_type n = 200) YY_NOEXCEPT + : seq_ (n) + {} + +#if 201103L <= YY_CPLUSPLUS + /// Non copyable. + stack (const stack&) = delete; + /// Non copyable. + stack& operator= (const stack&) = delete; +#endif + + /// Random access. + /// + /// Index 0 returns the topmost element. + const T& + operator[] (index_type i) const + { + return seq_[size_type (size () - 1 - i)]; + } + + /// Random access. + /// + /// Index 0 returns the topmost element. + T& + operator[] (index_type i) + { + return seq_[size_type (size () - 1 - i)]; + } + + /// Steal the contents of \a t. + /// + /// Close to move-semantics. + void + push (YY_MOVE_REF (T) t) + { + seq_.push_back (T ()); + operator[] (0).move (t); + } + + /// Pop elements from the stack. + void + pop (std::ptrdiff_t n = 1) YY_NOEXCEPT + { + for (; 0 < n; --n) + seq_.pop_back (); + } + + /// Pop all elements from the stack. + void + clear () YY_NOEXCEPT + { + seq_.clear (); + } + + /// Number of elements on the stack. + index_type + size () const YY_NOEXCEPT + { + return index_type (seq_.size ()); + } + + /// Iterator on top of the stack (going downwards). + const_iterator + begin () const YY_NOEXCEPT + { + return seq_.begin (); + } + + /// Bottom of the stack. + const_iterator + end () const YY_NOEXCEPT + { + return seq_.end (); + } + + /// Present a slice of the top of a stack. + class slice + { + public: + slice (const stack& stack, index_type range) YY_NOEXCEPT + : stack_ (stack) + , range_ (range) + {} + + const T& + operator[] (index_type i) const + { + return stack_[range_ - i]; + } + + private: + const stack& stack_; + index_type range_; + }; + + private: +#if YY_CPLUSPLUS < 201103L + /// Non copyable. + stack (const stack&); + /// Non copyable. + stack& operator= (const stack&); +#endif + /// The wrapped container. + S seq_; + }; + + + /// Stack type. + typedef stack<stack_symbol_type> stack_type; + + /// The stack. + stack_type yystack_; + + /// Push a new state on the stack. + /// \param m a debug message to display + /// if null, no trace is output. + /// \param sym the symbol + /// \warning the contents of \a s.value is stolen. + void yypush_ (const char* m, YY_MOVE_REF (stack_symbol_type) sym); + + /// Push a new look ahead token on the state on the stack. + /// \param m a debug message to display + /// if null, no trace is output. + /// \param s the state + /// \param sym the symbol (for its value and location). + /// \warning the contents of \a sym.value is stolen. + void yypush_ (const char* m, state_type s, YY_MOVE_REF (symbol_type) sym); + + /// Pop \a n symbols from the stack. + void yypop_ (int n = 1) YY_NOEXCEPT; + + /// Constants. + enum + { + yylast_ = 286, ///< Last index in yytable_. + yynnts_ = 18, ///< Number of nonterminal symbols. + yyfinal_ = 42 ///< Termination state number. + }; + + + // User arguments. + EvalContext& ctx; + + }; + + inline + EvalParser::symbol_kind_type + EvalParser::yytranslate_ (int t) YY_NOEXCEPT + { + // YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to + // TOKEN-NUM as returned by yylex. + static + const signed char + translate_table[] = + { + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63 + }; + // Last valid token kind. + const int code_max = 318; + + if (t <= 0) + return symbol_kind::S_YYEOF; + else if (t <= code_max) + return static_cast <symbol_kind_type> (translate_table[t]); + else + return symbol_kind::S_YYUNDEF; + } + + // basic_symbol. + template <typename Base> + EvalParser::basic_symbol<Base>::basic_symbol (const basic_symbol& that) + : Base (that) + , value () + , location (that.location) + { + switch (this->kind ()) + { + case symbol_kind::S_option_repr_type: // option_repr_type + value.copy< TokenOption::RepresentationType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + value.copy< TokenPkt4::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + value.copy< TokenPkt6::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + value.copy< TokenPkt::MetadataType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_relay6_field: // relay6_field + value.copy< TokenRelay6Field::FieldType > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_nest_level: // nest_level + value.copy< int8_t > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + value.copy< std::string > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + value.copy< uint16_t > (YY_MOVE (that.value)); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + value.copy< uint32_t > (YY_MOVE (that.value)); + break; + + default: + break; + } + + } + + + + + template <typename Base> + EvalParser::symbol_kind_type + EvalParser::basic_symbol<Base>::type_get () const YY_NOEXCEPT + { + return this->kind (); + } + + + template <typename Base> + bool + EvalParser::basic_symbol<Base>::empty () const YY_NOEXCEPT + { + return this->kind () == symbol_kind::S_YYEMPTY; + } + + template <typename Base> + void + EvalParser::basic_symbol<Base>::move (basic_symbol& s) + { + super_type::move (s); + switch (this->kind ()) + { + case symbol_kind::S_option_repr_type: // option_repr_type + value.move< TokenOption::RepresentationType > (YY_MOVE (s.value)); + break; + + case symbol_kind::S_pkt4_field: // pkt4_field + value.move< TokenPkt4::FieldType > (YY_MOVE (s.value)); + break; + + case symbol_kind::S_pkt6_field: // pkt6_field + value.move< TokenPkt6::FieldType > (YY_MOVE (s.value)); + break; + + case symbol_kind::S_pkt_metadata: // pkt_metadata + value.move< TokenPkt::MetadataType > (YY_MOVE (s.value)); + break; + + case symbol_kind::S_relay6_field: // relay6_field + value.move< TokenRelay6Field::FieldType > (YY_MOVE (s.value)); + break; + + case symbol_kind::S_nest_level: // nest_level + value.move< int8_t > (YY_MOVE (s.value)); + break; + + case symbol_kind::S_STRING: // "constant string" + case symbol_kind::S_INTEGER: // "integer" + case symbol_kind::S_HEXSTRING: // "constant hexstring" + case symbol_kind::S_OPTION_NAME: // "option name" + case symbol_kind::S_IP_ADDRESS: // "ip address" + value.move< std::string > (YY_MOVE (s.value)); + break; + + case symbol_kind::S_option_code: // option_code + case symbol_kind::S_sub_option_code: // sub_option_code + value.move< uint16_t > (YY_MOVE (s.value)); + break; + + case symbol_kind::S_integer_expr: // integer_expr + case symbol_kind::S_enterprise_id: // enterprise_id + value.move< uint32_t > (YY_MOVE (s.value)); + break; + + default: + break; + } + + location = YY_MOVE (s.location); + } + + // by_kind. + inline + EvalParser::by_kind::by_kind () YY_NOEXCEPT + : kind_ (symbol_kind::S_YYEMPTY) + {} + +#if 201103L <= YY_CPLUSPLUS + inline + EvalParser::by_kind::by_kind (by_kind&& that) YY_NOEXCEPT + : kind_ (that.kind_) + { + that.clear (); + } +#endif + + inline + EvalParser::by_kind::by_kind (const by_kind& that) YY_NOEXCEPT + : kind_ (that.kind_) + {} + + inline + EvalParser::by_kind::by_kind (token_kind_type t) YY_NOEXCEPT + : kind_ (yytranslate_ (t)) + {} + + + + inline + void + EvalParser::by_kind::clear () YY_NOEXCEPT + { + kind_ = symbol_kind::S_YYEMPTY; + } + + inline + void + EvalParser::by_kind::move (by_kind& that) + { + kind_ = that.kind_; + that.clear (); + } + + inline + EvalParser::symbol_kind_type + EvalParser::by_kind::kind () const YY_NOEXCEPT + { + return kind_; + } + + + inline + EvalParser::symbol_kind_type + EvalParser::by_kind::type_get () const YY_NOEXCEPT + { + return this->kind (); + } + + +#line 14 "parser.yy" +} } // isc::eval +#line 2673 "parser.h" + + + + +#endif // !YY_EVAL_PARSER_H_INCLUDED diff --git a/src/lib/eval/parser.yy b/src/lib/eval/parser.yy new file mode 100644 index 0000000..2015e83 --- /dev/null +++ b/src/lib/eval/parser.yy @@ -0,0 +1,649 @@ +/* Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC") + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +%skeleton "lalr1.cc" /* -*- C++ -*- */ +%require "3.3.0" +%defines +%define api.parser.class {EvalParser} +%define api.prefix {eval} +%define api.token.constructor +%define api.value.type variant +%define api.namespace {isc::eval} +%define parse.assert +%code requires +{ +#include <string> +#include <eval/token.h> +#include <eval/eval_context_decl.h> +#include <dhcp/option.h> +#include <boost/lexical_cast.hpp> + +using namespace isc::dhcp; +using namespace isc::eval; +} +// The parsing context. +%param { EvalContext& ctx } +%locations +%define parse.trace +%define parse.error verbose +%code +{ +# include "eval_context.h" + +// Avoid warnings with the error counter. +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +} + +%define api.token.prefix {TOKEN_} +// Tokens in an order which makes sense and related to the intended use. +%token + END 0 "end of file" + LPAREN "(" + RPAREN ")" + NOT "not" + AND "and" + OR "or" + EQUAL "==" + OPTION "option" + RELAY4 "relay4" + RELAY6 "relay6" + MEMBER "member" + PEERADDR "peeraddr" + LINKADDR "linkaddr" + LBRACKET "[" + RBRACKET "]" + DOT "." + TEXT "text" + HEX "hex" + EXISTS "exists" + PKT "pkt" + IFACE "iface" + SRC "src" + DST "dst" + LEN "len" + PKT4 "pkt4" + CHADDR "mac" + HLEN "hlen" + HTYPE "htype" + CIADDR "ciaddr" + GIADDR "giaddr" + YIADDR "yiaddr" + SIADDR "siaddr" + SUBSTRING "substring" + SPLIT "split" + ALL "all" + COMA "," + CONCAT "concat" + PLUS "+" + IFELSE "ifelse" + TOHEXSTRING "hexstring" + ADDRTOTEXT "addrtotext" + INT8TOTEXT "int8totext" + INT16TOTEXT "int16totext" + INT32TOTEXT "int32totext" + UINT8TOTEXT "uint8totext" + UINT16TOTEXT "uint16totext" + UINT32TOTEXT "uint32totext" + PKT6 "pkt6" + MSGTYPE "msgtype" + TRANSID "transid" + VENDOR_CLASS "vendor-class" + VENDOR "vendor" + ANY "*" + DATA "data" + ENTERPRISE "enterprise" + + TOPLEVEL_BOOL "top-level bool" + TOPLEVEL_STRING "top-level string" +; + +%token <std::string> STRING "constant string" +%token <std::string> INTEGER "integer" +%token <std::string> HEXSTRING "constant hexstring" +%token <std::string> OPTION_NAME "option name" +%token <std::string> IP_ADDRESS "ip address" + +%type <uint16_t> option_code +%type <uint16_t> sub_option_code +%type <uint32_t> enterprise_id +%type <uint32_t> integer_expr +%type <TokenOption::RepresentationType> option_repr_type +%type <TokenRelay6Field::FieldType> relay6_field +%type <int8_t> nest_level +%type <TokenPkt::MetadataType> pkt_metadata +%type <TokenPkt4::FieldType> pkt4_field +%type <TokenPkt6::FieldType> pkt6_field + +%left PLUS +%left OR +%left AND +%precedence NOT + +%printer { yyoutput << $$; } <*>; + +%% + +// The whole grammar starts with a 'start' symbol... +%start start; + +// ... that expects either TOPLEVEL_BOOL or TOPLEVEL_STRING. Depending on which +// token appears first, it will determine what is allowed and what it not. +start: TOPLEVEL_BOOL expression + | TOPLEVEL_STRING string_expr +; + +// Expression can either be a single token or a (something == something) expression + +expression : bool_expr + ; + +bool_expr : "(" bool_expr ")" + | NOT bool_expr + { + TokenPtr neg(new TokenNot()); + ctx.expression.push_back(neg); + } + | bool_expr AND bool_expr + { + TokenPtr neg(new TokenAnd()); + ctx.expression.push_back(neg); + } + | bool_expr OR bool_expr + { + TokenPtr neg(new TokenOr()); + ctx.expression.push_back(neg); + } + | string_expr EQUAL string_expr + { + TokenPtr eq(new TokenEqual()); + ctx.expression.push_back(eq); + } + | OPTION "[" option_code "]" "." EXISTS + { + TokenPtr opt(new TokenOption($3, TokenOption::EXISTS)); + ctx.expression.push_back(opt); + } + | OPTION "[" option_code "]" "." OPTION "[" sub_option_code "]" "." EXISTS + { + TokenPtr opt(new TokenSubOption($3, $8, TokenOption::EXISTS)); + ctx.expression.push_back(opt); + } + | RELAY4 "[" sub_option_code "]" "." EXISTS + { + switch (ctx.getUniverse()) { + case Option::V4: + { + TokenPtr opt(new TokenRelay4Option($3, TokenOption::EXISTS)); + ctx.expression.push_back(opt); + break; + } + case Option::V6: + // We will have relay6[123] for the DHCPv6. + // In a very distant future we'll possibly be able + // to mix both if we have DHCPv4-over-DHCPv6, so it + // has some sense to make it explicit whether we + // talk about DHCPv4 relay or DHCPv6 relay. However, + // for the time being relay4 can be used in DHCPv4 + // only. + error(@1, "relay4 can only be used in DHCPv4."); + } + } + | RELAY6 "[" nest_level "]" "." OPTION "[" sub_option_code "]" "." EXISTS + { + switch (ctx.getUniverse()) { + case Option::V6: + { + TokenPtr opt(new TokenRelay6Option($3, $8, TokenOption::EXISTS)); + ctx.expression.push_back(opt); + break; + } + case Option::V4: + // For now we only use relay6 in DHCPv6. + error(@1, "relay6 can only be used in DHCPv6."); + } + } + | VENDOR_CLASS "[" enterprise_id "]" "." EXISTS + { + // Expression: vendor-class[1234].exists + // + // This token will find option 124 (DHCPv4) or 16 (DHCPv6), + // and will check if enterprise-id equals specified value. + TokenPtr exist(new TokenVendorClass(ctx.getUniverse(), $3, TokenOption::EXISTS)); + ctx.expression.push_back(exist); + } + | VENDOR "[" enterprise_id "]" "." EXISTS + { + // Expression: vendor[1234].exists + // + // This token will find option 125 (DHCPv4) or 17 (DHCPv6), + // and will check if enterprise-id equals specified value. + TokenPtr exist(new TokenVendor(ctx.getUniverse(), $3, TokenOption::EXISTS)); + ctx.expression.push_back(exist); + } + | VENDOR "[" enterprise_id "]" "." OPTION "[" sub_option_code "]" "." EXISTS + { + // Expression vendor[1234].option[123].exists + // + // This token will check if specified vendor option + // exists, has specified enterprise-id and if has + // specified suboption. + TokenPtr exist(new TokenVendor(ctx.getUniverse(), $3, TokenOption::EXISTS, $8)); + ctx.expression.push_back(exist); + } + | MEMBER "(" STRING ")" + { + // Expression member('foo') + // + // This token will check if the packet is a member of + // the specified client class. + // To avoid loops at evaluation only already defined and + // built-in classes are allowed. + std::string cc = $3; + if (!ctx.isClientClassDefined(cc)) { + error(@3, "Not defined client class '" + cc + "'"); + } + TokenPtr member(new TokenMember(cc)); + ctx.expression.push_back(member); + } + ; + +string_expr : STRING + { + TokenPtr str(new TokenString($1)); + ctx.expression.push_back(str); + } + | HEXSTRING + { + TokenPtr hex(new TokenHexString($1)); + ctx.expression.push_back(hex); + } + | IP_ADDRESS + { + TokenPtr ip(new TokenIpAddress($1)); + ctx.expression.push_back(ip); + } + | OPTION "[" option_code "]" "." option_repr_type + { + TokenPtr opt(new TokenOption($3, $6)); + ctx.expression.push_back(opt); + } + | OPTION "[" option_code "]" "." OPTION "[" sub_option_code "]" "." option_repr_type + { + TokenPtr opt(new TokenSubOption($3, $8, $11)); + ctx.expression.push_back(opt); + } + | RELAY4 "[" sub_option_code "]" "." option_repr_type + { + switch (ctx.getUniverse()) { + case Option::V4: + { + TokenPtr opt(new TokenRelay4Option($3, $6)); + ctx.expression.push_back(opt); + break; + } + case Option::V6: + // We will have relay6[123] for the DHCPv6. + // In a very distant future we'll possibly be able + // to mix both if we have DHCPv4-over-DHCPv6, so it + // has some sense to make it explicit whether we + // talk about DHCPv4 relay or DHCPv6 relay. However, + // for the time being relay4 can be used in DHCPv4 + // only. + error(@1, "relay4 can only be used in DHCPv4."); + } + } + + | RELAY6 "[" nest_level "]" "." OPTION "[" sub_option_code "]" "." option_repr_type + { + switch (ctx.getUniverse()) { + case Option::V6: + { + TokenPtr opt(new TokenRelay6Option($3, $8, $11)); + ctx.expression.push_back(opt); + break; + } + case Option::V4: + // For now we only use relay6 in DHCPv6. + error(@1, "relay6 can only be used in DHCPv6."); + } + } + + | PKT "." pkt_metadata + { + TokenPtr pkt_metadata(new TokenPkt($3)); + ctx.expression.push_back(pkt_metadata); + } + | PKT4 "." pkt4_field + { + switch (ctx.getUniverse()) { + case Option::V4: + { + TokenPtr pkt4_field(new TokenPkt4($3)); + ctx.expression.push_back(pkt4_field); + break; + } + case Option::V6: + // For now we only use pkt4 in DHCPv4. + error(@1, "pkt4 can only be used in DHCPv4."); + } + } + | PKT6 "." pkt6_field + { + switch (ctx.getUniverse()) { + case Option::V6: + { + TokenPtr pkt6_field(new TokenPkt6($3)); + ctx.expression.push_back(pkt6_field); + break; + } + case Option::V4: + // For now we only use pkt6 in DHCPv6. + error(@1, "pkt6 can only be used in DHCPv6."); + } + } + | RELAY6 "[" nest_level "]" "." relay6_field + { + switch (ctx.getUniverse()) { + case Option::V6: + { + TokenPtr relay6field(new TokenRelay6Field($3, $6)); + ctx.expression.push_back(relay6field); + break; + } + case Option::V4: + // For now we only use relay6 in DHCPv6. + error(@1, "relay6 can only be used in DHCPv6."); + } + } + + | SUBSTRING "(" string_expr "," start_expr "," length_expr ")" + { + TokenPtr sub(new TokenSubstring()); + ctx.expression.push_back(sub); + } + | SPLIT "(" string_expr "," string_expr "," int_expr ")" + { + TokenPtr split(new TokenSplit()); + ctx.expression.push_back(split); + } + | CONCAT "(" string_expr "," string_expr ")" + { + TokenPtr conc(new TokenConcat()); + ctx.expression.push_back(conc); + } + | string_expr PLUS string_expr + { + TokenPtr conc(new TokenConcat()); + ctx.expression.push_back(conc); + } + | IFELSE "(" bool_expr "," string_expr "," string_expr ")" + { + TokenPtr cond(new TokenIfElse()); + ctx.expression.push_back(cond); + } + | TOHEXSTRING "(" string_expr "," string_expr ")" + { + TokenPtr tohex(new TokenToHexString()); + ctx.expression.push_back(tohex); + } + | ADDRTOTEXT "(" string_expr ")" + { + TokenPtr addrtotext(new TokenIpAddressToText()); + ctx.expression.push_back(addrtotext); + } + | INT8TOTEXT "(" string_expr ")" + { + TokenPtr int8totext(new TokenInt8ToText()); + ctx.expression.push_back(int8totext); + } + | INT16TOTEXT "(" string_expr ")" + { + TokenPtr int16totext(new TokenInt16ToText()); + ctx.expression.push_back(int16totext); + } + | INT32TOTEXT "(" string_expr ")" + { + TokenPtr int32totext(new TokenInt32ToText()); + ctx.expression.push_back(int32totext); + } + | UINT8TOTEXT "(" string_expr ")" + { + TokenPtr uint8totext(new TokenUInt8ToText()); + ctx.expression.push_back(uint8totext); + } + | UINT16TOTEXT "(" string_expr ")" + { + TokenPtr uint16totext(new TokenUInt16ToText()); + ctx.expression.push_back(uint16totext); + } + | UINT32TOTEXT "(" string_expr ")" + { + TokenPtr uint32totext(new TokenUInt32ToText()); + ctx.expression.push_back(uint32totext); + } + | VENDOR "." ENTERPRISE + { + // expression: vendor.enterprise + // + // This token will return enterprise-id number of + // received vendor option. + TokenPtr vendor(new TokenVendor(ctx.getUniverse(), 0, TokenVendor::ENTERPRISE_ID)); + ctx.expression.push_back(vendor); + } + | VENDOR_CLASS "." ENTERPRISE + { + // expression: vendor-class.enterprise + // + // This token will return enterprise-id number of + // received vendor class option. + TokenPtr vendor(new TokenVendorClass(ctx.getUniverse(), 0, + TokenVendor::ENTERPRISE_ID)); + ctx.expression.push_back(vendor); + } + | VENDOR "[" enterprise_id "]" "." OPTION "[" sub_option_code "]" "." option_repr_type + { + // This token will search for vendor option with + // specified enterprise-id. If found, will search + // for specified suboption and finally will return + // its content. + TokenPtr opt(new TokenVendor(ctx.getUniverse(), $3, $11, $8)); + ctx.expression.push_back(opt); + } + | VENDOR_CLASS "[" enterprise_id "]" "." DATA + { + // expression: vendor-class[1234].data + // + // Vendor class option does not have suboptions, + // but chunks of data (typically 1, but the option + // structure allows multiple of them). If chunk + // offset is not specified, we assume the first (0th) + // is requested. + TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), $3, + TokenVendor::DATA, 0)); + ctx.expression.push_back(vendor_class); + } + | VENDOR_CLASS "[" enterprise_id "]" "." DATA "[" INTEGER "]" + { + // expression: vendor-class[1234].data[5] + // + // Vendor class option does not have suboptions, + // but chunks of data (typically 1, but the option + // structure allows multiple of them). This syntax + // specifies which data chunk (tuple) we want. + uint8_t index = ctx.convertUint8($8, @8); + TokenPtr vendor_class(new TokenVendorClass(ctx.getUniverse(), $3, + TokenVendor::DATA, index)); + ctx.expression.push_back(vendor_class); + } + | integer_expr + { + TokenPtr integer(new TokenInteger($1)); + ctx.expression.push_back(integer); + } + | "(" string_expr ")" + ; + +integer_expr : INTEGER + { + $$ = ctx.convertUint32($1, @1); + } + ; + +option_code : INTEGER + { + $$ = ctx.convertOptionCode($1, @1); + } + | OPTION_NAME + { + $$ = ctx.convertOptionName($1, @1); + } + ; + +sub_option_code : INTEGER + { + $$ = ctx.convertOptionCode($1, @1); + } + ; + +option_repr_type : TEXT + { + $$ = TokenOption::TEXTUAL; + } + | HEX + { + $$ = TokenOption::HEXADECIMAL; + } + ; + +nest_level : INTEGER + { + $$ = ctx.convertNestLevelNumber($1, @1); + } + // Eventually we may add strings to handle different + // ways of choosing from which relay we want to extract + // an option or field. + ; + +pkt_metadata : IFACE + { + $$ = TokenPkt::IFACE; + } + | SRC + { + $$ = TokenPkt::SRC; + } + | DST + { + $$ = TokenPkt::DST; + } + | LEN + { + $$ = TokenPkt::LEN; + } + ; + +enterprise_id : INTEGER + { + $$ = ctx.convertUint32($1, @1); + } + | "*" + { + $$ = 0; + } + ; + +pkt4_field : CHADDR + { + $$ = TokenPkt4::CHADDR; + } + | HLEN + { + $$ = TokenPkt4::HLEN; + } + | HTYPE + { + $$ = TokenPkt4::HTYPE; + } + | CIADDR + { + $$ = TokenPkt4::CIADDR; + } + | GIADDR + { + $$ = TokenPkt4::GIADDR; + } + | YIADDR + { + $$ = TokenPkt4::YIADDR; + } + | SIADDR + { + $$ = TokenPkt4::SIADDR; + } + | MSGTYPE + { + $$ = TokenPkt4::MSGTYPE; + } + | TRANSID + { + $$ = TokenPkt4::TRANSID; + } + ; + +pkt6_field : MSGTYPE + { + $$ = TokenPkt6::MSGTYPE; + } + | TRANSID + { + $$ = TokenPkt6::TRANSID; + } + ; + +relay6_field : PEERADDR + { + $$ = TokenRelay6Field::PEERADDR; + } + | LINKADDR + { + $$ = TokenRelay6Field::LINKADDR; + } + ; + +start_expr : INTEGER + { + TokenPtr str(new TokenString($1)); + ctx.expression.push_back(str); + } + ; + +length_expr : INTEGER + { + TokenPtr str(new TokenString($1)); + ctx.expression.push_back(str); + } + | ALL + { + TokenPtr str(new TokenString("all")); + ctx.expression.push_back(str); + } + ; +int_expr : INTEGER + { + TokenPtr str(new TokenString($1)); + ctx.expression.push_back(str); + } + ; + +%% +void +isc::eval::EvalParser::error(const location_type& loc, + const std::string& what) +{ + ctx.error(loc, what); +} diff --git a/src/lib/eval/tests/Makefile.am b/src/lib/eval/tests/Makefile.am new file mode 100644 index 0000000..13ba097 --- /dev/null +++ b/src/lib/eval/tests/Makefile.am @@ -0,0 +1,46 @@ +SUBDIRS = . + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CPPFLAGS += -DLOGGING_SPEC_FILE=\"$(abs_top_srcdir)/src/lib/dhcpsrv/logging.spec\" + +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 += libeval_unittests + +libeval_unittests_SOURCES = boolean_unittest.cc +libeval_unittests_SOURCES += context_unittest.cc +libeval_unittests_SOURCES += dependency_unittest.cc +libeval_unittests_SOURCES += evaluate_unittest.cc +libeval_unittests_SOURCES += token_unittest.cc +libeval_unittests_SOURCES += run_unittests.cc +libeval_unittests_CXXFLAGS = $(AM_CXXFLAGS) +libeval_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +libeval_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) +libeval_unittests_LDADD = $(top_builddir)/src/lib/eval/libkea-eval.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +libeval_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libeval_unittests_LDADD += $(CRYPTO_LIBS) $(LOG4CPLUS_LIBS) +libeval_unittests_LDADD += $(BOOST_LIBS) $(GTEST_LDADD) +endif + +noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/eval/tests/Makefile.in b/src/lib/eval/tests/Makefile.in new file mode 100644 index 0000000..6aa01a2 --- /dev/null +++ b/src/lib/eval/tests/Makefile.in @@ -0,0 +1,1098 @@ +# 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 = libeval_unittests +noinst_PROGRAMS = $(am__EXEEXT_2) +subdir = src/lib/eval/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 = libeval_unittests$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +PROGRAMS = $(noinst_PROGRAMS) +am__libeval_unittests_SOURCES_DIST = boolean_unittest.cc \ + context_unittest.cc dependency_unittest.cc \ + evaluate_unittest.cc token_unittest.cc run_unittests.cc +@HAVE_GTEST_TRUE@am_libeval_unittests_OBJECTS = \ +@HAVE_GTEST_TRUE@ libeval_unittests-boolean_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libeval_unittests-context_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libeval_unittests-dependency_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libeval_unittests-evaluate_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libeval_unittests-token_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ libeval_unittests-run_unittests.$(OBJEXT) +libeval_unittests_OBJECTS = $(am_libeval_unittests_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_GTEST_TRUE@libeval_unittests_DEPENDENCIES = \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +libeval_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) \ + $(libeval_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)/libeval_unittests-boolean_unittest.Po \ + ./$(DEPDIR)/libeval_unittests-context_unittest.Po \ + ./$(DEPDIR)/libeval_unittests-dependency_unittest.Po \ + ./$(DEPDIR)/libeval_unittests-evaluate_unittest.Po \ + ./$(DEPDIR)/libeval_unittests-run_unittests.Po \ + ./$(DEPDIR)/libeval_unittests-token_unittest.Po +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +SOURCES = $(libeval_unittests_SOURCES) +DIST_SOURCES = $(am__libeval_unittests_SOURCES_DIST) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + fi; \ +} +DIST_SUBDIRS = $(SUBDIRS) +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +am__relativize = \ + dir0=`pwd`; \ + sed_first='s,^\([^/]*\)/.*$$,\1,'; \ + sed_rest='s,^[^/]*/*,,'; \ + sed_last='s,^.*/\([^/]*\)$$,\1,'; \ + sed_butlast='s,/*[^/]*$$,,'; \ + while test -n "$$dir1"; do \ + first=`echo "$$dir1" | sed -e "$$sed_first"`; \ + if test "$$first" != "."; then \ + if test "$$first" = ".."; then \ + dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \ + dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \ + else \ + first2=`echo "$$dir2" | sed -e "$$sed_first"`; \ + if test "$$first2" = "$$first"; then \ + dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \ + else \ + dir2="../$$dir2"; \ + fi; \ + dir0="$$dir0"/"$$first"; \ + fi; \ + fi; \ + dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \ + done; \ + reldir="$$dir2" +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CRYPTO_CFLAGS = @CRYPTO_CFLAGS@ +CRYPTO_INCLUDES = @CRYPTO_INCLUDES@ +CRYPTO_LDFLAGS = @CRYPTO_LDFLAGS@ +CRYPTO_LIBS = @CRYPTO_LIBS@ +CRYPTO_PACKAGE = @CRYPTO_PACKAGE@ +CRYPTO_RPATH = @CRYPTO_RPATH@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DISTCHECK_BOOST_CONFIGURE_FLAG = @DISTCHECK_BOOST_CONFIGURE_FLAG@ +DISTCHECK_CONTRIB_CONFIGURE_FLAG = @DISTCHECK_CONTRIB_CONFIGURE_FLAG@ +DISTCHECK_CRYPTO_CONFIGURE_FLAG = @DISTCHECK_CRYPTO_CONFIGURE_FLAG@ +DISTCHECK_GSSAPI_CONFIGURE_FLAG = @DISTCHECK_GSSAPI_CONFIGURE_FLAG@ +DISTCHECK_GTEST_CONFIGURE_FLAG = @DISTCHECK_GTEST_CONFIGURE_FLAG@ +DISTCHECK_KEA_SHELL_CONFIGURE_FLAG = @DISTCHECK_KEA_SHELL_CONFIGURE_FLAG@ +DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG = @DISTCHECK_LIBYANGCPP_CONFIGURE_FLAG@ +DISTCHECK_LIBYANG_CONFIGURE_FLAG = @DISTCHECK_LIBYANG_CONFIGURE_FLAG@ +DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG = @DISTCHECK_LOG4CPLUS_CONFIGURE_FLAG@ +DISTCHECK_MYSQL_CONFIGURE_FLAG = @DISTCHECK_MYSQL_CONFIGURE_FLAG@ +DISTCHECK_PERFDHCP_CONFIGURE_FLAG = @DISTCHECK_PERFDHCP_CONFIGURE_FLAG@ +DISTCHECK_PGSQL_CONFIGURE_FLAG = @DISTCHECK_PGSQL_CONFIGURE_FLAG@ +DISTCHECK_PREMIUM_CONFIGURE_FLAG = @DISTCHECK_PREMIUM_CONFIGURE_FLAG@ +DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG = @DISTCHECK_SYSREPOCPP_CONFIGURE_FLAG@ +DISTCHECK_SYSREPO_CONFIGURE_FLAG = @DISTCHECK_SYSREPO_CONFIGURE_FLAG@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GENHTML = @GENHTML@ +GREP = @GREP@ +GSSAPI_CFLAGS = @GSSAPI_CFLAGS@ +GSSAPI_LIBS = @GSSAPI_LIBS@ +GTEST_CONFIG = @GTEST_CONFIG@ +GTEST_INCLUDES = @GTEST_INCLUDES@ +GTEST_LDADD = @GTEST_LDADD@ +GTEST_LDFLAGS = @GTEST_LDFLAGS@ +GTEST_SOURCE = @GTEST_SOURCE@ +HAVE_NETCONF = @HAVE_NETCONF@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +KEA_CXXFLAGS = @KEA_CXXFLAGS@ +KEA_SRCID = @KEA_SRCID@ +KRB5_CONFIG = @KRB5_CONFIG@ +LCOV = @LCOV@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LEX = @LEX@ +LEXLIB = @LEXLIB@ +LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIBYANGCPP_CPPFLAGS = @LIBYANGCPP_CPPFLAGS@ +LIBYANGCPP_INCLUDEDIR = @LIBYANGCPP_INCLUDEDIR@ +LIBYANGCPP_LIBS = @LIBYANGCPP_LIBS@ +LIBYANGCPP_PREFIX = @LIBYANGCPP_PREFIX@ +LIBYANGCPP_VERSION = @LIBYANGCPP_VERSION@ +LIBYANG_CPPFLAGS = @LIBYANG_CPPFLAGS@ +LIBYANG_INCLUDEDIR = @LIBYANG_INCLUDEDIR@ +LIBYANG_LIBS = @LIBYANG_LIBS@ +LIBYANG_PREFIX = @LIBYANG_PREFIX@ +LIBYANG_VERSION = @LIBYANG_VERSION@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LOG4CPLUS_INCLUDES = @LOG4CPLUS_INCLUDES@ +LOG4CPLUS_LIBS = @LOG4CPLUS_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +MYSQL_CPPFLAGS = @MYSQL_CPPFLAGS@ +MYSQL_LIBS = @MYSQL_LIBS@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PACKAGE_VERSION_TYPE = @PACKAGE_VERSION_TYPE@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PDFLATEX = @PDFLATEX@ +PERL = @PERL@ +PGSQL_CPPFLAGS = @PGSQL_CPPFLAGS@ +PGSQL_LIBS = @PGSQL_LIBS@ +PKGPYTHONDIR = @PKGPYTHONDIR@ +PKG_CONFIG = @PKG_CONFIG@ +PLANTUML = @PLANTUML@ +PREMIUM_DIR = @PREMIUM_DIR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SED = @SED@ +SEP = @SEP@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPHINXBUILD = @SPHINXBUILD@ +SRPD_PLUGINS_PATH = @SRPD_PLUGINS_PATH@ +SR_PLUGINS_PATH = @SR_PLUGINS_PATH@ +SR_REPO_PATH = @SR_REPO_PATH@ +STRIP = @STRIP@ +SYSREPOCPP_CPPFLAGS = @SYSREPOCPP_CPPFLAGS@ +SYSREPOCPP_INCLUDEDIR = @SYSREPOCPP_INCLUDEDIR@ +SYSREPOCPP_LIBS = @SYSREPOCPP_LIBS@ +SYSREPOCPP_PREFIX = @SYSREPOCPP_PREFIX@ +SYSREPOCPP_VERSION = @SYSREPOCPP_VERSION@ +SYSREPO_CPPFLAGS = @SYSREPO_CPPFLAGS@ +SYSREPO_INCLUDEDIR = @SYSREPO_INCLUDEDIR@ +SYSREPO_LIBS = @SYSREPO_LIBS@ +SYSREPO_PREFIX = @SYSREPO_PREFIX@ +SYSREPO_VERSION = @SYSREPO_VERSION@ +USE_LCOV = @USE_LCOV@ +VALGRIND = @VALGRIND@ +VERSION = @VERSION@ +WARNING_GCC_44_STRICT_ALIASING_CFLAG = @WARNING_GCC_44_STRICT_ALIASING_CFLAG@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = . +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \ + $(BOOST_INCLUDES) \ + -DLOGGING_SPEC_FILE=\"$(abs_top_srcdir)/src/lib/dhcpsrv/logging.spec\" +AM_CXXFLAGS = $(KEA_CXXFLAGS) +@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static +CLEANFILES = *.gcno *.gcda +TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) +@HAVE_GTEST_TRUE@libeval_unittests_SOURCES = boolean_unittest.cc \ +@HAVE_GTEST_TRUE@ context_unittest.cc dependency_unittest.cc \ +@HAVE_GTEST_TRUE@ evaluate_unittest.cc token_unittest.cc \ +@HAVE_GTEST_TRUE@ run_unittests.cc +@HAVE_GTEST_TRUE@libeval_unittests_CXXFLAGS = $(AM_CXXFLAGS) +@HAVE_GTEST_TRUE@libeval_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +@HAVE_GTEST_TRUE@libeval_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) +@HAVE_GTEST_TRUE@libeval_unittests_LDADD = \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/eval/libkea-eval.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/hooks/libkea-hooks.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/testutils/libkea-testutils.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cc/libkea-cc.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/asiolink/libkea-asiolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/log/libkea-log.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(CRYPTO_LIBS) $(LOG4CPLUS_LIBS) \ +@HAVE_GTEST_TRUE@ $(BOOST_LIBS) $(GTEST_LDADD) +all: all-recursive + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/eval/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/eval/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 + +libeval_unittests$(EXEEXT): $(libeval_unittests_OBJECTS) $(libeval_unittests_DEPENDENCIES) $(EXTRA_libeval_unittests_DEPENDENCIES) + @rm -f libeval_unittests$(EXEEXT) + $(AM_V_CXXLD)$(libeval_unittests_LINK) $(libeval_unittests_OBJECTS) $(libeval_unittests_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-boolean_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-context_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-dependency_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-evaluate_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-run_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libeval_unittests-token_unittest.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +libeval_unittests-boolean_unittest.o: boolean_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-boolean_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-boolean_unittest.Tpo -c -o libeval_unittests-boolean_unittest.o `test -f 'boolean_unittest.cc' || echo '$(srcdir)/'`boolean_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-boolean_unittest.Tpo $(DEPDIR)/libeval_unittests-boolean_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='boolean_unittest.cc' object='libeval_unittests-boolean_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-boolean_unittest.o `test -f 'boolean_unittest.cc' || echo '$(srcdir)/'`boolean_unittest.cc + +libeval_unittests-boolean_unittest.obj: boolean_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-boolean_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-boolean_unittest.Tpo -c -o libeval_unittests-boolean_unittest.obj `if test -f 'boolean_unittest.cc'; then $(CYGPATH_W) 'boolean_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/boolean_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-boolean_unittest.Tpo $(DEPDIR)/libeval_unittests-boolean_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='boolean_unittest.cc' object='libeval_unittests-boolean_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-boolean_unittest.obj `if test -f 'boolean_unittest.cc'; then $(CYGPATH_W) 'boolean_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/boolean_unittest.cc'; fi` + +libeval_unittests-context_unittest.o: context_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-context_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-context_unittest.Tpo -c -o libeval_unittests-context_unittest.o `test -f 'context_unittest.cc' || echo '$(srcdir)/'`context_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-context_unittest.Tpo $(DEPDIR)/libeval_unittests-context_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='context_unittest.cc' object='libeval_unittests-context_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-context_unittest.o `test -f 'context_unittest.cc' || echo '$(srcdir)/'`context_unittest.cc + +libeval_unittests-context_unittest.obj: context_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-context_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-context_unittest.Tpo -c -o libeval_unittests-context_unittest.obj `if test -f 'context_unittest.cc'; then $(CYGPATH_W) 'context_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/context_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-context_unittest.Tpo $(DEPDIR)/libeval_unittests-context_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='context_unittest.cc' object='libeval_unittests-context_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-context_unittest.obj `if test -f 'context_unittest.cc'; then $(CYGPATH_W) 'context_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/context_unittest.cc'; fi` + +libeval_unittests-dependency_unittest.o: dependency_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-dependency_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-dependency_unittest.Tpo -c -o libeval_unittests-dependency_unittest.o `test -f 'dependency_unittest.cc' || echo '$(srcdir)/'`dependency_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-dependency_unittest.Tpo $(DEPDIR)/libeval_unittests-dependency_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dependency_unittest.cc' object='libeval_unittests-dependency_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-dependency_unittest.o `test -f 'dependency_unittest.cc' || echo '$(srcdir)/'`dependency_unittest.cc + +libeval_unittests-dependency_unittest.obj: dependency_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-dependency_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-dependency_unittest.Tpo -c -o libeval_unittests-dependency_unittest.obj `if test -f 'dependency_unittest.cc'; then $(CYGPATH_W) 'dependency_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dependency_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-dependency_unittest.Tpo $(DEPDIR)/libeval_unittests-dependency_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dependency_unittest.cc' object='libeval_unittests-dependency_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-dependency_unittest.obj `if test -f 'dependency_unittest.cc'; then $(CYGPATH_W) 'dependency_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dependency_unittest.cc'; fi` + +libeval_unittests-evaluate_unittest.o: evaluate_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-evaluate_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-evaluate_unittest.Tpo -c -o libeval_unittests-evaluate_unittest.o `test -f 'evaluate_unittest.cc' || echo '$(srcdir)/'`evaluate_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-evaluate_unittest.Tpo $(DEPDIR)/libeval_unittests-evaluate_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='evaluate_unittest.cc' object='libeval_unittests-evaluate_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-evaluate_unittest.o `test -f 'evaluate_unittest.cc' || echo '$(srcdir)/'`evaluate_unittest.cc + +libeval_unittests-evaluate_unittest.obj: evaluate_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-evaluate_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-evaluate_unittest.Tpo -c -o libeval_unittests-evaluate_unittest.obj `if test -f 'evaluate_unittest.cc'; then $(CYGPATH_W) 'evaluate_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/evaluate_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-evaluate_unittest.Tpo $(DEPDIR)/libeval_unittests-evaluate_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='evaluate_unittest.cc' object='libeval_unittests-evaluate_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-evaluate_unittest.obj `if test -f 'evaluate_unittest.cc'; then $(CYGPATH_W) 'evaluate_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/evaluate_unittest.cc'; fi` + +libeval_unittests-token_unittest.o: token_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-token_unittest.o -MD -MP -MF $(DEPDIR)/libeval_unittests-token_unittest.Tpo -c -o libeval_unittests-token_unittest.o `test -f 'token_unittest.cc' || echo '$(srcdir)/'`token_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-token_unittest.Tpo $(DEPDIR)/libeval_unittests-token_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='token_unittest.cc' object='libeval_unittests-token_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-token_unittest.o `test -f 'token_unittest.cc' || echo '$(srcdir)/'`token_unittest.cc + +libeval_unittests-token_unittest.obj: token_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-token_unittest.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-token_unittest.Tpo -c -o libeval_unittests-token_unittest.obj `if test -f 'token_unittest.cc'; then $(CYGPATH_W) 'token_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/token_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-token_unittest.Tpo $(DEPDIR)/libeval_unittests-token_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='token_unittest.cc' object='libeval_unittests-token_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-token_unittest.obj `if test -f 'token_unittest.cc'; then $(CYGPATH_W) 'token_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/token_unittest.cc'; fi` + +libeval_unittests-run_unittests.o: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/libeval_unittests-run_unittests.Tpo -c -o libeval_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libeval_unittests-run_unittests.Tpo $(DEPDIR)/libeval_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libeval_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc + +libeval_unittests-run_unittests.obj: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -MT libeval_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/libeval_unittests-run_unittests.Tpo -c -o libeval_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)/libeval_unittests-run_unittests.Tpo $(DEPDIR)/libeval_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='libeval_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) $(libeval_unittests_CPPFLAGS) $(CPPFLAGS) $(libeval_unittests_CXXFLAGS) $(CXXFLAGS) -c -o libeval_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 + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + fi; \ + echo "$${col}$$dashes$${std}"; \ + echo "$${col}$$banner$${std}"; \ + test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \ + test -z "$$report" || echo "$${col}$$report$${std}"; \ + echo "$${col}$$dashes$${std}"; \ + test "$$failed" -eq 0; \ + else :; fi + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-recursive +all-am: Makefile $(PROGRAMS) +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/libeval_unittests-boolean_unittest.Po + -rm -f ./$(DEPDIR)/libeval_unittests-context_unittest.Po + -rm -f ./$(DEPDIR)/libeval_unittests-dependency_unittest.Po + -rm -f ./$(DEPDIR)/libeval_unittests-evaluate_unittest.Po + -rm -f ./$(DEPDIR)/libeval_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/libeval_unittests-token_unittest.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/libeval_unittests-boolean_unittest.Po + -rm -f ./$(DEPDIR)/libeval_unittests-context_unittest.Po + -rm -f ./$(DEPDIR)/libeval_unittests-dependency_unittest.Po + -rm -f ./$(DEPDIR)/libeval_unittests-evaluate_unittest.Po + -rm -f ./$(DEPDIR)/libeval_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/libeval_unittests-token_unittest.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: + +.MAKE: $(am__recursive_targets) check-am install-am install-strip + +.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am \ + am--depfiles check check-TESTS check-am clean clean-generic \ + clean-libtool clean-noinstPROGRAMS cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/lib/eval/tests/boolean_unittest.cc b/src/lib/eval/tests/boolean_unittest.cc new file mode 100644 index 0000000..f620dae --- /dev/null +++ b/src/lib/eval/tests/boolean_unittest.cc @@ -0,0 +1,64 @@ +// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <eval/eval_context.h> +#include <eval/evaluate.h> +#include <eval/token.h> +#include <dhcp/pkt4.h> + +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc::dhcp; + +namespace { + +/// @brief Test fixture for testing booleans. +class BooleanTest : public ::testing::Test { +public: + void check(const string& expr, bool expected) { + EvalContext eval(Option::V4); + ASSERT_TRUE(eval.parseString(expr)); + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + if (expected) { + EXPECT_TRUE(evaluateBool(eval.expression, *pkt4)); + } else { + EXPECT_FALSE(evaluateBool(eval.expression, *pkt4)); + } + } +}; + +// A group of tests +TEST_F(BooleanTest, tests) { + // true and (false or false) + check("('a' == 'a') and (('a' == 'b') or ('b' == 'a'))", false); + // (true and false) or false + check("(('a' == 'a') and ('a' == 'b')) or ('b' == 'a')", false); + // not true + check("not ('a' == 'a')", false); + // not false + check("not ('a' == 'b')", true); + // true and true and true and false + check("('a' == 'a') and ('b' == 'b') and ('c' == 'c') and ('a' == 'c')", + false); + // false or false or false or true + check("('a' == 'b') or ('a' == 'c') or ('b' == 'c') or ('b' == 'b')", + true); + // true or false or false or false + check("('a' == 'a') or ('a' == 'b') or ('a' == 'c') or ('b' == 'c')", + true); + // not (true or false) + check("not (('a' == 'a') or ('a' == 'b'))", false); + // not (true and false) + check("not (('a' == 'a') and ('a' == 'b'))", true); + // (not true) and false + check("(not ('a' == 'a')) and ('a' == 'b')",false); +} + +}; diff --git a/src/lib/eval/tests/context_unittest.cc b/src/lib/eval/tests/context_unittest.cc new file mode 100644 index 0000000..26ef533 --- /dev/null +++ b/src/lib/eval/tests/context_unittest.cc @@ -0,0 +1,2118 @@ +// Copyright (C) 2015-2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <eval/eval_context.h> +#include <eval/token.h> +#include <dhcp/option.h> +#include <dhcp/pkt4.h> +#include <asiolink/io_address.h> + +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// @brief Test class for testing EvalContext aka class test parsing +class EvalContextTest : public ::testing::Test { +public: + /// @brief constructor to initialize members + EvalContextTest() : ::testing::Test(), + universe_(Option::V4), parsed_(false) + { } + + /// @brief checks if the given token is a string with the expected value + void checkTokenString(const TokenPtr& token, const std::string& expected) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenString> str = + boost::dynamic_pointer_cast<TokenString>(token); + ASSERT_TRUE(str); + + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + ValueStack values; + + EXPECT_NO_THROW(token->evaluate(*pkt4, values)); + + ASSERT_EQ(1, values.size()); + + EXPECT_EQ(expected, values.top()); + } + + /// @brief checks if the given token is a hex string with the expected value + void checkTokenHexString(const TokenPtr& token, + const std::string& expected) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenHexString> hex = + boost::dynamic_pointer_cast<TokenHexString>(token); + ASSERT_TRUE(hex); + + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + ValueStack values; + + EXPECT_NO_THROW(token->evaluate(*pkt4, values)); + + ASSERT_EQ(1, values.size()); + + EXPECT_EQ(expected, values.top()); + } + + /// @brief checks if the given token is an IP address with the expected value + void checkTokenIpAddress(const TokenPtr& token, + const std::string& expected) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenIpAddress> ipaddr = + boost::dynamic_pointer_cast<TokenIpAddress>(token); + ASSERT_TRUE(ipaddr); + + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + ValueStack values; + + EXPECT_NO_THROW(token->evaluate(*pkt4, values)); + + ASSERT_EQ(1, values.size()); + string value = values.top(); + + boost::scoped_ptr<IOAddress> exp_ip; + ASSERT_NO_THROW(exp_ip.reset(new IOAddress(expected))); + vector<uint8_t> exp_addr = exp_ip->toBytes(); + ASSERT_EQ(exp_addr.size(), value.size()); + EXPECT_EQ(0, memcmp(&exp_addr[0], &value[0], value.size())); + } + + /// @brief checks if the given token is an equal operator + void checkTokenEq(const TokenPtr& token) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenEqual> eq = + boost::dynamic_pointer_cast<TokenEqual>(token); + EXPECT_TRUE(eq); + } + + /// @brief Checks if the given token is integer with expected value + /// + /// @param token token to be inspected + /// @param exp_value expected integer value of the token + void checkTokenInteger(const TokenPtr& token, uint32_t exp_value) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenInteger> integer = + boost::dynamic_pointer_cast<TokenInteger>(token); + ASSERT_TRUE(integer); + EXPECT_EQ(exp_value, integer->getInteger()); + } + + /// @brief checks if the given token is an option with the expected code + /// and representation type + /// @param token token to be checked + /// @param expected_code expected option code + /// @param expected_repr expected representation (text, hex, exists) + void checkTokenOption(const TokenPtr& token, + uint16_t expected_code, + TokenOption::RepresentationType expected_repr) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenOption> opt = + boost::dynamic_pointer_cast<TokenOption>(token); + ASSERT_TRUE(opt); + + EXPECT_EQ(expected_code, opt->getCode()); + EXPECT_EQ(expected_repr, opt->getRepresentation()); + } + + /// @brief check if the given token is relay4 with the expected code + /// and representation type + /// @param token token to be checked + /// @param expected_code expected option code + /// @param expected_repr expected representation (text, hex, exists) + void checkTokenRelay4(const TokenPtr& token, + uint16_t expected_code, + TokenOption::RepresentationType expected_repr) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenRelay4Option> relay4 = + boost::dynamic_pointer_cast<TokenRelay4Option>(token); + EXPECT_TRUE(relay4); + + if (relay4) { + EXPECT_EQ(expected_code, relay4->getCode()); + EXPECT_EQ(expected_repr, relay4->getRepresentation()); + } + } + + /// @brief checks if the given token is a TokenRelay6Option with + /// the correct nesting level, option code and representation. + /// @param token token to be checked + /// @param expected_level expected nesting level + /// @param expected_code expected option code + /// @param expected_repr expected representation (text, hex, exists) + void checkTokenRelay6Option(const TokenPtr& token, + int8_t expected_level, + uint16_t expected_code, + TokenOption::RepresentationType expected_repr) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenRelay6Option> opt = + boost::dynamic_pointer_cast<TokenRelay6Option>(token); + ASSERT_TRUE(opt); + + EXPECT_EQ(expected_level, opt->getNest()); + EXPECT_EQ(expected_code, opt->getCode()); + EXPECT_EQ(expected_repr, opt->getRepresentation()); + } + + /// @brief This tests attempts to parse the expression then checks + /// if the number of tokens is correct and the TokenRelay6Option + /// is as expected. + /// + /// @param expr expression to be parsed + /// @param exp_level expected level to be parsed + /// @param exp_code expected option code to be parsed + /// @param exp_repr expected representation to be parsed + /// @param exp_tokens expected number of tokens + void testRelay6Option(const std::string& expr, + int8_t exp_level, + uint16_t exp_code, + TokenOption::RepresentationType exp_repr, + int exp_tokens) { + EvalContext eval(Option::V6); + + // parse the expression + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() <<"Exception thrown: " << ex.what(); + return; + } + + // Parsing should succeed and return a token. + EXPECT_TRUE(parsed_); + + // There should be the expected number of tokens. + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // checked that the first token is TokenRelay6Option and that + // is has the correct attributes + checkTokenRelay6Option(eval.expression.at(0), exp_level, exp_code, exp_repr); + } + + /// @brief check if the given token is a Pkt of specified type + /// @param token token to be checked + /// @param type expected type of the Pkt metadata + void checkTokenPkt(const TokenPtr& token, TokenPkt::MetadataType type) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenPkt> pkt = + boost::dynamic_pointer_cast<TokenPkt>(token); + ASSERT_TRUE(pkt); + + EXPECT_EQ(type, pkt->getType()); + } + + /// @brief Test that verifies access to the DHCP packet metadata. + /// + /// This test attempts to parse the expression, will check if the number + /// of tokens is exactly as expected and then will try to verify if the + /// first token represents the expected metadata in DHCP packet. + /// + /// @param expr expression to be parsed + /// @param exp_type expected metadata type to be parsed + /// @param exp_tokens expected number of tokens + void testPktMetadata(const std::string& expr, + TokenPkt::MetadataType exp_type, + int exp_tokens) { + EvalContext eval(Option::V6); + + // Parse the expression. + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() << "Exception thrown: " << ex.what(); + return; + } + + // Parsing should succeed and return a token. + EXPECT_TRUE(parsed_); + + // There should be exactly the expected number of tokens. + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // Check that the first token is TokenPkt instance and has correct type. + checkTokenPkt(eval.expression.at(0), exp_type); + } + + /// @brief checks if the given token is Pkt4 of specified type + /// @param token token to be checked + /// @param type expected type of the Pkt4 field + void checkTokenPkt4(const TokenPtr& token, TokenPkt4::FieldType type) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenPkt4> pkt = + boost::dynamic_pointer_cast<TokenPkt4>(token); + ASSERT_TRUE(pkt); + + EXPECT_EQ(type, pkt->getType()); + } + + /// @brief Test that verifies access to the DHCPv4 packet fields. + /// + /// This test attempts to parse the expression, will check if the number + /// of tokens is exactly as expected and then will try to verify if the + /// first token represents the expected field in DHCPv4 packet. + /// + /// @param expr expression to be parsed + /// @param exp_type expected field type to be parsed + /// @param exp_tokens expected number of tokens + void testPkt4Field(const std::string& expr, + TokenPkt4::FieldType exp_type, + int exp_tokens) { + EvalContext eval(Option::V4); + + // Parse the expression. + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() << "Exception thrown: " << ex.what(); + return; + } + + // Parsing should succeed and return a token. + EXPECT_TRUE(parsed_); + + // There should be exactly the expected number of tokens. + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // Check that the first token is TokenPkt4 instance and has correct type. + checkTokenPkt4(eval.expression.at(0), exp_type); + } + + /// @brief checks if the given token is Pkt6 of specified type + /// @param token token to be checked + /// @param exp_type expected type of the Pkt6 field + void checkTokenPkt6(const TokenPtr& token, + TokenPkt6::FieldType exp_type) { + ASSERT_TRUE(token); + + boost::shared_ptr<TokenPkt6> pkt = + boost::dynamic_pointer_cast<TokenPkt6>(token); + + ASSERT_TRUE(pkt); + + EXPECT_EQ(exp_type, pkt->getType()); + } + + /// @brief Test that verifies access to the DHCPv6 packet fields. + /// + /// This test attempts to parse the expression, will check if the number + /// of tokens is exactly as planned and then will try to verify if the + /// first token represents expected the field in DHCPv6 packet. + /// + /// @param expr expression to be parsed + /// @param exp_type expected field type to be parsed + /// @param exp_tokens expected number of tokens + void testPkt6Field(const std::string& expr, + TokenPkt6::FieldType exp_type, + int exp_tokens) { + EvalContext eval(Option::V6); + + // Parse the expression. + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() << "Exception thrown: " << ex.what(); + return; + } + + // Parsing should succeed and return a token. + EXPECT_TRUE(parsed_); + + // There should be the requested number of tokens + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // Check that the first token is TokenPkt6 instance and has correct type. + checkTokenPkt6(eval.expression.at(0), exp_type); + } + + /// @brief checks if the given token is a TokenRelay with the + /// correct nesting level and field type. + /// @param token token to be checked + /// @param expected_level expected nesting level + /// @param expected_code expected option code + /// @param expected_repr expected representation (text, hex, exists) + void checkTokenRelay6Field(const TokenPtr& token, + int8_t expected_level, + TokenRelay6Field::FieldType expected_type) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenRelay6Field> opt = + boost::dynamic_pointer_cast<TokenRelay6Field>(token); + ASSERT_TRUE(opt); + + EXPECT_EQ(expected_level, opt->getNest()); + EXPECT_EQ(expected_type, opt->getType()); + } + + /// @brief This tests attempts to parse the expression then checks + /// if the number of tokens is correct and the TokenRelay6Field is as + /// expected. + /// + /// @param expr expression to be parsed + /// @param exp_level expected level to be parsed + /// @param exp_type expected field type to be parsed + /// @param exp_tokens expected number of tokens + void testRelay6Field(const std::string& expr, + int8_t exp_level, + TokenRelay6Field::FieldType exp_type, + int exp_tokens) { + EvalContext eval(Option::V6); + + // parse the expression + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() <<"Exception thrown: " << ex.what(); + return; + } + + // Parsing should succeed and return a token. + EXPECT_TRUE(parsed_); + + // There should be the expected number of tokens. + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // checked that the first token is TokenRelay6Field and that + // is has the correct attributes + checkTokenRelay6Field(eval.expression.at(0), exp_level, exp_type); + } + + /// @brief checks if the given token is a TokenMember with the + /// correct client class name. + /// @param token token to be checked + /// @param expected_client_class expected client class name + void checkTokenMember(const TokenPtr& token, + const std::string& expected_client_class) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenMember> member = + boost::dynamic_pointer_cast<TokenMember>(token); + ASSERT_TRUE(member); + + EXPECT_EQ(expected_client_class, member->getClientClass()); + } + + /// @brief This tests attempts to parse the expression then checks + /// if the number of tokens is correct and the TokenMember is as + /// expected. + /// + /// @param expr expression to be parsed + /// @param check_defined closure checking if the client class is defined + /// @param exp_client_class expected client class name to be parsed + /// @param exp_tokens expected number of tokens + void testMember(const std::string& expr, + EvalContext::CheckDefined check_defined, + const std::string& exp_client_class, + int exp_tokens) { + EvalContext eval(Option::V6, check_defined); + + // parse the expression + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() <<"Exception thrown: " << ex.what(); + return; + } + + // Parsing should succeed and return a token. + EXPECT_TRUE(parsed_); + + // There should be the expected number of tokens. + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // checked that the first token is TokenRelay6Field and that + // is has the correct attributes + checkTokenMember(eval.expression.at(0), exp_client_class); + } + + /// @brief checks if the given token is a substring operator + void checkTokenSubstring(const TokenPtr& token) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenSubstring> sub = + boost::dynamic_pointer_cast<TokenSubstring>(token); + EXPECT_TRUE(sub); + } + + /// @brief checks if the given token is a concat operator + void checkTokenConcat(const TokenPtr& token) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenConcat> conc = + boost::dynamic_pointer_cast<TokenConcat>(token); + EXPECT_TRUE(conc); + } + + /// @brief checks if the given token is an ifelse operator + void checkTokenIfElse(const TokenPtr& token) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenIfElse> alt = + boost::dynamic_pointer_cast<TokenIfElse>(token); + EXPECT_TRUE(alt); + } + + /// @brief checks if the given token is a hexstring operator + void checkTokenToHexString(const TokenPtr& token) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenToHexString> tohex = + boost::dynamic_pointer_cast<TokenToHexString>(token); + EXPECT_TRUE(tohex); + } + + /// @brief checks if the given token is an addrtotext operator + void checkTokenIpAddressToText(const TokenPtr& token, + const std::string& expected) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenIpAddressToText> addrtotext = + boost::dynamic_pointer_cast<TokenIpAddressToText>(token); + EXPECT_TRUE(addrtotext); + + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + ValueStack values; + + std::vector<uint8_t> bytes = IOAddress(expected).toBytes(); + values.push(std::string(bytes.begin(), bytes.end())); + + EXPECT_NO_THROW(token->evaluate(*pkt4, values)); + + ASSERT_EQ(1, values.size()); + string value = values.top(); + + EXPECT_EQ(value, expected); + } + + /// @brief checks if the given token is a inttotext operator + template <typename IntegerType, typename TokenIntegerType> + void checkTokenIntToText(const TokenPtr& token, + const std::string& expected) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenIntegerType> inttotext = + boost::dynamic_pointer_cast<TokenIntegerType>(token); + EXPECT_TRUE(inttotext); + + Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 12345)); + ValueStack values; + + IntegerType n; + + try { + if (is_signed<IntegerType>()) { + n = static_cast<IntegerType>(boost::lexical_cast<int32_t>(expected)); + } else { + n = static_cast<IntegerType>(boost::lexical_cast<uint32_t>(expected)); + } + } catch (const boost::bad_lexical_cast& e) { + FAIL() << "invalid value " << expected << ", error: " << e.what(); + } + + values.push(std::string(const_cast<const char*>(reinterpret_cast<char*>(&n)), sizeof(IntegerType))); + + EXPECT_NO_THROW(token->evaluate(*pkt4, values)); + + ASSERT_EQ(1, values.size()); + string value = values.top(); + + EXPECT_EQ(value, expected); + } + + /// @brief checks if the given expression raises the expected message + /// when it is parsed. + void checkError(const string& expr, const string& msg) { + EvalContext eval(universe_); + parsed_ = false; + try { + parsed_ = eval.parseString(expr); + FAIL() << "Expected EvalParseError but nothing was raised"; + } + catch (const EvalParseError& ex) { + EXPECT_EQ(msg, ex.what()); + EXPECT_FALSE(parsed_); + } + catch (...) { + FAIL() << "Expected EvalParseError but something else was raised"; + } + } + + /// @brief sets the universe + /// @note the default universe is DHCPv4 + void setUniverse(const Option::Universe& universe) { + universe_ = universe; + } + + /// @brief Checks if the given token is TokenVendor and has expected characteristics + /// @param token token to be checked + /// @param exp_vendor_id expected vendor-id (aka enterprise number) + /// @param exp_repr expected representation (either 'exists' or 'hex') + /// @param exp_option_code expected option code (ignored if 0) + void checkTokenVendor(const TokenPtr& token, uint32_t exp_vendor_id, + uint16_t exp_option_code, + TokenOption::RepresentationType exp_repr) { + ASSERT_TRUE(token); + + boost::shared_ptr<TokenVendor> vendor = + boost::dynamic_pointer_cast<TokenVendor>(token); + + ASSERT_TRUE(vendor); + + EXPECT_EQ(exp_vendor_id, vendor->getVendorId()); + EXPECT_EQ(exp_repr, vendor->getRepresentation()); + EXPECT_EQ(exp_option_code, vendor->getCode()); + } + + /// @brief Tests if specified token vendor expression can be parsed + /// + /// This test assumes the first token will be token vendor. Any additional + /// tokens are ignored. Tests expressions: + /// vendor[1234].option[234].hex + /// vendor[1234].option[234].exists + /// + /// @param expr expression to be parsed + /// @param u universe (V4 or V6) + /// @param vendor_id expected vendor-id (aka enterprise number) + /// @param option_code expected option code (ignored if 0) + /// @param expected_repr expected representation (either 'exists' or 'hex') + void testVendor(const std::string& expr, Option::Universe u, + uint32_t vendor_id, uint16_t option_code, + TokenOption::RepresentationType expected_repr) { + EvalContext eval(u); + + EXPECT_NO_THROW(parsed_ = eval.parseString(expr)); + EXPECT_TRUE(parsed_); + + // We need at least one token, we will evaluate the first one. + ASSERT_FALSE(eval.expression.empty()); + + checkTokenVendor(eval.expression.at(0), vendor_id, option_code, expected_repr); + } + + /// @brief Checks if token is really a TokenVendor, that the vendor_id was + /// stored properly and that it has expected representation + /// + /// This test is able to handle expressions similar to: + /// vendor[4491].option[1].hex + /// vendor[4491].option[1].exists + /// vendor[4491].exists + /// vendor[*].exists + /// + /// @param expr expression to be parsed + /// @param u universe (V4 or V6) + /// @param vendor_id expected vendor-id (aka enterprise number) + /// @param expected_repr expected representation (either 'exists' or 'hex') + void testVendor(const std::string& expr, Option::Universe u, + uint32_t vendor_id, + TokenOption::RepresentationType expected_repr) { + testVendor(expr, u, vendor_id, 0, expected_repr); + } + + /// @brief Tests if the expression parses into token vendor that returns enterprise-id + /// + /// This test is able to handle expressions similar to: + /// vendor.enterprise + /// + /// @param expr expression to be parsed + /// @param u universe (V4 or V6) + void testVendorEnterprise(const std::string& expr, + Option::Universe u) { + EvalContext eval(u); + + EXPECT_NO_THROW(parsed_ = eval.parseString(expr)); + EXPECT_TRUE(parsed_); + + ASSERT_FALSE(eval.expression.empty()); + + boost::shared_ptr<TokenVendor> vendor = + boost::dynamic_pointer_cast<TokenVendor>(eval.expression.at(0)); + + ASSERT_TRUE(vendor); + EXPECT_EQ(TokenVendor::ENTERPRISE_ID, vendor->getField()); + } + + /// @brief This test checks if vendor-class token is correct + /// + /// This test checks if EXISTS representation is set correctly. + /// It covers cases like: + /// - vendor-class[4491].exists + /// - vendor-class[*].exists + /// + /// @param expr expression to be parsed + /// @param u universe (V4 or V6) + /// @param vendor_id expected vendor-id (aka enterprise number) + void testVendorClass(const std::string& expr, + Option::Universe u, uint32_t vendor_id) { + EvalContext eval(u); + + EXPECT_NO_THROW(parsed_ = eval.parseString(expr)); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(1, eval.expression.size()); + checkTokenVendorClass(eval.expression.at(0), vendor_id, 0, TokenOption::EXISTS, + TokenVendor::EXISTS); + } + + /// @brief Tests if specified token vendor class expression can be parsed + /// + /// This test assumes the first token will be token vendor-class. + /// Any additional tokens are ignored. Tests expressions: + /// - vendor-class[4491].exists + /// - vendor-class[*].exists + /// - vendor-class[4491].data + /// - vendor-class[4491].data[3] + /// + /// @param expr expression to be parsed + /// @param u universe (V4 or V6) + /// @param vendor_id expected vendor-id (aka enterprise number) + /// @param index expected data index + void testVendorClass(const std::string& expr, Option::Universe u, + uint32_t vendor_id, uint16_t index) { + EvalContext eval(u); + + EXPECT_NO_THROW(parsed_ = eval.parseString(expr)); + EXPECT_TRUE(parsed_); + + // Make sure there's at least one token + ASSERT_FALSE(eval.expression.empty()); + + // The first token should be TokenVendorClass, let's take a closer look. + checkTokenVendorClass(eval.expression.at(0), vendor_id, index, + TokenOption::HEXADECIMAL, TokenVendor::DATA); + + } + + /// @brief Tests if the expression parses into vendor class token that + /// returns enterprise-id. + /// + /// This test is able to handle expressions similar to: + /// - vendor-class.enterprise + /// + /// @param expr expression to be parsed + /// @param u universe (V4 or V6) + void testVendorClassEnterprise(const std::string& expr, + Option::Universe u) { + EvalContext eval(u); + + EXPECT_NO_THROW(parsed_ = eval.parseString(expr)); + EXPECT_TRUE(parsed_); + + // Make sure there's at least one token + ASSERT_FALSE(eval.expression.empty()); + + // The first token should be TokenVendorClass, let's take a closer look. + checkTokenVendorClass(eval.expression.at(0), 0, 0, TokenOption::HEXADECIMAL, + TokenVendor::ENTERPRISE_ID); + } + + /// @brief Checks if the given token is TokenVendorClass and has expected characteristics + /// + /// @param token token to be checked + /// @param vendor_id expected vendor-id (aka enterprise number) + /// @param index expected index (used for data field only) + /// @param repr expected representation (either 'exists' or 'hex') + /// @param field expected field (none, enterprise or data) + void checkTokenVendorClass(const TokenPtr& token, uint32_t vendor_id, + uint16_t index, TokenOption::RepresentationType repr, + TokenVendor::FieldType field) { + ASSERT_TRUE(token); + + boost::shared_ptr<TokenVendorClass> vendor = + boost::dynamic_pointer_cast<TokenVendorClass>(token); + + ASSERT_TRUE(vendor); + + EXPECT_EQ(vendor_id, vendor->getVendorId()); + EXPECT_EQ(index, vendor->getDataIndex()); + EXPECT_EQ(repr, vendor->getRepresentation()); + EXPECT_EQ(field, vendor->getField()); + } + + /// @brief checks if the given token is a sub-option with the expected + /// parent option and sub-option codes and representation type + /// @param token token to be checked + /// @param expected_code expected option code + /// @param expected_sub_code expected sub-option code + /// @param expected_repr expected representation (text, hex, exists) + void checkTokenSubOption(const TokenPtr& token, + uint16_t expected_code, + uint16_t expected_sub_code, + TokenOption::RepresentationType expected_repr) { + ASSERT_TRUE(token); + boost::shared_ptr<TokenSubOption> sub = + boost::dynamic_pointer_cast<TokenSubOption>(token); + ASSERT_TRUE(sub); + + EXPECT_EQ(expected_code, sub->getCode()); + EXPECT_EQ(expected_sub_code, sub->getSubCode()); + EXPECT_EQ(expected_repr, sub->getRepresentation()); + } + + Option::Universe universe_; ///< Universe (V4 or V6) + bool parsed_; ///< Parsing status + +}; + +// Test the error method without location +TEST_F(EvalContextTest, error) { + + EvalContext eval(Option::V4); + + EXPECT_THROW(eval.error("an error"), EvalParseError); +} + +// Test the fatal method +TEST_F(EvalContextTest, fatal) { + + EvalContext eval(Option::V4); + + EXPECT_THROW(eval.fatal("a fatal error"), isc::Unexpected); +} + +// Test the convertOptionCode method with an illegal input +TEST_F(EvalContextTest, badOptionCode) { + + EvalContext eval(Option::V4); + + // the option code must be a number + EXPECT_THROW(eval.convertOptionCode("bad", location(position())), + EvalParseError); +} + +// Test the convertNestLevelNumber method with an illegal input +TEST_F(EvalContextTest, badNestLevelNumber) { + + EvalContext eval(Option::V4); + + // the nest level number must be a number + EXPECT_THROW(eval.convertNestLevelNumber("bad", location(position())), + EvalParseError); +} + +// Test the parsing of a basic expression +TEST_F(EvalContextTest, basic) { + + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'MSFT'")); + EXPECT_TRUE(parsed_); +} + +// Test the parsing of a string terminal +TEST_F(EvalContextTest, string) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + + checkTokenString(tmp1, "foo"); + checkTokenString(tmp2, "bar"); +} + +// Test the parsing of a basic expression using integers +TEST_F(EvalContextTest, integer) { + + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = + eval.parseString("substring(option[123].text, 0, 2) == '42'")); + EXPECT_TRUE(parsed_); +} + +// Test the parsing of a hexstring terminal +TEST_F(EvalContextTest, hexstring) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("0x666f6f == 'foo'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + + checkTokenHexString(tmp, "foo"); +} + +// Test the parsing of a hexstring terminal with an odd number of +// hexadecimal digits +TEST_F(EvalContextTest, oddHexstring) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("0X7 == 'foo'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + + checkTokenHexString(tmp, "\a"); +} + +// Test the parsing of an IPv4 address +TEST_F(EvalContextTest, ipaddress4) { + EvalContext eval(Option::V6); + + EXPECT_NO_THROW(parsed_ = eval.parseString("10.0.0.1 == 'foo'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + + checkTokenIpAddress(tmp, "10.0.0.1"); +} + +// Test the parsing of an IPv6 address +TEST_F(EvalContextTest, ipaddress6) { + EvalContext eval(Option::V6); + + EXPECT_NO_THROW(parsed_ = eval.parseString("2001:db8::1 == 'foo'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + + checkTokenIpAddress(tmp, "2001:db8::1"); +} + +// Test the parsing of an IPv4 compatible IPv6 address +TEST_F(EvalContextTest, ipaddress46) { + EvalContext eval(Option::V6); + + EXPECT_NO_THROW(parsed_ = eval.parseString("::10.0.0.1 == 'foo'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + + checkTokenIpAddress(tmp, "::10.0.0.1"); +} + +// Test the parsing of the unspecified IPv6 address +TEST_F(EvalContextTest, ipaddress6unspec) { + EvalContext eval(Option::V6); + + EXPECT_NO_THROW(parsed_ = eval.parseString(":: == 'foo'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + + checkTokenIpAddress(tmp, "::"); +} + +// Test the parsing of an IPv6 prefix +TEST_F(EvalContextTest, ipaddress6prefix) { + EvalContext eval(Option::V6); + + EXPECT_NO_THROW(parsed_ = eval.parseString("2001:db8:: == 'foo'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + + checkTokenIpAddress(tmp, "2001:db8::"); +} + +// Test the parsing of an equal expression +TEST_F(EvalContextTest, equal) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("'foo' == 'bar'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + + checkTokenString(tmp1, "foo"); + checkTokenString(tmp2, "bar"); + checkTokenEq(tmp3); +} + +// Test the parsing of an option terminal +TEST_F(EvalContextTest, option) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].text == 'foo'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenOption(eval.expression.at(0), 123, TokenOption::TEXTUAL); +} + +// Test parsing of an option identified by name. +TEST_F(EvalContextTest, optionWithName) { + EvalContext eval(Option::V4); + + // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++. + EXPECT_NO_THROW(parsed_ = eval.parseString("option[host-name].text == 'foo'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenOption(eval.expression.at(0), 12, TokenOption::TEXTUAL); +} + +// Test parsing of an option existence +TEST_F(EvalContextTest, optionExists) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("option[100].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(1, eval.expression.size()); + checkTokenOption(eval.expression.at(0), 100, TokenOption::EXISTS); +} + +// Test checking that whitespace can surround option name. +TEST_F(EvalContextTest, optionWithNameAndWhitespace) { + EvalContext eval(Option::V4); + + // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++. + EXPECT_NO_THROW(parsed_ = eval.parseString("option[ host-name ].text == 'foo'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenOption(eval.expression.at(0), 12, TokenOption::TEXTUAL); +} + +// Test checking that newlines can surround option name. +TEST_F(EvalContextTest, optionWithNameAndNewline) { + EvalContext eval(Option::V4); + + // Option 'host-name' is a standard DHCPv4 option defined in the libdhcp++. + EXPECT_NO_THROW(parsed_ = + eval.parseString("option[\n host-name \n ].text == \n'foo'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenOption(eval.expression.at(0), 12, TokenOption::TEXTUAL); +} + +// Test parsing of an option represented as hexadecimal string. +TEST_F(EvalContextTest, optionHex) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].hex == 0x666F6F")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenOption(eval.expression.at(0), 123, TokenOption::HEXADECIMAL); +} + +// This test checks that the relay4[code].hex can be used in expressions. +TEST_F(EvalContextTest, relay4Option) { + + EvalContext eval(Option::V4); + EXPECT_NO_THROW(parsed_ = + eval.parseString("relay4[13].hex == 'thirteen'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + + checkTokenRelay4(tmp1, 13, TokenOption::HEXADECIMAL); + checkTokenString(tmp2, "thirteen"); + checkTokenEq(tmp3); +} + +// This test check the relay4[code].exists is supported. +TEST_F(EvalContextTest, relay4Exists) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("relay4[13].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(1, eval.expression.size()); + checkTokenRelay4(eval.expression.at(0), 13, TokenOption::EXISTS); +} + +// Verify that relay4[13] is not usable in v6 +// There will be a separate relay accessor for v6. +TEST_F(EvalContextTest, relay4Error) { + universe_ = Option::V6; + + checkError("relay4[13].hex == 'thirteen'", + "<string>:1.1-6: relay4 can only be used in DHCPv4."); +} + +// Test the parsing of a relay6 option +TEST_F(EvalContextTest, relay6Option) { + EvalContext eval(Option::V6); + + testRelay6Option("relay6[0].option[123].text == 'foo'", + 0, 123, TokenOption::TEXTUAL, 3); +} + +// Test the parsing of existence for a relay6 option +TEST_F(EvalContextTest, relay6OptionExists) { + EvalContext eval(Option::V6); + + testRelay6Option("relay6[1].option[75].exists", + 1, 75, TokenOption::EXISTS, 1); +} + +// Test the parsing of hex for a relay6 option +TEST_F(EvalContextTest, relay6OptionHex) { + EvalContext eval(Option::V6); + + testRelay6Option("relay6[2].option[85].hex == 'foo'", + 2, 85, TokenOption::HEXADECIMAL, 3); +} + +// Test the parsing of a relay6 option in reverse order +TEST_F(EvalContextTest, relay6OptionReverse) { + EvalContext eval(Option::V6); + + testRelay6Option("relay6[-1].option[123].text == 'foo'", + -1, 123, TokenOption::TEXTUAL, 3); +} + +// Test the nest level of a relay6 option should be in [-32..32[ +TEST_F(EvalContextTest, relay6OptionLimits) { + EvalContext eval(Option::V6); + + // max nest level is hop count limit minus one so 31 + testRelay6Option("relay6[31].option[123].text == 'foo'", + 31, 123, TokenOption::TEXTUAL, 3); + + universe_ = Option::V6; + + checkError("relay6[32].option[123].text == 'foo'", + "<string>:1.8-9: Nest level has invalid value in 32. " + "Allowed range: -32..31"); + + // min nest level is minus hop count limit + testRelay6Option("relay6[-32].option[123].text == 'foo'", + -32, 123, TokenOption::TEXTUAL, 3); + + checkError("relay6[-33].option[123].text == 'foo'", + "<string>:1.8-10: Nest level has invalid value in -33. Allowed range: -32..31"); +} + +// Verify that relay6[13].option is not usable in v4 +TEST_F(EvalContextTest, relay6OptionError) { + universe_ = Option::V4; + + // nest_level is reduced first so raises the error + // (if we'd like to get a relay6 error we have to insert an + // intermediate action to check the universe) + checkError("relay6[0].option[123].text == 'foo'", + "<string>:1.8: Nest level invalid for DHCPv4 packets"); +} + +// Tests whether iface metadata in DHCP can be accessed. +TEST_F(EvalContextTest, pktMetadataIface) { + testPktMetadata("pkt.iface == 'eth0'", TokenPkt::IFACE, 3); +} + +// Tests whether src metadata in DHCP can be accessed. +TEST_F(EvalContextTest, pktMetadataSrc) { + testPktMetadata("pkt.src == fe80::1", TokenPkt::SRC, 3); +} + +// Tests whether dst metadata in DHCP can be accessed. +TEST_F(EvalContextTest, pktMetadataDst) { + testPktMetadata("pkt.dst == fe80::2", TokenPkt::DST, 3); +} + +// Tests whether len metadata in DHCP can be accessed. +TEST_F(EvalContextTest, pktMetadataLen) { + testPktMetadata("pkt.len == 0x00000100", TokenPkt::LEN, 3); +} + +// Tests whether chaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldChaddr) { + testPkt4Field("pkt4.mac == 0x000102030405", TokenPkt4::CHADDR, 3); +} + +// Tests whether chaddr field in DHCPv4 can be accessed and converted. +TEST_F(EvalContextTest, pkt4FieldChaddrHexa) { + testPkt4Field("hexstring(pkt4.mac, ':') == '00:01:02:03:04:05'", + TokenPkt4::CHADDR, 5); +} + +// Tests whether hlen field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldHlen) { + testPkt4Field("pkt4.hlen == 0x6", TokenPkt4::HLEN, 3); +} + +// Tests whether htype field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldHtype) { + testPkt4Field("pkt4.htype == 0x1", TokenPkt4::HTYPE, 3); +} + +// Tests whether ciaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldCiaddr) { + testPkt4Field("pkt4.ciaddr == 192.0.2.1", TokenPkt4::CIADDR, 3); +} + +// Tests whether giaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldGiaddr) { + testPkt4Field("pkt4.giaddr == 192.0.2.1", TokenPkt4::GIADDR, 3); +} + +// Tests whether yiaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldYiaddr) { + testPkt4Field("pkt4.yiaddr == 192.0.2.1", TokenPkt4::YIADDR, 3); +} + +// Tests whether siaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldSiaddr) { + testPkt4Field("pkt4.siaddr == 192.0.2.1", TokenPkt4::SIADDR, 3); +} + +// Tests whether message type field in DHCPv6 can be accessed. +TEST_F(EvalContextTest, pkt6FieldMsgtype) { + testPkt6Field("pkt6.msgtype == 1", TokenPkt6::MSGTYPE, 3); +} + +// Tests whether transaction id field in DHCPv6 can be accessed. +TEST_F(EvalContextTest, pkt6FieldTransid) { + testPkt6Field("pkt6.transid == 1", TokenPkt6::TRANSID, 3); +} + +// Tests if the linkaddr field in a Relay6 encapsulation can be accessed. +TEST_F(EvalContextTest, relay6FieldLinkAddr) { + testRelay6Field("relay6[0].linkaddr == ::", + 0, TokenRelay6Field::LINKADDR, 3); +} + +// Tests if the peeraddr field in a Relay6 encapsulation can be accessed. +TEST_F(EvalContextTest, relay6FieldPeerAddr) { + testRelay6Field("relay6[1].peeraddr == ::", + 1, TokenRelay6Field::PEERADDR, 3); +} + +// Verify that relay6[0].<field> is not usable in v4 +TEST_F(EvalContextTest, relay6FieldError) { + universe_ = Option::V4; + + // nest_level is reduced first so raises the error + // (if we'd like to get a relay6 error we have to insert an + // intermediate action to check the universe) + checkError("relay6[0].linkaddr == ::", + "<string>:1.8: Nest level invalid for DHCPv4 packets"); +} + +// Tests parsing of member with defined class +TEST_F(EvalContextTest, member) { + auto check_defined = [](const ClientClass& cc) { return (cc == "foo"); }; + testMember("member('foo')", check_defined, "foo", 1); +} + +// Test parsing of member with not defined class +TEST_F(EvalContextTest, memberError) { + auto check_defined = [](const ClientClass& cc) { return (cc == "foo"); }; + EvalContext eval(Option::V6, check_defined); + parsed_ = false; + try { + parsed_ = eval.parseString("member('bar')"); + FAIL() << "Expected EvalParseError but nothing was raised"; + } + catch (const EvalParseError& ex) { + EXPECT_EQ("<string>:1.8-12: Not defined client class 'bar'", + std::string(ex.what())); + EXPECT_FALSE(parsed_); + } + catch (...) { + FAIL() << "Expected EvalParseError but something else was raised"; + } +} + +// Test parsing of logical operators +TEST_F(EvalContextTest, logicalOps) { + // option.exists + EvalContext eval0(Option::V4); + EXPECT_NO_THROW(parsed_ = eval0.parseString("option[123].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(1, eval0.expression.size()); + TokenPtr token = eval0.expression.at(0); + ASSERT_TRUE(token); + boost::shared_ptr<TokenOption> opt = + boost::dynamic_pointer_cast<TokenOption>(token); + EXPECT_TRUE(opt); + + // not + EvalContext evaln(Option::V4); + EXPECT_NO_THROW(parsed_ = evaln.parseString("not option[123].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(2, evaln.expression.size()); + token = evaln.expression.at(1); + ASSERT_TRUE(token); + boost::shared_ptr<TokenNot> tnot = + boost::dynamic_pointer_cast<TokenNot>(token); + EXPECT_TRUE(tnot); + + // and + EvalContext evala(Option::V4); + EXPECT_NO_THROW(parsed_ = + evala.parseString("option[123].exists and option[123].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, evala.expression.size()); + token = evala.expression.at(2); + ASSERT_TRUE(token); + boost::shared_ptr<TokenAnd> tand = + boost::dynamic_pointer_cast<TokenAnd>(token); + EXPECT_TRUE(tand); + + // or + EvalContext evalo(Option::V4); + EXPECT_NO_THROW(parsed_ = + evalo.parseString("option[123].exists or option[123].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, evalo.expression.size()); + token = evalo.expression.at(2); + ASSERT_TRUE(token); + boost::shared_ptr<TokenOr> tor = + boost::dynamic_pointer_cast<TokenOr>(token); + EXPECT_TRUE(tor); +} + +// Test parsing of logical operators with precedence +TEST_F(EvalContextTest, logicalPrecedence) { + // not precedence > and precedence + EvalContext evalna(Option::V4); + EXPECT_NO_THROW(parsed_ = + evalna.parseString("not option[123].exists and option[123].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(4, evalna.expression.size()); + TokenPtr token = evalna.expression.at(3); + ASSERT_TRUE(token); + boost::shared_ptr<TokenAnd> tand = + boost::dynamic_pointer_cast<TokenAnd>(token); + EXPECT_TRUE(tand); + + // and precedence > or precedence + EvalContext evaloa(Option::V4); + EXPECT_NO_THROW(parsed_ = + evaloa.parseString("option[123].exists or option[123].exists " + "and option[123].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(5, evaloa.expression.size()); + token = evaloa.expression.at(4); + ASSERT_TRUE(token); + boost::shared_ptr<TokenOr> tor = + boost::dynamic_pointer_cast<TokenOr>(token); + EXPECT_TRUE(tor); +} + +// Test parsing of logical operators with parentheses (same than +// with precedence but using parentheses to overwrite precedence) +TEST_F(EvalContextTest, logicalParentheses) { + // not precedence > and precedence + EvalContext evalna(Option::V4); + EXPECT_NO_THROW(parsed_ = + evalna.parseString("not (option[123].exists and option[123].exists)")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(4, evalna.expression.size()); + TokenPtr token = evalna.expression.at(3); + ASSERT_TRUE(token); + boost::shared_ptr<TokenNot> tnot = + boost::dynamic_pointer_cast<TokenNot>(token); + EXPECT_TRUE(tnot); + + // and precedence > or precedence + EvalContext evaloa(Option::V4); + EXPECT_NO_THROW(parsed_ = + evaloa.parseString("(option[123].exists or option[123].exists) " + "and option[123].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(5, evaloa.expression.size()); + token = evaloa.expression.at(4); + ASSERT_TRUE(token); + boost::shared_ptr<TokenAnd> tand = + boost::dynamic_pointer_cast<TokenAnd>(token); + EXPECT_TRUE(tand); +} + +// Test the parsing of a substring expression +TEST_F(EvalContextTest, substring) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = + eval.parseString("substring('foobar',2,all) == 'obar'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(6, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenString(tmp1, "foobar"); + checkTokenString(tmp2, "2"); + checkTokenString(tmp3, "all"); + checkTokenSubstring(tmp4); +} + +// Test the parsing of a concat expression +TEST_F(EvalContextTest, concat) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = + eval.parseString("concat('foo','bar') == 'foobar'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(5, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + + checkTokenString(tmp1, "foo"); + checkTokenString(tmp2, "bar"); + checkTokenConcat(tmp3); +} + +// Test the parsing of a plus expression +TEST_F(EvalContextTest, plus) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = + eval.parseString("'foo' + 'bar' == 'foobar'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(5, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + + checkTokenString(tmp1, "foo"); + checkTokenString(tmp2, "bar"); + checkTokenConcat(tmp3); +} + +// Test the parsing of plus expressions +TEST_F(EvalContextTest, assocPlus) { + EvalContext eval(Option::V4); + + // Operator '+' is (left) associative + EXPECT_NO_THROW(parsed_ = + eval.parseString("'a' + 'b' + 'c' == 'abc'")); + + ASSERT_EQ(7, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + TokenPtr tmp5 = eval.expression.at(4); + + checkTokenString(tmp1, "a"); + checkTokenString(tmp2, "b"); + checkTokenConcat(tmp3); + checkTokenString(tmp4, "c"); + checkTokenConcat(tmp5); +} + +// Test the parsing of plus expressions with enforced associativity +TEST_F(EvalContextTest, assocRightPlus) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = + eval.parseString("'a' + ('b' + 'c') == 'abc'")); + + ASSERT_EQ(7, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + TokenPtr tmp5 = eval.expression.at(4); + + checkTokenString(tmp1, "a"); + checkTokenString(tmp2, "b"); + checkTokenString(tmp3, "c"); + checkTokenConcat(tmp4); + checkTokenConcat(tmp5); +} + +// Test the parsing of an ifelse expression +TEST_F(EvalContextTest, ifElse) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = + eval.parseString("ifelse('foo' == 'bar', 'us', 'them') == 'you'")); + + ASSERT_EQ(8, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(2); + TokenPtr tmp2 = eval.expression.at(3); + TokenPtr tmp3 = eval.expression.at(4); + TokenPtr tmp4 = eval.expression.at(5); + + checkTokenEq(tmp1); + checkTokenString(tmp2, "us"); + checkTokenString(tmp3, "them"); + checkTokenIfElse(tmp4); +} + +// Test the parsing of a plus operator and ifelse expression +TEST_F(EvalContextTest, plusIfElse) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = + eval.parseString("'foo' + ifelse('a' == 'a', 'bar', '') == 'foobar'")); + + ASSERT_EQ(10, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + TokenPtr tmp5 = eval.expression.at(4); + TokenPtr tmp6 = eval.expression.at(5); + TokenPtr tmp7 = eval.expression.at(6); + TokenPtr tmp8 = eval.expression.at(7); + + checkTokenString(tmp1, "foo"); + checkTokenString(tmp2, "a"); + checkTokenString(tmp3, "a"); + checkTokenEq(tmp4); + checkTokenString(tmp5, "bar"); + checkTokenString(tmp6, ""); + checkTokenIfElse(tmp7); + checkTokenConcat(tmp8); +} + +// Test the parsing of a hexstring expression +TEST_F(EvalContextTest, toHexString) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = + eval.parseString("hexstring(0x666f,'-') == '66-6f'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(5, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + + checkTokenHexString(tmp1, "fo"); + checkTokenString(tmp2, "-"); + checkTokenToHexString(tmp3); +} + +// Test the parsing of an addrtotext expression +TEST_F(EvalContextTest, addressToText) { + { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("addrtotext(10.0.0.1) == '10.0.0.1'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(4, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenIpAddress(tmp1, "10.0.0.1"); + checkTokenIpAddressToText(tmp2, "10.0.0.1"); + checkTokenString(tmp3, "10.0.0.1"); + checkTokenEq(tmp4); + } + + { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("addrtotext(2001:db8::1) == '2001:db8::1'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(4, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenIpAddress(tmp1, "2001:db8::1"); + checkTokenIpAddressToText(tmp2, "2001:db8::1"); + checkTokenString(tmp3, "2001:db8::1"); + checkTokenEq(tmp4); + } +} + +// Test the parsing of a int8_t expression +TEST_F(EvalContextTest, int8ToText) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("int8totext(255) == '-1'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(4, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenInteger(tmp1, 255); + checkTokenIntToText<int8_t, TokenInt8ToText>(tmp2, "-1"); + checkTokenString(tmp3, "-1"); + checkTokenEq(tmp4); +} + +// Test the parsing of a int16_t expression +TEST_F(EvalContextTest, int16ToText) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("int16totext(65535) == '-1'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(4, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenInteger(tmp1, 65535); + checkTokenIntToText<int16_t, TokenInt16ToText>(tmp2, "-1"); + checkTokenString(tmp3, "-1"); + checkTokenEq(tmp4); +} + +// Test the parsing of a int32_t expression +TEST_F(EvalContextTest, int32ToText) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("int32totext(4294967295) == '-1'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(4, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenInteger(tmp1, 4294967295); + checkTokenIntToText<int32_t, TokenInt32ToText>(tmp2, "-1"); + checkTokenString(tmp3, "-1"); + checkTokenEq(tmp4); +} + +// Test the parsing of a uint8_t expression +TEST_F(EvalContextTest, uint8ToText) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("uint8totext(255) == '255'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(4, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenInteger(tmp1, 255); + checkTokenIntToText<uint8_t, TokenUInt8ToText>(tmp2, "255"); + checkTokenString(tmp3, "255"); + checkTokenEq(tmp4); +} + +// Test the parsing of a uint16_t expression +TEST_F(EvalContextTest, uint16ToText) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("uint16totext(65535) == '65535'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(4, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenInteger(tmp1, 65535); + checkTokenIntToText<uint16_t, TokenUInt16ToText>(tmp2, "65535"); + checkTokenString(tmp3, "65535"); + checkTokenEq(tmp4); +} + +// Test the parsing of a uint32_t expression +TEST_F(EvalContextTest, uint32ToText) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("uint32totext(4294967295) == '4294967295'")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(4, eval.expression.size()); + + TokenPtr tmp1 = eval.expression.at(0); + TokenPtr tmp2 = eval.expression.at(1); + TokenPtr tmp3 = eval.expression.at(2); + TokenPtr tmp4 = eval.expression.at(3); + + checkTokenInteger(tmp1, 4294967295); + checkTokenIntToText<uint32_t, TokenUInt32ToText>(tmp2, "4294967295"); + checkTokenString(tmp3, "4294967295"); + checkTokenEq(tmp4); +} + +// +// Test some scanner error cases +TEST_F(EvalContextTest, scanErrors) { + checkError("'", "<string>:1.1: Invalid character: '"); + checkError("'\''", "<string>:1.3: Invalid character: '"); + checkError("'\n'", "<string>:1.1: Invalid character: '"); + checkError("0x123h", "<string>:1.6: Invalid character: h"); + checkError(":1", "<string>:1.1: Invalid character: :"); + checkError("=", "<string>:1.1: Invalid character: ="); + + // Typo should be handled as well. + checkError("subtring", "<string>:1.1: Invalid character: s"); + checkError("foo", "<string>:1.1: Invalid character: f"); + checkError(" bar", "<string>:1.2: Invalid character: b"); + checkError("relay[12].hex == 'foo'", "<string>:1.1: Invalid character: r"); + checkError("pkt4.ziaddr", "<string>:1.6: Invalid character: z"); + checkError("members('foo'", "<string>:1.7: Invalid character: s"); +} + +// Tests some scanner/parser error cases +TEST_F(EvalContextTest, scanParseErrors) { + checkError("", "<string>:1.1: syntax error, unexpected end of file"); + checkError(" ", "<string>:1.2: syntax error, unexpected end of file"); + checkError("0x", "<string>:1.2: Invalid character: x"); + checkError("0abc", + "<string>:1.2: Invalid character: a"); + + // This one is a little bid odd. This is a truncated address, so it's not + // recognized as an address. Instead, the first token (10) is recognized as + // an integer. The only thing we can do with integers right now is to + // apply equality or concat operators, so the only possible next token + // are == and +. There's a dot instead, so an error is reported. + checkError("10.0.1", "<string>:1.3: syntax error, unexpected ., " + "expecting == or +"); + + checkError("10.256.0.1", + "<string>:1.1-10: Failed to convert 10.256.0.1 to " + "an IP address."); + checkError(":::", + "<string>:1.1-3: Failed to convert ::: to an IP address."); + checkError("===", "<string>:1.1-2: syntax error, unexpected =="); + checkError("option[-1].text", + "<string>:1.8-9: Option code has invalid " + "value in -1. Allowed range: 0..255"); + checkError("option[256].text", + "<string>:1.8-10: Option code has invalid " + "value in 256. Allowed range: 0..255"); + setUniverse(Option::V6); + checkError("option[65536].text", + "<string>:1.8-12: Option code has invalid " + "value in 65536. Allowed range: 0..65535"); + setUniverse(Option::V4); + checkError("option[12345678901234567890].text", + "<string>:1.8-27: Failed to convert 12345678901234567890 " + "to an integer."); + checkError("option[123]", + "<string>:1.12: syntax error, unexpected end of file," + " expecting ."); + checkError("option[123].text < 'foo'", "<string>:1.18: Invalid" + " character: <"); + checkError("option[-ab].text", "<string>:1.8: Invalid character: -"); + checkError("option[0ab].text", + "<string>:1.9-10: syntax error, unexpected option name, " + "expecting ]"); + checkError("option[ab_].hex", "<string>:1.8: Invalid character: a"); + checkError("option[\nhost-name\n].hex =\n= 'foo'", + "<string>:3.7: Invalid character: ="); + checkError("substring('foo',12345678901234567890,1)", + "<string>:1.17-36: Failed to convert 12345678901234567890 " + "to an integer."); +} + +// Tests some parser error cases +TEST_F(EvalContextTest, parseErrors) { + checkError("'foo''bar'", + "<string>:1.6-10: syntax error, unexpected constant string, " + "expecting == or +"); + checkError("'foo' (", + "<string>:1.7: syntax error, unexpected (, expecting == or +"); + checkError("== 'ab'", "<string>:1.1-2: syntax error, unexpected =="); + checkError("'foo' ==", + "<string>:1.9: syntax error, unexpected end of file"); + checkError("('foo' == 'bar'", + "<string>:1.16: syntax error, unexpected end of file, " + "expecting ) or and or or"); + checkError("('foo' == 'bar') ''", + "<string>:1.18-19: syntax error, unexpected constant string, " + "expecting end of file"); + checkError("not", + "<string>:1.4: syntax error, unexpected end of file"); + checkError("not 'foo'", + "<string>:1.10: syntax error, unexpected end of file, " + "expecting == or +"); + checkError("not()", + "<string>:1.5: syntax error, unexpected )"); + checkError("(not('foo' 'bar')", + "<string>:1.12-16: syntax error, unexpected constant string, " + "expecting ) or == or +"); + checkError("and", + "<string>:1.1-3: syntax error, unexpected and"); + checkError("'foo' and", + "<string>:1.7-9: syntax error, unexpected and, " + "expecting == or +"); + checkError("'foo' == 'bar' and", + "<string>:1.19: syntax error, unexpected end of file"); + checkError("'foo' == 'bar' and ''", + "<string>:1.22: syntax error, unexpected end of file, " + "expecting == or +"); + checkError("or", + "<string>:1.1-2: syntax error, unexpected or"); + checkError("'foo' or", + "<string>:1.7-8: syntax error, unexpected or, " + "expecting == or +"); + checkError("'foo' == 'bar' or", + "<string>:1.18: syntax error, unexpected end of file"); + checkError("'foo' == 'bar' or ''", + "<string>:1.21: syntax error, unexpected end of file, " + "expecting == or +"); + checkError("option 'ab'", + "<string>:1.8-11: syntax error, unexpected " + "constant string, expecting ["); + checkError("option(10) == 'ab'", + "<string>:1.7: syntax error, " + "unexpected (, expecting ["); + checkError("option['ab'].text == 'foo'", + "<string>:1.8-11: syntax error, " + "unexpected constant string, " + "expecting integer or option name"); + checkError("option[ab].text == 'foo'", + "<string>:1.8-9: option 'ab' is not defined"); + checkError("option[0xa].text == 'ab'", + "<string>:1.8-10: syntax error, " + "unexpected constant hexstring, " + "expecting integer or option name"); + checkError("option[10].bin", "<string>:1.12: Invalid character: b"); + checkError("option[boot-size].bin", "<string>:1.19: Invalid character: b"); + checkError("option[10].exists == 'foo'", + "<string>:1.19-20: syntax error, unexpected ==, " + "expecting end of file"); + checkError("substring('foobar') == 'f'", + "<string>:1.19: syntax error, unexpected ), " + "expecting \",\" or +"); + checkError("substring('foobar',3) == 'bar'", + "<string>:1.21: syntax error, unexpected ), expecting \",\""); + checkError("substring('foobar','3',3) == 'bar'", + "<string>:1.20-22: syntax error, unexpected constant string, " + "expecting integer"); + checkError("substring('foobar',1,a) == 'foo'", + "<string>:1.22: Invalid character: a"); + string long_text = "substring('foobar',1,65535) == "; + for (int i = 0; i < (1 << 16); ++i) { + long_text += "0"; + } + long_text += "'"; + checkError(long_text, + "<string>:1.65568: Invalid character: '"); + checkError("concat('foobar') == 'f'", + "<string>:1.16: syntax error, unexpected ), " + "expecting \",\" or +"); + checkError("concat('foo','bar','') == 'foobar'", + "<string>:1.19: syntax error, unexpected \",\", " + "expecting ) or +"); + checkError("ifelse('foo'=='bar','foo')", + "<string>:1.26: syntax error, unexpected ), " + "expecting \",\" or +"); + checkError("ifelse('foo'=='bar','foo','bar','')", + "<string>:1.32: syntax error, unexpected \",\", " + "expecting ) or +"); + checkError("+ 'a' = 'a'", "<string>:1.1: syntax error, unexpected +"); + checkError("'a' + == 'a'", "<string>:1.7-8: syntax error, unexpected =="); + checkError("'a' ++ 'b' == 'ab'", + "<string>:1.6: syntax error, unexpected +"); + checkError("addrtotext(10.0.0.1, 10.0.0.2)", + "<string>:1.20: syntax error, unexpected \",\", expecting ) or +"); + checkError("addrtotext('cafebabecafebabe')", + "<string>:1.31: syntax error, unexpected end of file, expecting == or +"); + checkError("addrtotext('')", + "<string>:1.15: syntax error, unexpected end of file, expecting == or +"); + checkError("int8totext('01', '01')", + "<string>:1.16: syntax error, unexpected \",\", expecting ) or +"); + checkError("int8totext('0123')", + "<string>:1.19: syntax error, unexpected end of file, expecting == or +"); + checkError("int8totext('')", + "<string>:1.15: syntax error, unexpected end of file, expecting == or +"); + checkError("int16totext('0123', '0123')", + "<string>:1.19: syntax error, unexpected \",\", expecting ) or +"); + checkError("int16totext('01')", + "<string>:1.18: syntax error, unexpected end of file, expecting == or +"); + checkError("int16totext('')", + "<string>:1.16: syntax error, unexpected end of file, expecting == or +"); + checkError("int32totext('01234567', '01234567')", + "<string>:1.23: syntax error, unexpected \",\", expecting ) or +"); + checkError("int32totext('01')", + "<string>:1.18: syntax error, unexpected end of file, expecting == or +"); + checkError("int32totext('')", + "<string>:1.16: syntax error, unexpected end of file, expecting == or +"); + checkError("uint8totext('01', '01')", + "<string>:1.17: syntax error, unexpected \",\", expecting ) or +"); + checkError("uint8totext('0123')", + "<string>:1.20: syntax error, unexpected end of file, expecting == or +"); + checkError("uint8totext('')", + "<string>:1.16: syntax error, unexpected end of file, expecting == or +"); + checkError("uint16totext('0123', '0123')", + "<string>:1.20: syntax error, unexpected \",\", expecting ) or +"); + checkError("uint16totext('01')", + "<string>:1.19: syntax error, unexpected end of file, expecting == or +"); + checkError("uint16totext('')", + "<string>:1.17: syntax error, unexpected end of file, expecting == or +"); + checkError("uint32totext('01234567', '01234567')", + "<string>:1.24: syntax error, unexpected \",\", expecting ) or +"); + checkError("uint32totext('01')", + "<string>:1.19: syntax error, unexpected end of file, expecting == or +"); + checkError("uint32totext('')", + "<string>:1.17: syntax error, unexpected end of file, expecting == or +"); +} + +// Tests some type error cases +TEST_F(EvalContextTest, typeErrors) { + checkError("'foobar'", + "<string>:1.9: syntax error, unexpected end of file, " + "expecting == or +"); + checkError("substring('foobar',all,1) == 'foo'", + "<string>:1.20-22: syntax error, unexpected all, " + "expecting integer"); + checkError("substring('foobar',0x32,1) == 'foo'", + "<string>:1.20-23: syntax error, unexpected constant " + "hexstring, expecting integer"); + + // With the #4483 addition, all integers are treated as 4 byte strings, + // so those checks no longer makes sense. Commenting it out. + // checkError("concat('foo',3) == 'foo3'", + // "<string>:1.14: syntax error, unexpected integer"); + // checkError("concat(3,'foo') == '3foo'", + // "<string>:1.8: syntax error, unexpected integer"); + checkError("('foo' == 'bar') == 'false'", + "<string>:1.18-19: syntax error, unexpected ==, " + "expecting end of file"); + checkError("not 'true'", + "<string>:1.11: syntax error, unexpected end of file, " + "expecting == or +"); + checkError("'true' and 'false'", + "<string>:1.8-10: syntax error, unexpected and, " + "expecting == or +"); + checkError("'true' or 'false'", + "<string>:1.8-9: syntax error, unexpected or, " + "expecting == or +"); + + // Ifelse requires a boolean condition and string branches. + checkError("ifelse('foobar','foo','bar')", + "<string>:1.16: syntax error, unexpected \",\", " + "expecting == or +"); + checkError("ifelse('foo'=='bar','foo'=='foo','bar')", + "<string>:1.26-27: syntax error, unexpected ==, " + "expecting \",\" or +"); + checkError("ifelse('foo'=='bar','foo','bar'=='bar')", + "<string>:1.32-33: syntax error, unexpected ==, " + "expecting ) or +"); + + // Member uses quotes around the client class name. + checkError("member(foo)", "<string>:1.8: Invalid character: f"); + + // sub-option by name is not supported. + checkError("option[123].option[host-name].exists", + "<string>:1.20-28: syntax error, unexpected option name, " + "expecting integer"); + + // Addrtotext requires string storing the binary representation of the address. + checkError("addrtotext('192.100.1.1')", + "<string>:1.26: syntax error, unexpected end of file, expecting == or +"); + + // Int8totext requires string storing the binary representation of the 8 bit integer. + checkError("int8totext('0123')", + "<string>:1.19: syntax error, unexpected end of file, expecting == or +"); + + // Int16totext requires string storing the binary representation of the 16 bit integer. + checkError("int16totext('01')", + "<string>:1.18: syntax error, unexpected end of file, expecting == or +"); + + // Int32totext requires string storing the binary representation of the 32 bit integer. + checkError("int32totext('01')", + "<string>:1.18: syntax error, unexpected end of file, expecting == or +"); + + // Uint8totext requires string storing the binary representation of the 8 bit unsigned integer. + checkError("uint8totext('0123')", + "<string>:1.20: syntax error, unexpected end of file, expecting == or +"); + + // Uint16totext requires string storing the binary representation of the 16 bit unsigned integer. + checkError("uint16totext('01')", + "<string>:1.19: syntax error, unexpected end of file, expecting == or +"); + + // Uint32totext requires string storing the binary representation of the 32 bit unsigned integer. + checkError("uint32totext('01')", + "<string>:1.19: syntax error, unexpected end of file, expecting == or +"); +} + +TEST_F(EvalContextTest, vendor4SpecificVendorExists) { + testVendor("vendor[4491].exists", Option::V4, 4491, TokenOption::EXISTS); +} + +TEST_F(EvalContextTest, vendor6SpecificVendorExists) { + testVendor("vendor[4491].exists", Option::V6, 4491, TokenOption::EXISTS); +} + +TEST_F(EvalContextTest, vendor4AnyVendorExists) { + testVendor("vendor[*].exists", Option::V4, 0, TokenOption::EXISTS); +} + +TEST_F(EvalContextTest, vendor6AnyVendorExists) { + testVendor("vendor[*].exists", Option::V6, 0, TokenOption::EXISTS); +} + +TEST_F(EvalContextTest, vendor4enterprise) { + testVendorEnterprise("vendor.enterprise == 0x1234", Option::V4); +} + +TEST_F(EvalContextTest, vendor6enterprise) { + testVendorEnterprise("vendor.enterprise == 0x1234", Option::V6); +} + +TEST_F(EvalContextTest, vendor4SuboptionExists) { + testVendor("vendor[4491].option[1].exists", Option::V4, 4491, 1, TokenOption::EXISTS); +} + +TEST_F(EvalContextTest, vendor6SuboptionExists) { + testVendor("vendor[4491].option[1].exists", Option::V6, 4491, 1, TokenOption::EXISTS); +} + +TEST_F(EvalContextTest, vendor4SuboptionHex) { + testVendor("vendor[4491].option[1].hex == 0x1234", Option::V4, 4491, 1, + TokenOption::HEXADECIMAL); +} + +TEST_F(EvalContextTest, vendor6SuboptionHex) { + testVendor("vendor[4491].option[1].hex == 0x1234", Option::V6, 4491, 1, + TokenOption::HEXADECIMAL); +} + +TEST_F(EvalContextTest, vendorClass4SpecificVendorExists) { + testVendorClass("vendor-class[4491].exists", Option::V4, 4491); +} + +TEST_F(EvalContextTest, vendorClass6SpecificVendorExists) { + testVendorClass("vendor-class[4491].exists", Option::V6, 4491); +} + +TEST_F(EvalContextTest, vendorClass4AnyVendorExists) { + testVendorClass("vendor-class[*].exists", Option::V4, 0); +} + +TEST_F(EvalContextTest, vendorClass6AnyVendorExists) { + testVendorClass("vendor-class[*].exists", Option::V6, 0); +} + +TEST_F(EvalContextTest, vendorClass4enterprise) { + testVendorClassEnterprise("vendor-class.enterprise == 0x1234", Option::V4); +} + +TEST_F(EvalContextTest, vendorClass6enterprise) { + testVendorClassEnterprise("vendor-class.enterprise == 0x1234", Option::V6); +} + +TEST_F(EvalContextTest, vendorClass4SpecificVendorData) { + testVendorClass("vendor-class[4491].data == 0x1234", Option::V4, 4491, 0); +} + +TEST_F(EvalContextTest, vendorClass6SpecificVendorData) { + testVendorClass("vendor-class[4491].data == 0x1234", Option::V6, 4491, 0); +} + +TEST_F(EvalContextTest, vendorClass4AnyVendorData) { + testVendorClass("vendor-class[*].data == 0x1234", Option::V4, 0, 0); +} + +TEST_F(EvalContextTest, vendorClass6AnyVendorData) { + testVendorClass("vendor-class[*].data == 0x1234", Option::V6, 0, 0); +} + +TEST_F(EvalContextTest, vendorClass4DataIndex) { + testVendorClass("vendor-class[4491].data[3] == 0x1234", Option::V4, 4491, 3); +} + +TEST_F(EvalContextTest, vendorClass6DataIndex) { + testVendorClass("vendor-class[4491].data[3] == 0x1234", Option::V6, 4491, 3); +} + +// Test the parsing of a sub-option with parent by code. +TEST_F(EvalContextTest, subOptionWithCode) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].option[234].text == 'foo'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenSubOption(eval.expression.at(0), 123, 234, TokenOption::TEXTUAL); +} + +// Test the parsing of a sub-option with parent by name. +TEST_F(EvalContextTest, subOptionWithName) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("option[host-name].option[123].text == 'foo'")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenSubOption(eval.expression.at(0), 12, 123, TokenOption::TEXTUAL); +} + +// Test the parsing of a sub-option existence +TEST_F(EvalContextTest, subOptionExists) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("option[100].option[200].exists")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(1, eval.expression.size()); + checkTokenSubOption(eval.expression.at(0), 100, 200, TokenOption::EXISTS); +} + +// Test parsing of a sub-option represented as hexadecimal string. +TEST_F(EvalContextTest, subOptionHex) { + EvalContext eval(Option::V4); + + EXPECT_NO_THROW(parsed_ = eval.parseString("option[123].option[234].hex == 0x666F6F")); + EXPECT_TRUE(parsed_); + ASSERT_EQ(3, eval.expression.size()); + checkTokenSubOption(eval.expression.at(0), 123, 234, TokenOption::HEXADECIMAL); +} + +// Checks if integer expressions can be parsed and checked for equality. +TEST_F(EvalContextTest, integer1) { + + EvalContext eval(Option::V6); + + EXPECT_NO_THROW(parsed_ = eval.parseString("1 == 2")); + EXPECT_TRUE(parsed_); + + ASSERT_EQ(3, eval.expression.size()); + + TokenPtr tmp = eval.expression.at(0); + ASSERT_TRUE(tmp); + checkTokenInteger(tmp, 1); + tmp = eval.expression.at(1); + + ASSERT_TRUE(tmp); + checkTokenInteger(tmp, 2); +} + +} diff --git a/src/lib/eval/tests/dependency_unittest.cc b/src/lib/eval/tests/dependency_unittest.cc new file mode 100644 index 0000000..19f9121 --- /dev/null +++ b/src/lib/eval/tests/dependency_unittest.cc @@ -0,0 +1,103 @@ +// Copyright (C) 2018,2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <eval/dependency.h> +#include <eval/eval_context.h> +#include <eval/token.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option_string.h> + +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc::dhcp; + +namespace { + +/// @brief Test fixture for testing dependency. +/// +/// This class provides several convenience objects to be used during testing +/// of the dependency of classification expressions. +class DependencyTest : public ::testing::Test { +public: + /// @brief Constructor + DependencyTest() : result_(true) { + } + + /// @brief Destructor + /// + /// Reset expression and result. + ~DependencyTest() { + e_.reset(); + result_ = false; + } + + ExpressionPtr e_; ///< An expression + + bool result_; ///< A decision +}; + +// This checks the null expression: it should return false. +TEST_F(DependencyTest, nullExpr) { + TokenPtr token; + ASSERT_NO_THROW(result_ = dependOnClass(token, "foobar")); + EXPECT_FALSE(result_); + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_FALSE(result_); +} + +// This checks the empty expression: it should return false. +TEST_F(DependencyTest, emptyExpr) { + e_.reset(new Expression()); + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_FALSE(result_); +} + +// This checks the { "true" } expression: it should return false. +TEST_F(DependencyTest, trueExpr) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + ASSERT_NO_THROW(result_ = dependOnClass(ttrue, "foobar")); + EXPECT_FALSE(result_); + e_.reset(new Expression()); + e_->push_back(ttrue); + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_FALSE(result_); +} + +// This checks the { member('not-matching') } expression: +// it should return false. +TEST_F(DependencyTest, notMatching) { + TokenPtr notmatching; + ASSERT_NO_THROW(notmatching.reset(new TokenMember("not-matching"))); + ASSERT_NO_THROW(result_ = dependOnClass(notmatching, "foobar")); + EXPECT_FALSE(result_); + e_.reset(new Expression()); + e_->push_back(notmatching); + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_FALSE(result_); +} + +// This checks the { member('foobar') } expression: it should return true. +TEST_F(DependencyTest, matching) { + TokenPtr matching; + ASSERT_NO_THROW(matching.reset(new TokenMember("foobar"))); + ASSERT_NO_THROW(result_ = dependOnClass(matching, "foobar")); + EXPECT_TRUE(result_); + e_.reset(new Expression()); + e_->push_back(matching); + result_ = false; + ASSERT_NO_THROW(result_ = dependOnClass(e_, "foobar")); + EXPECT_TRUE(result_); +} + +}; diff --git a/src/lib/eval/tests/evaluate_unittest.cc b/src/lib/eval/tests/evaluate_unittest.cc new file mode 100644 index 0000000..37c4f83 --- /dev/null +++ b/src/lib/eval/tests/evaluate_unittest.cc @@ -0,0 +1,515 @@ +// Copyright (C) 2015-2018,2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <eval/evaluate.h> +#include <eval/eval_context.h> +#include <eval/token.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option_string.h> + +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +using namespace std; +using namespace isc::dhcp; + +namespace { + +/// @brief Test fixture for testing evaluation. +/// +/// This class provides several convenience objects to be used during testing +/// of the evaluation of classification expressions. +class EvaluateTest : public ::testing::Test { +public: + + /// @brief Initializes Pkt4,Pkt6 and options that can be useful for + /// evaluation tests. + EvaluateTest() { + e_.clear(); + + result_ = false; + + pkt4_.reset(new Pkt4(DHCPDISCOVER, 12345)); + pkt6_.reset(new Pkt6(DHCPV6_SOLICIT, 12345)); + + // Add options with easily identifiable strings in them + option_str4_.reset(new OptionString(Option::V4, 100, "hundred4")); + option_str6_.reset(new OptionString(Option::V6, 100, "hundred6")); + + pkt4_->addOption(option_str4_); + pkt6_->addOption(option_str6_); + } + + Expression e_; ///< An expression + + bool result_; ///< A decision + + Pkt4Ptr pkt4_; ///< A stub DHCPv4 packet + Pkt6Ptr pkt6_; ///< A stub DHCPv6 packet + + OptionPtr option_str4_; ///< A string option for DHCPv4 + OptionPtr option_str6_; ///< A string option for DHCPv6 + + /// @todo: Add more option types here +}; + +// This checks the empty expression: it should raise EvalBadStack +// when evaluated with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, empty4) { + ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack); +} + +// This checks the empty expression: it should raise EvalBadStack +// when evaluated with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, empty6) { + ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack); +} + +// This checks the { "false" } expression: it should return false +// when evaluated with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, false4) { + TokenPtr tfalse; + ASSERT_NO_THROW(tfalse.reset(new TokenString("false"))); + e_.push_back(tfalse); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_FALSE(result_); +} + +// This checks the { "false" } expression: it should return false +// when evaluated with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, false6) { + TokenPtr tfalse; + ASSERT_NO_THROW(tfalse.reset(new TokenString("false"))); + e_.push_back(tfalse); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// This checks the { "true" } expression: it should return true +// when evaluated with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, true4) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + e_.push_back(ttrue); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); +} + +// This checks the { "true" } expression: it should return true +// when evaluated with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, true6) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + e_.push_back(ttrue); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_TRUE(result_); +} + +// This checks the evaluation must lead to "false" or "true" +// with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, bad4) { + TokenPtr bad; + ASSERT_NO_THROW(bad.reset(new TokenString("bad"))); + e_.push_back(bad); + ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalTypeError); +} + +// This checks the evaluation must lead to "false" or "true" +// with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, bad6) { + TokenPtr bad; + ASSERT_NO_THROW(bad.reset(new TokenString("bad"))); + e_.push_back(bad); + ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalTypeError); +} + +// This checks the evaluation must leave only one value on the stack +// with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, two4) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + e_.push_back(ttrue); + e_.push_back(ttrue); + ASSERT_THROW(evaluateBool(e_, *pkt4_), EvalBadStack); +} + +// This checks the evaluation must leave only one value on the stack +// with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, two6) { + TokenPtr ttrue; + ASSERT_NO_THROW(ttrue.reset(new TokenString("true"))); + e_.push_back(ttrue); + e_.push_back(ttrue); + ASSERT_THROW(evaluateBool(e_, *pkt6_), EvalBadStack); +} + +// A more complex test evaluated with a Pkt4. (The actual packet is not used) +TEST_F(EvaluateTest, compare4) { + TokenPtr tfoo; + TokenPtr tbar; + TokenPtr tequal; + + ASSERT_NO_THROW(tfoo.reset(new TokenString("foo"))); + e_.push_back(tfoo); + ASSERT_NO_THROW(tbar.reset(new TokenString("bar"))); + e_.push_back(tbar); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_FALSE(result_); +} + +// A more complex test evaluated with a Pkt6. (The actual packet is not used) +TEST_F(EvaluateTest, compare6) { + TokenPtr tfoo; + TokenPtr tbar; + TokenPtr tequal; + + ASSERT_NO_THROW(tfoo.reset(new TokenString("foo"))); + e_.push_back(tfoo); + ASSERT_NO_THROW(tbar.reset(new TokenString("bar"))); + e_.push_back(tbar); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// A test using option existence +TEST_F(EvaluateTest, exists) { + TokenPtr toption; + + ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::EXISTS))); + e_.push_back(toption); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_TRUE(result_); +} + +// A test using option non-existence +TEST_F(EvaluateTest, dontExists) { + TokenPtr toption; + + ASSERT_NO_THROW(toption.reset(new TokenOption(101, TokenOption::EXISTS))); + e_.push_back(toption); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_FALSE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// A test using packets. +TEST_F(EvaluateTest, packet) { + TokenPtr toption; + TokenPtr tstring; + TokenPtr tequal; + + ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::TEXTUAL))); + e_.push_back(toption); + ASSERT_NO_THROW(tstring.reset(new TokenString("hundred4"))); + e_.push_back(tstring); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// A test which compares option value represented in hexadecimal format. +TEST_F(EvaluateTest, optionHex) { + TokenPtr toption; + TokenPtr tstring; + TokenPtr tequal; + + ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::HEXADECIMAL))); + e_.push_back(toption); + ASSERT_NO_THROW(tstring.reset(new TokenString("hundred4"))); + e_.push_back(tstring); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_FALSE(result_); +} + +// A test using substring on an option. +TEST_F(EvaluateTest, complex) { + TokenPtr toption; + TokenPtr tstart; + TokenPtr tlength; + TokenPtr tsubstring; + TokenPtr tstring; + TokenPtr tequal; + + // Get the option, i.e., "hundred[46]" + ASSERT_NO_THROW(toption.reset(new TokenOption(100, TokenOption::TEXTUAL))); + e_.push_back(toption); + + // Get substring("hundred[46]", 0, 7), i.e., "hundred" + ASSERT_NO_THROW(tstart.reset(new TokenString("0"))); + e_.push_back(tstart); + ASSERT_NO_THROW(tlength.reset(new TokenString("7"))); + e_.push_back(tlength); + ASSERT_NO_THROW(tsubstring.reset(new TokenSubstring())); + e_.push_back(tsubstring); + + // Compare with "hundred" + ASSERT_NO_THROW(tstring.reset(new TokenString("hundred"))); + e_.push_back(tstring); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + e_.push_back(tequal); + + // Should return true for v4 and v6 packets + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt4_)); + EXPECT_TRUE(result_); + ASSERT_NO_THROW(result_ = evaluateBool(e_, *pkt6_)); + EXPECT_TRUE(result_); +} + +/// @brief Generic class for parsing expressions and evaluating them. +/// +/// The main purpose of this class is to provide a generic interface to the +/// eval library, so everything (expression parsing and then evaluation for +/// given packets) can be done in one simple call. +/// +/// These tests may be somewhat redundant to other more specialized tests, but +/// the idea here is to mass produce tests that are trivial to write. +class ExpressionsTest : public EvaluateTest { +public: + + /// @brief Checks if expression can be parsed and evaluated to bool + /// + /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you + /// tweak them as needed before calling this method. + /// + /// @param u universe (V4 or V6) + /// @param expr expression to be parsed + /// @param exp_result expected result (true or false) + void testExpression(const Option::Universe& u, const std::string& expr, + const bool exp_result) { + + EvalContext eval(u); + bool result = false; + bool parsed = false; + + EXPECT_NO_THROW(parsed = eval.parseString(expr)) + << " while parsing expression " << expr; + EXPECT_TRUE(parsed) << " for expression " << expr; + + switch (u) { + case Option::V4: + ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt4_)) + << " for expression " << expr; + break; + case Option::V6: + ASSERT_NO_THROW(result = evaluateBool(eval.expression, *pkt6_)) + << " for expression " << expr; + break; + } + + EXPECT_EQ(exp_result, result) << " for expression " << expr; + } + + /// @brief Checks if expression can be parsed and evaluated to string + /// + /// There are skeleton packets created in pkt4_ and pkt6_. Make sure you + /// tweak them as needed before calling this method. + /// + /// @param u universe (V4 or V6) + /// @param expr expression to be parsed + /// @param exp_result expected result (string) + void testExpressionString(const Option::Universe& u, const std::string& expr, + const std::string& exp_result) { + + EvalContext eval(u); + string result; + bool parsed = false; + + EXPECT_NO_THROW(parsed = eval.parseString(expr, EvalContext::PARSER_STRING)) + << " while parsing expression " << expr; + EXPECT_TRUE(parsed) << " for expression " << expr; + + switch (u) { + case Option::V4: + ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt4_)) + << " for expression " << expr; + break; + case Option::V6: + ASSERT_NO_THROW(result = evaluateString(eval.expression, *pkt6_)) + << " for expression " << expr; + break; + } + + EXPECT_EQ(exp_result, result) << " for expression " << expr; + } + + /// @brief Checks that specified expression throws expected exception. + /// + /// @tparam ex exception type expected to be thrown + /// @param expr expression to be evaluated + template<typename ex> + void testExpressionNegative(const std::string& expr, + const Option::Universe& u = Option::V4, + EvalContext::ParserType type = EvalContext::PARSER_BOOL) { + EvalContext eval(u); + + EXPECT_THROW(eval.parseString(expr, type), ex) << "while parsing expression " + << expr; + } +}; + +// This is a quick way to check if certain expressions are valid or not and +// whether the whole expression makes sense. This particular test checks if +// integers can be used properly in expressions. There are many places where +// integers are used. This particular test checks if pkt6.msgtype returns +// something that can be compared with integers. +// +// For basic things we can take advantage of the skeleton packets created in +// EvaluateTest constructors: The packet type is DISCOVER in DHCPv4 and +// SOLICIT in DHCPv6. There is one option added with code 100 and content +// being either "hundred4" or "hundred6" depending on the universe. + +// Tests if pkt6.msgtype returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger1) { + testExpression(Option::V6, "pkt6.msgtype == 1", true); + testExpression(Option::V6, "pkt6.msgtype == 2", false); + + testExpression(Option::V6, "pkt6.msgtype == 0x00000001", true); + testExpression(Option::V6, "pkt6.msgtype == 0x00000002", false); +} + +// Tests if pkt6.transid returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger2) { + testExpression(Option::V6, "pkt6.transid == 0", false); + testExpression(Option::V6, "pkt6.transid == 12345", true); + testExpression(Option::V6, "pkt6.transid == 12346", false); +} + +// Tests if pkt4.transid returns something that can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger3) { + testExpression(Option::V4, "pkt4.transid == 0", false); + testExpression(Option::V4, "pkt4.transid == 12345", true); + testExpression(Option::V4, "pkt4.transid == 12346", false); +} + +// Tests if integers can be compared with integers. +TEST_F(ExpressionsTest, expressionsInteger4) { + testExpression(Option::V6, "0 == 0", true); + testExpression(Option::V6, "2 == 3", false); +} + +// Tests if pkt4.hlen and pkt4.htype return values that can be compared with integers. +TEST_F(ExpressionsTest, expressionsPkt4Hlen) { + + // By default there's no hardware set up. The default Pkt4 constructor + // creates HWAddr(), which has hlen=0 and htype set to HTYPE_ETHER. + testExpression(Option::V4, "pkt4.hlen == 0", true); + testExpression(Option::V4, "pkt4.htype == 1", true); + + // Ok, let's initialize the hardware address to something plausible. + const size_t hwaddr_len = 6; + const uint16_t expected_htype = 123; + std::vector<uint8_t> hw(hwaddr_len,0); + for (int i = 0; i < hwaddr_len; i++) { + hw[i] = i + 1; + } + pkt4_->setHWAddr(expected_htype, hwaddr_len, hw); + + testExpression(Option::V4, "pkt4.hlen == 0", false); + testExpression(Option::V4, "pkt4.hlen == 5", false); + testExpression(Option::V4, "pkt4.hlen == 6", true); + testExpression(Option::V4, "pkt4.hlen == 7", false); + + testExpression(Option::V4, "pkt4.htype == 0", false); + testExpression(Option::V4, "pkt4.htype == 122", false); + testExpression(Option::V4, "pkt4.htype == 123", true); + testExpression(Option::V4, "pkt4.htype == 124", false); + + testExpression(Option::V4, "pkt4.mac == 0x010203040506", true); +} + +// Test if expressions message type can be detected in Pkt4. +// It also doubles as a check for integer comparison here. +TEST_F(ExpressionsTest, expressionsPkt4type) { + + // We can inspect the option content directly, but + // it requires knowledge of the option type and its format. + testExpression(Option::V4, "option[53].hex == 0x0", false); + testExpression(Option::V4, "option[53].hex == 0x1", true); + testExpression(Option::V4, "option[53].hex == 0x2", false); + + // It's easier to simply use the pkt4.msgtype + testExpression(Option::V4, "pkt4.msgtype == 0", false); + testExpression(Option::V4, "pkt4.msgtype == 1", true); + testExpression(Option::V4, "pkt4.msgtype == 2", false); +} + +// This tests if inappropriate values (negative, too large) are +// rejected, but extreme values still allowed for uint32_t are ok. +TEST_F(ExpressionsTest, invalidIntegers) { + + // These are the extreme uint32_t values that still should be accepted. + testExpression(Option::V4, "4294967295 == 0", false); + + // Negative integers should be rejected. + testExpressionNegative<EvalParseError>("4294967295 == -1"); + + // Oops, one too much. + testExpressionNegative<EvalParseError>("4294967296 == 0"); +} + +// Tests whether expressions can be evaluated to a string. +TEST_F(ExpressionsTest, evaluateString) { + + // Check that content of the options is returned properly. + testExpressionString(Option::V4, "option[100].hex", "hundred4"); + testExpressionString(Option::V6, "option[100].hex", "hundred6"); + + // Check that content of non-existing option returns empty string. + testExpressionString(Option::V4, "option[200].hex", ""); + testExpressionString(Option::V6, "option[200].hex", ""); + + testExpressionNegative<EvalParseError>("pkt4.msgtype == 1", Option::V4, + EvalContext::PARSER_STRING); + testExpressionNegative<EvalParseError>("pkt6.msgtype == 1", Option::V6, + EvalContext::PARSER_STRING); + + // Check that ifelse works as expecting (it was added explicitly for + // the string evaluation). + testExpressionString(Option::V4, + "ifelse(option[100].exists,'foo','bar')", "foo"); + testExpressionString(Option::V4, + "ifelse(option[200].exists,'foo','bar')", "bar"); + + // Check that ifelse can be chained. + testExpressionString(Option::V4, + "ifelse(option[200].exists,option[200].hex," + "ifelse(option[100].exists," + "option[100].hex,'none?'))", + "hundred4"); + + // Check that hexstring works as expecting. + testExpressionString(Option::V4, "hexstring(0x1234,':')", "12:34"); + testExpressionString(Option::V4, "hexstring(0x56789a,'-')", "56-78-9a"); + testExpressionString(Option::V4, "hexstring(0xbcde,'')", "bcde"); + testExpressionString(Option::V4, "hexstring(0xf01234,'..')", "f0..12..34"); +} + +}; diff --git a/src/lib/eval/tests/run_unittests.cc b/src/lib/eval/tests/run_unittests.cc new file mode 100644 index 0000000..08f04d1 --- /dev/null +++ b/src/lib/eval/tests/run_unittests.cc @@ -0,0 +1,21 @@ +// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <log/logger_support.h> + +#include <gtest/gtest.h> + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + isc::log::initLogger(); + + int result = RUN_ALL_TESTS(); + + return (result); +} diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc new file mode 100644 index 0000000..bc70927 --- /dev/null +++ b/src/lib/eval/tests/token_unittest.cc @@ -0,0 +1,3487 @@ +// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> +#include <fstream> +#include <eval/token.h> +#include <eval/eval_context.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option_string.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_vendor_class.h> +#include <log/logger_manager.h> +#include <log/logger_name.h> +#include <log/logger_support.h> +#include <testutils/log_utils.h> + +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/constants.hpp> +#include <boost/algorithm/string/split.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <arpa/inet.h> + +using namespace std; +using namespace isc::dhcp; +using namespace isc::asiolink; +using namespace isc::log; +using namespace isc::dhcp::test; + +namespace { + +/// @brief Test fixture for testing Tokens. +/// +/// This class provides several convenience objects to be used during testing +/// of the Token family of classes. + +class TokenTest : public LogContentTest { +public: + + /// @brief Initializes Pkt4, Pkt6 and options that can be useful for + /// evaluation tests. + TokenTest() { + pkt4_.reset(new Pkt4(DHCPDISCOVER, 12345)); + pkt6_.reset(new Pkt6(DHCPV6_SOLICIT, 12345)); + + // Add options with easily identifiable strings in them + option_str4_.reset(new OptionString(Option::V4, 100, "hundred4")); + option_str6_.reset(new OptionString(Option::V6, 100, "hundred6")); + + pkt4_->addOption(option_str4_); + pkt6_->addOption(option_str6_); + + // Change this to true if you need extra information about logging + // checks to be printed. + logCheckVerbose(false); + } + + /// @brief Inserts RAI option with several suboptions + /// + /// The structure inserted is: + /// - RAI (option 82) + /// - option 1 (containing string "one") + /// - option 13 (containing string "thirteen") + void insertRelay4Option() { + + // RAI (Relay Agent Information) option + OptionPtr rai(new Option(Option::V4, DHO_DHCP_AGENT_OPTIONS)); + OptionPtr sub1(new OptionString(Option::V4, 1, "one")); + OptionPtr sub13(new OptionString(Option::V4, 13, "thirteen")); + + rai->addOption(sub1); + rai->addOption(sub13); + pkt4_->addOption(rai); + } + + /// @brief Adds relay encapsulations with some suboptions + /// + /// This will add 2 relay encapsulations all will have + /// msg_type of RELAY_FORW + /// Relay 0 (closest to server) will have + /// linkaddr = peeraddr = 0, hop-count = 1 + /// option 100 "hundred.zero", option 101 "hundredone.zero" + /// Relay 1 (closest to client) will have + /// linkaddr 1::1= peeraddr = 1::2, hop-count = 0 + /// option 100 "hundred.one", option 102 "hundredtwo.one" + void addRelay6Encapsulations() { + // First relay + Pkt6::RelayInfo relay0; + relay0.msg_type_ = DHCPV6_RELAY_FORW; + relay0.hop_count_ = 1; + relay0.linkaddr_ = isc::asiolink::IOAddress("::"); + relay0.peeraddr_ = isc::asiolink::IOAddress("::"); + OptionPtr optRelay01(new OptionString(Option::V6, 100, + "hundred.zero")); + OptionPtr optRelay02(new OptionString(Option::V6, 101, + "hundredone.zero")); + + relay0.options_.insert(make_pair(optRelay01->getType(), optRelay01)); + relay0.options_.insert(make_pair(optRelay02->getType(), optRelay02)); + + pkt6_->addRelayInfo(relay0); + // Second relay + Pkt6::RelayInfo relay1; + relay1.msg_type_ = DHCPV6_RELAY_FORW; + relay1.hop_count_ = 0; + relay1.linkaddr_ = isc::asiolink::IOAddress("1::1"); + relay1.peeraddr_ = isc::asiolink::IOAddress("1::2"); + OptionPtr optRelay11(new OptionString(Option::V6, 100, + "hundred.one")); + OptionPtr optRelay12(new OptionString(Option::V6, 102, + "hundredtwo.one")); + + relay1.options_.insert(make_pair(optRelay11->getType(), optRelay11)); + relay1.options_.insert(make_pair(optRelay12->getType(), optRelay12)); + pkt6_->addRelayInfo(relay1); + } + + /// @brief Verify that the relay6 option evaluations work properly + /// + /// Given the nesting level and option code extract the option + /// and compare it to the expected string. + /// + /// @param test_level The nesting level + /// @param test_code The code of the option to extract + /// @param result_addr The expected result of the address as a string + void verifyRelay6Option(const int8_t test_level, + const uint16_t test_code, + const TokenOption::RepresentationType& test_rep, + const std::string& result_string) { + // Create the token + ASSERT_NO_THROW(t_.reset(new TokenRelay6Option(test_level, + test_code, + test_rep))); + + // We should be able to evaluate it + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + + // We should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // And it should match the expected result + // Invalid nesting levels result in a 0 length string + EXPECT_EQ(result_string, values_.top()); + + // Then we clear the stack + clearStack(); + } + + /// @brief Verify that the relay6 field evaluations work properly + /// + /// Given the nesting level, the field to extract and the expected + /// address create a token and evaluate it then compare the addresses + /// + /// @param test_level The nesting level + /// @param test_field The type of the field to extract + /// @param result_addr The expected result of the address as a string + void verifyRelay6Eval(const int8_t test_level, + const TokenRelay6Field::FieldType test_field, + const int result_len, + const uint8_t result_addr[]) { + // Create the token + ASSERT_NO_THROW(t_.reset(new TokenRelay6Field(test_level, test_field))); + + // We should be able to evaluate it + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + + // We should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // And it should match the expected result + // Invalid nesting levels result in a 0 length string + EXPECT_EQ(result_len, values_.top().size()); + if (result_len != 0) { + EXPECT_EQ(0, memcmp(result_addr, &values_.top()[0], result_len)); + } + + // Then we clear the stack + clearStack(); + } + + /// @brief Convenience function. Removes token and values stacks. + /// @param token specifies if the convenience token should be removed or not + void clearStack(bool token = true) { + while (!values_.empty()) { + values_.pop(); + } + if (token) { + t_.reset(); + } + } + + /// @brief Aux. function that stores integer values as 4 byte string. + /// + /// @param value integer value to be stored + /// @return 4 byte long string with encoded value. + string encode(uint32_t value) { + return EvalContext::fromUint32(value); + } + + TokenPtr t_; ///< Just a convenience pointer + + ValueStack values_; ///< evaluated values will be stored here + + Pkt4Ptr pkt4_; ///< A stub DHCPv4 packet + Pkt6Ptr pkt6_; ///< A stub DHCPv6 packet + + OptionPtr option_str4_; ///< A string option for DHCPv4 + OptionPtr option_str6_; ///< A string option for DHCPv6 + + OptionVendorPtr vendor_; ///< Vendor option used during tests + OptionVendorClassPtr vendor_class_; ///< Vendor class option used during tests + + /// @brief Verify that the substring eval works properly + /// + /// This function takes the parameters and sets up the value + /// stack then executes the eval and checks the results. + /// + /// @param test_string The string to operate on + /// @param test_start The position to start when getting a substring + /// @param test_length The length of the substring to get + /// @param result_string The expected result of the eval + /// @param should_throw The eval will throw + void verifySubstringEval(const std::string& test_string, + const std::string& test_start, + const std::string& test_length, + const std::string& result_string, + bool should_throw = false) { + + // create the token + ASSERT_NO_THROW(t_.reset(new TokenSubstring())); + + // push values on stack + values_.push(test_string); + values_.push(test_start); + values_.push(test_length); + + // evaluate the token + if (should_throw) { + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + ASSERT_EQ(0, values_.size()); + } else { + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // verify results + ASSERT_EQ(1, values_.size()); + EXPECT_EQ(result_string, values_.top()); + + // remove result + values_.pop(); + } + } + + /// @brief Verify that the split eval works properly + /// + /// This function takes the parameters and sets up the value + /// stack then executes the eval and checks the results. + /// + /// @param test_string The string to operate on + /// @param test_delimiters The string of delimiter characters to split upon + /// @param test_field The field number of the desired field + /// @param result_string The expected result of the eval + /// @param should_throw The eval will throw + void verifySplitEval(const std::string& test_string, + const std::string& test_delimiters, + const std::string& test_field, + const std::string& result_string, + bool should_throw = false) { + // create the token + ASSERT_NO_THROW(t_.reset(new TokenSplit())); + + // push values on stack + values_.push(test_string); + values_.push(test_delimiters); + values_.push(test_field); + + // evaluate the token + if (should_throw) { + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + ASSERT_EQ(0, values_.size()); + } else { + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // verify results + ASSERT_EQ(1, values_.size()); + EXPECT_EQ(result_string, values_.top()); + + // remove result + values_.pop(); + } + } + + /// @brief Creates vendor-option with specified value and adds it to packet + /// + /// This method creates specified vendor option, removes any existing + /// vendor options and adds the new one to v4 or v6 packet. + /// + /// @param u universe (V4 or V6) + /// @param vendor_id specifies enterprise-id value. + void setVendorOption(Option::Universe u, uint32_t vendor_id) { + vendor_.reset(new OptionVendor(u, vendor_id)); + switch (u) { + case Option::V4: + pkt4_->delOption(DHO_VIVSO_SUBOPTIONS); + pkt4_->addOption(vendor_); + break; + case Option::V6: + pkt6_->delOption(D6O_VENDOR_OPTS); + pkt6_->addOption(vendor_); + break; + } + } + + /// @brief Creates vendor-class option with specified values and adds it to packet + /// + /// This method creates specified vendor-class option, removes any existing + /// vendor class options and adds the new one to v4 or v6 packet. + /// It also creates data tuples with greek alphabet names. + /// + /// @param u universe (V4 or V6) + /// @param vendor_id specifies enterprise-id value. + /// @param tuples_size number of data tuples to create. + void setVendorClassOption(Option::Universe u, uint32_t vendor_id, + size_t tuples_size = 0) { + // Create the option first. + vendor_class_.reset(new OptionVendorClass(u, vendor_id)); + + // Now let's add specified number of data tuples + OpaqueDataTuple::LengthFieldType len = (u == Option::V4?OpaqueDataTuple::LENGTH_1_BYTE: + OpaqueDataTuple::LENGTH_2_BYTES); + const char* content[] = { "alpha", "beta", "delta", "gamma", "epsilon", + "zeta", "eta", "theta", "iota", "kappa" }; + const size_t nb_content = sizeof(content) / sizeof(char*); + ASSERT_TRUE(tuples_size < nb_content); + for (size_t i = 0; i < tuples_size; ++i) { + OpaqueDataTuple tuple(len); + tuple.assign(string(content[i])); + if (u == Option::V4 && i == 0) { + // vendor-class for v4 has a peculiar quirk. The first tuple is being + // added, even if there's no data at all. + vendor_class_->setTuple(0, tuple); + } else { + vendor_class_->addTuple(tuple); + } + } + + switch (u) { + case Option::V4: + pkt4_->delOption(DHO_VIVCO_SUBOPTIONS); + pkt4_->addOption(vendor_class_); + break; + case Option::V6: + pkt6_->delOption(D6O_VENDOR_CLASS); + pkt6_->addOption(vendor_class_); + break; + } + } + + /// @brief Auxiliary function that evaluates tokens and checks result + /// + /// Depending on the universe, either pkt4_ or pkt6_ are supposed to have + /// all the necessary values and options set. The result is checked + /// on the values_ stack. + /// + /// @param u universe (V4 or V6) + /// @param expected_result text representation of the expected outcome + void evaluate(Option::Universe u, std::string expected_result) { + switch (u) { + case Option::V4: + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + break; + case Option::V6: + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + break; + default: + ADD_FAILURE() << "Invalid universe specified."; + } + ASSERT_EQ(1, values_.size()); + EXPECT_EQ(expected_result, values_.top()); + } + + /// @brief Tests if vendor token behaves properly. + /// + /// @param u universe (V4 or V6) + /// @param token_vendor_id enterprise-id used in the token + /// @param option_vendor_id enterprise-id used in option (0 means don't + /// create the option) + /// @param expected_result text representation of the expected outcome + void testVendorExists(Option::Universe u, uint32_t token_vendor_id, + uint32_t option_vendor_id, + const std::string& expected_result) { + // Let's clear any old values, so we can run multiple cases in each test + clearStack(); + + // Create the token + ASSERT_NO_THROW(t_.reset(new TokenVendor(u, token_vendor_id, + TokenOption::EXISTS))); + + // If specified option is non-zero, create it. + if (option_vendor_id) { + setVendorOption(u, option_vendor_id); + } + + evaluate(u, expected_result); + } + + /// @brief Tests if vendor token properly returns enterprise-id. + /// + /// @param u universe (V4 or V6) + /// @param option_vendor_id enterprise-id used in option (0 means don't + /// create the option) + /// @param expected_result text representation of the expected outcome + void testVendorEnterprise(Option::Universe u, uint32_t option_vendor_id, + const std::string& expected_result) { + // Let's clear any old values, so we can run multiple cases in each test + clearStack(); + + ASSERT_NO_THROW(t_.reset(new TokenVendor(u, 0, TokenVendor::ENTERPRISE_ID))); + if (option_vendor_id) { + setVendorOption(u, option_vendor_id); + } + + evaluate(u, expected_result); + } + + /// @brief Tests if vendor class token properly returns enterprise-id. + /// + /// @param u universe (V4 or V6) + /// @param option_vendor_id enterprise-id used in option (0 means don't + /// create the option) + /// @param expected_result text representation of the expected outcome + void testVendorClassEnterprise(Option::Universe u, uint32_t option_vendor_id, + const std::string& expected_result) { + // Let's clear any old values, so we can run multiple cases in each test + clearStack(); + + ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, 0, TokenVendor::ENTERPRISE_ID))); + if (option_vendor_id) { + setVendorClassOption(u, option_vendor_id); + } + + evaluate(u, expected_result); + } + + /// @brief Tests if vendor class token can report existence properly. + /// + /// @param u universe (V4 or V6) + /// @param token_vendor_id enterprise-id used in the token + /// @param option_vendor_id enterprise-id used in option (0 means don't + /// create the option) + /// @param expected_result text representation of the expected outcome + void testVendorClassExists(Option::Universe u, uint32_t token_vendor_id, + uint32_t option_vendor_id, + const std::string& expected_result) { + // Let's clear any old values, so we can run multiple cases in each test + clearStack(); + + ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, token_vendor_id, + TokenOption::EXISTS))); + + if (option_vendor_id) { + setVendorClassOption(u, option_vendor_id); + } + + evaluate(u, expected_result); + } + + /// @brief Tests if vendor token can handle sub-options properly. + /// + /// @param u universe (V4 or V6) + /// @param token_vendor_id enterprise-id used in the token + /// @param token_option_code option code in the token + /// @param option_vendor_id enterprise-id used in option (0 means don't + /// create the option) + /// @param option_code sub-option code (0 means don't create suboption) + /// @param repr representation (TokenOption::EXISTS or HEXADECIMAL) + /// @param expected_result text representation of the expected outcome + void testVendorSuboption(Option::Universe u, + uint32_t token_vendor_id, uint16_t token_option_code, + uint32_t option_vendor_id, uint16_t option_code, + TokenOption::RepresentationType repr, + const std::string& expected) { + // Let's clear any old values, so we can run multiple cases in each test + clearStack(); + + ASSERT_NO_THROW(t_.reset(new TokenVendor(u, token_vendor_id, repr, + token_option_code))); + if (option_vendor_id) { + setVendorOption(u, option_vendor_id); + if (option_code) { + ASSERT_TRUE(vendor_); + OptionPtr subopt(new OptionString(u, option_code, "alpha")); + vendor_->addOption(subopt); + } + } + + evaluate(u, expected); + } + + /// @brief Tests if vendor class token can handle data chunks properly. + /// + /// @param u universe (V4 or V6) + /// @param token_vendor_id enterprise-id used in the token + /// @param token_index data index used in the token + /// @param option_vendor_id enterprise-id used in option (0 means don't + /// create the option) + /// @param data_tuples number of data tuples in the option + /// @param expected_result text representation of the expected outcome + void testVendorClassData(Option::Universe u, + uint32_t token_vendor_id, uint16_t token_index, + uint32_t option_vendor_id, uint16_t data_tuples, + const std::string& expected) { + // Let's clear any old values, so we can run multiple cases in each test + clearStack(); + + ASSERT_NO_THROW(t_.reset(new TokenVendorClass(u, token_vendor_id, + TokenVendor::DATA, token_index))); + if (option_vendor_id) { + setVendorClassOption(u, option_vendor_id, data_tuples); + } + + evaluate(u, expected); + } + + /// @brief Tests if TokenInteger evaluates to the proper value + /// + /// @param expected expected string representation on stack after evaluation + /// @param value integer value passed to constructor + void testInteger(const std::string& expected, uint32_t value) { + + clearStack(); + + ASSERT_NO_THROW(t_.reset(new TokenInteger(value))); + + // The universe (v4 or v6) shouldn't have any impact on this, + // but let's check it anyway. + evaluate(Option::V4, expected); + + clearStack(false); + evaluate(Option::V6, expected); + + clearStack(true); + } +}; + +// This tests the toBool() conversions +TEST_F(TokenTest, toBool) { + + ASSERT_NO_THROW(Token::toBool("true")); + EXPECT_TRUE(Token::toBool("true")); + ASSERT_NO_THROW(Token::toBool("false")); + EXPECT_FALSE(Token::toBool("false")); + + // Token::toBool() is case-sensitive + EXPECT_THROW(Token::toBool("True"), EvalTypeError); + EXPECT_THROW(Token::toBool("TRUE"), EvalTypeError); + + // Proposed aliases + EXPECT_THROW(Token::toBool("1"), EvalTypeError); + EXPECT_THROW(Token::toBool("0"), EvalTypeError); + EXPECT_THROW(Token::toBool(""), EvalTypeError); +} + +// This simple test checks that a TokenString, representing a constant string, +// can be used in Pkt4 evaluation. (The actual packet is not used) +TEST_F(TokenTest, string4) { + + // Store constant string "foo" in the TokenString object. + ASSERT_NO_THROW(t_.reset(new TokenString("foo"))); + + // Make sure that the token can be evaluated without exceptions. + ASSERT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("foo", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_STRING Pushing text string 'foo'"); + EXPECT_TRUE(checkFile()); +} + +// This simple test checks that a TokenString, representing a constant string, +// can be used in Pkt6 evaluation. (The actual packet is not used) +TEST_F(TokenTest, string6) { + + // Store constant string "foo" in the TokenString object. + ASSERT_NO_THROW(t_.reset(new TokenString("foo"))); + + // Make sure that the token can be evaluated without exceptions. + ASSERT_NO_THROW(t_->evaluate(*pkt6_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("foo", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_STRING Pushing text string 'foo'"); + EXPECT_TRUE(checkFile()); +} + +// This simple test checks that a TokenHexString, representing a constant +// string coded in hexadecimal, can be used in Pkt4 evaluation. +// (The actual packet is not used) +TEST_F(TokenTest, hexstring4) { + TokenPtr empty; + TokenPtr bad; + TokenPtr nodigit; + TokenPtr baddigit; + TokenPtr bell; + TokenPtr foo; + TokenPtr cookie; + + // Store constant empty hexstring "" ("") in the TokenHexString object. + ASSERT_NO_THROW(empty.reset(new TokenHexString(""))); + // Store bad encoded hexstring "0abc" (""). + ASSERT_NO_THROW(bad.reset(new TokenHexString("0abc"))); + // Store hexstring with no digits "0x" (""). + ASSERT_NO_THROW(nodigit.reset(new TokenHexString("0x"))); + // Store hexstring with a bad hexdigit "0xxabc" (""). + ASSERT_NO_THROW(baddigit.reset(new TokenHexString("0xxabc"))); + // Store hexstring with an odd number of hexdigits "0x7" ("\a"). + ASSERT_NO_THROW(bell.reset(new TokenHexString("0x7"))); + // Store constant hexstring "0x666f6f" ("foo"). + ASSERT_NO_THROW(foo.reset(new TokenHexString("0x666f6f"))); + // Store constant hexstring "0x63825363" (DHCP_OPTIONS_COOKIE). + ASSERT_NO_THROW(cookie.reset(new TokenHexString("0x63825363"))); + + // Make sure that tokens can be evaluated without exceptions, + // and verify the debug output + ASSERT_NO_THROW(empty->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(bad->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(nodigit->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(baddigit->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(bell->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(foo->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(cookie->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(7, values_.size()); + uint32_t expected = htonl(DHCP_OPTIONS_COOKIE); + EXPECT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4)); + values_.pop(); + EXPECT_EQ("foo", values_.top()); + values_.pop(); + EXPECT_EQ("\a", values_.top()); + values_.pop(); + EXPECT_EQ("", values_.top()); + values_.pop(); + EXPECT_EQ("", values_.top()); + values_.pop(); + EXPECT_EQ("", values_.top()); + values_.pop(); + EXPECT_EQ("", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x07"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x666F6F"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x63825363"); + EXPECT_TRUE(checkFile()); +} + +// This simple test checks that a TokenHexString, representing a constant +// string coded in hexadecimal, can be used in Pkt6 evaluation. +// (The actual packet is not used) +TEST_F(TokenTest, hexstring6) { + TokenPtr empty; + TokenPtr bad; + TokenPtr nodigit; + TokenPtr baddigit; + TokenPtr bell; + TokenPtr foo; + TokenPtr cookie; + + // Store constant empty hexstring "" ("") in the TokenHexString object. + ASSERT_NO_THROW(empty.reset(new TokenHexString(""))); + // Store bad encoded hexstring "0abc" (""). + ASSERT_NO_THROW(bad.reset(new TokenHexString("0abc"))); + // Store hexstring with no digits "0x" (""). + ASSERT_NO_THROW(nodigit.reset(new TokenHexString("0x"))); + // Store hexstring with a bad hexdigit "0xxabc" (""). + ASSERT_NO_THROW(baddigit.reset(new TokenHexString("0xxabc"))); + // Store hexstring with an odd number of hexdigits "0x7" ("\a"). + ASSERT_NO_THROW(bell.reset(new TokenHexString("0x7"))); + // Store constant hexstring "0x666f6f" ("foo"). + ASSERT_NO_THROW(foo.reset(new TokenHexString("0x666f6f"))); + // Store constant hexstring "0x63825363" (DHCP_OPTIONS_COOKIE). + ASSERT_NO_THROW(cookie.reset(new TokenHexString("0x63825363"))); + + // Make sure that tokens can be evaluated without exceptions. + ASSERT_NO_THROW(empty->evaluate(*pkt6_, values_)); + ASSERT_NO_THROW(bad->evaluate(*pkt6_, values_)); + ASSERT_NO_THROW(nodigit->evaluate(*pkt6_, values_)); + ASSERT_NO_THROW(baddigit->evaluate(*pkt6_, values_)); + ASSERT_NO_THROW(bell->evaluate(*pkt6_, values_)); + ASSERT_NO_THROW(foo->evaluate(*pkt6_, values_)); + ASSERT_NO_THROW(cookie->evaluate(*pkt6_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(7, values_.size()); + uint32_t expected = htonl(DHCP_OPTIONS_COOKIE); + EXPECT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4)); + values_.pop(); + EXPECT_EQ("foo", values_.top()); + values_.pop(); + EXPECT_EQ("\a", values_.top()); + values_.pop(); + EXPECT_EQ("", values_.top()); + values_.pop(); + EXPECT_EQ("", values_.top()); + values_.pop(); + EXPECT_EQ("", values_.top()); + values_.pop(); + EXPECT_EQ("", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x07"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x666F6F"); + addString("EVAL_DEBUG_HEXSTRING Pushing hex string 0x63825363"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that a TokenIpAddress, representing an IP address as +// a constant string, can be used in Pkt4/Pkt6 evaluation. +// (The actual packet is not used) +TEST_F(TokenTest, ipaddress) { + TokenPtr bad4; + TokenPtr bad6; + TokenPtr ip4; + TokenPtr ip6; + + // Bad IP addresses + ASSERT_NO_THROW(bad4.reset(new TokenIpAddress("10.0.0.0.1"))); + ASSERT_NO_THROW(bad6.reset(new TokenIpAddress(":::"))); + + // IP addresses + ASSERT_NO_THROW(ip4.reset(new TokenIpAddress("10.0.0.1"))); + ASSERT_NO_THROW(ip6.reset(new TokenIpAddress("2001:db8::1"))); + + // Make sure that tokens can be evaluated without exceptions. + ASSERT_NO_THROW(ip4->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(ip6->evaluate(*pkt6_, values_)); + ASSERT_NO_THROW(bad4->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(bad6->evaluate(*pkt6_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(4, values_.size()); + + // Check bad addresses (they pushed '' on the value stack) + EXPECT_EQ(0, values_.top().size()); + values_.pop(); + EXPECT_EQ(0, values_.top().size()); + values_.pop(); + + // Check IPv6 address + uint8_t expected6[] = { 0x20, 1, 0xd, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1 }; + EXPECT_EQ(16, values_.top().size()); + EXPECT_EQ(0, memcmp(expected6, &values_.top()[0], 16)); + values_.pop(); + + // Check IPv4 address + uint8_t expected4[] = { 10, 0, 0, 1 }; + EXPECT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected4, &values_.top()[0], 4)); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x0A000001"); + addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress " + "0x20010DB8000000000000000000000001"); + addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x"); + addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress 0x"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that a TokenIpAddressToText, representing an IP address as +// a string, can be used in Pkt4/Pkt6 evaluation. +// (The actual packet is not used) +TEST_F(TokenTest, addressToText) { + TokenPtr address((new TokenIpAddressToText())); + std::vector<uint8_t> bytes; + + std::string value = "10.0.0.1"; + values_.push(value); + + // Invalid data size fails. + EXPECT_THROW(address->evaluate(*pkt4_, values_), EvalTypeError); + + bytes = IOAddress(value).toBytes(); + values_.push(std::string(bytes.begin(), bytes.end())); + + EXPECT_NO_THROW(address->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(1, values_.size()); + + value = "2001:db8::1"; + bytes = IOAddress(value).toBytes(); + values_.push(std::string(bytes.begin(), bytes.end())); + + EXPECT_NO_THROW(address->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(2, values_.size()); + + values_.push(std::string()); + EXPECT_NO_THROW(address->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(3, values_.size()); + + // Check empty data + EXPECT_EQ(0, values_.top().size()); + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Check IPv6 address + EXPECT_EQ(11, values_.top().size()); + EXPECT_EQ("2001:db8::1", values_.top()); + values_.pop(); + + // Check IPv4 address + EXPECT_EQ(8, values_.top().size()); + EXPECT_EQ("10.0.0.1", values_.top()); + values_.pop(); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_IPADDRESSTOTEXT Pushing IPAddress 10.0.0.1"); + addString("EVAL_DEBUG_IPADDRESSTOTEXT Pushing IPAddress 2001:db8::1"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that a TokenIntToText, representing an integer as a string, +// can be used in Pkt4/Pkt6 evaluation. +// (The actual packet is not used) +TEST_F(TokenTest, integerToText) { + TokenPtr int8token((new TokenInt8ToText())); + TokenPtr int16token((new TokenInt16ToText())); + TokenPtr int32token((new TokenInt32ToText())); + TokenPtr uint8token((new TokenUInt8ToText())); + TokenPtr uint16token((new TokenUInt16ToText())); + TokenPtr uint32token((new TokenUInt32ToText())); + + std::vector<uint8_t> bytes; + std::string value = "0123456789"; + + // Invalid data size fails. + values_.push(value); + EXPECT_THROW(int8token->evaluate(*pkt4_, values_), EvalTypeError); + values_.push(value); + EXPECT_THROW(int16token->evaluate(*pkt4_, values_), EvalTypeError); + values_.push(value); + EXPECT_THROW(int32token->evaluate(*pkt4_, values_), EvalTypeError); + values_.push(value); + EXPECT_THROW(uint8token->evaluate(*pkt4_, values_), EvalTypeError); + values_.push(value); + EXPECT_THROW(uint16token->evaluate(*pkt4_, values_), EvalTypeError); + values_.push(value); + EXPECT_THROW(uint32token->evaluate(*pkt4_, values_), EvalTypeError); + + uint64_t data = -1; + + values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&data)), sizeof(int8_t))); + + EXPECT_NO_THROW(int8token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(1, values_.size()); + + int16_t i16 = 0; + memcpy(&i16, &data, sizeof(int16_t)); + values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&i16)), sizeof(int16_t))); + + EXPECT_NO_THROW(int16token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(2, values_.size()); + + int32_t i32 = 0; + memcpy(&i32, &data, sizeof(int32_t)); + values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&i32)), sizeof(int32_t))); + + EXPECT_NO_THROW(int32token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(3, values_.size()); + + values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&data)), sizeof(uint8_t))); + + EXPECT_NO_THROW(uint8token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(4, values_.size()); + + uint16_t ui16 = 0; + memcpy(&ui16, &data, sizeof(uint16_t)); + values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&ui16)), sizeof(uint16_t))); + + EXPECT_NO_THROW(uint16token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(5, values_.size()); + + uint32_t ui32 = 0; + memcpy(&ui32, &data, sizeof(uint32_t)); + values_.push(std::string(const_cast<const char *>(reinterpret_cast<char*>(&ui32)), sizeof(uint32_t))); + + EXPECT_NO_THROW(uint32token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(6, values_.size()); + + value = ""; + + values_.push(value); + EXPECT_NO_THROW(int8token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(7, values_.size()); + + values_.push(value); + EXPECT_NO_THROW(int16token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(8, values_.size()); + + values_.push(value); + EXPECT_NO_THROW(int32token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(9, values_.size()); + + values_.push(value); + EXPECT_NO_THROW(uint8token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(10, values_.size()); + + values_.push(value); + EXPECT_NO_THROW(uint16token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(11, values_.size()); + + values_.push(value); + EXPECT_NO_THROW(uint32token->evaluate(*pkt4_, values_)); + + // Check that the evaluation put its value on the values stack. + ASSERT_EQ(12, values_.size()); + + // Check empty data + EXPECT_EQ(0, values_.top().size()); + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Check empty data + EXPECT_EQ(0, values_.top().size()); + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Check empty data + EXPECT_EQ(0, values_.top().size()); + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Check empty data + EXPECT_EQ(0, values_.top().size()); + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Check empty data + EXPECT_EQ(0, values_.top().size()); + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Check empty data + EXPECT_EQ(0, values_.top().size()); + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Check uint32 + EXPECT_EQ(10, values_.top().size()); + EXPECT_EQ("4294967295", values_.top()); + values_.pop(); + + // Check uint16 + EXPECT_EQ(5, values_.top().size()); + EXPECT_EQ("65535", values_.top()); + values_.pop(); + + // Check uint8 + EXPECT_EQ(3, values_.top().size()); + EXPECT_EQ("255", values_.top()); + values_.pop(); + + // Check int32 + EXPECT_EQ(2, values_.top().size()); + EXPECT_EQ("-1", values_.top()); + values_.pop(); + + // Check int16 + EXPECT_EQ(2, values_.top().size()); + EXPECT_EQ("-1", values_.top()); + values_.pop(); + + // Check int8 + EXPECT_EQ(2, values_.top().size()); + EXPECT_EQ("-1", values_.top()); + values_.pop(); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_INT8TOTEXT Pushing Int8 -1"); + addString("EVAL_DEBUG_INT16TOTEXT Pushing Int16 -1"); + addString("EVAL_DEBUG_INT32TOTEXT Pushing Int32 -1"); + addString("EVAL_DEBUG_UINT8TOTEXT Pushing UInt8 255"); + addString("EVAL_DEBUG_UINT16TOTEXT Pushing UInt16 65535"); + addString("EVAL_DEBUG_UINT32TOTEXT Pushing UInt32 4294967295"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an option value is able to extract +// the option from an IPv4 packet and properly store the option's value. +TEST_F(TokenTest, optionString4) { + TokenPtr found; + TokenPtr not_found; + + // The packets we use have option 100 with a string in them. + ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::TEXTUAL))); + ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::TEXTUAL))); + + // This should evaluate to the content of the option 100 (i.e. "hundred4") + ASSERT_NO_THROW(found->evaluate(*pkt4_, values_)); + + // This should evaluate to "" as there is no option 101. + ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_)); + + // There should be 2 values evaluated. + ASSERT_EQ(2, values_.size()); + + // This is a stack, so the pop order is inversed. We should get the empty + // string first. + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Then the content of the option 100. + EXPECT_EQ("hundred4", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred4'"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing option value is able to extract +// the option from an IPv4 packet and properly store its value in a +// hexadecimal format. +TEST_F(TokenTest, optionHexString4) { + TokenPtr found; + TokenPtr not_found; + + // The packets we use have option 100 with a string in them. + ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::HEXADECIMAL))); + ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::HEXADECIMAL))); + + // This should evaluate to the content of the option 100 (i.e. "hundred4") + ASSERT_NO_THROW(found->evaluate(*pkt4_, values_)); + + // This should evaluate to "" as there is no option 101. + ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_)); + + // There should be 2 values evaluated. + ASSERT_EQ(2, values_.size()); + + // This is a stack, so the pop order is inversed. We should get the empty + // string first. + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Then the content of the option 100. + EXPECT_EQ("hundred4", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 0x68756E6472656434"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value 0x"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an option value is able to check +// the existence of the option from an IPv4 packet. +TEST_F(TokenTest, optionExistsString4) { + TokenPtr found; + TokenPtr not_found; + + // The packets we use have option 100 with a string in them. + ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::EXISTS))); + ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::EXISTS))); + + ASSERT_NO_THROW(found->evaluate(*pkt4_, values_)); + ASSERT_NO_THROW(not_found->evaluate(*pkt4_, values_)); + + // There should be 2 values evaluated. + ASSERT_EQ(2, values_.size()); + + // This is a stack, so the pop order is inversed. + EXPECT_EQ("false", values_.top()); + values_.pop(); + EXPECT_EQ("true", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'false'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an option value is able to extract +// the option from an IPv6 packet and properly store the option's value. +TEST_F(TokenTest, optionString6) { + TokenPtr found; + TokenPtr not_found; + + // The packets we use have option 100 with a string in them. + ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::TEXTUAL))); + ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::TEXTUAL))); + + // This should evaluate to the content of the option 100 (i.e. "hundred6") + ASSERT_NO_THROW(found->evaluate(*pkt6_, values_)); + + // This should evaluate to "" as there is no option 101. + ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_)); + + // There should be 2 values evaluated. + ASSERT_EQ(2, values_.size()); + + // This is a stack, so the pop order is inversed. We should get the empty + // string first. + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Then the content of the option 100. + EXPECT_EQ("hundred6", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred6'"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an option value is able to extract +// the option from an IPv6 packet and properly store its value in hexadecimal +// format. +TEST_F(TokenTest, optionHexString6) { + TokenPtr found; + TokenPtr not_found; + + // The packets we use have option 100 with a string in them. + ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::HEXADECIMAL))); + ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::HEXADECIMAL))); + + // This should evaluate to the content of the option 100 (i.e. "hundred6") + ASSERT_NO_THROW(found->evaluate(*pkt6_, values_)); + + // This should evaluate to "" as there is no option 101. + ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_)); + + // There should be 2 values evaluated. + ASSERT_EQ(2, values_.size()); + + // This is a stack, so the pop order is inversed. We should get the empty + // string first. + EXPECT_EQ("", values_.top()); + values_.pop(); + + // Then the content of the option 100. + EXPECT_EQ("hundred6", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 0x68756E6472656436"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value 0x"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an option value is able to check +// the existence of the option from an IPv6 packet. +TEST_F(TokenTest, optionExistsString6) { + TokenPtr found; + TokenPtr not_found; + + // The packets we use have option 100 with a string in them. + ASSERT_NO_THROW(found.reset(new TokenOption(100, TokenOption::EXISTS))); + ASSERT_NO_THROW(not_found.reset(new TokenOption(101, TokenOption::EXISTS))); + + ASSERT_NO_THROW(found->evaluate(*pkt6_, values_)); + ASSERT_NO_THROW(not_found->evaluate(*pkt6_, values_)); + + // There should be 2 values evaluated. + ASSERT_EQ(2, values_.size()); + + // This is a stack, so the pop order is inversed. + EXPECT_EQ("false", values_.top()); + values_.pop(); + EXPECT_EQ("true", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'false'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that the existing relay4 option can be found. +TEST_F(TokenTest, relay4Option) { + + // Insert relay option with sub-options 1 and 13 + insertRelay4Option(); + + // Creating the token should be safe. + ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL))); + + // We should be able to evaluate it. + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // we should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // The option should be found and relay4[13] should evaluate to the + // content of that sub-option, i.e. "thirteen" + EXPECT_EQ("thirteen", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 13 with value 'thirteen'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that the code properly handles cases when +// there is a RAI option, but there's no requested sub-option. +TEST_F(TokenTest, relay4OptionNoSuboption) { + + // Insert relay option with sub-options 1 and 13 + insertRelay4Option(); + + // Creating the token should be safe. + ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(15, TokenOption::TEXTUAL))); + + // We should be able to evaluate it. + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // we should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // The option should NOT be found (there is no sub-option 15), + // so the expression should evaluate to "" + EXPECT_EQ("", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 15 with value ''"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that the code properly handles cases when +// there's no RAI option at all. +TEST_F(TokenTest, relay4OptionNoRai) { + + // We didn't call insertRelay4Option(), so there's no RAI option. + + // Creating the token should be safe. + ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL))); + + // We should be able to evaluate it. + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // we should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // The option should NOT be found (there is no sub-option 13), + // so the expression should evaluate to "" + EXPECT_EQ("", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 13 with value ''"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that only the RAI is searched for the requested +// sub-option. +TEST_F(TokenTest, relay4RAIOnly) { + + // Insert relay option with sub-options 1 and 13 + insertRelay4Option(); + + // Add options 13 and 70 to the packet. + OptionPtr opt13(new OptionString(Option::V4, 13, "THIRTEEN")); + OptionPtr opt70(new OptionString(Option::V4, 70, "SEVENTY")); + pkt4_->addOption(opt13); + pkt4_->addOption(opt70); + + // The situation is as follows: + // Packet: + // - option 13 (containing "THIRTEEN") + // - option 82 (rai) + // - option 1 (containing "one") + // - option 13 (containing "thirteen") + + // Let's try to get option 13. It should get the one from RAI + ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(13, TokenOption::TEXTUAL))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("thirteen", values_.top()); + + // Try to get option 1. It should get the one from RAI + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(1, TokenOption::TEXTUAL))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("one", values_.top()); + + // Try to get option 70. It should fail, as there's no such + // sub option in RAI. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(70, TokenOption::TEXTUAL))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("", values_.top()); + + // Try to check option 1. It should return "true" + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(1, TokenOption::EXISTS))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // Try to check option 70. It should return "false" + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenRelay4Option(70, TokenOption::EXISTS))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 13 with value 'thirteen'"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'one'"); + addString("EVAL_DEBUG_OPTION Pushing option 70 with value ''"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'true'"); + addString("EVAL_DEBUG_OPTION Pushing option 70 with value 'false'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if we can properly extract an option +// from relay encapsulations. Our packet has two relay +// encapsulations. Both include a common option with the +// original message (option 100) and both include their +// own option (101 and 102). We attempt to extract the +// options and compare them to expected values. We also +// try to extract an option from an encapsulation +// that doesn't exist (level 2), this should result in an empty +// string. +TEST_F(TokenTest, relay6Option) { + // We start by adding a set of relay encapsulations to the + // basic v6 packet. + addRelay6Encapsulations(); + + // Then we work our way through the set of choices + // Level 0 both options it has and the check that + // the checking for an option it doesn't have results + // in an empty string. + verifyRelay6Option(0, 100, TokenOption::TEXTUAL, "hundred.zero"); + verifyRelay6Option(0, 100, TokenOption::EXISTS, "true"); + verifyRelay6Option(0, 101, TokenOption::TEXTUAL, "hundredone.zero"); + verifyRelay6Option(0, 102, TokenOption::TEXTUAL, ""); + verifyRelay6Option(0, 102, TokenOption::EXISTS, "false"); + + // Level 1, again both options it has and the one for level 0 + verifyRelay6Option(1, 100, TokenOption::TEXTUAL, "hundred.one"); + verifyRelay6Option(1, 101, TokenOption::TEXTUAL, ""); + verifyRelay6Option(1, 102, TokenOption::TEXTUAL, "hundredtwo.one"); + + // Level 2, no encapsulation so no options + verifyRelay6Option(2, 100, TokenOption::TEXTUAL, ""); + + // Level -1, the same as level 1 + verifyRelay6Option(-1, 100, TokenOption::TEXTUAL, "hundred.one"); + verifyRelay6Option(-1, 101, TokenOption::TEXTUAL, ""); + verifyRelay6Option(-1, 102, TokenOption::TEXTUAL, "hundredtwo.one"); + + // Level -2, the same as level 0 + verifyRelay6Option(-2, 100, TokenOption::TEXTUAL, "hundred.zero"); + verifyRelay6Option(-2, 100, TokenOption::EXISTS, "true"); + verifyRelay6Option(-2, 101, TokenOption::TEXTUAL, "hundredone.zero"); + verifyRelay6Option(-2, 102, TokenOption::TEXTUAL, ""); + verifyRelay6Option(-2, 102, TokenOption::EXISTS, "false"); + + // Level -3, no encapsulation so no options + verifyRelay6Option(-3, 100, TokenOption::TEXTUAL, ""); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.zero'"); + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'hundredone.zero'"); + addString("EVAL_DEBUG_OPTION Pushing option 102 with value ''"); + addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'false'"); + + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.one'"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''"); + addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'hundredtwo.one'"); + + addString("EVAL_DEBUG_OPTION Pushing option 100 with value ''"); + + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.one'"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value ''"); + addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'hundredtwo.one'"); + + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'hundred.zero'"); + addString("EVAL_DEBUG_OPTION Pushing option 100 with value 'true'"); + addString("EVAL_DEBUG_OPTION Pushing option 101 with value 'hundredone.zero'"); + addString("EVAL_DEBUG_OPTION Pushing option 102 with value ''"); + addString("EVAL_DEBUG_OPTION Pushing option 102 with value 'false'"); + + addString("EVAL_DEBUG_OPTION Pushing option 100 with value ''"); + + EXPECT_TRUE(checkFile()); +} + +// Verifies that relay6 option requires DHCPv6 +TEST_F(TokenTest, relay6OptionError) { + // Create a relay6 option token + ASSERT_NO_THROW(t_.reset(new TokenRelay6Option(0, 13, TokenOption::TEXTUAL))); + + // A DHCPv6 packet is required + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); +} + +// Verifies that DHCPv4 packet metadata can be extracted. +TEST_F(TokenTest, pkt4MetaData) { + pkt4_->setIface("eth0"); + pkt4_->setLocalAddr(IOAddress("10.0.0.1")); + pkt4_->setRemoteAddr(IOAddress("10.0.0.2")); + + // Check interface (expect eth0) + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::IFACE))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ("eth0", values_.top()); + + // Check source (expect 10.0.0.2) + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::SRC))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + vector<uint8_t> a2 = IOAddress("10.0.0.2").toBytes(); + ASSERT_EQ(a2.size(), values_.top().size()); + EXPECT_EQ(0, memcmp(&a2[0], &values_.top()[0], a2.size())); + + // Check destination (expect 10.0.0.1) + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::DST))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + vector<uint8_t> a1 = IOAddress("10.0.0.1").toBytes(); + ASSERT_EQ(a1.size(), values_.top().size()); + EXPECT_EQ(0, memcmp(&a1[0], &values_.top()[0], a1.size())); + + // Check length (expect 249) + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::LEN))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + uint32_t length = htonl(static_cast<uint32_t>(pkt4_->len())); + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(&length, &values_.top()[0], 4)); + + // Unknown metadata type fails + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::MetadataType(100)))); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_PKT Pushing PKT meta data iface with value eth0"); + addString("EVAL_DEBUG_PKT Pushing PKT meta data src with value 0x0A000002"); + addString("EVAL_DEBUG_PKT Pushing PKT meta data dst with value 0x0A000001"); + addString("EVAL_DEBUG_PKT Pushing PKT meta data len with value 0x000000F9"); + EXPECT_TRUE(checkFile()); +} + +// Verifies that DHCPv6 packet metadata can be extracted. +TEST_F(TokenTest, pkt6MetaData) { + pkt6_->setIface("eth0"); + pkt6_->setLocalAddr(IOAddress("ff02::1:2")); + pkt6_->setRemoteAddr(IOAddress("fe80::1234")); + + // Check interface (expect eth0) + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::IFACE))); + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ("eth0", values_.top()); + + // Check source (expect fe80::1234) + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::SRC))); + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(16, values_.top().size()); + EXPECT_EQ(0xfe, static_cast<uint8_t>(values_.top()[0])); + EXPECT_EQ(0x80, static_cast<uint8_t>(values_.top()[1])); + for (unsigned i = 2; i < 14; ++i) { + EXPECT_EQ(0, values_.top()[i]); + } + EXPECT_EQ(0x12, values_.top()[14]); + EXPECT_EQ(0x34, values_.top()[15]); + + // Check destination (expect ff02::1:2) + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::DST))); + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + ASSERT_EQ(1, values_.size()); + vector<uint8_t> ma = IOAddress("ff02::1:2").toBytes(); + ASSERT_EQ(ma.size(), values_.top().size()); + EXPECT_EQ(0, memcmp(&ma[0], &values_.top()[0], ma.size())); + + // Check length (expect 16) + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::LEN))); + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + ASSERT_EQ(1, values_.size()); + uint32_t length = htonl(static_cast<uint32_t>(pkt6_->len())); + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(&length, &values_.top()[0], 4)); + + // Unknown meta data type fails + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt(TokenPkt::MetadataType(100)))); + EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_PKT Pushing PKT meta data iface with value eth0"); + addString("EVAL_DEBUG_PKT Pushing PKT meta data src with value " + "0xFE800000000000000000000000001234"); + addString("EVAL_DEBUG_PKT Pushing PKT meta data dst with value " + "0xFF020000000000000000000000010002"); + addString("EVAL_DEBUG_PKT Pushing PKT meta data len with value 0x00000010"); + EXPECT_TRUE(checkFile()); +} + +// Verifies if the DHCPv4 packet fields can be extracted. +TEST_F(TokenTest, pkt4Fields) { + pkt4_->setGiaddr(IOAddress("192.0.2.1")); + pkt4_->setCiaddr(IOAddress("192.0.2.2")); + pkt4_->setYiaddr(IOAddress("192.0.2.3")); + pkt4_->setSiaddr(IOAddress("192.0.2.4")); + + // We're setting hardware address to uncommon (7 bytes rather than 6 and + // hardware type 123) HW address. We'll use it in hlen and htype checks. + HWAddrPtr hw(new HWAddr(HWAddr::fromText("01:02:03:04:05:06:07", 123))); + pkt4_->setHWAddr(hw); + + // Check hardware address field. + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::CHADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + uint8_t expected_hw[] = { 1, 2, 3, 4, 5, 6, 7 }; + ASSERT_EQ(7, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_hw, &values_.top()[0], 7)); + + // Check hlen value field. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HLEN))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(4, values_.top().size()); + uint32_t expected_hlen = htonl(7); + EXPECT_EQ(0, memcmp(&expected_hlen, &values_.top()[0], 4)); + + // Check htype value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HTYPE))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(4, values_.top().size()); + uint32_t expected_htype = htonl(123); + EXPECT_EQ(0, memcmp(&expected_htype, &values_.top()[0], 4)); + + // Check giaddr value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::GIADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + uint8_t expected_addr[] = { 192, 0, 2, 1 }; + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + + // Check ciaddr value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::CIADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + expected_addr[3] = 2; + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + + // Check yiaddr value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::YIADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + expected_addr[3] = 3; + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + + // Check siaddr value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::SIADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + expected_addr[3] = 4; + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + + // Check msgtype. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::MSGTYPE))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(4, values_.top().size()); + string exp_msgtype = encode(DHCPDISCOVER); + EXPECT_EQ(0, memcmp(&exp_msgtype[0], &values_.top()[0], 4)); + + // Check transaction-id + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::TRANSID))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(4, values_.top().size()); + string exp_transid = encode(12345); + EXPECT_EQ(0, memcmp(&exp_transid[0], &values_.top()[0], 4)); + + // Check a DHCPv6 packet throws. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HLEN))); + EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError); + + // Unknown field fails + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(static_cast<TokenPkt4::FieldType>(100)))); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field mac with value 0x01020304050607"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field hlen with value 0x00000007"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field htype with value 0x0000007B"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field giaddr with value 0xC0000201"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field ciaddr with value 0xC0000202"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field yiaddr with value 0xC0000203"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field siaddr with value 0xC0000204"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field msgtype with value 0x00000001"); + addString("EVAL_DEBUG_PKT4 Pushing PKT4 field transid with value 0x00003039"); + EXPECT_TRUE(checkFile()); +} + +// Verifies if the DHCPv6 packet fields can be extracted. +TEST_F(TokenTest, pkt6Fields) { + // The default test creates a v6 DHCPV6_SOLICIT packet with a + // transaction id of 12345. + + // Check the message type + ASSERT_NO_THROW(t_.reset(new TokenPkt6(TokenPkt6::MSGTYPE))); + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + ASSERT_EQ(1, values_.size()); + uint32_t expected = htonl(1); + EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4)); + + // Check the transaction id field + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt6(TokenPkt6::TRANSID))); + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + ASSERT_EQ(1, values_.size()); + expected = htonl(12345); + EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4)); + + // Check that working with a v4 packet generates an error + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt6(TokenPkt6::TRANSID))); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + + // Unknown field fails + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt6(static_cast<TokenPkt6::FieldType>(100)))); + EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_PKT6 Pushing PKT6 field msgtype with value 0x00000001"); + addString("EVAL_DEBUG_PKT6 Pushing PKT6 field transid with value 0x00003039"); + + EXPECT_TRUE(checkFile()); +} + +// This test checks if we can properly extract the link and peer +// address fields from relay encapsulations. Our packet has +// two relay encapsulations. We attempt to extract the two +// fields from both of the encapsulations and compare them. +// We also try to extract one of the fields from an encapsulation +// that doesn't exist (level 2), this should result in an empty +// string. +TEST_F(TokenTest, relay6Field) { + // Values for the address results + uint8_t zeroaddr[] = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t linkaddr[] = { 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1 }; + uint8_t peeraddr[] = { 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2 }; + + // We start by adding a set of relay encapsulations to the + // basic v6 packet. + addRelay6Encapsulations(); + + // Then we work our way through the set of choices + // Level 0 both link and peer address should be 0::0 + verifyRelay6Eval(0, TokenRelay6Field::LINKADDR, 16, zeroaddr); + verifyRelay6Eval(0, TokenRelay6Field::PEERADDR, 16, zeroaddr); + + // Level 1 link and peer should have different non-zero addresses + verifyRelay6Eval(1, TokenRelay6Field::LINKADDR, 16, linkaddr); + verifyRelay6Eval(1, TokenRelay6Field::PEERADDR, 16, peeraddr); + + // Level 2 has no encapsulation so the address should be zero length + verifyRelay6Eval(2, TokenRelay6Field::LINKADDR, 0, zeroaddr); + + // Level -1 is the same as level 1 + verifyRelay6Eval(-1, TokenRelay6Field::LINKADDR, 16, linkaddr); + verifyRelay6Eval(-1, TokenRelay6Field::PEERADDR, 16, peeraddr); + + // Level -2 is the same as level 0 + verifyRelay6Eval(-2, TokenRelay6Field::LINKADDR, 16, zeroaddr); + verifyRelay6Eval(-2, TokenRelay6Field::PEERADDR, 16, zeroaddr); + + // Level -3 has no encapsulation so the address should be zero length + verifyRelay6Eval(-3, TokenRelay6Field::LINKADDR, 0, zeroaddr); + + // Lets check that the layout of the address returned by the + // token matches that of the TokenIpAddress + TokenPtr trelay; + TokenPtr taddr; + TokenPtr tequal; + ASSERT_NO_THROW(trelay.reset(new TokenRelay6Field(1, TokenRelay6Field::LINKADDR))); + ASSERT_NO_THROW(taddr.reset(new TokenIpAddress("1::1"))); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + + EXPECT_NO_THROW(trelay->evaluate(*pkt6_, values_)); + EXPECT_NO_THROW(taddr->evaluate(*pkt6_, values_)); + EXPECT_NO_THROW(tequal->evaluate(*pkt6_, values_)); + + // We should have a single value on the stack and it should be "true" + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // be tidy + clearStack(); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest 0 " + "with value 0x00000000000000000000000000000000"); + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest 0 " + "with value 0x00000000000000000000000000000000"); + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest 1 " + "with value 0x00010000000000000000000000000001"); + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest 1 " + "with value 0x00010000000000000000000000000002"); + addString("EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field linkaddr " + "nest 2 with value 0x"); + + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest -1 " + "with value 0x00010000000000000000000000000001"); + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest -1 " + "with value 0x00010000000000000000000000000002"); + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest -2 " + "with value 0x00000000000000000000000000000000"); + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field peeraddr nest -2 " + "with value 0x00000000000000000000000000000000"); + addString("EVAL_DEBUG_RELAY6_RANGE Pushing PKT6 relay field linkaddr " + "nest -3 with value 0x"); + + addString("EVAL_DEBUG_RELAY6 Pushing PKT6 relay field linkaddr nest 1 " + "with value 0x00010000000000000000000000000001"); + addString("EVAL_DEBUG_IPADDRESS Pushing IPAddress " + "0x00010000000000000000000000000001"); + addString("EVAL_DEBUG_EQUAL Popping 0x00010000000000000000000000000001 " + "and 0x00010000000000000000000000000001 pushing result 'true'"); + + EXPECT_TRUE(checkFile()); +} + +// This test checks some error cases for a relay6 field token +TEST_F(TokenTest, relay6FieldError) { + // Create a valid relay6 field token + ASSERT_NO_THROW(t_.reset(new TokenRelay6Field(0, TokenRelay6Field::LINKADDR))); + + // a DHCPv6 packet is required + ASSERT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + + // No test for unknown field as it is not (yet) checked?! +} + +// This test checks if a token representing an == operator is able to +// compare two values (with incorrectly built stack). +TEST_F(TokenTest, optionEqualInvalid) { + + ASSERT_NO_THROW(t_.reset(new TokenEqual())); + + // CASE 1: There's not enough values on the stack. == is an operator that + // takes two parameters. There are 0 on the stack. + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // CASE 2: One value is still not enough. + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); +} + +// This test checks if a token representing an == operator is able to +// compare two different values. +TEST_F(TokenTest, optionEqualFalse) { + + ASSERT_NO_THROW(t_.reset(new TokenEqual())); + + values_.push("foo"); + values_.push("bar"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // After evaluation there should be a single value that represents + // result of "foo" == "bar" comparison. + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_EQUAL Popping 0x626172 and 0x666F6F " + "pushing result 'false'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an == operator is able to +// compare two identical values. +TEST_F(TokenTest, optionEqualTrue) { + + ASSERT_NO_THROW(t_.reset(new TokenEqual())); + + values_.push("foo"); + values_.push("foo"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // After evaluation there should be a single value that represents + // result of "foo" == "foo" comparison. + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_EQUAL Popping 0x666F6F and 0x666F6F " + "pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing a substring request +// throws an exception if there aren't enough values on the stack. +// The stack from the top is: length, start, string. +// The actual packet is not used. +TEST_F(TokenTest, substringNotEnoughValues) { + ASSERT_NO_THROW(t_.reset(new TokenSubstring())); + + // Substring requires three values on the stack, try + // with 0, 1 and 2 all should throw an exception + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + values_.push(""); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + values_.push("0"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // Three should work + values_.push("0"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // As we had an empty string to start with we should have an empty + // one after the evaluate + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING_EMPTY Popping length 0, start 0, " + "string 0x pushing result 0x"); + EXPECT_TRUE(checkFile()); +} + +// Test getting the whole string in different ways +TEST_F(TokenTest, substringWholeString) { + // Get the whole string + verifySubstringEval("foobar", "0", "6", "foobar"); + + // Get the whole string with "all" + verifySubstringEval("foobar", "0", "all", "foobar"); + + // Get the whole string with an extra long number + verifySubstringEval("foobar", "0", "123456", "foobar"); + + // Get the whole string counting from the back + verifySubstringEval("foobar", "-6", "all", "foobar"); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING Popping length 6, start 0, " + "string 0x666F6F626172 pushing result 0x666F6F626172"); + addString("EVAL_DEBUG_SUBSTRING Popping length all, start 0, " + "string 0x666F6F626172 pushing result 0x666F6F626172"); + addString("EVAL_DEBUG_SUBSTRING Popping length 123456, start 0, " + "string 0x666F6F626172 pushing result 0x666F6F626172"); + addString("EVAL_DEBUG_SUBSTRING Popping length all, start -6, " + "string 0x666F6F626172 pushing result 0x666F6F626172"); + EXPECT_TRUE(checkFile()); +} + +// Test getting a suffix, in this case the last 3 characters +TEST_F(TokenTest, substringTrailer) { + verifySubstringEval("foobar", "3", "3", "bar"); + verifySubstringEval("foobar", "3", "all", "bar"); + verifySubstringEval("foobar", "-3", "all", "bar"); + verifySubstringEval("foobar", "-3", "123", "bar"); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING Popping length 3, start 3, " + "string 0x666F6F626172 pushing result 0x626172"); + addString("EVAL_DEBUG_SUBSTRING Popping length all, start 3, " + "string 0x666F6F626172 pushing result 0x626172"); + addString("EVAL_DEBUG_SUBSTRING Popping length all, start -3, " + "string 0x666F6F626172 pushing result 0x626172"); + addString("EVAL_DEBUG_SUBSTRING Popping length 123, start -3, " + "string 0x666F6F626172 pushing result 0x626172"); + EXPECT_TRUE(checkFile()); +} + +// Test getting the middle of the string in different ways +TEST_F(TokenTest, substringMiddle) { + verifySubstringEval("foobar", "1", "4", "ooba"); + verifySubstringEval("foobar", "-5", "4", "ooba"); + verifySubstringEval("foobar", "-1", "-4", "ooba"); + verifySubstringEval("foobar", "5", "-4", "ooba"); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 1, " + "string 0x666F6F626172 pushing result 0x6F6F6261"); + addString("EVAL_DEBUG_SUBSTRING Popping length 4, start -5, " + "string 0x666F6F626172 pushing result 0x6F6F6261"); + addString("EVAL_DEBUG_SUBSTRING Popping length -4, start -1, " + "string 0x666F6F626172 pushing result 0x6F6F6261"); + addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 5, " + "string 0x666F6F626172 pushing result 0x6F6F6261"); + EXPECT_TRUE(checkFile()); +} + +// Test getting the last letter in different ways +TEST_F(TokenTest, substringLastLetter) { + verifySubstringEval("foobar", "5", "all", "r"); + verifySubstringEval("foobar", "5", "1", "r"); + verifySubstringEval("foobar", "5", "5", "r"); + verifySubstringEval("foobar", "-1", "all", "r"); + verifySubstringEval("foobar", "-1", "1", "r"); + verifySubstringEval("foobar", "-1", "5", "r"); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING Popping length all, start 5, " + "string 0x666F6F626172 pushing result 0x72"); + addString("EVAL_DEBUG_SUBSTRING Popping length 1, start 5, " + "string 0x666F6F626172 pushing result 0x72"); + addString("EVAL_DEBUG_SUBSTRING Popping length 5, start 5, " + "string 0x666F6F626172 pushing result 0x72"); + addString("EVAL_DEBUG_SUBSTRING Popping length all, start -1, " + "string 0x666F6F626172 pushing result 0x72"); + addString("EVAL_DEBUG_SUBSTRING Popping length 1, start -1, " + "string 0x666F6F626172 pushing result 0x72"); + addString("EVAL_DEBUG_SUBSTRING Popping length 5, start -1, " + "string 0x666F6F626172 pushing result 0x72"); + EXPECT_TRUE(checkFile()); +} + +// Test we get only what is available if we ask for a longer string +TEST_F(TokenTest, substringLength) { + // Test off the front + verifySubstringEval("foobar", "0", "-4", ""); + verifySubstringEval("foobar", "1", "-4", "f"); + verifySubstringEval("foobar", "2", "-4", "fo"); + verifySubstringEval("foobar", "3", "-4", "foo"); + + // and the back + verifySubstringEval("foobar", "3", "4", "bar"); + verifySubstringEval("foobar", "4", "4", "ar"); + verifySubstringEval("foobar", "5", "4", "r"); + verifySubstringEval("foobar", "6", "4", ""); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 0, " + "string 0x666F6F626172 pushing result 0x"); + addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 1, " + "string 0x666F6F626172 pushing result 0x66"); + addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 2, " + "string 0x666F6F626172 pushing result 0x666F"); + addString("EVAL_DEBUG_SUBSTRING Popping length -4, start 3, " + "string 0x666F6F626172 pushing result 0x666F6F"); + addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 3, " + "string 0x666F6F626172 pushing result 0x626172"); + addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 4, " + "string 0x666F6F626172 pushing result 0x6172"); + addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 5, " + "string 0x666F6F626172 pushing result 0x72"); + addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length 4, start 6, " + "string 0x666F6F626172 pushing result 0x"); + EXPECT_TRUE(checkFile()); +} + +// Test that we get nothing if the starting position is out of the string +TEST_F(TokenTest, substringStartingPosition) { + // Off the front + verifySubstringEval("foobar", "-7", "1", ""); + verifySubstringEval("foobar", "-7", "-11", ""); + verifySubstringEval("foobar", "-7", "all", ""); + + // and the back + verifySubstringEval("foobar", "6", "1", ""); + verifySubstringEval("foobar", "6", "-11", ""); + verifySubstringEval("foobar", "6", "all", ""); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length 1, start -7, " + "string 0x666F6F626172 pushing result 0x"); + addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length -11, start -7, " + "string 0x666F6F626172 pushing result 0x"); + addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length all, start -7, " + "string 0x666F6F626172 pushing result 0x"); + addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length 1, start 6, " + "string 0x666F6F626172 pushing result 0x"); + addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length -11, start 6, " + "string 0x666F6F626172 pushing result 0x"); + addString("EVAL_DEBUG_SUBSTRING_RANGE Popping length all, start 6, " + "string 0x666F6F626172 pushing result 0x"); + EXPECT_TRUE(checkFile()); +} + +// Check what happens if we use strings that aren't numbers for start or length +// We should return the empty string +TEST_F(TokenTest, substringBadParams) { + verifySubstringEval("foobar", "0ick", "all", "", true); + verifySubstringEval("foobar", "ick0", "all", "", true); + verifySubstringEval("foobar", "ick", "all", "", true); + verifySubstringEval("foobar", "0", "ick", "", true); + verifySubstringEval("foobar", "0", "0ick", "", true); + verifySubstringEval("foobar", "0", "ick0", "", true); + verifySubstringEval("foobar", "0", "allaboard", "", true); + + // These should result in a throw which should generate it's own + // log entry +} + +// lastly check that we don't get anything if the string is empty or +// we don't ask for any characters from it. +TEST_F(TokenTest, substringReturnEmpty) { + verifySubstringEval("", "0", "all", ""); + verifySubstringEval("foobar", "0", "0", ""); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING_EMPTY Popping length all, start 0, " + "string 0x pushing result 0x"); + addString("EVAL_DEBUG_SUBSTRING Popping length 0, start 0, " + "string 0x666F6F626172 pushing result 0x"); + EXPECT_TRUE(checkFile()); +} + +// Check if we can use the substring and equal tokens together +// We put the result on the stack first then the substring values +// then evaluate the substring which should leave the original +// result on the bottom with the substring result on next. +// Evaluating the equals should produce true for the first +// and false for the second. +// throws an exception if there aren't enough values on the stack. +// The stack from the top is: length, start, string. +// The actual packet is not used. +TEST_F(TokenTest, substringEquals) { + TokenPtr tequal; + + ASSERT_NO_THROW(t_.reset(new TokenSubstring())); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + + // The final expected value + values_.push("ooba"); + + // The substring values + values_.push("foobar"); + values_.push("1"); + values_.push("4"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // we should have two values on the stack + ASSERT_EQ(2, values_.size()); + + // next the equals eval + EXPECT_NO_THROW(tequal->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // get rid of the result + values_.pop(); + + // and try it again but with a bad final value + // The final expected value + values_.push("foob"); + + // The substring values + values_.push("foobar"); + values_.push("1"); + values_.push("4"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // we should have two values on the stack + ASSERT_EQ(2, values_.size()); + + // next the equals eval + EXPECT_NO_THROW(tequal->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 1, " + "string 0x666F6F626172 pushing result 0x6F6F6261"); + addString("EVAL_DEBUG_EQUAL Popping 0x6F6F6261 and 0x6F6F6261 " + "pushing result 'true'"); + addString("EVAL_DEBUG_SUBSTRING Popping length 4, start 1, " + "string 0x666F6F626172 pushing result 0x6F6F6261"); + addString("EVAL_DEBUG_EQUAL Popping 0x6F6F6261 and 0x666F6F62 " + "pushing result 'false'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing a concat request +// throws an exception if there aren't enough values on the stack. +// The actual packet is not used. +TEST_F(TokenTest, concat) { + ASSERT_NO_THROW(t_.reset(new TokenConcat())); + + // Concat requires two values on the stack, try + // with 0 and 1 both should throw an exception + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // Two should work + values_.push("bar"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // Check the result + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("foobar", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_CONCAT Popping 0x626172 and 0x666F6F " + "pushing 0x666F6F626172"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing a hexstring request +// throws an exception if there aren't enough values on the stack. +// The actual packet is not used. +TEST_F(TokenTest, tohexstring) { + ASSERT_NO_THROW(t_.reset(new TokenToHexString())); + + // Hexstring requires two values on the stack, try + // with 0 and 1 both should throw an exception + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // Two should work + values_.push("-"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // Check the result + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("66-6f-6f", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_TOHEXSTRING Popping binary value 0x666F6F and " + "separator -, pushing result 66-6f-6f"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an ifelse is able +// to select the branch following the condition. +TEST_F(TokenTest, ifElse) { + ASSERT_NO_THROW(t_.reset(new TokenIfElse())); + + // Ifelse requires three values on the stack, try + // with 0, 1 and 2 all should throw an exception + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + values_.push("bar"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // The condition must be a boolean + values_.push("bar"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + + // Check if what it returns + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenIfElse())); + values_.push("true"); + values_.push("foo"); + values_.push("bar"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("foo", values_.top()); + + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenIfElse())); + values_.push("false"); + values_.push("foo"); + values_.push("bar"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("bar", values_.top()); +} + +// This test checks if a token representing a not is able to +// negate a boolean value (with incorrectly built stack). +TEST_F(TokenTest, operatorNotInvalid) { + + ASSERT_NO_THROW(t_.reset(new TokenNot())); + + // CASE 1: The stack is empty. + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // CASE 2: The top value is not a boolean + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); +} + +// This test checks if a token representing a not operator is able to +// negate a boolean value. +TEST_F(TokenTest, operatorNot) { + + ASSERT_NO_THROW(t_.reset(new TokenNot())); + + values_.push("true"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // After evaluation there should be the negation of the value. + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // Double negation is identity. + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_NOT Popping 'true' pushing 'false'"); + addString("EVAL_DEBUG_NOT Popping 'false' pushing 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an and is able to +// conjugate two values (with incorrectly built stack). +TEST_F(TokenTest, operatorAndInvalid) { + + ASSERT_NO_THROW(t_.reset(new TokenAnd())); + + // CASE 1: There's not enough values on the stack. and is an operator that + // takes two parameters. There are 0 on the stack. + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // CASE 2: One value is still not enough. + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // CASE 3: The two values must be logical + values_.push("true"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + + // Swap the 2 values + values_.push("true"); + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); +} + +// This test checks if a token representing an and operator is able to +// conjugate false with another logical +TEST_F(TokenTest, operatorAndFalse) { + + ASSERT_NO_THROW(t_.reset(new TokenAnd())); + + values_.push("true"); + values_.push("false"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // After evaluation there should be a single "false" value + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // After true and false, check false and true + values_.push("true"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // And false and false + values_.push("false"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_AND Popping 'false' and 'true' pushing 'false'"); + addString("EVAL_DEBUG_AND Popping 'true' and 'false' pushing 'false'"); + addString("EVAL_DEBUG_AND Popping 'false' and 'false' pushing 'false'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an and is able to +// conjugate two true values. +TEST_F(TokenTest, operatorAndTrue) { + + ASSERT_NO_THROW(t_.reset(new TokenAnd())); + + values_.push("true"); + values_.push("true"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // After evaluation there should be a single "true" value + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_AND Popping 'true' and 'true' pushing 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an or is able to +// combine two values (with incorrectly built stack). +TEST_F(TokenTest, operatorOrInvalid) { + + ASSERT_NO_THROW(t_.reset(new TokenOr())); + + // CASE 1: There's not enough values on the stack. or is an operator that + // takes two parameters. There are 0 on the stack. + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // CASE 2: One value is still not enough. + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack); + + // CASE 3: The two values must be logical + values_.push("true"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); + + // Swap the 2 values + values_.push("true"); + values_.push("foo"); + EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalTypeError); +} + +// This test checks if a token representing an or is able to +// conjugate two false values. +TEST_F(TokenTest, operatorOrFalse) { + + ASSERT_NO_THROW(t_.reset(new TokenOr())); + + values_.push("false"); + values_.push("false"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // After evaluation there should be a single "false" value + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OR Popping 'false' and 'false' pushing 'false'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks if a token representing an == operator is able to +// conjugate true with another logical +TEST_F(TokenTest, operatorOrTrue) { + + ASSERT_NO_THROW(t_.reset(new TokenOr())); + + values_.push("false"); + values_.push("true"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // After evaluation there should be a single "true" value + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // After false or true, checks true or false + values_.push("false"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // And true or true + values_.push("true"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_OR Popping 'true' and 'false' pushing 'true'"); + addString("EVAL_DEBUG_OR Popping 'false' and 'true' pushing 'true'"); + addString("EVAL_DEBUG_OR Popping 'true' and 'true' pushing 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies client class membership +TEST_F(TokenTest, member) { + + ASSERT_NO_THROW(t_.reset(new TokenMember("foo"))); + + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // the packet has no classes so false was left on the stack + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + values_.pop(); + + // Add bar and retry + pkt4_->addClass("bar"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // the packet has a class but it is not foo + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + values_.pop(); + + // Add foo and retry + pkt4_->addClass("foo"); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // Now the packet is in the foo class + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); +} + +// This test verifies if expression vendor[4491].exists works properly in DHCPv4. +TEST_F(TokenTest, vendor4SpecificVendorExists) { + // Case 1: no option, should evaluate to false + testVendorExists(Option::V4, 4491, 0, "false"); + + // Case 2: option present, but uses different enterprise-id, should fail + testVendorExists(Option::V4, 4491, 1234, "false"); + + // Case 3: option present and has matching enterprise-id, should succeed + testVendorExists(Option::V4, 4491, 4491, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, " + "pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 4491 " + "found, pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies if expression vendor[4491].exists works properly in DHCPv6. +TEST_F(TokenTest, vendor6SpecificVendorExists) { + // Case 1: no option, should evaluate to false + testVendorExists(Option::V6, 4491, 0, "false"); + + // Case 2: option present, but uses different enterprise-id, should fail + testVendorExists(Option::V6, 4491, 1234, "false"); + + // Case 3: option present and has matching enterprise-id, should succeed + testVendorExists(Option::V6, 4491, 4491, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, " + "pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 4491 " + "found, pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +/// Test if expression vendor[*].exists works properly for DHCPv4. +TEST_F(TokenTest, vendor4AnyVendorExists) { + // Case 1: no option, should evaluate to false + testVendorExists(Option::V4, 0, 0, "false"); + + // Case 2: option present with vendor-id 1234, should succeed + testVendorExists(Option::V4, 0, 1234, "true"); + + // Case 3: option present with vendor-id 4491, should succeed + testVendorExists(Option::V4, 0, 4491, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, " + "pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 1234 " + "found, pushing result 'true'"); + addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 4491 " + "found, pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +// Test if expression vendor[*].exists works properly for DHCPv6. +TEST_F(TokenTest, vendor6AnyVendorExists) { + // Case 1: no option, should evaluate to false + testVendorExists(Option::V6, 0, 0, "false"); + + // Case 2: option present with vendor-id 1234, should succeed + testVendorExists(Option::V6, 0, 1234, "true"); + + // Case 3: option present with vendor-id 4491, should succeed + testVendorExists(Option::V6, 0, 4491, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, " + "pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 1234 " + "found, pushing result 'true'"); + addString("EVAL_DEBUG_VENDOR_EXISTS Option with enterprise-id 4491 " + "found, pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +// Test if expression vendor[*].enterprise works properly for DHCPv4. +TEST_F(TokenTest, vendor4enterprise) { + // Case 1: No option present, should return empty string + testVendorEnterprise(Option::V4, 0, ""); + + // Case 2: Option with vendor-id 1234, should return "1234" + testVendorEnterprise(Option::V4, 1234, encode(1234)); + + // Case 3: Option with vendor-id set to maximum value, should still + // be able to handle it + testVendorEnterprise(Option::V4, 4294967295, encode(4294967295)); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, pushing" + " result ''"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id 1234 as " + "result 0x000004D2"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id 4294967295" + " as result 0xFFFFFFFF"); + EXPECT_TRUE(checkFile()); +} + +// Test if expression vendor[*].enterprise works properly for DHCPv6. +TEST_F(TokenTest, vendor6enterprise) { + // Case 1: No option present, should return empty string + testVendorEnterprise(Option::V6, 0, ""); + + // Case 2: Option with vendor-id 1234, should return "1234" + testVendorEnterprise(Option::V6, 1234, encode(1234)); + + // Case 3: Option with vendor-id set to maximum value, should still + // be able to handle it + testVendorEnterprise(Option::V6, 4294967295, encode(4294967295)); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, pushing" + " result ''"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id 1234 as " + "result 0x000004D2"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID Pushing enterprise-id 4294967295 " + "as result 0xFFFFFFFF"); + EXPECT_TRUE(checkFile()); +} + +// This one tests "vendor[4491].option[1].exists" expression. There are so many +// wonderful ways in which this could fail: the option could not be there, +// it could have different enterprise-id, may not have suboption 1. Or may +// have the suboption with valid type, but enterprise may be different. +TEST_F(TokenTest, vendor4SuboptionExists) { + // Case 1: expression vendor[4491].option[1].exists, no option present + testVendorSuboption(Option::V4, 4491, 1, 0, 0, TokenOption::EXISTS, "false"); + + // Case 2: expression vendor[4491].option[1].exists, option with vendor-id = 1234, + // no suboptions, expected result "false" + testVendorSuboption(Option::V4, 4491, 1, 1234, 0, TokenOption::EXISTS, "false"); + + // Case 3: expression vendor[4491].option[1].exists, option with vendor-id = 1234, + // suboption 1, expected result "false" + testVendorSuboption(Option::V4, 4491, 1, 1234, 1, TokenOption::EXISTS, "false"); + + // Case 4: expression vendor[4491].option[1].exists, option with vendor-id = 4491, + // suboption 2, expected result "false" + testVendorSuboption(Option::V4, 4491, 1, 4491, 2, TokenOption::EXISTS, "false"); + + // Case 5: expression vendor[4491].option[1].exists, option with vendor-id = 4491, + // suboption 1, expected result "true" + testVendorSuboption(Option::V4, 4491, 1, 4491, 1, TokenOption::EXISTS, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, pushing " + "result 'false'"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result 'false'"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'false'"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This is similar to the previous one, but tests vendor[4491].option[1].exists +// for DHCPv6. +TEST_F(TokenTest, vendor6SuboptionExists) { + // Case 1: expression vendor[4491].option[1].exists, no option present + testVendorSuboption(Option::V6, 4491, 1, 0, 0, TokenOption::EXISTS, "false"); + + // Case 2: expression vendor[4491].option[1].exists, option with vendor-id = 1234, + // no suboptions, expected result "false" + testVendorSuboption(Option::V6, 4491, 1, 1234, 0, TokenOption::EXISTS, "false"); + + // Case 3: expression vendor[4491].option[1].exists, option with vendor-id = 1234, + // suboption 1, expected result "false" + testVendorSuboption(Option::V6, 4491, 1, 1234, 1, TokenOption::EXISTS, "false"); + + // Case 4: expression vendor[4491].option[1].exists, option with vendor-id = 4491, + // suboption 2, expected result "false" + testVendorSuboption(Option::V6, 4491, 1, 4491, 2, TokenOption::EXISTS, "false"); + + // Case 5: expression vendor[4491].option[1].exists, option with vendor-id = 4491, + // suboption 1, expected result "true" + testVendorSuboption(Option::V6, 4491, 1, 4491, 1, TokenOption::EXISTS, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, pushing " + "result 'false'"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result 'false'"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'false'"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies if vendor[4491].option[1].hex expression properly returns +// value of said sub-option or empty string if desired option is not present. +// This test is for DHCPv4. +TEST_F(TokenTest, vendor4SuboptionHex) { + // Case 1: no option present, should return empty string + testVendorSuboption(Option::V4, 4491, 1, 0, 0, TokenOption::HEXADECIMAL, ""); + + // Case 2: option with vendor-id = 1234, no suboptions, expected result "" + testVendorSuboption(Option::V4, 4491, 1, 1234, 0, TokenOption::HEXADECIMAL, ""); + + // Case 3: option with vendor-id = 1234, suboption 1, expected result "" + testVendorSuboption(Option::V4, 4491, 1, 1234, 1, TokenOption::HEXADECIMAL, ""); + + // Case 4: option with vendor-id = 4491, suboption 2, expected result "" + testVendorSuboption(Option::V4, 4491, 1, 4491, 2, TokenOption::HEXADECIMAL, ""); + + // Case 5: option with vendor-id = 4491, suboption 1, expected result content + // of the option + testVendorSuboption(Option::V4, 4491, 1, 4491, 1, TokenOption::HEXADECIMAL, "alpha"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 125 missing, pushing " + "result ''"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result ''"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 0x"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 0x616C706861"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies if vendor[4491].option[1].hex expression properly returns +// value of said sub-option or empty string if desired option is not present. +// This test is for DHCPv4. +TEST_F(TokenTest, vendor6SuboptionHex) { + // Case 1: no option present, should return empty string + testVendorSuboption(Option::V6, 4491, 1, 0, 0, TokenOption::HEXADECIMAL, ""); + + // Case 2: option with vendor-id = 1234, no suboptions, expected result "" + testVendorSuboption(Option::V6, 4491, 1, 1234, 0, TokenOption::HEXADECIMAL, ""); + + // Case 3: option with vendor-id = 1234, suboption 1, expected result "" + testVendorSuboption(Option::V6, 4491, 1, 1234, 1, TokenOption::HEXADECIMAL, ""); + + // Case 4: option with vendor-id = 4491, suboption 2, expected result "" + testVendorSuboption(Option::V6, 4491, 1, 4491, 2, TokenOption::HEXADECIMAL, ""); + + // Case 5: option with vendor-id = 4491, suboption 1, expected result content + // of the option + testVendorSuboption(Option::V6, 4491, 1, 4491, 1, TokenOption::HEXADECIMAL, "alpha"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_NO_OPTION Option with code 17 missing, pushing " + "result ''"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH Was looking for 4491, " + "option had 1234, pushing result ''"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 0x"); + addString("EVAL_DEBUG_OPTION Pushing option 1 with value 0x616C706861"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies that "vendor-class[4491].exists" expression can be used +// in DHCPv4. +TEST_F(TokenTest, vendorClass4SpecificVendorExists) { + // Case 1: no option present, should fail + testVendorClassExists(Option::V4, 4491, 0, "false"); + + // Case 2: option exists, but has different vendor-id (1234), should fail + testVendorClassExists(Option::V4, 4491, 1234, "false"); + + // Case 3: option exists and has matching vendor-id, should succeed + testVendorClassExists(Option::V4, 4491, 4491, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, " + "pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 4491 " + "found, pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies that "vendor-class[4491].exists" expression can be used +// in DHCPv6. +TEST_F(TokenTest, vendorClass6SpecificVendorExists) { + // Case 1: no option present, should fail + testVendorClassExists(Option::V6, 4491, 0, "false"); + + // Case 2: option exists, but has different vendor-id (1234), should fail + testVendorClassExists(Option::V6, 4491, 1234, "false"); + + // Case 3: option exists and has matching vendor-id, should succeed + testVendorClassExists(Option::V6, 4491, 4491, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, pushing " + "result 'false'"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 4491 " + "found, pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies that "vendor-class[*].exists" can be used in DHCPv4 +// and it matches a vendor class option with any vendor-id. +TEST_F(TokenTest, vendorClass4AnyVendorExists) { + // Case 1: no option present, should fail + testVendorClassExists(Option::V4, 0, 0, "false"); + + // Case 2: option exists, should succeed, regardless of the vendor-id + testVendorClassExists(Option::V4, 0, 1234, "true"); + + // Case 3: option exists, should succeed, regardless of the vendor-id + testVendorClassExists(Option::V4, 0, 4491, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, " + "pushing result 'false'"); + addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 1234 " + "found, pushing result 'true'"); + addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 4491 " + "found, pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies that "vendor-class[*].exists" can be used in DHCPv6 +// and it matches a vendor class option with any vendor-id. +TEST_F(TokenTest, vendorClass6AnyVendorExists) { + // Case 1: no option present, should fail + testVendorClassExists(Option::V6, 0, 0, "false"); + + // Case 2: option exists, should succeed, regardless of the vendor-id + testVendorClassExists(Option::V6, 0, 1234, "true"); + + // Case 3: option exists, should succeed, regardless of the vendor-id + testVendorClassExists(Option::V6, 0, 4491, "true"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, pushing " + "result 'false'"); + addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 1234 " + "found, pushing result 'true'"); + addString("EVAL_DEBUG_VENDOR_CLASS_EXISTS Option with enterprise-id 4491 " + "found, pushing result 'true'"); + EXPECT_TRUE(checkFile()); +} + +// Test if expression "vendor-class.enterprise" works properly for DHCPv4. +TEST_F(TokenTest, vendorClass4enterprise) { + // Case 1: No option present, should return empty string + testVendorClassEnterprise(Option::V4, 0, ""); + + // Case 2: Option with vendor-id 1234, should return "1234" + testVendorClassEnterprise(Option::V4, 1234, encode(1234)); + + // Case 3: Option with vendor-id set to maximum value, should still + // be able to handle it + testVendorClassEnterprise(Option::V4, 4294967295, encode(4294967295)); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, pushing " + "result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id " + "1234 as result 0x000004D2"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id " + "4294967295 as result 0xFFFFFFFF"); + EXPECT_TRUE(checkFile()); +} + +// Test if expression "vendor-class.enterprise" works properly for DHCPv6. +TEST_F(TokenTest, vendorClass6enterprise) { + // Case 1: No option present, should return empty string + testVendorClassEnterprise(Option::V6, 0, ""); + + // Case 2: Option with vendor-id 1234, should return "1234" + testVendorClassEnterprise(Option::V6, 1234, encode(1234)); + + // Case 3: Option with vendor-id set to maximum value, should still + // be able to handle it. + testVendorClassEnterprise(Option::V6, 4294967295, encode(4294967295)); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, pushing " + "result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id " + "1234 as result 0x000004D2"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID Pushing enterprise-id " + "4294967295 as result 0xFFFFFFFF"); + EXPECT_TRUE(checkFile()); +} + +// Test that expression "vendor-class[4491].data" is able to retrieve content +// of the first tuple of the vendor-class option in DHCPv4. +TEST_F(TokenTest, vendorClass4SpecificVendorData) { + // Case 1: Expression looks for vendor-id 4491, data[0], there is no + // vendor-class option at all, expected result is empty string. + testVendorClassData(Option::V4, 4491, 0, 0, 0, ""); + + // Case 2: Expression looks for vendor-id 4491, data[0], there is + // vendor-class with vendor-id 1234 and no data, expected result is empty string. + testVendorClassData(Option::V4, 4491, 0, 1234, 0, ""); + + // Case 3: Expression looks for vendor-id 4491, data[0], there is + // vendor-class with vendor-id 4491 and no data, expected result is empty string. + // Note that vendor option in v4 always have at least one data chunk, even though + // it may be empty. The OptionVendor code was told to not create any special + // tuples, but it creates one empty on its own. So the code finds that one + // tuple and extracts its content (an empty string). + testVendorClassData(Option::V4, 4491, 0, 4491, 0, ""); + + // Case 4: Expression looks for vendor-id 4491, data[0], there is + // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string + testVendorClassData(Option::V4, 4491, 0, 1234, 1, ""); + + // Case 5: Expression looks for vendor-id 4491, data[0], there is + // vendor-class with vendor-id 4491 and 1 data tuple, expected result is + // content of that data ("alpha") + testVendorClassData(Option::V4, 4491, 0, 4491, 1, "alpha"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor " + "class found, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor " + "class found, pushing result 'alpha'"); + EXPECT_TRUE(checkFile()); +} + +// Test that expression "vendor-class[4491].data" is able to retrieve content +// of the first tuple of the vendor-class option in DHCPv6. +TEST_F(TokenTest, vendorClass6SpecificVendorData) { + // Case 1: Expression looks for vendor-id 4491, data[0], there is no + // vendor-class option at all, expected result is empty string. + testVendorClassData(Option::V6, 4491, 0, 0, 0, ""); + + // Case 2: Expression looks for vendor-id 4491, data[0], there is + // vendor-class with vendor-id 1234 and no data, expected result is empty string. + testVendorClassData(Option::V6, 4491, 0, 1234, 0, ""); + + // Case 3: Expression looks for vendor-id 4491, data[0], there is + // vendor-class with vendor-id 4491 and no data, expected result is empty string + testVendorClassData(Option::V6, 4491, 0, 4491, 0, ""); + + // Case 4: Expression looks for vendor-id 4491, data[0], there is + // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string + testVendorClassData(Option::V6, 4491, 0, 1234, 1, ""); + + // Case 5: Expression looks for vendor-id 4491, data[0], there is + // vendor-class with vendor-id 4491 and 1 data tuple, expected result is + // content of that data ("alpha") + testVendorClassData(Option::V6, 4491, 0, 4491, 1, "alpha"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 0, " + "but option with enterprise-id 4491 has only 0 data tuple(s), " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor " + "class found, pushing result 'alpha'"); + EXPECT_TRUE(checkFile()); +} + +// Test that expression "vendor-class[*].data" is able to retrieve content +// of the first tuple of the vendor-class option in DHCPv4. +TEST_F(TokenTest, vendorClass4AnyVendorData) { + // Case 1: Expression looks for any vendor-id (0), data[0], there is no + // vendor-class option at all, expected result is empty string. + testVendorClassData(Option::V4, 0, 0, 0, 0, ""); + + // Case 2: Expression looks for any vendor-id (0), data[0], there is + // vendor-class with vendor-id 1234 and no data (one empty tuple), expected + // result is empty string. + testVendorClassData(Option::V4, 0, 0, 1234, 0, ""); + + // Case 3: Expression looks for any vendor-id (0), data[0], there is + // vendor-class with vendor-id 4491 and no data (one empty tuple), expected + // result is empty string. + testVendorClassData(Option::V4, 0, 0, 4491, 0, ""); + + // Case 4: Expression looks for any vendor-id (0), data[0], there is + // vendor-class with vendor-id 1234 and 1 data tuple, expected result is + // content of that data ("alpha") + testVendorClassData(Option::V4, 0, 0, 1234, 1, "alpha"); + + // Case 5: Expression looks for any vendor-id (0), data[0], there is + // vendor-class with vendor-id 4491 and 1 data tuple, expected result is + // content of that data ("alpha") + testVendorClassData(Option::V4, 0, 0, 4491, 1, "alpha"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in " + "vendor class found, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in " + "vendor class found, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in " + "vendor class found, pushing result 'alpha'"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in " + "vendor class found, pushing result 'alpha'"); + EXPECT_TRUE(checkFile()); +} + +// Test that expression "vendor-class[*].data" is able to retrieve content +// of the first tuple of the vendor-class option in DHCPv6. +TEST_F(TokenTest, vendorClass6AnyVendorData) { + // Case 1: Expression looks for any vendor-id (0), data[0], there is no + // vendor-class option at all, expected result is empty string. + testVendorClassData(Option::V6, 0, 0, 0, 0, ""); + + // Case 2: Expression looks for any vendor-id (0), data[0], there is + // vendor-class with vendor-id 1234 and no data, expected result is empty string. + testVendorClassData(Option::V6, 0, 0, 1234, 0, ""); + + // Case 3: Expression looks for any vendor-id (0), data[0], there is + // vendor-class with vendor-id 4491 and no data, expected result is empty string + testVendorClassData(Option::V6, 0, 0, 4491, 0, ""); + + // Case 4: Expression looks for any vendor-id (0), data[0], there is + // vendor-class with vendor-id 1234 and 1 data tuple, expected result is + // content of that data ("alpha") + testVendorClassData(Option::V6, 0, 0, 1234, 1, "alpha"); + + // Case 5: Expression looks for any vendor-id (0), data[0], there is + // vendor-class with vendor-id 4491 and 1 data tuple, expected result is + // content of that data ("alpha") + testVendorClassData(Option::V6, 0, 0, 4491, 1, "alpha"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 0, " + "but option with enterprise-id 1234 has only 0 data tuple(s), " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 0, " + "but option with enterprise-id 4491 has only 0 data tuple(s), " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor " + "class found, pushing result 'alpha'"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 0 (out of 1 received) in vendor " + "class found, pushing result 'alpha'"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies if expression vendor-class[4491].data[3] is able to access +// the tuple specified by index. This is a DHCPv4 test. +TEST_F(TokenTest, vendorClass4DataIndex) { + // Case 1: Expression looks for vendor-id 4491, data[3], there is no + // vendor-class option at all, expected result is empty string. + testVendorClassData(Option::V4, 4491, 3, 0, 0, ""); + + // Case 2: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 1234 and no data, expected result is empty string. + testVendorClassData(Option::V4, 4491, 3, 1234, 0, ""); + + // Case 3: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 4491 and no data, expected result is empty string + testVendorClassData(Option::V4, 4491, 3, 4491, 0, ""); + + // Case 4: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 1234 and 1 data tuple, expected result is empty string. + testVendorClassData(Option::V4, 4491, 3, 1234, 1, ""); + + // Case 5: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 4491, but has only 3 data tuples, expected + // result is empty string. + testVendorClassData(Option::V4, 4491, 3, 4491, 3, ""); + + // Case 6: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 4491 and 5 data tuples, expected result is + // content of that tuple ("gamma") + testVendorClassData(Option::V4, 4491, 3, 4491, 5, "gamma"); + + // Case 6: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 1234 and 5 data tuples, expected result is + // empty string, because vendor-id does not match. + testVendorClassData(Option::V4, 4491, 3, 1234, 5, ""); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 124 missing, " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 3, " + "but option with enterprise-id 4491 has only 1 data tuple(s), " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 3, " + "but option with enterprise-id 4491 has only 3 data tuple(s), " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 3 (out of 5 received) in vendor " + "class found, pushing result 'gamma'"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + EXPECT_TRUE(checkFile()); +} + +// This test verifies if expression vendor-class[4491].data[3] is able to access +// the tuple specified by index. This is a DHCPv6 test. +TEST_F(TokenTest, vendorClass6DataIndex) { + // Case 1: Expression looks for vendor-id 4491, data[3], there is no + // vendor-class option at all, expected result is empty string. + testVendorClassData(Option::V6, 4491, 3, 0, 0, ""); + + // Case 2: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 1234 and no data, expected result is empty string. + testVendorClassData(Option::V6, 4491, 3, 1234, 0, ""); + + // Case 3: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 4491 and no data, expected result is empty string + testVendorClassData(Option::V6, 4491, 3, 4491, 0, ""); + + // Case 4: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 1234 and 5 data tuples, expected result is empty string. + testVendorClassData(Option::V6, 4491, 3, 1234, 5, ""); + + // Case 5: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 4491, but has only 3 data tuples, expected + // result is empty string. + testVendorClassData(Option::V6, 4491, 3, 4491, 3, ""); + + // Case 6: Expression looks for vendor-id 4491, data[3], there is + // vendor-class with vendor-id 4491 and 5 data tuples, expected result is + // content of that tuple ("gamma") + testVendorClassData(Option::V6, 4491, 3, 4491, 5, "gamma"); + + // Check if the logged messages are correct. + addString("EVAL_DEBUG_VENDOR_CLASS_NO_OPTION Option with code 16 missing, " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 3, " + "but option with enterprise-id 4491 has only 0 data tuple(s), " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH Was looking for " + "4491, option had 1234, pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND Requested data index 3, " + "but option with enterprise-id 4491 has only 3 data tuple(s), " + "pushing result ''"); + addString("EVAL_DEBUG_VENDOR_CLASS_DATA Data 3 (out of 5 received) in vendor" + " class found, pushing result 'gamma'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that the existing RAI -sunoption can be found. +TEST_F(TokenTest, subOption) { + + // Insert relay option with sub-options 1 and 13 + insertRelay4Option(); + + // Creating the token should be safe. + ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS, + 13, TokenOption::TEXTUAL))); + + // We should be able to evaluate it. + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // we should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // The option should be found and option[82].option[13] should evaluate + // to thecontent of that sub-option, i.e. "thirteen" + EXPECT_EQ("thirteen", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 " + "sub-option 13 with value 'thirteen'"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that the code properly handles cases when +// there is a RAI option, but there's no requested sub-option. +TEST_F(TokenTest, subOptionNoSubOption) { + + // Insert relay option with sub-options 1 and 13 + insertRelay4Option(); + + + // Creating the token should be safe. + ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS, + 15, TokenOption::TEXTUAL))); + + // We should be able to evaluate it. + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // we should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // The option should NOT be found (there is no sub-option 15), + // so the expression should evaluate to "" + EXPECT_EQ("", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 " + "sub-option 15 with value ''"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that the code properly handles cases when +// there's no RAI option at all. +TEST_F(TokenTest, subOptionNoOption) { + + // We didn't call insertRelay4Option(), so there's no RAI option. + + // Creating the token should be safe. + ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS, + 13, TokenOption::TEXTUAL))); + + // We should be able to evaluate it. + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + + // we should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // The option should NOT be found (there is no option 82), + // so the expression should evaluate to "" + EXPECT_EQ("", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUB_OPTION_NO_OPTION Requested option 82 " + "sub-option 13, but the parent option is not present, " + "pushing result ''"); + EXPECT_TRUE(checkFile()); +} + +// This test checks that only the requested parent is searched for +// the requested sub-option. +TEST_F(TokenTest, subOptionOptionOnly) { + + // Insert relay option with sub-options 1 and 13 + insertRelay4Option(); + + // Add options 13 and 70 to the packet. + OptionPtr opt13(new OptionString(Option::V4, 13, "THIRTEEN")); + OptionPtr opt70(new OptionString(Option::V4, 70, "SEVENTY")); + pkt4_->addOption(opt13); + pkt4_->addOption(opt70); + + // The situation is as follows: + // Packet: + // - option 13 (containing "THIRTEEN") + // - option 82 (rai) + // - option 1 (containing "one") + // - option 13 (containing "thirteen") + + // Let's try to get option 13. It should get the one from RAI + ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS, + 13, TokenOption::TEXTUAL))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("thirteen", values_.top()); + + // Try to get option 1. It should get the one from RAI + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS, + 1, TokenOption::TEXTUAL))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("one", values_.top()); + + // Try to get option 70. It should fail, as there's no such + // sub option in RAI. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS, + 70, TokenOption::TEXTUAL))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("", values_.top()); + + // Try to check option 1. It should return "true" + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS, + 1, TokenOption::EXISTS))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // Try to check option 70. It should return "false" + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenSubOption(DHO_DHCP_AGENT_OPTIONS, + 70, TokenOption::EXISTS))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("false", values_.top()); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 " + "sub-option 13 with value 'thirteen'"); + addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 " + "sub-option 1 with value 'one'"); + addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 " + "sub-option 70 with value ''"); + addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 " + "sub-option 1 with value 'true'"); + addString("EVAL_DEBUG_SUB_OPTION Pushing option 82 " + "sub-option 70 with value 'false'"); + EXPECT_TRUE(checkFile()); +} + +// Checks if various values can be represented as integer tokens +TEST_F(TokenTest, integer) { + testInteger(encode(0), 0); + testInteger(encode(6), 6); + testInteger(encode(255), 255); + testInteger(encode(256), 256); + testInteger(encode(1410), 1410); + testInteger(encode(4294967295), 4294967295); +} + +// Verify TokenSplit::eval, single delimiter. +TEST_F(TokenTest, split) { + // Get the whole string + std::string input(".two.three..five."); + std::string delims("."); + + // Empty input string should yield empty result. + verifySplitEval("", delims, "1", ""); + + // Empty delimiters string should yield original string result. + verifySplitEval(input, "", "1", input); + + // Field number less than one should yield empty result. + verifySplitEval(input, delims, "0", ""); + + // Now get each field in succession. + verifySplitEval(input, delims, "1", ""); + verifySplitEval(input, delims, "2", "two"); + verifySplitEval(input, delims, "3", "three"); + verifySplitEval(input, delims, "4", ""); + verifySplitEval(input, delims, "5", "five"); + verifySplitEval(input, delims, "6", ""); + + // Too large of a field should yield empty result. + verifySplitEval(input, delims, "7", ""); + + // A string without delimiters returns as field 1. + verifySplitEval("just_one", delims, "1", "just_one"); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + addString("EVAL_DEBUG_SPLIT_EMPTY Popping field 1, delimiters .," + " string , pushing result 0x"); + addString("EVAL_DEBUG_SPLIT_DELIM_EMPTY Popping field 1, delimiters ," + " string .two.three..five., pushing result 0x2E74776F2E74687265652E2E666976652E"); + addString("EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field 0, delimiters .," + " string .two.three..five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT Popping field 1, delimiters .," + " string .two.three..five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT Popping field 2, delimiters .," + " string .two.three..five., pushing result 0x74776F"); + addString("EVAL_DEBUG_SPLIT Popping field 3, delimiters .," + " string .two.three..five., pushing result 0x7468726565"); + addString("EVAL_DEBUG_SPLIT Popping field 4, delimiters .," + " string .two.three..five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT Popping field 5, delimiters .," + " string .two.three..five., pushing result 0x66697665"); + addString("EVAL_DEBUG_SPLIT Popping field 6, delimiters .," + " string .two.three..five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field 7, delimiters .," + " string .two.three..five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT Popping field 1, delimiters .," + " string just_one, pushing result 0x6A7573745F6F6E65"); + EXPECT_TRUE(checkFile()); +} + +// Verify TokenSplit::eval with more than one delimiter. +TEST_F(TokenTest, splitMultipleDelims) { + // Get the whole string + std::string input(".two:three.:five."); + std::string delims(".:"); + + // Empty input string should yield empty result. + verifySplitEval("", delims, "1", ""); + + // Too small of a field should yield empty result. + verifySplitEval(input, delims, "0", ""); + + // Now get each field in succession. + verifySplitEval(input, delims, "1", ""); + verifySplitEval(input, delims, "2", "two"); + verifySplitEval(input, delims, "3", "three"); + verifySplitEval(input, delims, "4", ""); + verifySplitEval(input, delims, "5", "five"); + verifySplitEval(input, delims, "6", ""); + + // Too large of a field should yield empty result. + verifySplitEval(input, delims, "7", ""); + + // A string without delimiters returns as field 1. + verifySplitEval("just_one", delims, "1", "just_one"); + + // Check that the debug output was correct. Add the strings + // to the test vector in the class and then call checkFile + // for comparison + + addString("EVAL_DEBUG_SPLIT_EMPTY Popping field 1, delimiters .:," + " string , pushing result 0x"); + addString("EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field 0, delimiters .:," + " string .two:three.:five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT Popping field 1, delimiters .:," + " string .two:three.:five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT Popping field 2, delimiters .:," + " string .two:three.:five., pushing result 0x74776F"); + addString("EVAL_DEBUG_SPLIT Popping field 3, delimiters .:," + " string .two:three.:five., pushing result 0x7468726565"); + addString("EVAL_DEBUG_SPLIT Popping field 4, delimiters .:," + " string .two:three.:five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT Popping field 5, delimiters .:," + " string .two:three.:five., pushing result 0x66697665"); + addString("EVAL_DEBUG_SPLIT Popping field 6, delimiters .:," + " string .two:three.:five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE Popping field 7, delimiters .:," + " string .two:three.:five., pushing result 0x"); + addString("EVAL_DEBUG_SPLIT Popping field 1, delimiters .:," + " string just_one, pushing result 0x6A7573745F6F6E65"); + EXPECT_TRUE(checkFile()); +} + +}; diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc new file mode 100644 index 0000000..017f22d --- /dev/null +++ b/src/lib/eval/token.cc @@ -0,0 +1,1319 @@ +// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <eval/token.h> +#include <eval/eval_log.h> +#include <eval/eval_context.h> +#include <util/encode/hex.h> +#include <util/io_utilities.h> +#include <asiolink/io_address.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <boost/lexical_cast.hpp> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/option_vendor.h> +#include <dhcp/option_vendor_class.h> + +#include <boost/algorithm/string/classification.hpp> +#include <boost/algorithm/string/split.hpp> + +#include <cstring> +#include <string> +#include <iomanip> +#include <sstream> + +using namespace isc::asiolink; +using namespace isc::dhcp; +using namespace isc::util; +using namespace std; + +using isc::util::encode::toHex; + +void +TokenString::evaluate(Pkt& /*pkt*/, ValueStack& values) { + // Literals only push, nothing to pop + values.push(value_); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_STRING) + .arg('\'' + value_ + '\''); +} + +TokenHexString::TokenHexString(const string& str) : value_("") { + // Check string starts "0x" or "0x" and has at least one additional character. + if ((str.size() < 3) || + (str[0] != '0') || + ((str[1] != 'x') && (str[1] != 'X'))) { + return; + } + string digits = str.substr(2); + + // Transform string of hexadecimal digits into binary format + vector<uint8_t> binary; + try { + // The decodeHex function expects that the string contains an + // even number of digits. If we don't meet this requirement, + // we have to insert a leading 0. + if ((digits.length() % 2) != 0) { + digits = digits.insert(0, "0"); + } + util::encode::decodeHex(digits, binary); + } catch (...) { + return; + } + + // Convert to a string (note that binary.size() cannot be 0) + value_.resize(binary.size()); + memmove(&value_[0], &binary[0], binary.size()); +} + +void +TokenHexString::evaluate(Pkt& /*pkt*/, ValueStack& values) { + // Literals only push, nothing to pop + values.push(value_); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_HEXSTRING) + .arg(toHex(value_)); +} + +TokenIpAddress::TokenIpAddress(const string& addr) : value_("") { + // Transform IP address into binary format + vector<uint8_t> binary; + try { + asiolink::IOAddress ip(addr); + binary = ip.toBytes(); + } catch (...) { + return; + } + + // Convert to a string (note that binary.size() is 4 or 16, so not 0) + value_.resize(binary.size()); + memmove(&value_[0], &binary[0], binary.size()); +} + +void +TokenIpAddress::evaluate(Pkt& /*pkt*/, ValueStack& values) { + // Literals only push, nothing to pop + values.push(value_); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IPADDRESS) + .arg(toHex(value_)); +} + +void +TokenIpAddressToText::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() == 0) { + isc_throw(EvalBadStack, "Incorrect empty stack."); + } + + string op = values.top(); + size_t size = op.size(); + + if (!size) { + return; + } + + values.pop(); + + if ((size != V4ADDRESS_LEN) && (size != V6ADDRESS_LEN)) { + isc_throw(EvalTypeError, "Can not convert to valid address."); + } + + std::vector<uint8_t> binary(op.begin(), op.end()); + + if (size == V4ADDRESS_LEN) { + op = asiolink::IOAddress::fromBytes(AF_INET, binary.data()).toText(); + } else { + op = asiolink::IOAddress::fromBytes(AF_INET6, binary.data()).toText(); + } + + values.push(op); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IPADDRESSTOTEXT) + .arg(op); +} + +void +TokenInt8ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() == 0) { + isc_throw(EvalBadStack, "Incorrect empty stack."); + } + + string op = values.top(); + size_t size = op.size(); + + if (!size) { + return; + } + + values.pop(); + + if (size != sizeof(int8_t)) { + isc_throw(EvalTypeError, "Can not convert to valid int8."); + } + + stringstream tmp; + tmp << static_cast<int32_t>(*(reinterpret_cast<const int8_t*>(op.data()))); + op = tmp.str(); + values.push(op); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_INT8TOTEXT) + .arg(op); +} + +void +TokenInt16ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() == 0) { + isc_throw(EvalBadStack, "Incorrect empty stack."); + } + + string op = values.top(); + size_t size = op.size(); + + if (!size) { + return; + } + + values.pop(); + + if (size != sizeof(int16_t)) { + isc_throw(EvalTypeError, "Can not convert to valid int16."); + } + + stringstream tmp; + int16_t value = static_cast<int16_t>(readUint16(const_cast<const char*>(op.data()), size)); + tmp << value; + op = tmp.str(); + values.push(op); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_INT16TOTEXT) + .arg(op); +} + +void +TokenInt32ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() == 0) { + isc_throw(EvalBadStack, "Incorrect empty stack."); + } + + string op = values.top(); + size_t size = op.size(); + + if (!size) { + return; + } + + values.pop(); + + if (size != sizeof(int32_t)) { + isc_throw(EvalTypeError, "Can not convert to valid int32."); + } + + stringstream tmp; + int32_t value = static_cast<int32_t>(readUint32(const_cast<const char*>(op.data()), size)); + tmp << value; + op = tmp.str(); + values.push(op); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_INT32TOTEXT) + .arg(op); +} + +void +TokenUInt8ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() == 0) { + isc_throw(EvalBadStack, "Incorrect empty stack."); + } + + string op = values.top(); + size_t size = op.size(); + + if (!size) { + return; + } + + values.pop(); + + if (size != sizeof(uint8_t)) { + isc_throw(EvalTypeError, "Can not convert to valid uint8."); + } + + stringstream tmp; + tmp << static_cast<uint32_t>(*(reinterpret_cast<const uint8_t*>(op.data()))); + op = tmp.str(); + values.push(op); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_UINT8TOTEXT) + .arg(op); +} + +void +TokenUInt16ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() == 0) { + isc_throw(EvalBadStack, "Incorrect empty stack."); + } + + string op = values.top(); + size_t size = op.size(); + + if (!size) { + return; + } + + values.pop(); + + if (size != sizeof(uint16_t)) { + isc_throw(EvalTypeError, "Can not convert to valid uint16."); + } + + stringstream tmp; + uint16_t value = readUint16(const_cast<const char*>(op.data()), size); + tmp << value; + op = tmp.str(); + values.push(op); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_UINT16TOTEXT) + .arg(op); +} + +void +TokenUInt32ToText::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() == 0) { + isc_throw(EvalBadStack, "Incorrect empty stack."); + } + + string op = values.top(); + size_t size = op.size(); + + if (!size) { + return; + } + + values.pop(); + + if (size != sizeof(uint32_t)) { + isc_throw(EvalTypeError, "Can not convert to valid uint32."); + } + + stringstream tmp; + uint32_t value = readUint32(const_cast<const char*>(op.data()), size); + tmp << value; + op = tmp.str(); + values.push(op); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_UINT32TOTEXT) + .arg(op); +} + +OptionPtr +TokenOption::getOption(Pkt& pkt) { + return (pkt.getOption(option_code_)); +} + +void +TokenOption::evaluate(Pkt& pkt, ValueStack& values) { + OptionPtr opt = getOption(pkt); + std::string opt_str; + if (opt) { + if (representation_type_ == TEXTUAL) { + opt_str = opt->toString(); + } else if (representation_type_ == HEXADECIMAL) { + std::vector<uint8_t> binary = opt->toBinary(); + opt_str.resize(binary.size()); + if (!binary.empty()) { + memmove(&opt_str[0], &binary[0], binary.size()); + } + } else { + opt_str = "true"; + } + } else if (representation_type_ == EXISTS) { + opt_str = "false"; + } + + // Push value of the option or empty string if there was no such option + // in the packet. + values.push(opt_str); + + // Log what we pushed, both exists and textual are simple text + // and can be output directly. We also include the code number + // of the requested option. + if (representation_type_ == HEXADECIMAL) { + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OPTION) + .arg(option_code_) + .arg(toHex(opt_str)); + } else { + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OPTION) + .arg(option_code_) + .arg('\'' + opt_str + '\''); + } +} + +std::string +TokenOption::pushFailure(ValueStack& values) { + std::string txt; + if (representation_type_ == EXISTS) { + txt = "false"; + } + values.push(txt); + return (txt); +} + +TokenRelay4Option::TokenRelay4Option(const uint16_t option_code, + const RepresentationType& rep_type) + : TokenOption(option_code, rep_type) { +} + +OptionPtr TokenRelay4Option::getOption(Pkt& pkt) { + // Check if there is Relay Agent Option. + OptionPtr rai = pkt.getOption(DHO_DHCP_AGENT_OPTIONS); + if (!rai) { + return (OptionPtr()); + } + + // If there is, try to return its suboption + return (rai->getOption(option_code_)); +} + +OptionPtr TokenRelay6Option::getOption(Pkt& pkt) { + try { + // Check if it's a Pkt6. If it's not the dynamic_cast will + // throw std::bad_cast. + Pkt6& pkt6 = dynamic_cast<Pkt6&>(pkt); + + try { + // Now that we have the right type of packet we can + // get the option and return it. + if (nest_level_ >= 0) { + uint8_t nesting_level = static_cast<uint8_t>(nest_level_); + return(pkt6.getRelayOption(option_code_, nesting_level)); + } else { + int nesting_level = pkt6.relay_info_.size() + nest_level_; + if (nesting_level < 0) { + return (OptionPtr()); + } + return(pkt6.getRelayOption(option_code_, + static_cast<uint8_t>(nesting_level))); + } + } + catch (const isc::OutOfRange&) { + // The only exception we expect is OutOfRange if the nest + // level is out of range of the encapsulations, for example + // if nest_level_ is 4 and there are only 2 encapsulations. + // We return a NULL in that case. + return (OptionPtr()); + } + + } catch (const std::bad_cast&) { + isc_throw(EvalTypeError, "Specified packet is not Pkt6"); + } + +} + +void +TokenPkt::evaluate(Pkt& pkt, ValueStack& values) { + string value; + vector<uint8_t> binary; + string type_str; + bool is_binary = true; + bool print_hex = true; + switch (type_) { + case IFACE: + is_binary = false; + print_hex = false; + value = pkt.getIface(); + type_str = "iface"; + break; + case SRC: + binary = pkt.getRemoteAddr().toBytes(); + type_str = "src"; + break; + case DST: + binary = pkt.getLocalAddr().toBytes(); + type_str = "dst"; + break; + case LEN: + // len() returns a size_t but in fact it can't be very large + // (with UDP transport it fits in 16 bits) + // the len() method is not const because of DHCPv6 relays. + // We assume here it has no bad side effects... + value = EvalContext::fromUint32(static_cast<uint32_t>(const_cast<Pkt&>(pkt).len())); + is_binary = false; + type_str = "len"; + break; + + default: + isc_throw(EvalTypeError, "Bad meta data specified: " << static_cast<int>(type_)); + } + + if (is_binary) { + value.resize(binary.size()); + if (!binary.empty()) { + memmove(&value[0], &binary[0], binary.size()); + } + } + values.push(value); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT) + .arg(type_str) + .arg(print_hex ? toHex(value) : value); +} + +void +TokenPkt4::evaluate(Pkt& pkt, ValueStack& values) { + vector<uint8_t> binary; + string value; + string type_str; + try { + // Check if it's a Pkt4. If it's not, the dynamic_cast will throw + // std::bad_cast (failed dynamic_cast returns NULL for pointers and + // throws for references). + const Pkt4& pkt4 = dynamic_cast<const Pkt4&>(pkt); + + switch (type_) { + case CHADDR: { + HWAddrPtr hwaddr = pkt4.getHWAddr(); + if (!hwaddr) { + // This should never happen. Every Pkt4 should always have + // a hardware address. + isc_throw(EvalTypeError, + "Packet does not have hardware address"); + } + binary = hwaddr->hwaddr_; + type_str = "mac"; + break; + } + case GIADDR: + binary = pkt4.getGiaddr().toBytes(); + type_str = "giaddr"; + break; + case CIADDR: + binary = pkt4.getCiaddr().toBytes(); + type_str = "ciaddr"; + break; + case YIADDR: + binary = pkt4.getYiaddr().toBytes(); + type_str = "yiaddr"; + break; + case SIADDR: + binary = pkt4.getSiaddr().toBytes(); + type_str = "siaddr"; + break; + case HLEN: + // Pad the uint8_t field to 4 bytes. + value = EvalContext::fromUint32(pkt4.getHlen()); + type_str = "hlen"; + break; + case HTYPE: + // Pad the uint8_t field to 4 bytes. + value = EvalContext::fromUint32(pkt4.getHtype()); + type_str = "htype"; + break; + case MSGTYPE: + value = EvalContext::fromUint32(pkt4.getType()); + type_str = "msgtype"; + break; + case TRANSID: + value = EvalContext::fromUint32(pkt4.getTransid()); + type_str = "transid"; + break; + default: + isc_throw(EvalTypeError, "Bad field specified: " << static_cast<int>(type_)); + } + + } catch (const std::bad_cast&) { + isc_throw(EvalTypeError, "Specified packet is not a Pkt4"); + } + + if (!binary.empty()) { + value.resize(binary.size()); + memmove(&value[0], &binary[0], binary.size()); + } + values.push(value); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT4) + .arg(type_str) + .arg(toHex(value)); +} + +void +TokenPkt6::evaluate(Pkt& pkt, ValueStack& values) { + string value; + string type_str; + try { + // Check if it's a Pkt6. If it's not the dynamic_cast will throw + // std::bad_cast (failed dynamic_cast returns NULL for pointers and + // throws for references). + const Pkt6& pkt6 = dynamic_cast<const Pkt6&>(pkt); + + switch (type_) { + case MSGTYPE: { + // msg type is an uint8_t integer. We want a 4 byte string so 0 pad. + value = EvalContext::fromUint32(pkt6.getType()); + type_str = "msgtype"; + break; + } + case TRANSID: { + // transaction id is an uint32_t integer. We want a 4 byte string so copy + value = EvalContext::fromUint32(pkt6.getTransid()); + type_str = "transid"; + break; + } + default: + isc_throw(EvalTypeError, "Bad field specified: " << static_cast<int>(type_)); + } + + } catch (const std::bad_cast&) { + isc_throw(EvalTypeError, "Specified packet is not Pkt6"); + } + + values.push(value); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_PKT6) + .arg(type_str) + .arg(toHex(value)); +} + +void +TokenRelay6Field::evaluate(Pkt& pkt, ValueStack& values) { + vector<uint8_t> binary; + string type_str; + try { + // Check if it's a Pkt6. If it's not the dynamic_cast will + // throw std::bad_cast. + const Pkt6& pkt6 = dynamic_cast<const Pkt6&>(pkt); + uint8_t relay_level; + + try { + if (nest_level_ >= 0) { + relay_level = static_cast<uint8_t>(nest_level_); + } else { + int nesting_level = pkt6.relay_info_.size() + nest_level_; + if (nesting_level < 0) { + // Don't throw OutOfRange here + nesting_level = 32; + } + relay_level = static_cast<uint8_t>(nesting_level); + } + switch (type_) { + // Now that we have the right type of packet we can + // get the option and return it. + case LINKADDR: + type_str = "linkaddr"; + binary = pkt6.getRelay6LinkAddress(relay_level).toBytes(); + break; + case PEERADDR: + type_str = "peeraddr"; + binary = pkt6.getRelay6PeerAddress(relay_level).toBytes(); + break; + } + } catch (const isc::OutOfRange&) { + // The only exception we expect is OutOfRange if the nest + // level is invalid. We push "" in that case. + values.push(""); + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_RELAY6_RANGE) + .arg(type_str) + .arg(int(nest_level_)) + .arg("0x"); + return; + } + } catch (const std::bad_cast&) { + isc_throw(EvalTypeError, "Specified packet is not Pkt6"); + } + + string value; + value.resize(binary.size()); + if (!binary.empty()) { + memmove(&value[0], &binary[0], binary.size()); + } + values.push(value); + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_RELAY6) + .arg(type_str) + .arg(int(nest_level_)) + .arg(toHex(value)); +} + +void +TokenEqual::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() < 2) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " + "2 values for == operator, got " << values.size()); + } + + string op1 = values.top(); + values.pop(); + string op2 = values.top(); + values.pop(); // Dammit, std::stack interface is awkward. + + if (op1 == op2) + values.push("true"); + else + values.push("false"); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_EQUAL) + .arg(toHex(op1)) + .arg(toHex(op2)) + .arg('\'' + values.top() + '\''); +} + +void +TokenSubstring::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() < 3) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " + "3 values for substring operator, got " << values.size()); + } + + string len_str = values.top(); + values.pop(); + string start_str = values.top(); + values.pop(); + string string_str = values.top(); + values.pop(); + + // If we have no string to start with we push an empty string and leave + if (string_str.empty()) { + values.push(""); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING_EMPTY) + .arg(len_str) + .arg(start_str) + .arg("0x") + .arg("0x"); + return; + } + + // Convert the starting position and length from strings to numbers + // the length may also be "all" in which case simply make it the + // length of the string. + // If we have a problem push an empty string and leave + int start_pos; + int length; + try { + start_pos = boost::lexical_cast<int>(start_str); + } catch (const boost::bad_lexical_cast&) { + isc_throw(EvalTypeError, "the parameter '" << start_str + << "' for the starting position of the substring " + << "couldn't be converted to an integer."); + } + try { + if (len_str == "all") { + length = string_str.length(); + } else { + length = boost::lexical_cast<int>(len_str); + } + } catch (const boost::bad_lexical_cast&) { + isc_throw(EvalTypeError, "the parameter '" << len_str + << "' for the length of the substring " + << "couldn't be converted to an integer."); + } + + const int string_length = string_str.length(); + // If the starting position is outside of the string push an + // empty string and leave + if ((start_pos < -string_length) || (start_pos >= string_length)) { + values.push(""); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING_RANGE) + .arg(len_str) + .arg(start_str) + .arg(toHex(string_str)) + .arg("0x"); + return; + } + + // Adjust the values to be something for substr. We first figure out + // the starting position, then update it and the length to get the + // characters before or after it depending on the sign of length + if (start_pos < 0) { + start_pos = string_length + start_pos; + } + + if (length < 0) { + length = -length; + if (length <= start_pos){ + start_pos -= length; + } else { + length = start_pos; + start_pos = 0; + } + } + + // and finally get the substring + values.push(string_str.substr(start_pos, length)); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SUBSTRING) + .arg(len_str) + .arg(start_str) + .arg(toHex(string_str)) + .arg(toHex(values.top())); +} + +void +TokenSplit::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() < 3) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " + "3 values for split operator, got " << values.size()); + } + + // Pop the parameters. + string field_str = values.top(); + values.pop(); + string delim_str = values.top(); + values.pop(); + string string_str = values.top(); + values.pop(); + + // If we have no string to start with we push an empty string and leave + if (string_str.empty()) { + values.push(""); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SPLIT_EMPTY) + .arg(field_str) + .arg(delim_str) + .arg(string_str) + .arg("0x"); + return; + } + + // Convert the field position from string to number + // If we have a problem push an empty string and leave + int field; + try { + field = boost::lexical_cast<int>(field_str); + } catch (const boost::bad_lexical_cast&) { + isc_throw(EvalTypeError, "the parameter '" << field_str + << "' for the field field for split " + << "couldn't be converted to an integer."); + } + + // If we have no delimiter to start with we push the input string and leave + if (delim_str.empty()) { + values.push(string_str); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SPLIT_DELIM_EMPTY) + .arg(field_str) + .arg(delim_str) + .arg(string_str) + .arg(toHex(values.top())); + return; + } + + // Split the string into fields. + std::vector<std::string> fields; + boost::split(fields, string_str, boost::is_any_of(delim_str), + boost::algorithm::token_compress_off); + + // Range check the field. + if (field < 1 || field > fields.size()) { + // Push an empty string if field is out of range. + values.push(""); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SPLIT_FIELD_OUT_OF_RANGE) + .arg(field_str) + .arg(delim_str) + .arg(string_str) + .arg("0x"); + return; + } + + // Push the desired field. + values.push(fields[field - 1]); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_SPLIT) + .arg(field_str) + .arg(delim_str) + .arg(string_str) + .arg(toHex(values.top())); +} + +void +TokenConcat::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() < 2) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " + "2 values for concat, got " << values.size()); + } + + string op1 = values.top(); + values.pop(); + string op2 = values.top(); + values.pop(); // Dammit, std::stack interface is awkward. + + // The top of the stack was evaluated last so this is the right order + values.push(op2 + op1); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_CONCAT) + .arg(toHex(op1)) + .arg(toHex(op2)) + .arg(toHex(values.top())); +} + +void +TokenIfElse::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() < 3) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " + "3 values for ifelse, got " << values.size()); + } + + string iffalse = values.top(); + values.pop(); + string iftrue = values.top(); + values.pop(); + string cond = values.top(); + values.pop(); + bool val = toBool(cond); + + if (val) { + values.push(iftrue); + } else { + values.push(iffalse); + } + + // Log what we popped and pushed + if (val) { + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IFELSE_TRUE) + .arg('\'' + cond + '\'') + .arg(toHex(iffalse)) + .arg(toHex(iftrue)); + } else { + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_IFELSE_FALSE) + .arg('\'' +cond + '\'') + .arg(toHex(iftrue)) + .arg(toHex(iffalse)); + } +} + +void +TokenToHexString::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() < 2) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " + "2 values for hexstring, got " << values.size()); + } + + string separator = values.top(); + values.pop(); + string binary = values.top(); + values.pop(); + + bool first = true; + stringstream tmp; + tmp << hex; + for (size_t i = 0; i < binary.size(); ++i) { + if (!first) { + tmp << separator; + } else { + first = false; + } + tmp << setw(2) << setfill('0') + << (static_cast<unsigned>(binary[i]) & 0xff); + } + values.push(tmp.str()); + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_TOHEXSTRING) + .arg(toHex(binary)) + .arg(separator) + .arg(tmp.str()); +} + +void +TokenNot::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() == 0) { + isc_throw(EvalBadStack, "Incorrect empty stack."); + } + + string op = values.top(); + values.pop(); + bool val = toBool(op); + + if (!val) { + values.push("true"); + } else { + values.push("false"); + } + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_NOT) + .arg('\'' + op + '\'') + .arg('\'' + values.top() + '\''); +} + +void +TokenAnd::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() < 2) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " + "2 values for and operator, got " << values.size()); + } + + string op1 = values.top(); + values.pop(); + bool val1 = toBool(op1); + string op2 = values.top(); + values.pop(); // Dammit, std::stack interface is awkward. + bool val2 = toBool(op2); + + if (val1 && val2) { + values.push("true"); + } else { + values.push("false"); + } + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_AND) + .arg('\'' + op1 + '\'') + .arg('\'' + op2 + '\'') + .arg('\'' + values.top() + '\''); +} + +void +TokenOr::evaluate(Pkt& /*pkt*/, ValueStack& values) { + if (values.size() < 2) { + isc_throw(EvalBadStack, "Incorrect stack order. Expected at least " + "2 values for or operator, got " << values.size()); + } + + string op1 = values.top(); + values.pop(); + bool val1 = toBool(op1); + string op2 = values.top(); + values.pop(); // Dammit, std::stack interface is awkward. + bool val2 = toBool(op2); + + if (val1 || val2) { + values.push("true"); + } else { + values.push("false"); + } + + // Log what we popped and pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_OR) + .arg('\'' + op1 + '\'') + .arg('\'' + op2 + '\'') + .arg('\'' + values.top() + '\''); +} + +void +TokenMember::evaluate(Pkt& pkt, ValueStack& values) { + if (pkt.inClass(client_class_)) { + values.push("true"); + } else { + values.push("false"); + } + + // Log what we pushed + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_MEMBER) + .arg(client_class_) + .arg('\'' + values.top() + '\''); +} + +TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr, + uint16_t option_code) + : TokenOption(option_code, repr), universe_(u), vendor_id_(vendor_id), + field_(option_code ? SUBOPTION : EXISTS) { +} + +TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, FieldType field) + : TokenOption(0, TokenOption::HEXADECIMAL), universe_(u), vendor_id_(vendor_id), + field_(field) { + if (field_ == EXISTS) { + representation_type_ = TokenOption::EXISTS; + } +} + +uint32_t TokenVendor::getVendorId() const { + return (vendor_id_); +} + +TokenVendor::FieldType TokenVendor::getField() const { + return (field_); +} + +void TokenVendor::evaluate(Pkt& pkt, ValueStack& values) { + // Get the option first. + uint16_t code = 0; + switch (universe_) { + case Option::V4: + code = DHO_VIVSO_SUBOPTIONS; + break; + case Option::V6: + code = D6O_VENDOR_OPTS; + break; + } + + OptionPtr opt = pkt.getOption(code); + OptionVendorPtr vendor = boost::dynamic_pointer_cast<OptionVendor>(opt); + if (!vendor) { + // There's no vendor option, give up. + std::string txt = pushFailure(values); + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_NO_OPTION) + .arg(code) + .arg(txt); + return; + } + + if (vendor_id_ && (vendor_id_ != vendor->getVendorId())) { + // There is vendor option, but it has other vendor-id value + // than we're looking for. (0 means accept any vendor-id) + std::string txt = pushFailure(values); + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_ENTERPRISE_ID_MISMATCH) + .arg(vendor_id_) + .arg(vendor->getVendorId()) + .arg(txt); + return; + } + + switch (field_) { + case ENTERPRISE_ID: + { + // Extract enterprise-id + string txt(sizeof(uint32_t), 0); + uint32_t value = htonl(vendor->getVendorId()); + memcpy(&txt[0], &value, sizeof(uint32_t)); + values.push(txt); + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_ENTERPRISE_ID) + .arg(vendor->getVendorId()) + .arg(util::encode::encodeHex(std::vector<uint8_t>(txt.begin(), + txt.end()))); + return; + } + case SUBOPTION: + /// This is vendor[X].option[Y].exists, let's try to + /// extract the option + TokenOption::evaluate(pkt, values); + return; + case EXISTS: + // We already passed all the checks: the option is there and has specified + // enterprise-id. + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_EXISTS) + .arg(vendor->getVendorId()) + .arg("true"); + values.push("true"); + return; + case DATA: + // This is for vendor-class option, we can skip it here. + isc_throw(EvalTypeError, "Field None is not valid for vendor-class"); + return; + } +} + +OptionPtr TokenVendor::getOption(Pkt& pkt) { + uint16_t code = 0; + switch (universe_) { + case Option::V4: + code = DHO_VIVSO_SUBOPTIONS; + break; + case Option::V6: + code = D6O_VENDOR_OPTS; + break; + } + + OptionPtr opt = pkt.getOption(code); + if (!opt) { + // If vendor option is not found, return NULL + return (opt); + } + + // If vendor option is found, try to return its + // encapsulated option. + return (opt->getOption(option_code_)); +} + +TokenVendorClass::TokenVendorClass(Option::Universe u, uint32_t vendor_id, + RepresentationType repr) + : TokenVendor(u, vendor_id, repr, 0), index_(0) { +} + +TokenVendorClass::TokenVendorClass(Option::Universe u, uint32_t vendor_id, + FieldType field, uint16_t index) + : TokenVendor(u, vendor_id, TokenOption::HEXADECIMAL, 0), index_(index) { + field_ = field; +} + +uint16_t TokenVendorClass::getDataIndex() const { + return (index_); +} + +void TokenVendorClass::evaluate(Pkt& pkt, ValueStack& values) { + // Get the option first. + uint16_t code = 0; + switch (universe_) { + case Option::V4: + code = DHO_VIVCO_SUBOPTIONS; + break; + case Option::V6: + code = D6O_VENDOR_CLASS; + break; + } + + OptionPtr opt = pkt.getOption(code); + OptionVendorClassPtr vendor = boost::dynamic_pointer_cast<OptionVendorClass>(opt); + if (!vendor) { + // There's no vendor class option, give up. + std::string txt = pushFailure(values); + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_NO_OPTION) + .arg(code) + .arg(txt); + return; + } + + if (vendor_id_ && (vendor_id_ != vendor->getVendorId())) { + // There is vendor option, but it has other vendor-id value + // than we're looking for. (0 means accept any vendor-id) + std::string txt = pushFailure(values); + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID_MISMATCH) + .arg(vendor_id_) + .arg(vendor->getVendorId()) + .arg(txt); + return; + } + + switch (field_) { + case ENTERPRISE_ID: + { + // Extract enterprise-id + string txt(sizeof(uint32_t), 0); + uint32_t value = htonl(vendor->getVendorId()); + memcpy(&txt[0], &value, sizeof(uint32_t)); + values.push(txt); + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_ENTERPRISE_ID) + .arg(vendor->getVendorId()) + .arg(util::encode::encodeHex(std::vector<uint8_t>(txt.begin(), + txt.end()))); + return; + } + case SUBOPTION: + // Extract sub-options + isc_throw(EvalTypeError, "Field None is not valid for vendor-class"); + return; + case EXISTS: + // We already passed all the checks: the option is there and has specified + // enterprise-id. + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_EXISTS) + .arg(vendor->getVendorId()) + .arg("true"); + values.push("true"); + return; + case DATA: + { + size_t max = vendor->getTuplesNum(); + if (index_ + 1 > max) { + // The index specified is out of bounds, e.g. there are only + // 2 tuples and index specified is 5. + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_DATA_NOT_FOUND) + .arg(index_) + .arg(vendor->getVendorId()) + .arg(max) + .arg(""); + values.push(""); + return; + } + + OpaqueDataTuple tuple = vendor->getTuple(index_); + OpaqueDataTuple::Buffer buf = tuple.getData(); + string txt(buf.begin(), buf.end()); + + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_VENDOR_CLASS_DATA) + .arg(index_) + .arg(max) + .arg(txt); + + values.push(txt); + return; + } + default: + isc_throw(EvalTypeError, "Invalid field specified." << field_); + } +} + +TokenInteger::TokenInteger(const uint32_t value) + : TokenString(EvalContext::fromUint32(value)), int_value_(value) { +} + +OptionPtr +TokenSubOption::getSubOption(const OptionPtr& parent) { + if (!parent) { + return (OptionPtr()); + } + return (parent->getOption(sub_option_code_)); +} + +void +TokenSubOption::evaluate(Pkt& pkt, ValueStack& values) { + OptionPtr parent = getOption(pkt); + std::string txt; + isc::log::MessageID msgid = EVAL_DEBUG_SUB_OPTION; + if (!parent) { + // There's no parent option, notify that. + msgid = EVAL_DEBUG_SUB_OPTION_NO_OPTION; + if (representation_type_ == EXISTS) { + txt = "false"; + } + } else { + OptionPtr sub = getSubOption(parent); + if (!sub) { + // Failed to find the sub-option + if (representation_type_ == EXISTS) { + txt = "false"; + } + } else { + if (representation_type_ == TEXTUAL) { + txt = sub->toString(); + } else if (representation_type_ == HEXADECIMAL) { + std::vector<uint8_t> binary = sub->toBinary(); + txt.resize(binary.size()); + if (!binary.empty()) { + memmove(&txt[0], &binary[0], binary.size()); + } + } else { + txt = "true"; + } + } + } + + // Push value of the sub-option or empty string if there was no + // such parent option in the packet or sub-option in the parent. + values.push(txt); + + // Log what we pushed, both exists and textual are simple text + // and can be output directly. We also include the code numbers + // of the requested parent option and sub-option. + if (representation_type_ == HEXADECIMAL) { + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, msgid) + .arg(option_code_) + .arg(sub_option_code_) + .arg(toHex(txt)); + } else { + LOG_DEBUG(eval_logger, EVAL_DBG_STACK, msgid) + .arg(option_code_) + .arg(sub_option_code_) + .arg('\'' + txt + '\''); + } +} diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h new file mode 100644 index 0000000..83833af --- /dev/null +++ b/src/lib/eval/token.h @@ -0,0 +1,1300 @@ +// Copyright (C) 2015-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef TOKEN_H +#define TOKEN_H + +#include <exceptions/exceptions.h> +#include <dhcp/pkt.h> +#include <stack> + +namespace isc { +namespace dhcp { + +class Token; + +/// @brief Pointer to a single Token +typedef boost::shared_ptr<Token> TokenPtr; + +/// This is a structure that holds an expression converted to RPN +/// +/// For example expression: option[123].text == 'foo' will be converted to: +/// [0] = option[123].text (TokenOption object) +/// [1] = 'foo' (TokenString object) +/// [2] = == operator (TokenEqual object) +typedef std::vector<TokenPtr> Expression; + +typedef boost::shared_ptr<Expression> ExpressionPtr; + +/// Evaluated values are stored as a stack of strings +typedef std::stack<std::string> ValueStack; + +/// @brief EvalBadStack is thrown when more or less parameters are on the +/// stack than expected. +class EvalBadStack : public Exception { +public: + EvalBadStack(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief EvalTypeError is thrown when a value on the stack has a content +/// with an unexpected type. +class EvalTypeError : public Exception { +public: + EvalTypeError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + + +/// @brief Base class for all tokens +/// +/// It provides an interface for all tokens and storage for string representation +/// (all tokens evaluate to string). +/// +/// This class represents a single token. Examples of a token are: +/// - "foo" (a constant string) +/// - option[123].text (a token that extracts textual value of option 123) +/// - == (an operator that compares two other tokens) +/// - substring(a,b,c) (an operator that takes three arguments: a string, +/// first character and length) +class Token { +public: + + /// @brief This is a generic method for evaluating a packet. + /// + /// We need to pass the packet being evaluated and possibly previously + /// evaluated values. Specific implementations may ignore the packet altogether + /// and just put their own value on the stack (constant tokens), look at the + /// packet and put some data extracted from it on the stack (option tokens), + /// or pop arguments from the stack and put back the result (operators). + /// + /// The parameters passed will be: + /// + /// @param pkt - packet being classified + /// @param values - stack of values with previously evaluated tokens + virtual void evaluate(Pkt& pkt, ValueStack& values) = 0; + + /// @brief Virtual destructor + virtual ~Token() {} + + /// @brief Coverts a (string) value to a boolean + /// + /// Only "true" and "false" are expected. + /// + /// @param value the (string) value + /// @return the boolean represented by the value + /// @throw EvalTypeError when the value is not either "true" or "false". + static inline bool toBool(std::string value) { + if (value == "true") { + return (true); + } else if (value == "false") { + return (false); + } else { + isc_throw(EvalTypeError, "Incorrect boolean. Expected exactly " + "\"false\" or \"true\", got \"" << value << "\""); + } + } +}; + +/// The order where Token subtypes are declared should be: +/// - literal terminals +/// - option & co +/// - pkt field & co +/// - == +/// - substring & co +/// - not, and, or + +/// @brief Token representing a constant string +/// +/// This token holds value of a constant string, e.g. it represents +/// "MSFT" in expression option[vendor-class].text == "MSFT" +class TokenString : public Token { +public: + /// Value is set during token construction. + /// + /// @param str constant string to be represented. + TokenString(const std::string& str) + : value_(str){ + } + + /// @brief Token evaluation (puts value of the constant string on the stack) + /// + /// @param pkt (ignored) + /// @param values (represented string will be pushed here) + void evaluate(Pkt& pkt, ValueStack& values); + +protected: + std::string value_; ///< Constant value +}; + +/// @brief Token representing a constant string in hexadecimal format +/// +/// This token holds value of a constant string giving in an hexadecimal +/// format, for instance 0x666f6f is "foo" +class TokenHexString : public Token { +public: + /// Value is set during token construction. + /// + /// @param str constant string to be represented + /// (must be "0x" or "0X" followed by a string of hexadecimal digits + /// or decoding will fail) + TokenHexString(const std::string& str); + + /// @brief Token evaluation (puts value of the constant string on + /// the stack after decoding or an empty string if decoding fails + /// (note it should not if the parser is correct) + /// + /// @param pkt (ignored) + /// @param values (represented string will be pushed here) + void evaluate(Pkt& pkt, ValueStack& values); + +protected: + std::string value_; ///< Constant value +}; + +/// @brief Token representing an unsigned 32 bit integer +/// +/// For performance reasons, the constant integer value is converted to a string +/// just once (in the constructor). Afterwards, this effectively works as a constant +/// 4 byte long string. Hence this class is derived from TokenString and +/// does not even need its own evaluate() method. +class TokenInteger : public TokenString { +public: + /// @brief Integer value set during construction. + /// + /// The value is converted to string and stored in value_ provided by the + /// base class. + /// + /// @param value integer value to be stored. + TokenInteger(const uint32_t value); + + /// @brief Returns integer value + /// + /// Used in tests only. + /// + /// @return integer value + uint32_t getInteger() const { + return (int_value_); + } + +protected: + uint32_t int_value_; ///< value as integer (stored for testing only) +}; + +/// @brief Token representing an IP address as a constant string +/// +/// This token holds the value of an IP address as a constant string, +/// for instance 10.0.0.1 is 0x10000001 +class TokenIpAddress : public Token { +public: + /// Value is set during token construction. + /// + /// @param addr IP address to be represented as a constant string + TokenIpAddress(const std::string& addr); + + /// @brief Token evaluation (puts value of the constant string on + /// the stack after decoding) + /// + /// @param pkt (ignored) + /// @param values (represented IP address will be pushed here) + void evaluate(Pkt& pkt, ValueStack& values); + +protected: + ///< Constant value (empty string if the IP address cannot be converted) + std::string value_; +}; + +/// @brief Token representing an IP address as a string +/// +/// This token holds the value of an IP address as a string, for instance +/// 10.0.0.1 is '10.0.0.1' +class TokenIpAddressToText : public Token { +public: + /// @brief Constructor (does nothing) + TokenIpAddressToText() {} + + /// @brief Token evaluation (puts value of the string on the stack after + /// decoding) + /// + /// @param pkt (ignored) + /// @param values (represented IP address as a string will be pushed here) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token representing an 8 bit integer as a string +/// +/// This token holds the value of an 8 bit integer as a string, for instance +/// 0xff is '-1' +class TokenInt8ToText : public Token { +public: + /// @brief Constructor (does nothing) + TokenInt8ToText() {} + + /// @brief Token evaluation (puts value of the string on the stack after + /// decoding) + /// + /// @param pkt (ignored) + /// @param values (represented 8 bit integer as a string will be pushed + /// here) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token representing a 16 bit integer as a string +/// +/// This token holds the value of a 16 bit integer as a string, for instance +/// 0xffff is '-1' +class TokenInt16ToText : public Token { +public: + /// @brief Constructor (does nothing) + TokenInt16ToText() {} + + /// @brief Token evaluation (puts value of the string on the stack after + /// decoding) + /// + /// @param pkt (ignored) + /// @param values (represented 16 bit integer as a string will be pushed + /// here) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token representing a 32 bit integer as a string +/// +/// This token holds the value of a 32 bit integer as a string, for instance +/// 0xffffffff is '-1' +class TokenInt32ToText : public Token { +public: + /// @brief Constructor (does nothing) + TokenInt32ToText() {} + + /// @brief Token evaluation (puts value of the string on the stack after + /// decoding) + /// + /// @param pkt (ignored) + /// @param values (represented 32 bit integer as a string will be pushed + /// here) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token representing an 8 bit unsigned integer as a string +/// +/// This token holds the value of an 8 bit unsigned integer as a string, for +/// instance 0xff is '255' +class TokenUInt8ToText : public Token { +public: + /// @brief Constructor (does nothing) + TokenUInt8ToText() {} + + /// @brief Token evaluation (puts value of the string on the stack after + /// decoding) + /// + /// @param pkt (ignored) + /// @param values (represented 8 bit unsigned integer as a string will be + /// pushed here) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token representing a 16 bit unsigned integer as a string +/// +/// This token holds the value of a 16 bit unsigned integer as a string, for +/// instance 0xffff is '65535' +class TokenUInt16ToText : public Token { +public: + /// @brief Constructor (does nothing) + TokenUInt16ToText() {} + + /// @brief Token evaluation (puts value of the string on the stack after + /// decoding) + /// + /// @param pkt (ignored) + /// @param values (represented 16 bit unsigned integer as a string will be + /// pushed here) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token representing a 32 bit unsigned integer as a string +/// +/// This token holds the value of a 32 bit unsigned integer as a string, for +/// instance 0xffffffff is '4294967295' +class TokenUInt32ToText : public Token { +public: + /// @brief Constructor (does nothing) + TokenUInt32ToText() {} + + /// @brief Token evaluation (puts value of the string on the stack after + /// decoding) + /// + /// @param pkt (ignored) + /// @param values (represented 32 bit unsigned integer as a string will be + /// pushed here) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that represents a value of an option +/// +/// This represents a reference to a given option, e.g. in the expression +/// option[vendor-class].text == "MSFT", it represents +/// option[vendor-class].text +/// +/// During the evaluation it tries to extract the value of the specified +/// option. If the option is not found, an empty string ("") is returned +/// (or "false" when the representation is EXISTS). +class TokenOption : public Token { +public: + + /// @brief Token representation type. + /// + /// There are many possible ways in which option can be presented. + /// Currently the textual, hexadecimal and exists representations are + /// supported. The type of representation is specified in the + /// constructor and it affects the value generated by the + /// @c TokenOption::evaluate function. + enum RepresentationType { + TEXTUAL, + HEXADECIMAL, + EXISTS + }; + + /// @brief Constructor that takes an option code as a parameter + /// + /// Note: There is no constructor that takes option_name, as it would + /// introduce complex dependency of the libkea-eval on libdhcpsrv. + /// + /// @param option_code code of the option to be represented. + /// @param rep_type Token representation type. + TokenOption(const uint16_t option_code, const RepresentationType& rep_type) + : option_code_(option_code), representation_type_(rep_type) {} + + /// @brief Evaluates the values of the option + /// + /// This token represents a value of the option, so this method attempts + /// to extract the option from the packet and put its value on the stack. + /// If the option is not there, an empty string ("") is put on the stack. + /// + /// @param pkt specified option will be extracted from this packet (if present) + /// @param values value of the option will be pushed here (or "") + void evaluate(Pkt& pkt, ValueStack& values); + + /// @brief Returns option-code + /// + /// This method is used in testing to determine if the parser had + /// instantiated TokenOption with correct parameters. + /// + /// @return option-code of the option this token expects to extract. + uint16_t getCode() const { + return (option_code_); + } + + /// @brief Returns representation-type + /// + /// This method is used in testing to determine if the parser had + /// instantiated TokenOption with correct parameters. + /// + /// @return representation-type of the option this token expects to use. + RepresentationType getRepresentation() const { + return (representation_type_); + } + +protected: + /// @brief Attempts to retrieve an option + /// + /// For this class it simply attempts to retrieve the option from the packet, + /// but there may be derived classes that would attempt to extract it from + /// other places (e.g. relay option, or as a suboption of other specific option). + /// + /// + /// @param pkt the option will be retrieved from here + /// @return option instance (or NULL if not found) + virtual OptionPtr getOption(Pkt& pkt); + + /// @brief Auxiliary method that puts string representing a failure + /// + /// Depending on the representation type, this is either "" or "false". + /// + /// @param values a string representing failure will be pushed here. + /// @return value pushed + virtual std::string pushFailure(ValueStack& values); + + uint16_t option_code_; ///< Code of the option to be extracted + RepresentationType representation_type_; ///< Representation type. +}; + +/// @brief Represents a sub-option inserted by the DHCPv4 relay. +/// +/// DHCPv4 relays insert sub-options in option 82. This token attempts to extract +/// such sub-options. Note in DHCPv6 it is radically different (possibly +/// many encapsulation levels), thus there are separate classes for v4 and v6. +/// +/// This token can represent the following expressions: +/// relay[13].text - Textual representation of sub-option 13 in RAI (option 82) +/// relay[13].hex - Binary representation of sub-option 13 in RAI (option 82) +/// relay[vendor-class].text - Text representation of sub-option X in RAI (option 82) +/// relay[vendor-class].hex - Binary representation of sub-option X in RAI (option 82) +class TokenRelay4Option : public TokenOption { +public: + + /// @brief Constructor for extracting sub-option from RAI (option 82) + /// + /// @param option_code code of the requested sub-option + /// @param rep_type code representation (currently .hex and .text are supported) + TokenRelay4Option(const uint16_t option_code, + const RepresentationType& rep_type); + +protected: + /// @brief Attempts to obtain specified sub-option of option 82 from the packet + /// @param pkt DHCPv4 packet (that hopefully contains option 82) + /// @return found sub-option from option 82 + virtual OptionPtr getOption(Pkt& pkt); +}; + +/// @brief Token that represents a value of an option within a DHCPv6 relay +/// encapsulation +/// +/// This represents a reference to a given option similar to TokenOption +/// but from within the information from a relay. In the expression +/// relay6[nest-level].option[option-code], nest-level indicates which +/// of the relays to examine and option-code which option to extract. +/// +/// During the evaluation it tries to extract the value of the specified +/// option from the requested relay block. If the relay block doesn't +/// exist or the option is not found an empty string ("") is returned +/// (or "false" when the representation is EXISTS). +/// +/// The nesting level can go from 0 (closest to the server) to 31, +/// or from -1 (closest to the client) to -32 +class TokenRelay6Option : public TokenOption { +public: + /// @brief Constructor that takes a nesting level and an option + /// code as parameters. + /// + /// @param nest_level the nesting for which relay to examine. + /// @param option_code code of the option. + /// @param rep_type Token representation type. + TokenRelay6Option(const int8_t nest_level, const uint16_t option_code, + const RepresentationType& rep_type) + : TokenOption(option_code, rep_type), nest_level_(nest_level) {} + + /// @brief Returns nest-level + /// + /// This method is used in testing to determine if the parser has + /// instantiated TokenRelay6Option with correct parameters. + /// + /// @return nest-level of the relay block this token expects to use + /// for extraction. + int8_t getNest() const { + return (nest_level_); + } + +protected: + /// @brief Attempts to obtain specified option from the specified relay block + /// @param pkt DHCPv6 packet that hopefully contains the proper relay block + /// @return option instance if available + virtual OptionPtr getOption(Pkt& pkt); + + int8_t nest_level_; ///< nesting level of the relay block to use +}; + +/// @brief Token that represents meta data of a DHCP packet. +/// +/// For example in the expression pkt.iface == 'eth0' +/// this token represents the pkt.iface expression. +/// +/// Currently supported meta datas are: +/// - iface (incoming/outgoinginterface name) +/// - src (source IP address, 4 or 16 octets) +/// - dst (destination IP address, 4 or 16 octets) +/// - len (length field in the UDP header, padded to 4 octets) +class TokenPkt : public Token { +public: + + /// @brief enum value that determines the field. + enum MetadataType { + IFACE, ///< interface name (string) + SRC, ///< source (IP address) + DST, ///< destination (IP address) + LEN ///< length (4 octets) + }; + + /// @brief Constructor (does nothing) + TokenPkt(const MetadataType type) + : type_(type) {} + + /// @brief Gets a value from the specified packet. + /// + /// Evaluation uses metadata available in the packet. It does not + /// require any values to be present on the stack. + /// + /// @param pkt - metadata will be extracted from here + /// @param values - stack of values (1 result will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); + + /// @brief Returns metadata type + /// + /// This method is used only in tests. + /// @return type of the metadata. + MetadataType getType() { + return (type_); + } + +private: + /// @brief Specifies metadata of the DHCP packet + MetadataType type_; +}; + +/// @brief Token that represents fields of a DHCPv4 packet. +/// +/// For example in the expression pkt4.chaddr == 0x0102030405 +/// this token represents the pkt4.chaddr expression. +/// +/// Currently supported fields are: +/// - chaddr (client hardware address, hlen [0..16] octets) +/// - giaddr (relay agent IP address, 4 octets) +/// - ciaddr (client IP address, 4 octets) +/// - yiaddr ('your' (client) IP address, 4 octets) +/// - siaddr (next server IP address, 4 octets) +/// - hlen (hardware address length, padded to 4 octets) +/// - htype (hardware address type, padded to 4 octets) +class TokenPkt4 : public Token { +public: + + /// @brief enum value that determines the field. + enum FieldType { + CHADDR, ///< chaddr field (up to 16 bytes link-layer address) + GIADDR, ///< giaddr (IPv4 address) + CIADDR, ///< ciaddr (IPv4 address) + YIADDR, ///< yiaddr (IPv4 address) + SIADDR, ///< siaddr (IPv4 address) + HLEN, ///< hlen (hardware address length) + HTYPE, ///< htype (hardware address type) + MSGTYPE, ///< message type (not really a field, content of option 53) + TRANSID, ///< transaction-id (xid) + }; + + /// @brief Constructor (does nothing) + TokenPkt4(const FieldType type) + : type_(type) {} + + /// @brief Gets a value from the specified packet. + /// + /// Evaluation uses fields available in the packet. It does not require + /// any values to be present on the stack. + /// + /// @throw EvalTypeError when called for DHCPv6 packet + /// + /// @param pkt - fields will be extracted from here + /// @param values - stack of values (1 result will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); + + /// @brief Returns field type + /// + /// This method is used only in tests. + /// @return type of the field. + FieldType getType() { + return (type_); + } + +private: + /// @brief Specifies field of the DHCPv4 packet + FieldType type_; +}; + +/// @brief Token that represents fields of DHCPv6 packet. +/// +/// For example in the expression pkt6.msgtype == 1 +/// this token represents the message type of the DHCPv6 packet. +/// The integer values are placed on the value stack as 4 byte +/// strings. +/// +/// Currently supported fields are: +/// - msgtype +/// - transid +class TokenPkt6 : public Token { +public: + /// @brief enum value that determines the field. + enum FieldType { + MSGTYPE, ///< msg type + TRANSID ///< transaction id (integer but manipulated as a string) + }; + + /// @brief Constructor (does nothing) + TokenPkt6(const FieldType type) + : type_(type) {} + + /// @brief Gets a value of the specified packet. + /// + /// The evaluation uses fields that are available in the packet. It does not + /// require any values to be present on the stack. + /// + /// @throw EvalTypeError when called for a DHCPv4 packet + /// + /// @param pkt - packet from which to extract the fields + /// @param values - stack of values, 1 result will be pushed + void evaluate(Pkt& pkt, ValueStack& values); + + /// @brief Returns field type + /// + /// This method is used only in tests. + /// @return type of the field. + FieldType getType() { + return (type_); + } + +private: + /// @brief Specifies field of the DHCPv6 packet to get + FieldType type_; +}; + +/// @brief Token that represents a value of a field within a DHCPv6 relay +/// encapsulation +/// +/// This represents a reference to a field with a given DHCPv6 relay encapsulation. +/// In the expression relay6[nest-level].field-name, nest-level indicates which of +/// the relays to examine and field-name which of the fields to extract. +/// +/// During the evaluation it tries to extract the value of the specified +/// field from the requested relay block. If the relay block doesn't exist +/// an empty string ("") is returned. If the relay block does exist the field +/// is always returned as a 16 byte IPv6 address. As the relay may not have +/// set the field it may be 0s. +/// +/// The nesting level can go from 0 (closest to the server) to 31, +/// or from -1 (closest to the client) to -32 +class TokenRelay6Field : public Token { +public: + + /// @brief enum value that determines the field. + enum FieldType { + PEERADDR, ///< Peer address field (IPv6 address) + LINKADDR ///< Link address field (IPv6 address) + }; + + /// @brief Constructor that takes a nesting level and field type + /// as parameters. + /// + /// @param nest_level the nesting level for which relay to examine. + /// @param type which field to extract. + TokenRelay6Field(const int8_t nest_level, const FieldType type) + : nest_level_(nest_level), type_(type) {} + + /// @brief Extracts the specified field from the requested relay + /// + /// Evaluation uses fields available in the packet. It does not require + /// any values to be present on the stack. + /// + /// @param pkt fields will be extracted from here + /// @param values - stack of values (1 result will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); + + /// @brief Returns nest-level + /// + /// This method is used in testing to determine if the parser has + /// instantiated TokenRelay6Field with correct parameters. + /// + /// @return nest-level of the relay block this token expects to use + /// for extraction. + int8_t getNest() const { + return (nest_level_); + } + + /// @brief Returns field type + /// + /// This method is used only in testing to determine if the parser has + /// instantiated TokenRelay6Field with correct parameters. + /// + /// @return type of the field. + FieldType getType() { + return (type_); + } + +protected: + /// @brief Specifies field of the DHCPv6 relay option to get + int8_t nest_level_; ///< nesting level of the relay block to use + FieldType type_; ///< field to get +}; + +/// @brief Token that represents equality operator (compares two other tokens) +/// +/// For example in the expression option[vendor-class].text == "MSFT" +/// this token represents the equal (==) sign. +class TokenEqual : public Token { +public: + /// @brief Constructor (does nothing) + TokenEqual() {} + + /// @brief Compare two values. + /// + /// Evaluation does not use packet information, but rather consumes the last + /// two parameters. It does a simple string comparison and sets the value to + /// either "true" or "false". It requires at least two parameters to be + /// present on stack. + /// + /// @throw EvalBadStack if there are less than 2 values on stack + /// + /// @param pkt (unused) + /// @param values - stack of values (2 arguments will be popped, 1 result + /// will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that represents the substring operator (returns a portion +/// of the supplied string) +/// +/// This token represents substring(str, start, len) An operator that takes three +/// arguments: a string, the first character and the length. +class TokenSubstring : public Token { +public: + /// @brief Constructor (does nothing) + TokenSubstring() {} + + /// @brief Extract a substring from a string + /// + /// Evaluation does not use packet information. It requires at least + /// three values to be present on the stack. It will consume the top + /// three values on the stack as parameters and push the resulting substring + /// onto the stack. From the top it expects the values on the stack as: + /// - len + /// - start + /// - str + /// + /// str is the string to extract a substring from. If it is empty, an empty + /// string is pushed onto the value stack. + /// + /// start is the position from which the code starts extracting the substring. + /// 0 is the first character and a negative number starts from the end, with + /// -1 being the last character. If the starting point is outside of the + /// original string an empty string is pushed onto the value stack. + /// + /// length is the number of characters from the string to extract. + /// "all" means all remaining characters from start to the end of string. + /// A negative number means to go from start towards the beginning of + /// the string, but doesn't include start. + /// If length is longer than the remaining portion of string + /// then the entire remaining portion is placed on the value stack. + /// + /// The following examples all use the base string "foobar", the first number + /// is the starting position and the second is the length. Note that + /// a negative length only selects which characters to extract it does not + /// indicate an attempt to reverse the string. + /// - 0, all => "foobar" + /// - 0, 6 => "foobar" + /// - 0, 4 => "foob" + /// - 2, all => "obar" + /// - 2, 6 => "obar" + /// - -1, all => "r" + /// - -1, -4 => "ooba" + /// + /// @throw EvalBadStack if there are less than 3 values on stack + /// @throw EvalTypeError if start is not a number or length a number or + /// the special value "all". + /// + /// @param pkt (unused) + /// @param values - stack of values (3 arguments will be popped, 1 result + /// will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +class TokenSplit : public Token { +public: + /// @brief Constructor (does nothing) + TokenSplit() {} + + /// @brief Extract a field from a delimited string + /// + /// Evaluation does not use packet information. It requires at least + /// three values to be present on the stack. It will consume the top + /// three values on the stack as parameters and push the resulting substring + /// onto the stack. From the top it expects the values on the stack as: + /// - field + /// - delims + /// - str + /// + /// str is the string to split. If it is empty, an empty + /// string is pushed onto the value stack. + /// delims is string of character delimiters by which to split str. If it is + /// empty the entire value of str will be pushed on onto the value stack. + /// field is the field number (starting at 1) of the desired field. If it is + /// out of range an empty string is pushed on the value stack. + /// + /// The following examples all use the base string "one.two..four" and shows + /// the value returned for a given field: + /// ``` + /// field => value + /// -------------- + /// - 0 => "" + /// - 1 => "one" + /// - 2 => "two" + /// - 3 => "" + /// - 4 => "four" + /// - 5 => "" + /// ``` + /// + /// @throw EvalBadStack if there are less than 3 values on stack + /// @throw EvalTypeError if field is not a number + /// + /// @param pkt (unused) + /// @param values - stack of values (3 arguments will be popped, 1 result + /// will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that represents concat operator (concatenates two other tokens) +/// +/// For example in the sub-expression "concat('foo','bar')" the result +/// of the evaluation is "foobar" +/// For user convenience the "'foo' + 'bar'" alternative does the same. +class TokenConcat : public Token { +public: + /// @brief Constructor (does nothing) + TokenConcat() {} + + /// @brief Concatenate two values. + /// + /// Evaluation does not use packet information, but rather consumes the last + /// two parameters. It does a simple string concatenation. It requires + /// at least two parameters to be present on stack. + /// + /// @throw EvalBadStack if there are less than 2 values on stack + /// + /// @param pkt (unused) + /// @param values - stack of values (2 arguments will be popped, 1 result + /// will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that represents an alternative +/// +/// For example in the sub-expression "ifelse(cond, iftrue, iffalse)" +/// the boolean "cond" expression is evaluated, if it is true then +/// the "iftrue" value is returned else the "iffalse" value is returned. +/// Please note that "iftrue" and "iffalse" must be plain string (vs. boolean) +/// expressions and they are always evaluated. If you want a similar +/// operator on boolean expressions it can be built from "and", "or" and +/// "not" boolean operators. +class TokenIfElse : public Token { +public: + /// @brief Constructor (does nothing) + TokenIfElse() { } + + /// @brief Alternative. + /// + /// Evaluation does not use packet information, but rather consumes the + /// last three results. It does a simple string comparison on the + /// condition (third value on the stack) which is required to be + /// either "true" or "false", and leaves the second and first + /// value if the condition is "true" or "false". + /// + /// @throw EvalBadStack if there are less than 3 values on stack + /// @throw EvalTypeError if the third value (the condition) is not + /// either "true" or "false" + /// + /// @param pkt (unused) + /// @param values - stack of values (two items are removed) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that converts to hexadecimal string +/// +/// For example in the sub-expression "hexstring(pkt4.mac, ':')" +/// the binary MAC address is converted to its usual hexadecimal +/// representation as a list of (6) pairs of hexadecimal digits +/// separated by colons (':'). +/// Please note the token is named TokenToHexString when the syntax +/// use the hexstring name without a leading "to". +class TokenToHexString : public Token { +public: + /// @brief Constructor (does nothing) + TokenToHexString() { } + + /// @brief Convert a binary value to its hexadecimal string representation + /// + /// Evaluation does not use packet information. It requires at least + /// two values to be present on the stack. It will consume the top + /// two values on the stack as parameters and push the resulting + /// hexadecimal string onto the stack. + /// From the top it expects the values on the stack as: + /// - separator + /// - binary + /// + /// binary is the binary value (note it can be any value, i.e. + /// it is not checked to really be not printable). + /// separator is literal for instance '-' or ':'. The empty separator + /// means no separator. + /// + /// The following example use a binary MAC address 06:ce:8f:55:b3:33: + /// - mac, '-' => "06-ce-8f-55-b3-33" + /// + /// @throw EvalBadStack if there are less than 2 values on stack + /// + /// @param pkt (unused) + /// @param values - stack of values (2 arguments will be popped, 1 result + /// will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that represents logical negation operator +/// +/// For example in the expression "not(option[vendor-class].text == 'MSF')" +/// this token represents the leading "not" +class TokenNot : public Token { +public: + /// @brief Constructor (does nothing) + TokenNot() {} + + /// @brief Logical negation. + /// + /// Evaluation does not use packet information, but rather consumes the last + /// result. It does a simple string comparison and sets the value to + /// either "true" or "false". It requires at least one value to be + /// present on stack and to be either "true" or "false". + /// + /// @throw EvalBadStack if there are less than 1 value on stack + /// @throw EvalTypeError if the top value on the stack is not either + /// "true" or "false" + /// + /// @param pkt (unused) + /// @param values - stack of values (logical top value negated) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that represents logical and operator +/// +/// For example "option[10].exists and option[11].exists" +class TokenAnd : public Token { +public: + /// @brief Constructor (does nothing) + TokenAnd() {} + + /// @brief Logical and. + /// + /// Evaluation does not use packet information, but rather consumes the last + /// two parameters. It returns "true" if and only if both are "true". + /// It requires at least two logical (i.e., "true" or "false') values + /// present on stack. + /// + /// @throw EvalBadStack if there are less than 2 values on stack + /// @throw EvalTypeError if one of the 2 values on stack is not + /// "true" or "false" + /// + /// @param pkt (unused) + /// @param values - stack of values (2 arguments will be popped, 1 result + /// will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that represents logical or operator +/// +/// For example "option[10].exists or option[11].exists" +class TokenOr : public Token { +public: + /// @brief Constructor (does nothing) + TokenOr() {} + + /// @brief Logical or. + /// + /// Evaluation does not use packet information, but rather consumes the last + /// two parameters. It returns "false" if and only if both are "false". + /// It requires at least two logical (i.e., "true" or "false') values + /// present on stack. + /// + /// @throw EvalBadStack if there are less than 2 values on stack + /// @throw EvalTypeError if one of the 2 values on stack is not + /// "true" or "false" + /// + /// @param pkt (unused) + /// @param values - stack of values (2 arguments will be popped, 1 result + /// will be pushed) + void evaluate(Pkt& pkt, ValueStack& values); +}; + +/// @brief Token that represents client class membership +/// +/// For example "not member('foo')" is the complement of class foo +class TokenMember : public Token { +public: + /// @brief Constructor + /// + /// @param client_class client class name + TokenMember(const std::string& client_class) + : client_class_(client_class){ + } + + /// @brief Token evaluation (check if client_class_ was added to + /// packet client classes) + /// + /// @param pkt the class name will be check from this packet's client classes + /// @param values true (if found) or false (if not found) will be pushed here + void evaluate(Pkt& pkt, ValueStack& values); + + /// @brief Returns client class name + /// + /// This method is used in testing to determine if the parser had + /// instantiated TokenMember with correct parameters. + /// + /// @return client class name the token expects to check membership. + const ClientClass& getClientClass() const { + return (client_class_); + } + +protected: + /// @brief The client class name + ClientClass client_class_; +}; + +/// @brief Token that represents vendor options in DHCPv4 and DHCPv6. +/// +/// It covers vendor independent vendor information option (125, DHCPv4) +/// and vendor option (17, DHCPv6). Since both of those options may have +/// suboptions, this class is derived from TokenOption and leverages its +/// ability to operate on sub-options. It also adds additional capabilities. +/// In particular, it allows retrieving enterprise-id. +/// +/// It can represent the following expressions: +/// vendor[4491].exists - if vendor option with enterprise-id = 4491 exists +/// vendor[*].exists - if any vendor option exists +/// vendor.enterprise - returns enterprise-id from vendor option +/// vendor[4491].option[1].exists - check if suboption 1 exists for vendor 4491 +/// vendor[4491].option[1].hex - return content of suboption 1 for vendor 4491 +class TokenVendor : public TokenOption { +public: + + /// @brief Specifies a field of the vendor option + enum FieldType { + SUBOPTION, ///< If this token fetches a suboption, not a field. + ENTERPRISE_ID, ///< enterprise-id field (vendor-info, vendor-class) + EXISTS, ///< vendor[123].exists + DATA ///< data chunk, used in derived vendor-class only + }; + + /// @brief Constructor used for accessing a field + /// + /// @param u universe (either V4 or V6) + /// @param vendor_id specifies enterprise-id (0 means any) + /// @param field specifies which field should be returned + TokenVendor(Option::Universe u, uint32_t vendor_id, FieldType field); + + + /// @brief Constructor used for accessing an option + /// + /// This constructor is used for accessing suboptions. In general + /// option_code is mandatory, except when repr is EXISTS. For + /// option_code = 0 and repr = EXISTS, the token will return true + /// if the whole option exists, not suboptions. + /// + /// @param u universe (either V4 or V6) + /// @param vendor_id specifies enterprise-id (0 means any) + /// @param repr representation type (hex or exists) + /// @param option_code sub-option code + TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr, + uint16_t option_code = 0); + + /// @brief Returns enterprise-id + /// + /// Used in tests only. + /// + /// @return enterprise-id + uint32_t getVendorId() const; + + /// @brief Returns field. + /// + /// Used in tests only. + /// + /// @return field type. + FieldType getField() const; + + /// @brief This is a method for evaluating a packet. + /// + /// Depending on the value of vendor_id, field type, representation and + /// option code, it will attempt to return specified characteristic of the + /// vendor option + /// + /// If vendor-id is specified, check only option with that particular + /// enterprise-id. If vendor-id is 0, check any vendor option, regardless + /// of its enterprise-id value. + /// + /// If FieldType is NONE, get specified suboption represented by option_code + /// and represent it as specified by repr. + /// + /// If FieldType is ENTERPRISE_ID, return value of the enterprise-id field + /// or "" if there's no vendor option. + /// + /// @throw EvalTypeError for any other FieldType values. + /// + /// The parameters passed are: + /// + /// @param pkt - vendor options will be searched for here. + /// @param values - the evaluated value will be pushed here. + virtual void evaluate(Pkt& pkt, ValueStack& values); + +protected: + /// @brief Attempts to get a suboption. + /// + /// This method overrides behavior of TokenOption method. It attempts to retrieve + /// the sub-option of the vendor option. Using derived method allows usage of + /// TokenOption routines. + /// + /// @param pkt vendor option will be searched here. + /// @return suboption of the vendor option (if exists) + virtual OptionPtr getOption(Pkt& pkt); + + /// @brief Universe (V4 or V6) + /// + /// We need to remember it, because depending on the universe, the code needs + /// to retrieve either option 125 (DHCPv4) or 17 (DHCPv6). + Option::Universe universe_; + + /// @brief Enterprise-id value + /// + /// Yeah, I know it technically should be called enterprise-id, but that's + /// too long and everyone calls it vendor-id. + uint32_t vendor_id_; + + /// @brief Specifies which field should be accessed. + FieldType field_; +}; + +/// @brief Token that represents vendor class options in DHCPv4 and DHCPv6. +/// +/// It covers vendor independent vendor information option (124, DHCPv4) +/// and vendor option (16, DHCPv6). Contrary to vendor options, vendor class +/// options don't have suboptions, but have data chunks (tuples) instead. +/// Therefore they're not referenced by option codes, but by indexes. +/// The first data chunk is data[0], the second is data[1] etc. +/// +/// This class is derived from OptionVendor to take advantage of the +/// enterprise handling field and field type. +/// +/// It can represent the following expressions: +/// vendor-class[4491].exists +/// vendor-class[*].exists +/// vendor-class[*].enterprise +/// vendor-class[4491].data - content of the opaque-data of the first tuple +/// vendor-class[4491].data[3] - content of the opaque-data of the 4th tuple +class TokenVendorClass : public TokenVendor { +public: + + /// @brief This constructor is used to access fields. + /// + /// @param u universe (V4 or V6) + /// @param vendor_id value of enterprise-id field (0 means any) + /// @param repr representation type (EXISTS or HEX) + TokenVendorClass(Option::Universe u, uint32_t vendor_id, RepresentationType repr); + + /// @brief This constructor is used to access data chunks. + /// + /// @param u universe (V4 or V6) + /// @param vendor_id value of enterprise-id field (0 means any) + /// @param field type of the field (usually DATA or ENTERPRISE) + /// @param index specifies which data chunk to retrieve + TokenVendorClass(Option::Universe u, uint32_t vendor_id, FieldType field, + uint16_t index = 0); + + /// @brief Returns data index. + /// + /// Used in testing. + /// @return data index (specifies which data chunk to retrieve) + uint16_t getDataIndex() const; + +protected: + + /// @brief This is a method for evaluating a packet. + /// + /// Depending on the value of vendor_id, field type, representation and + /// option code, it will attempt to return specified characteristic of the + /// vendor option + /// + /// If vendor-id is specified, check only option with that particular + /// enterprise-id. If vendor-id is 0, check any vendor option, regardless + /// of its enterprise-id value. + /// + /// If FieldType is ENTERPRISE_ID, return value of the enterprise-id field + /// or "" if there's no vendor option. + /// + /// If FieldType is DATA, get specified data chunk represented by index_. + /// + /// If FieldType is EXISTS, return true if vendor-id matches. + /// + /// @throw EvalTypeError for any other FieldType values. + /// + /// The parameters passed are: + /// + /// @param pkt - vendor options will be searched for here. + /// @param values - the evaluated value will be pushed here. + void evaluate(Pkt& pkt, ValueStack& values); + + /// @brief Data chunk index. + uint16_t index_; +}; + +/// @brief Token that represents sub-options in DHCPv4 and DHCPv6. +/// +/// It covers any options which encapsulate sub-options, for instance +/// dhcp-agent-options (82, DHCPv4) or rsoo (66, DHCPv6). +/// This class is derived from TokenOption and leverages its ability +/// to operate on sub-options. It also adds additional capabilities. +/// +/// Note: @c TokenSubOption virtually derives @c TokenOption because both +/// classes are inherited together in more complex classes in other parts of +/// the code. This makes the base class @c TokenOption to exist only once in +/// such complex classes. +/// +/// It can represent the following expressions: +/// option[149].exists - check if option 149 exists +/// option[149].option[1].exists - check if suboption 1 exists in the option 149 +/// option[149].option[1].hex - return content of suboption 1 for option 149 +class TokenSubOption : public virtual TokenOption { +public: + + /// @note Does not define its own representation type: + /// simply use the @c TokenOption::RepresentationType + + /// @brief Constructor that takes an option and sub-option codes as parameter + /// + /// Note: There is no constructor that takes names. + /// + /// @param option_code code of the parent option. + /// @param sub_option_code code of the sub-option to be represented. + /// @param rep_type Token representation type. + TokenSubOption(const uint16_t option_code, + const uint16_t sub_option_code, + const RepresentationType& rep_type) + : TokenOption(option_code, rep_type), sub_option_code_(sub_option_code) {} + + /// @brief This is a method for evaluating a packet. + /// + /// This token represents a value of the sub-option, so this method + /// attempts to extract the parent option from the packet and when + /// it succeeds to extract the sub-option from the option and + /// its value on the stack. + /// If the parent option or the sub-option is not there, an empty + /// string ("") is put on the stack. + /// + /// @param pkt specified parent option will be extracted from this packet + /// @param values value of the sub-option will be pushed here (or "") + virtual void evaluate(Pkt& pkt, ValueStack& values); + + /// @brief Returns sub-option-code + /// + /// This method is used in testing to determine if the parser had + /// instantiated TokenSubOption with correct parameters. + /// + /// @return option-code of the sub-option this token expects to extract. + uint16_t getSubCode() const { + return (sub_option_code_); + } + +protected: + /// @brief Attempts to retrieve a sub-option. + /// + /// @param parent the sub-option will be retrieved from here + /// @return sub-option instance (or NULL if not found) + virtual OptionPtr getSubOption(const OptionPtr& parent); + + uint16_t sub_option_code_; ///< Code of the sub-option to be extracted +}; + +} // end of isc::dhcp namespace +} // end of isc namespace + +#endif |