diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 14:53:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 14:53:22 +0000 |
commit | 52c021ee0b0c6ad2128ed550c694aad0d11d4c3f (patch) | |
tree | 83cf8627b94336cf4bee7479b9749263bbfd3a06 /src/lib/dns | |
parent | Initial commit. (diff) | |
download | isc-kea-upstream.tar.xz isc-kea-upstream.zip |
Adding upstream version 2.5.7.upstream/2.5.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dns')
317 files changed, 43761 insertions, 0 deletions
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am new file mode 100644 index 0000000..eefa489 --- /dev/null +++ b/src/lib/dns/Makefile.am @@ -0,0 +1,95 @@ +AUTOMAKE_OPTIONS = subdir-objects + +SUBDIRS = . tests + +AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +CLEANFILES = *.gcno *.gcda + +BUILT_SOURCES = rrclass.h rrtype.h rrparamregistry.cc +BUILT_SOURCES += rdataclass.h rdataclass.cc + +lib_LTLIBRARIES = libkea-dns++.la + +libkea_dns___la_LDFLAGS = -no-undefined -version-info 55:0:0 +libkea_dns___la_LDFLAGS += $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) + +libkea_dns___la_SOURCES = +libkea_dns___la_SOURCES += edns.h edns.cc +libkea_dns___la_SOURCES += exceptions.h exceptions.cc +libkea_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc +libkea_dns___la_SOURCES += labelsequence.h labelsequence.cc +libkea_dns___la_SOURCES += master_lexer.h master_lexer.cc +libkea_dns___la_SOURCES += master_lexer_state.h +libkea_dns___la_SOURCES += master_loader.h master_loader.cc +libkea_dns___la_SOURCES += message.h message.cc +libkea_dns___la_SOURCES += messagerenderer.h messagerenderer.cc +libkea_dns___la_SOURCES += name.h name.cc +libkea_dns___la_SOURCES += name_internal.h +libkea_dns___la_SOURCES += opcode.h opcode.cc +libkea_dns___la_SOURCES += rcode.h rcode.cc +libkea_dns___la_SOURCES += rdata.h rdata.cc +libkea_dns___la_SOURCES += rrclass.cc +libkea_dns___la_SOURCES += rrparamregistry.h +libkea_dns___la_SOURCES += rrset.h rrset.cc +libkea_dns___la_SOURCES += rrttl.h rrttl.cc +libkea_dns___la_SOURCES += rrtype.cc +libkea_dns___la_SOURCES += question.h question.cc +libkea_dns___la_SOURCES += serial.h serial.cc +libkea_dns___la_SOURCES += time_utils.h time_utils.cc +libkea_dns___la_SOURCES += tsig.h tsig.cc +libkea_dns___la_SOURCES += tsigerror.h tsigerror.cc +libkea_dns___la_SOURCES += tsigkey.h tsigkey.cc +libkea_dns___la_SOURCES += tsigrecord.h tsigrecord.cc +libkea_dns___la_SOURCES += master_loader_callbacks.h +libkea_dns___la_SOURCES += master_loader.h +libkea_dns___la_SOURCES += txt_like.h +libkea_dns___la_SOURCES += char_string.h char_string.cc + + +libkea_dns___la_CPPFLAGS = $(AM_CPPFLAGS) +libkea_dns___la_LIBADD = $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +libkea_dns___la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la +libkea_dns___la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +libkea_dns___la_LIBADD += $(CRYPTO_LIBS) + +# The following files used to be generated, but they are now part of the git tree: +# rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc +libkea_dns___la_SOURCES += rdataclass.h rrclass.h rrtype.h +libkea_dns___la_SOURCES += rdataclass.cc rrparamregistry.cc + +libdns___includedir = $(pkgincludedir)/dns +libdns___include_HEADERS = \ + char_string.h \ + edns.h \ + exceptions.h \ + labelsequence.h \ + master_lexer.h \ + master_lexer_inputsource.h \ + master_lexer_state.h \ + master_loader.h \ + master_loader_callbacks.h \ + message.h \ + messagerenderer.h \ + name.h \ + opcode.h \ + question.h \ + rcode.h \ + rdata.h \ + rdataclass.h \ + rrclass.h \ + rrparamregistry.h \ + rrset.h \ + rrttl.h \ + rrtype.h \ + serial.h \ + time_utils.h \ + tsig.h \ + tsigerror.h \ + tsigkey.h \ + tsigrecord.h \ + txt_like.h +# Purposely not installing these headers: +# name_internal.h: used only internally, and not actually DNS specific diff --git a/src/lib/dns/Makefile.in b/src/lib/dns/Makefile.in new file mode 100644 index 0000000..89ab0a1 --- /dev/null +++ b/src/lib/dns/Makefile.in @@ -0,0 +1,1304 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 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/dns +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp14.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 $(libdns___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)$(libdns___includedir)" +LTLIBRARIES = $(lib_LTLIBRARIES) +am__DEPENDENCIES_1 = +libkea_dns___la_DEPENDENCIES = \ + $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(am__DEPENDENCIES_1) +am_libkea_dns___la_OBJECTS = libkea_dns___la-edns.lo \ + libkea_dns___la-exceptions.lo \ + libkea_dns___la-master_lexer_inputsource.lo \ + libkea_dns___la-labelsequence.lo \ + libkea_dns___la-master_lexer.lo \ + libkea_dns___la-master_loader.lo libkea_dns___la-message.lo \ + libkea_dns___la-messagerenderer.lo libkea_dns___la-name.lo \ + libkea_dns___la-opcode.lo libkea_dns___la-rcode.lo \ + libkea_dns___la-rdata.lo libkea_dns___la-rrclass.lo \ + libkea_dns___la-rrset.lo libkea_dns___la-rrttl.lo \ + libkea_dns___la-rrtype.lo libkea_dns___la-question.lo \ + libkea_dns___la-serial.lo libkea_dns___la-time_utils.lo \ + libkea_dns___la-tsig.lo libkea_dns___la-tsigerror.lo \ + libkea_dns___la-tsigkey.lo libkea_dns___la-tsigrecord.lo \ + libkea_dns___la-char_string.lo libkea_dns___la-rdataclass.lo \ + libkea_dns___la-rrparamregistry.lo +libkea_dns___la_OBJECTS = $(am_libkea_dns___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_dns___la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(libkea_dns___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_dns___la-char_string.Plo \ + ./$(DEPDIR)/libkea_dns___la-edns.Plo \ + ./$(DEPDIR)/libkea_dns___la-exceptions.Plo \ + ./$(DEPDIR)/libkea_dns___la-labelsequence.Plo \ + ./$(DEPDIR)/libkea_dns___la-master_lexer.Plo \ + ./$(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo \ + ./$(DEPDIR)/libkea_dns___la-master_loader.Plo \ + ./$(DEPDIR)/libkea_dns___la-message.Plo \ + ./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo \ + ./$(DEPDIR)/libkea_dns___la-name.Plo \ + ./$(DEPDIR)/libkea_dns___la-opcode.Plo \ + ./$(DEPDIR)/libkea_dns___la-question.Plo \ + ./$(DEPDIR)/libkea_dns___la-rcode.Plo \ + ./$(DEPDIR)/libkea_dns___la-rdata.Plo \ + ./$(DEPDIR)/libkea_dns___la-rdataclass.Plo \ + ./$(DEPDIR)/libkea_dns___la-rrclass.Plo \ + ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo \ + ./$(DEPDIR)/libkea_dns___la-rrset.Plo \ + ./$(DEPDIR)/libkea_dns___la-rrttl.Plo \ + ./$(DEPDIR)/libkea_dns___la-rrtype.Plo \ + ./$(DEPDIR)/libkea_dns___la-serial.Plo \ + ./$(DEPDIR)/libkea_dns___la-time_utils.Plo \ + ./$(DEPDIR)/libkea_dns___la-tsig.Plo \ + ./$(DEPDIR)/libkea_dns___la-tsigerror.Plo \ + ./$(DEPDIR)/libkea_dns___la-tsigkey.Plo \ + ./$(DEPDIR)/libkea_dns___la-tsigrecord.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_dns___la_SOURCES) +DIST_SOURCES = $(libkea_dns___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 = $(libdns___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)` +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@ +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@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +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@ +DPKG = @DPKG@ +DPKGQUERY = @DPKGQUERY@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +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@ +XMLLINT = @XMLLINT@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AUTOMAKE_OPTIONS = subdir-objects +SUBDIRS = . tests +AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib \ + $(BOOST_INCLUDES) +AM_CXXFLAGS = $(KEA_CXXFLAGS) +CLEANFILES = *.gcno *.gcda +BUILT_SOURCES = rrclass.h rrtype.h rrparamregistry.cc rdataclass.h \ + rdataclass.cc +lib_LTLIBRARIES = libkea-dns++.la +libkea_dns___la_LDFLAGS = -no-undefined -version-info 55:0:0 \ + $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) + +# The following files used to be generated, but they are now part of the git tree: +# rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc +libkea_dns___la_SOURCES = edns.h edns.cc exceptions.h exceptions.cc \ + master_lexer_inputsource.h master_lexer_inputsource.cc \ + labelsequence.h labelsequence.cc master_lexer.h \ + master_lexer.cc master_lexer_state.h master_loader.h \ + master_loader.cc message.h message.cc messagerenderer.h \ + messagerenderer.cc name.h name.cc name_internal.h opcode.h \ + opcode.cc rcode.h rcode.cc rdata.h rdata.cc rrclass.cc \ + rrparamregistry.h rrset.h rrset.cc rrttl.h rrttl.cc rrtype.cc \ + question.h question.cc serial.h serial.cc time_utils.h \ + time_utils.cc tsig.h tsig.cc tsigerror.h tsigerror.cc \ + tsigkey.h tsigkey.cc tsigrecord.h tsigrecord.cc \ + master_loader_callbacks.h master_loader.h txt_like.h \ + char_string.h char_string.cc rdataclass.h rrclass.h rrtype.h \ + rdataclass.cc rrparamregistry.cc +libkea_dns___la_CPPFLAGS = $(AM_CPPFLAGS) +libkea_dns___la_LIBADD = \ + $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ + $(top_builddir)/src/lib/util/libkea-util.la \ + $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ + $(CRYPTO_LIBS) +libdns___includedir = $(pkgincludedir)/dns +libdns___include_HEADERS = \ + char_string.h \ + edns.h \ + exceptions.h \ + labelsequence.h \ + master_lexer.h \ + master_lexer_inputsource.h \ + master_lexer_state.h \ + master_loader.h \ + master_loader_callbacks.h \ + message.h \ + messagerenderer.h \ + name.h \ + opcode.h \ + question.h \ + rcode.h \ + rdata.h \ + rdataclass.h \ + rrclass.h \ + rrparamregistry.h \ + rrset.h \ + rrttl.h \ + rrtype.h \ + serial.h \ + time_utils.h \ + tsig.h \ + tsigerror.h \ + tsigkey.h \ + tsigrecord.h \ + txt_like.h + +all: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) 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/dns/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/dns/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-dns++.la: $(libkea_dns___la_OBJECTS) $(libkea_dns___la_DEPENDENCIES) $(EXTRA_libkea_dns___la_DEPENDENCIES) + $(AM_V_CXXLD)$(libkea_dns___la_LINK) -rpath $(libdir) $(libkea_dns___la_OBJECTS) $(libkea_dns___la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-char_string.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-edns.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-exceptions.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-labelsequence.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-master_lexer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-master_loader.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-message.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-name.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-opcode.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-question.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rcode.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rdata.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rdataclass.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrclass.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrset.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrttl.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-rrtype.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-serial.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-time_utils.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-tsig.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-tsigerror.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-tsigkey.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libkea_dns___la-tsigrecord.Plo@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCXX_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +libkea_dns___la-edns.lo: edns.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-edns.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-edns.Tpo -c -o libkea_dns___la-edns.lo `test -f 'edns.cc' || echo '$(srcdir)/'`edns.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-edns.Tpo $(DEPDIR)/libkea_dns___la-edns.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='edns.cc' object='libkea_dns___la-edns.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-edns.lo `test -f 'edns.cc' || echo '$(srcdir)/'`edns.cc + +libkea_dns___la-exceptions.lo: exceptions.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-exceptions.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-exceptions.Tpo -c -o libkea_dns___la-exceptions.lo `test -f 'exceptions.cc' || echo '$(srcdir)/'`exceptions.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-exceptions.Tpo $(DEPDIR)/libkea_dns___la-exceptions.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='exceptions.cc' object='libkea_dns___la-exceptions.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-exceptions.lo `test -f 'exceptions.cc' || echo '$(srcdir)/'`exceptions.cc + +libkea_dns___la-master_lexer_inputsource.lo: master_lexer_inputsource.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-master_lexer_inputsource.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Tpo -c -o libkea_dns___la-master_lexer_inputsource.lo `test -f 'master_lexer_inputsource.cc' || echo '$(srcdir)/'`master_lexer_inputsource.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Tpo $(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_inputsource.cc' object='libkea_dns___la-master_lexer_inputsource.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-master_lexer_inputsource.lo `test -f 'master_lexer_inputsource.cc' || echo '$(srcdir)/'`master_lexer_inputsource.cc + +libkea_dns___la-labelsequence.lo: labelsequence.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-labelsequence.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-labelsequence.Tpo -c -o libkea_dns___la-labelsequence.lo `test -f 'labelsequence.cc' || echo '$(srcdir)/'`labelsequence.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-labelsequence.Tpo $(DEPDIR)/libkea_dns___la-labelsequence.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labelsequence.cc' object='libkea_dns___la-labelsequence.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-labelsequence.lo `test -f 'labelsequence.cc' || echo '$(srcdir)/'`labelsequence.cc + +libkea_dns___la-master_lexer.lo: master_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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-master_lexer.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-master_lexer.Tpo -c -o libkea_dns___la-master_lexer.lo `test -f 'master_lexer.cc' || echo '$(srcdir)/'`master_lexer.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-master_lexer.Tpo $(DEPDIR)/libkea_dns___la-master_lexer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer.cc' object='libkea_dns___la-master_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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-master_lexer.lo `test -f 'master_lexer.cc' || echo '$(srcdir)/'`master_lexer.cc + +libkea_dns___la-master_loader.lo: master_loader.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-master_loader.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-master_loader.Tpo -c -o libkea_dns___la-master_loader.lo `test -f 'master_loader.cc' || echo '$(srcdir)/'`master_loader.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-master_loader.Tpo $(DEPDIR)/libkea_dns___la-master_loader.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader.cc' object='libkea_dns___la-master_loader.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-master_loader.lo `test -f 'master_loader.cc' || echo '$(srcdir)/'`master_loader.cc + +libkea_dns___la-message.lo: message.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-message.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-message.Tpo -c -o libkea_dns___la-message.lo `test -f 'message.cc' || echo '$(srcdir)/'`message.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-message.Tpo $(DEPDIR)/libkea_dns___la-message.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message.cc' object='libkea_dns___la-message.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-message.lo `test -f 'message.cc' || echo '$(srcdir)/'`message.cc + +libkea_dns___la-messagerenderer.lo: messagerenderer.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-messagerenderer.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-messagerenderer.Tpo -c -o libkea_dns___la-messagerenderer.lo `test -f 'messagerenderer.cc' || echo '$(srcdir)/'`messagerenderer.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-messagerenderer.Tpo $(DEPDIR)/libkea_dns___la-messagerenderer.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='messagerenderer.cc' object='libkea_dns___la-messagerenderer.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-messagerenderer.lo `test -f 'messagerenderer.cc' || echo '$(srcdir)/'`messagerenderer.cc + +libkea_dns___la-name.lo: name.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-name.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-name.Tpo -c -o libkea_dns___la-name.lo `test -f 'name.cc' || echo '$(srcdir)/'`name.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-name.Tpo $(DEPDIR)/libkea_dns___la-name.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='name.cc' object='libkea_dns___la-name.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-name.lo `test -f 'name.cc' || echo '$(srcdir)/'`name.cc + +libkea_dns___la-opcode.lo: opcode.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-opcode.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-opcode.Tpo -c -o libkea_dns___la-opcode.lo `test -f 'opcode.cc' || echo '$(srcdir)/'`opcode.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-opcode.Tpo $(DEPDIR)/libkea_dns___la-opcode.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opcode.cc' object='libkea_dns___la-opcode.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-opcode.lo `test -f 'opcode.cc' || echo '$(srcdir)/'`opcode.cc + +libkea_dns___la-rcode.lo: rcode.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rcode.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rcode.Tpo -c -o libkea_dns___la-rcode.lo `test -f 'rcode.cc' || echo '$(srcdir)/'`rcode.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rcode.Tpo $(DEPDIR)/libkea_dns___la-rcode.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rcode.cc' object='libkea_dns___la-rcode.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rcode.lo `test -f 'rcode.cc' || echo '$(srcdir)/'`rcode.cc + +libkea_dns___la-rdata.lo: rdata.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rdata.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rdata.Tpo -c -o libkea_dns___la-rdata.lo `test -f 'rdata.cc' || echo '$(srcdir)/'`rdata.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rdata.Tpo $(DEPDIR)/libkea_dns___la-rdata.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata.cc' object='libkea_dns___la-rdata.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rdata.lo `test -f 'rdata.cc' || echo '$(srcdir)/'`rdata.cc + +libkea_dns___la-rrclass.lo: rrclass.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrclass.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrclass.Tpo -c -o libkea_dns___la-rrclass.lo `test -f 'rrclass.cc' || echo '$(srcdir)/'`rrclass.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrclass.Tpo $(DEPDIR)/libkea_dns___la-rrclass.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrclass.cc' object='libkea_dns___la-rrclass.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrclass.lo `test -f 'rrclass.cc' || echo '$(srcdir)/'`rrclass.cc + +libkea_dns___la-rrset.lo: rrset.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrset.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrset.Tpo -c -o libkea_dns___la-rrset.lo `test -f 'rrset.cc' || echo '$(srcdir)/'`rrset.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrset.Tpo $(DEPDIR)/libkea_dns___la-rrset.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset.cc' object='libkea_dns___la-rrset.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrset.lo `test -f 'rrset.cc' || echo '$(srcdir)/'`rrset.cc + +libkea_dns___la-rrttl.lo: rrttl.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrttl.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrttl.Tpo -c -o libkea_dns___la-rrttl.lo `test -f 'rrttl.cc' || echo '$(srcdir)/'`rrttl.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrttl.Tpo $(DEPDIR)/libkea_dns___la-rrttl.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrttl.cc' object='libkea_dns___la-rrttl.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrttl.lo `test -f 'rrttl.cc' || echo '$(srcdir)/'`rrttl.cc + +libkea_dns___la-rrtype.lo: rrtype.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrtype.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrtype.Tpo -c -o libkea_dns___la-rrtype.lo `test -f 'rrtype.cc' || echo '$(srcdir)/'`rrtype.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrtype.Tpo $(DEPDIR)/libkea_dns___la-rrtype.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrtype.cc' object='libkea_dns___la-rrtype.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrtype.lo `test -f 'rrtype.cc' || echo '$(srcdir)/'`rrtype.cc + +libkea_dns___la-question.lo: question.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-question.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-question.Tpo -c -o libkea_dns___la-question.lo `test -f 'question.cc' || echo '$(srcdir)/'`question.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-question.Tpo $(DEPDIR)/libkea_dns___la-question.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='question.cc' object='libkea_dns___la-question.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-question.lo `test -f 'question.cc' || echo '$(srcdir)/'`question.cc + +libkea_dns___la-serial.lo: serial.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-serial.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-serial.Tpo -c -o libkea_dns___la-serial.lo `test -f 'serial.cc' || echo '$(srcdir)/'`serial.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-serial.Tpo $(DEPDIR)/libkea_dns___la-serial.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='serial.cc' object='libkea_dns___la-serial.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-serial.lo `test -f 'serial.cc' || echo '$(srcdir)/'`serial.cc + +libkea_dns___la-time_utils.lo: time_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-time_utils.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-time_utils.Tpo -c -o libkea_dns___la-time_utils.lo `test -f 'time_utils.cc' || echo '$(srcdir)/'`time_utils.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-time_utils.Tpo $(DEPDIR)/libkea_dns___la-time_utils.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utils.cc' object='libkea_dns___la-time_utils.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-time_utils.lo `test -f 'time_utils.cc' || echo '$(srcdir)/'`time_utils.cc + +libkea_dns___la-tsig.lo: tsig.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-tsig.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-tsig.Tpo -c -o libkea_dns___la-tsig.lo `test -f 'tsig.cc' || echo '$(srcdir)/'`tsig.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-tsig.Tpo $(DEPDIR)/libkea_dns___la-tsig.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsig.cc' object='libkea_dns___la-tsig.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-tsig.lo `test -f 'tsig.cc' || echo '$(srcdir)/'`tsig.cc + +libkea_dns___la-tsigerror.lo: tsigerror.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-tsigerror.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-tsigerror.Tpo -c -o libkea_dns___la-tsigerror.lo `test -f 'tsigerror.cc' || echo '$(srcdir)/'`tsigerror.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-tsigerror.Tpo $(DEPDIR)/libkea_dns___la-tsigerror.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigerror.cc' object='libkea_dns___la-tsigerror.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-tsigerror.lo `test -f 'tsigerror.cc' || echo '$(srcdir)/'`tsigerror.cc + +libkea_dns___la-tsigkey.lo: tsigkey.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-tsigkey.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-tsigkey.Tpo -c -o libkea_dns___la-tsigkey.lo `test -f 'tsigkey.cc' || echo '$(srcdir)/'`tsigkey.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-tsigkey.Tpo $(DEPDIR)/libkea_dns___la-tsigkey.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigkey.cc' object='libkea_dns___la-tsigkey.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-tsigkey.lo `test -f 'tsigkey.cc' || echo '$(srcdir)/'`tsigkey.cc + +libkea_dns___la-tsigrecord.lo: tsigrecord.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-tsigrecord.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-tsigrecord.Tpo -c -o libkea_dns___la-tsigrecord.lo `test -f 'tsigrecord.cc' || echo '$(srcdir)/'`tsigrecord.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-tsigrecord.Tpo $(DEPDIR)/libkea_dns___la-tsigrecord.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigrecord.cc' object='libkea_dns___la-tsigrecord.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-tsigrecord.lo `test -f 'tsigrecord.cc' || echo '$(srcdir)/'`tsigrecord.cc + +libkea_dns___la-char_string.lo: char_string.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-char_string.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-char_string.Tpo -c -o libkea_dns___la-char_string.lo `test -f 'char_string.cc' || echo '$(srcdir)/'`char_string.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-char_string.Tpo $(DEPDIR)/libkea_dns___la-char_string.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='char_string.cc' object='libkea_dns___la-char_string.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-char_string.lo `test -f 'char_string.cc' || echo '$(srcdir)/'`char_string.cc + +libkea_dns___la-rdataclass.lo: rdataclass.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rdataclass.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rdataclass.Tpo -c -o libkea_dns___la-rdataclass.lo `test -f 'rdataclass.cc' || echo '$(srcdir)/'`rdataclass.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rdataclass.Tpo $(DEPDIR)/libkea_dns___la-rdataclass.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdataclass.cc' object='libkea_dns___la-rdataclass.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rdataclass.lo `test -f 'rdataclass.cc' || echo '$(srcdir)/'`rdataclass.cc + +libkea_dns___la-rrparamregistry.lo: rrparamregistry.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libkea_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT libkea_dns___la-rrparamregistry.lo -MD -MP -MF $(DEPDIR)/libkea_dns___la-rrparamregistry.Tpo -c -o libkea_dns___la-rrparamregistry.lo `test -f 'rrparamregistry.cc' || echo '$(srcdir)/'`rrparamregistry.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libkea_dns___la-rrparamregistry.Tpo $(DEPDIR)/libkea_dns___la-rrparamregistry.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrparamregistry.cc' object='libkea_dns___la-rrparamregistry.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_dns___la_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o libkea_dns___la-rrparamregistry.lo `test -f 'rrparamregistry.cc' || echo '$(srcdir)/'`rrparamregistry.cc + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +install-libdns___includeHEADERS: $(libdns___include_HEADERS) + @$(NORMAL_INSTALL) + @list='$(libdns___include_HEADERS)'; test -n "$(libdns___includedir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libdns___includedir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libdns___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)$(libdns___includedir)'"; \ + $(INSTALL_HEADER) $$files "$(DESTDIR)$(libdns___includedir)" || exit $$?; \ + done + +uninstall-libdns___includeHEADERS: + @$(NORMAL_UNINSTALL) + @list='$(libdns___include_HEADERS)'; test -n "$(libdns___includedir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(libdns___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: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) check-recursive +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: installdirs-recursive +installdirs-am: + for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libdns___includedir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) install-recursive +install-exec: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) 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." + -test -z "$(BUILT_SOURCES)" || rm -f $(BUILT_SOURCES) +clean: clean-recursive + +clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/libkea_dns___la-char_string.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-edns.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-exceptions.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-labelsequence.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-master_lexer.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-master_loader.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-message.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-name.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-opcode.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-question.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rcode.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rdata.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rdataclass.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrclass.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrset.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrttl.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrtype.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-serial.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-time_utils.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-tsig.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-tsigerror.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-tsigkey.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-tsigrecord.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-libdns___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_dns___la-char_string.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-edns.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-exceptions.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-labelsequence.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-master_lexer.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-master_lexer_inputsource.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-master_loader.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-message.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-messagerenderer.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-name.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-opcode.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-question.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rcode.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rdata.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rdataclass.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrclass.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrparamregistry.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrset.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrttl.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-rrtype.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-serial.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-time_utils.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-tsig.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-tsigerror.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-tsigkey.Plo + -rm -f ./$(DEPDIR)/libkea_dns___la-tsigrecord.Plo + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-recursive + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-recursive + +pdf-am: + +ps: ps-recursive + +ps-am: + +uninstall-am: uninstall-libLTLIBRARIES \ + uninstall-libdns___includeHEADERS + +.MAKE: $(am__recursive_targets) all check install install-am \ + install-exec 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-libdns___includeHEADERS install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-libLTLIBRARIES uninstall-libdns___includeHEADERS + +.PRECIOUS: Makefile + +# Purposely not installing these headers: +# name_internal.h: used only internally, and not actually DNS specific + +# 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/dns/char_string.cc b/src/lib/dns/char_string.cc new file mode 100644 index 0000000..25cb1f1 --- /dev/null +++ b/src/lib/dns/char_string.cc @@ -0,0 +1,264 @@ +// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> + +#include <dns/exceptions.h> +#include <dns/rdata.h> +#include <dns/master_lexer.h> +#include <dns/char_string.h> +#include <util/buffer.h> + +#include <boost/lexical_cast.hpp> + +#include <cctype> +#include <cstring> +#include <vector> + +#include <stdint.h> + +namespace isc { +namespace dns { +namespace rdata { +namespace generic { +namespace detail { + +namespace { +// Convert a DDD form to the corresponding integer +int +decimalToNumber(const char* s, const char* s_end) { + if (s_end - s < 3) { + isc_throw(InvalidRdataText, "Escaped digits too short"); + } + + const std::string num_str(s, s + 3); + try { + const int i = boost::lexical_cast<int>(num_str); + if (i > 255) { + isc_throw(InvalidRdataText, "Escaped digits too large: " + << num_str); + } + return (i); + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidRdataText, + "Invalid form for escaped digits: " << num_str); + } +} +} + +void +stringToCharString(const MasterToken::StringRegion& str_region, + CharString& result) { + // make a space for the 1-byte length field; filled in at the end + result.push_back(0); + + bool escape = false; + const char* s = str_region.beg; + const char* const s_end = str_region.beg + str_region.len; + + for (size_t n = str_region.len; n != 0; --n, ++s) { + int c = (*s & 0xff); + if (escape && std::isdigit(c) != 0) { + c = decimalToNumber(s, s_end); + // decimalToNumber() already throws if (s_end - s) is less + // than 3. 'n' is an unsigned type (size_t) and can underflow. + // 'n' and 's' are also updated by 1 in the for statement's + // expression, so we update them by 2 instead of 3 here. + n -= 2; + s += 2; + } else if (!escape && c == '\\') { + escape = true; + continue; + } + escape = false; + result.push_back(c); + } + if (escape) { // terminated by non-escaped '\' + isc_throw(InvalidRdataText, "character-string ends with '\\'"); + } + if (result.size() > MAX_CHARSTRING_LEN + 1) { // '+ 1' due to the len field + isc_throw(CharStringTooLong, "character-string is too long: " << + (result.size() - 1) << "(+1) characters"); + } + result[0] = result.size() - 1; +} + +void +stringToCharStringData(const MasterToken::StringRegion& str_region, + CharStringData& result) { + bool escape = false; + const char* s = str_region.beg; + const char* const s_end = str_region.beg + str_region.len; + + for (size_t n = str_region.len; n != 0; --n, ++s) { + int c = (*s & 0xff); + if (escape && std::isdigit(c) != 0) { + c = decimalToNumber(s, s_end); + // decimalToNumber() already throws if (s_end - s) is less + // than 3. 'n' is an unsigned type (size_t) and can underflow. + // 'n' and 's' are also updated by 1 in the for statement's + // expression, so we update them by 2 instead of 3 here. + n -= 2; + s += 2; + } else if (!escape && c == '\\') { + escape = true; + continue; + } + escape = false; + result.push_back(c); + } + if (escape) { // terminated by non-escaped '\' + isc_throw(InvalidRdataText, "character-string ends with '\\'"); + } +} + +std::string +charStringToString(const CharString& char_string) { + std::string s; + bool first = true; + for (auto const& it : char_string) { + if (first) { + first = false; + continue; + } + const uint8_t ch = it; + if ((ch < 0x20) || (ch >= 0x7f)) { + // convert to escaped \xxx (decimal) format + s.push_back('\\'); + s.push_back('0' + ((ch / 100) % 10)); + s.push_back('0' + ((ch / 10) % 10)); + s.push_back('0' + (ch % 10)); + continue; + } + if ((ch == '"') || (ch == ';') || (ch == '\\')) { + s.push_back('\\'); + } + s.push_back(ch); + } + + return (s); +} + +std::string +charStringDataToString(const CharStringData& char_string) { + std::string s; + for (auto const& it : char_string) { + const uint8_t ch = it; + if ((ch < 0x20) || (ch >= 0x7f)) { + // convert to escaped \xxx (decimal) format + s.push_back('\\'); + s.push_back('0' + ((ch / 100) % 10)); + s.push_back('0' + ((ch / 10) % 10)); + s.push_back('0' + (ch % 10)); + continue; + } + if ((ch == '"') || (ch == ';') || (ch == '\\')) { + s.push_back('\\'); + } + s.push_back(ch); + } + + return (s); +} + +int compareCharStrings(const detail::CharString& self, + const detail::CharString& other) { + if (self.size() == 0 && other.size() == 0) { + return (0); + } + if (self.size() == 0) { + return (-1); + } + if (other.size() == 0) { + return (1); + } + const size_t self_len = self[0]; + const size_t other_len = other[0]; + const size_t cmp_len = std::min(self_len, other_len); + if (cmp_len == 0) { + if (self_len < other_len) { + return (-1); + } else if (self_len > other_len) { + return (1); + } else { + return (0); + } + } + const int cmp = std::memcmp(&self[1], &other[1], cmp_len); + if (cmp < 0) { + return (-1); + } else if (cmp > 0) { + return (1); + } else if (self_len < other_len) { + return (-1); + } else if (self_len > other_len) { + return (1); + } else { + return (0); + } +} + +int compareCharStringDatas(const detail::CharStringData& self, + const detail::CharStringData& other) { + if (self.size() == 0 && other.size() == 0) { + return (0); + } + if (self.size() == 0) { + return (-1); + } + if (other.size() == 0) { + return (1); + } + const size_t self_len = self.size(); + const size_t other_len = other.size(); + const size_t cmp_len = std::min(self_len, other_len); + const int cmp = std::memcmp(&self[0], &other[0], cmp_len); + if (cmp < 0) { + return (-1); + } else if (cmp > 0) { + return (1); + } else if (self_len < other_len) { + return (-1); + } else if (self_len > other_len) { + return (1); + } else { + return (0); + } +} + +size_t +bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len, + CharString& target) { + if (rdata_len < 1 || buffer.getLength() - buffer.getPosition() < 1) { + isc_throw(isc::dns::DNSMessageFORMERR, + "insufficient data to read character-string length"); + } + const uint8_t len = buffer.readUint8(); + if (rdata_len < len + 1) { + isc_throw(isc::dns::DNSMessageFORMERR, + "character string length is too large: " << + static_cast<int>(len)); + } + if (buffer.getLength() - buffer.getPosition() < len) { + isc_throw(isc::dns::DNSMessageFORMERR, + "not enough data in buffer to read character-string of len" + << static_cast<int>(len)); + } + + target.resize(len + 1); + target[0] = len; + buffer.readData(&target[0] + 1, len); + + return (len + 1); +} + +} // end of detail +} // end of generic +} // end of rdata +} // end of dns +} // end of isc diff --git a/src/lib/dns/char_string.h b/src/lib/dns/char_string.h new file mode 100644 index 0000000..61c9925 --- /dev/null +++ b/src/lib/dns/char_string.h @@ -0,0 +1,136 @@ +// Copyright (C) 2012-2024 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 DNS_RDATA_CHARSTRING_H +#define DNS_RDATA_CHARSTRING_H + +#include <dns/master_lexer.h> + +#include <string> +#include <vector> +#include <algorithm> +#include <stdint.h> + +namespace isc { +namespace dns { +namespace rdata { +namespace generic { +namespace detail { + +/// \brief Type for DNS character string. +/// +/// A character string can contain any unsigned 8-bit value, so this cannot +/// be the bare char basis. +typedef std::vector<uint8_t> CharString; + +/// \brief Type for DNS character string without the length prefix. +typedef std::vector<uint8_t> CharStringData; + +/// \brief Convert a DNS character-string into corresponding binary data. +/// +/// This helper function takes a string object that is expected to be a +/// textual representation of a valid DNS character-string, and dumps +/// the corresponding binary sequence in the given placeholder (passed +/// via the \c result parameter). It handles escape notations of +/// character-strings with a backslash ('\'), and checks the length +/// restriction. +/// +/// \throw CharStringTooLong The resulting binary data are too large for a +/// valid character-string. +/// \throw InvalidRdataText Other syntax errors. +/// +/// \brief str_region A string that represents a character-string. +/// \brief result A placeholder vector where the resulting data are to be +/// stored. Expected to be empty, but it's not checked. +void stringToCharString(const MasterToken::StringRegion& str_region, + CharString& result); + +/// \brief Convert a DNS character-string into corresponding binary data. +/// +/// This method functions similar to \c stringToCharString() except it +/// does not include the 1-octet length prefix in the \c result, and the +/// result is not limited to MAX_CHARSTRING_LEN octets. +/// +/// \throw InvalidRdataText Upon syntax errors. +/// +/// \brief str_region A string that represents a character-string. +/// \brief result A placeholder vector where the resulting data are to be +/// stored. Expected to be empty, but it's not checked. +void stringToCharStringData(const MasterToken::StringRegion& str_region, + CharStringData& result); + +/// \brief Convert a CharString into a textual DNS character-string. +/// +/// This method converts a binary 8-bit representation of a DNS +/// character string into a textual string representation, escaping any +/// special characters in the process. For example, characters like +/// double-quotes, semi-colon and backspace are prefixed with backspace +/// character, and characters not in the printable range of [0x20, 0x7e] +/// (inclusive) are converted to the \\xxx 3-digit decimal +/// representation. +/// +/// \param char_string The \c CharString to convert. +/// \return A string representation of \c char_string. +std::string charStringToString(const CharString& char_string); + +/// \brief Convert a CharStringData into a textual DNS character-string. +/// +/// Reverse of \c stringToCharStringData(). See \c stringToCharString() +/// vs. \c stringToCharStringData(). +/// +/// \param char_string The \c CharStringData to convert. +/// \return A string representation of \c char_string. +std::string charStringDataToString(const CharStringData& char_string); + +/// \brief Compare two CharString objects +/// +/// \param self The CharString field to compare +/// \param other The CharString field to compare to +/// +/// \return -1 if \c self would be sorted before \c other +/// 1 if \c self would be sorted after \c other +/// 0 if \c self and \c other are equal +int compareCharStrings(const CharString& self, const CharString& other); + +/// \brief Compare two CharStringData objects +/// +/// \param self The CharStringData field to compare +/// \param other The CharStringData field to compare to +/// +/// \return -1 if \c self would be sorted before \c other +/// 1 if \c self would be sorted after \c other +/// 0 if \c self and \c other are equal +int compareCharStringDatas(const CharStringData& self, + const CharStringData& other); + +/// \brief Convert a buffer containing a character-string to CharString +/// +/// This method reads one character-string from the given buffer (in wire +/// format) and places the result in the given \c CharString object. +/// Since this is expected to be used in message parsing, the exception it +/// raises is of that type. +/// +/// On success, the buffer position is advanced to the end of the char-string, +/// and the number of bytes read is returned. +/// +/// \param buffer The buffer to read from. +/// \param rdata_len The total size of the rr's rdata currently being read +/// (used for integrity checks in the wire data) +/// \param target The \c CharString where the result will be stored. Any +/// existing data in the target will be overwritten. +/// \throw DNSMessageFORMERR If the available data is not enough to read +/// the character-string, or if the character-string length is out of bounds +/// \return The number of bytes read +size_t bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len, + CharString& target); + + +} // namespace detail +} // namespace generic +} // namespace rdata +} // namespace dns +} // namespace isc +#endif // DNS_RDATA_CHARSTRING_H diff --git a/src/lib/dns/edns.cc b/src/lib/dns/edns.cc new file mode 100644 index 0000000..6bb7604 --- /dev/null +++ b/src/lib/dns/edns.cc @@ -0,0 +1,169 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dns/edns.h> +#include <dns/exceptions.h> +#include <dns/message.h> +#include <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrttl.h> +#include <dns/rrtype.h> + +#include <stdint.h> +#include <boost/lexical_cast.hpp> + +using namespace isc::util; +using namespace isc::dns::rdata; + +using namespace std; +using boost::lexical_cast; + +namespace isc { +namespace dns { + +namespace { +// This diagram shows the wire-format representation of the TTL field of +// OPT RR and its relationship with implementation specific parameters. +// +// 0 7 15 31 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | EXTENDED-RCODE| VERSION |D| Z | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// <= VERSION_SHIFT (16 bits) +// <= EXTRCODE_SHIFT (24 bits) +//EXTFLAG_DO:0 0 0 ....................... 0 1 0 0 0 0.....................0 +//VER_MASK: 0 0 0 ........0 1 1 1 1 1 1 1 1 0 0 ..........................0 + +const unsigned int VERSION_SHIFT = 16; +const unsigned int EXTRCODE_SHIFT = 24; +const uint32_t VERSION_MASK = 0x00ff0000; +const uint32_t EXTFLAG_DO = 0x00008000; +} + +EDNS::EDNS(const uint8_t version) : + version_(version), + udp_size_(Message::DEFAULT_MAX_UDPSIZE), + dnssec_aware_(false) { + if (version_ > SUPPORTED_VERSION) { + isc_throw(isc::InvalidParameter, + "failed to construct EDNS: unsupported version: " << + static_cast<unsigned int>(version_)); + } +} + +EDNS::EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype, + const RRTTL& ttl, const Rdata&) : + version_((ttl.getValue() & VERSION_MASK) >> VERSION_SHIFT) { + if (rrtype != RRType::OPT()) { + isc_throw(isc::InvalidParameter, + "EDNS is being created with incompatible RR type: " + << rrtype); + } + + if (version_ > EDNS::SUPPORTED_VERSION) { + isc_throw(DNSMessageBADVERS, "unsupported EDNS version: " << + static_cast<unsigned int>(version_)); + } + + if (name != Name::ROOT_NAME()) { + isc_throw(DNSMessageFORMERR, "invalid owner name for EDNS OPT RR: " << + name); + } + + dnssec_aware_ = ((ttl.getValue() & EXTFLAG_DO) != 0); + udp_size_ = rrclass.getCode(); +} + +string +EDNS::toText() const { + string ret = "; EDNS: version: "; + + ret += lexical_cast<string>(static_cast<int>(getVersion())); + ret += ", flags:"; + if (getDNSSECAwareness()) { + ret += " do"; + } + ret += "; udp: " + lexical_cast<string>(getUDPSize()) + "\n"; + + return (ret); +} + +namespace { +/// Helper function to define unified implementation for the public versions +/// of toWire(). +template <typename Output> +uint32_t +toWireCommon(Output& output, const uint8_t version, + const uint16_t udp_size, const bool dnssec_aware, + const uint8_t extended_rcode) { + // Render EDNS OPT RR + uint32_t extrcode_flags = extended_rcode << EXTRCODE_SHIFT; + extrcode_flags |= (version << VERSION_SHIFT) & VERSION_MASK; + if (dnssec_aware) { + extrcode_flags |= EXTFLAG_DO; + } + + // Construct an RRset corresponding to the EDNS. + // We don't support any options for now, so the OPT RR can be empty. + RRsetPtr edns_rrset(new RRset(Name::ROOT_NAME(), RRClass(udp_size), + RRType::OPT(), RRTTL(extrcode_flags))); + edns_rrset->addRdata(ConstRdataPtr(new generic::OPT())); + + edns_rrset->toWire(output); + + return (1); +} +} + +uint32_t +EDNS::toWire(AbstractMessageRenderer& renderer, + const uint8_t extended_rcode) const { + // If adding the OPT RR would exceed the size limit, don't do it. + // 11 = len(".") + type(2byte) + class(2byte) + TTL(4byte) + RDLEN(2byte) + // (RDATA is empty in this simple implementation) + if (renderer.getLength() + 11 > renderer.getLengthLimit()) { + return (0); + } + + return (toWireCommon(renderer, version_, udp_size_, dnssec_aware_, + extended_rcode)); +} + +uint32_t +EDNS::toWire(isc::util::OutputBuffer& buffer, + const uint8_t extended_rcode) const { + return (toWireCommon(buffer, version_, udp_size_, dnssec_aware_, + extended_rcode)); +} + +EDNS* +createEDNSFromRR(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& ttl, + const Rdata& rdata, + uint8_t& extended_rcode) { + // Create a new EDNS object first for exception guarantee. + EDNS* edns = new EDNS(name, rrclass, rrtype, ttl, rdata); + + // At this point we can update extended_rcode safely. + extended_rcode = ttl.getValue() >> EXTRCODE_SHIFT; + + return (edns); +} + +ostream& +operator<<(std::ostream& os, const EDNS& edns) { + os << edns.toText(); + return (os); +} + +} // end of namespace dns +} // end of namespace isc diff --git a/src/lib/dns/edns.h b/src/lib/dns/edns.h new file mode 100644 index 0000000..b604a9b --- /dev/null +++ b/src/lib/dns/edns.h @@ -0,0 +1,431 @@ +// Copyright (C) 2010-2024 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 EDNS_H +#define EDNS_H + +#include <util/buffer.h> + +#include <stdint.h> + +#include <boost/shared_ptr.hpp> + +#include <ostream> + +#include <dns/rdata.h> + +namespace isc { +namespace dns { + +class EDNS; +class Name; +class AbstractMessageRenderer; +class RRClass; +class RRTTL; +class RRType; +class Rcode; + +/// \brief A pointer-like type pointing to an \c EDNS object. +typedef boost::shared_ptr<EDNS> EDNSPtr; + +/// \brief A pointer-like type pointing to an immutable \c EDNS object. +typedef boost::shared_ptr<const EDNS> ConstEDNSPtr; + +/// The \c EDNS class represents the %EDNS OPT RR defined in RFC2671. +/// +/// This class encapsulates various optional features of %EDNS such as +/// the UDP payload size or the DNSSEC DO bit, and provides interfaces +/// to manage these features. It is also responsible for conversion +/// to and from wire-format OPT RR. +/// One important exception is about the extended RCODE: +/// The \c EDNS class is only responsible for extracting the 8-bit part +/// of the 12-bit extended RCODE from the OPT RR's TTL field of an +/// incoming message, and for setting the 8-bit part into the OPT RR TTL +/// of an outgoing message. It's not supposed to know how to construct the +/// complete RCODE, much less maintain the RCODE in it. +/// It is the caller's responsibility (typically the \c Message class). +/// +/// When converting wire-format OPT RR into an \c EDNS object, it normalizes +/// the information, i.e., unknown flags will be ignored on construction. +/// +/// This class is also supposed to support %EDNS options such as NSID, +/// but the initial implementation does not include it. This is a near term +/// TODO item. +/// +/// <b>Notes to developers</b> +/// +/// The rest of the description is for developers who need to or want to +/// understand the design of this API. +/// +/// Representing %EDNS is tricky. An OPT RR is no different from other RRs +/// in terms of the wire format syntax, and in that sense we could use the +/// generic \c RRset class to represent an OPT RR (BIND 9 adopts this +/// approach). But the resulting interface would be inconvenient for +/// developers. For example, the developer would need to know that the +/// UDP size is encoded in the RR Class field. It's better to provide +/// a more abstract interface along with the special semantics of OPT RR. +/// +/// Another approach would be to realize each optional feature of EDNS +/// as an attribute of the DNS message. +/// NLnet Labs' ldns takes this approach. +/// This way an operation for specifying the UDP size would be written +/// like this: +/// \code message->setUDPSize(4096); \endcode +/// which should be more intuitive. +/// A drawback of this approach is that OPT RR is itself optional and the +/// separate parameters may not necessarily indicate whether to include an +/// OPT RR per se. +/// For example, consider what should be done with this code: +/// \code message->setUDPSize(512); \endcode +/// Since the payload size of 512 is the default, it may mean the OPT RR +/// should be skipped. But it might also mean the caller intentionally +/// (for some reason) wants to insert an OPT RR specifying the default UDP +/// size explicitly. +/// +/// So, we use a separate class that encapsulates the EDNS semantics and +/// knows the mapping between the semantics and the wire format representation. +/// This way the interface can be semantics-based and is intuitive: +/// \code edns->setUDPSize(4096); \endcode +/// while we can explicitly specify whether to include an OPT RR by setting +/// (or not setting) an \c EDNS object in a message: +/// \code message->setEDNS(edns); // unless we do this OPT RR is skipped +/// \endcode +/// +/// There is still a non trivial point: How to manage extended RCODEs. +/// An OPT RR encodes the upper 8 bits of extended 12-bit RCODE. +/// In general, it would be better to provide a unified interface to get +/// access to RCODEs whether or not they are traditional 4 bit codes or +/// extended ones that have non 0 upper bits. +/// However, since an OPT RR may not appear in a message the RCODE cannot be +/// maintained in the \c EDNS class. +/// But it would not be desirable to maintain the extended RCODEs completely +/// in the \c Message class, either, because we wanted to hide the mapping +/// between %EDNS semantics and its wire format representation within the +/// \c EDNS class; if we moved the responsibility about RCODEs to the +/// \c Message class, it would have to parse and render the upper 8 bits of +/// the RCODEs, dealing with wire representation of OPT RR. +/// This is suboptimal in the sense of encapsulation. +/// +/// As a compromise, our decision is to separate the knowledge about the +/// relationship with RCODE from the knowledge about the wire format as +/// noted in the beginning of this description. +/// +/// This decoupling is based on the observation that the extended RCODE +/// is a very special case where %EDNS only has partial information. +/// If a future version of the %EDNS protocol introduces further relationship +/// between the message and the %EDNS, we might reconsider the interface, +/// probably with higher abstraction. +class EDNS { +public: + /// + /// \name Constructors and Destructor + /// + /// We use the default copy constructor, default copy assignment operator, + /// and default destructors intentionally. + /// + /// Note about copyability: This version of this class is copyable, + /// but we may want to change it once we support EDNS options, when + /// we want to revise this class using the pimpl idiom. + /// But we should be careful about that: the python binding currently + /// assumes this class is copyable. + //@{ + /// Constructor with the EDNS version. + /// An application would use this constructor to specify EDNS parameters + /// and/or options for outgoing DNS messages. + /// + /// All other parameters than the version number will be initialized to + /// reasonable defaults. + /// Specifically, the UDP payload size is set to + /// \c Message::DEFAULT_MAX_UDPSIZE, and DNSSEC is assumed to be not + /// supported. + /// These parameters can be altered via setter methods of this class. + /// Note, however, that the version number cannot be changed once + /// constructed. + /// + /// The version number parameter can be omitted, in which case the highest + /// supported version in this implementation will be assumed. + /// When specified, if it is larger than the highest supported version, + /// an exception of class \c isc::InvalidParameter will be thrown. + /// + /// This constructor throws no other exception. + /// + /// \param version The version number of the EDNS to be constructed. + explicit EDNS(const uint8_t version = SUPPORTED_VERSION); + + /// \brief Constructor from resource record (RR) parameters. + /// + /// This constructor is intended to be used to construct an EDNS object + /// from an OPT RR contained in an incoming DNS message. + /// + /// Unlike many other constructors for this purpose, this constructor + /// does not take the bare wire-format %data in the form of an + /// \c InputBuffer object. This is because parsing incoming EDNS is + /// highly context dependent and it's not feasible to handle it in a + /// completely polymorphic way. For example, a DNS message parser would + /// have to check an OPT RR appears at most once in the message, and if + /// it appears it should be in the additional section. So, the parser + /// needs to have an explicit check to see if an RR is of type OPT, and + /// then (if other conditions are met) construct a corresponding \c EDNS + /// object. At that point the parser would have already converted the + /// wire %data into corresponding objects of \c Name, \c RRClass, + /// \c RRType, etc, and it makes more sense to pass them directly to the + /// constructor. + /// + /// In practice, top level applications rarely need to use this + /// constructor directly. It should normally suffice to have a higher + /// level class such as \c Message do that job. + /// + /// This constructor checks the passed parameters to see if they are + /// valid in terms of the EDNS protocol specification. + /// \c name must be the root name ("."); otherwise, an exception of + /// class \c DNSMessageFORMERR will be thrown. + /// \c rrtype must specify the OPT RR type; otherwise, an exception of + /// class \c isc::InvalidParameter will be thrown. + /// The ENDS version number is extracted from \c rrttl. If it is larger + /// than the higher supported version, an exception of class + /// \c DNSMessageBADVERS will be thrown. Note that this is different from + /// the case of the same error in the other constructor. + /// This is intentional, so that the application can transparently convert + /// the exception to a response RCODE according to the protocol + /// specification. + /// + /// This initial implementation does not support EDNS options at all, + /// and \c rdata is simply ignored. Future versions will support + /// options, and may throw exceptions while validating the given parameter. + /// + /// \b Note: since no other type than OPT for \c rrtype is allowed, this + /// parameter could actually have been omitted. But it is intentionally + /// included as a parameter so that invalid usage of the construction + /// can be detected. As noted above the caller should normally have + /// the corresponding \c RRType object at the time of call to this + /// constructor, so the overhead of having the additional parameter + /// should be marginal. + /// + /// \param name The owner name of the OPT RR. This must be the root name. + /// \param rrclass The RR class of the OPT RR. + /// \param rrtype This must specify the OPT RR type. + /// \param ttl The TTL of the OPT RR. + /// \param rdata The RDATA of the OPT RR. + EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype, + const RRTTL& ttl, const rdata::Rdata& rdata); + //@} + + /// + /// \name Getter and Setter Methods + /// + //@{ + /// \brief Returns the version of EDNS. + /// + /// This method never throws an exception. + uint8_t getVersion() const { return (version_); } + + /// \brief Returns the maximum payload size of UDP messages for the sender + /// of the message containing this \c EDNS. + /// + /// This method never throws an exception. + uint16_t getUDPSize() const { return (udp_size_); } + + /// \brief Specify the maximum payload size of UDP messages that use + /// this EDNS. + /// + /// Unless explicitly specified, \c DEFAULT_MAX_UDPSIZE will be assumed + /// for the maximum payload size, regardless of whether EDNS OPT RR is + /// included or not. This means if an application wants to send a message + /// with an EDNS OPT RR for specifying a larger UDP size, it must + /// explicitly specify the value using this method. + /// + /// This method never throws an exception. + /// + /// \param udp_size The maximum payload size of UDP messages for the sender + /// of the message containing this \c EDNS. + void setUDPSize(const uint16_t udp_size) { udp_size_ = udp_size; } + + /// \brief Returns whether the message sender is DNSSEC aware. + /// + /// This method never throws an exception. + /// + /// \return true if DNSSEC is supported; otherwise false. + bool getDNSSECAwareness() const { return (dnssec_aware_); } + + /// \brief Specifies whether the sender of the message containing this + /// \c EDNS is DNSSEC aware. + /// + /// If the parameter is true, a subsequent call to \c toWire() will + /// set the DNSSEC DO bit on for the corresponding OPT RR. + /// + /// This method never throws an exception. + /// + /// \param is_aware \c true if DNSSEC is supported; \c false otherwise. + void setDNSSECAwareness(const bool is_aware) { dnssec_aware_ = is_aware; } + //@} + + /// + /// \name Converter Methods + /// + //@{ + /// \brief Render the \c EDNS in the wire format. + /// + /// This method renders the \c EDNS object as a form of DNS OPT RR + /// via \c renderer, which encapsulates output buffer and other rendering + /// contexts. + /// Since the \c EDNS object does not maintain the extended RCODE + /// information, a separate parameter \c extended_rcode must be passed to + /// this method. + /// + /// If by adding the OPT RR the message size would exceed the limit + /// maintained in \c renderer, this method skips rendering the RR + /// and returns 0; otherwise it returns 1, which is the number of RR + /// rendered. + /// + /// In the current implementation the return value is either 0 or 1, but + /// the return type is <code>unsigned int</code> to be consistent with + /// \c RRset::toWire(). In any case the caller shouldn't assume these are + /// only possible return values from this method. + /// + /// This method is mostly exception free, but it requires memory + /// allocation and if it fails a corresponding standard exception will be + /// thrown. + /// + /// In practice, top level applications rarely need to use this + /// method directly. It should normally suffice to have a higher + /// level class such as \c Message do that job. + /// + /// <b>Note to developer:</b> the current implementation constructs an + /// \c RRset object for the OPT RR and calls its \c toWire() method, + /// which is inefficient. In future, we may want to optimize this method + /// by caching the rendered image and having the application reuse the + /// same \c EDNS object when possible. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// \param extended_rcode Upper 8 bits of extended RCODE to be rendered as + /// part of the EDNS OPT RR. + /// \return 1 if the OPT RR fits in the message size limit; otherwise 0. + uint32_t toWire(AbstractMessageRenderer& renderer, + const uint8_t extended_rcode) const; + + /// \brief Render the \c EDNS in the wire format. + /// + /// This method is same as \c toWire(MessageRenderer&,uint8_t)const + /// except it renders the OPT RR in an \c OutputBuffer and therefore + /// does not care about message size limit. + /// As a consequence it always returns 1. + uint32_t toWire(isc::util::OutputBuffer& buffer, + const uint8_t extended_rcode) const; + + /// \brief Convert the EDNS to a string. + /// + /// The format of the resulting string is as follows: + /// \code ; EDNS: version: <version>, flags: <edns flags>; udp: <udp size> + /// \endcode + /// where + /// - \em version is the EDNS version number (integer). + /// - <em>edns flags</em> is a sequence of EDNS flag bits. The only + /// possible flag is the "DNSSEC OK", which is represented as "do". + /// - <em>udp size</em> is sender's UDP payload size in bytes. + /// + /// The string will be terminated with a trailing newline character. + /// + /// When EDNS options are supported the output of this method will be + /// extended. + /// + /// This method is mostly exception free, but it may require memory + /// allocation and if it fails a corresponding standard exception will be + /// thrown. + /// + /// \return A string representation of \c EDNS. See above for the format. + std::string toText() const; + //@} + + // TBD: This method is currently not implemented. We'll eventually need + // something like this. + //void addOption(); + +public: + /// \brief The highest EDNS version this implementation supports. + static const uint8_t SUPPORTED_VERSION = 0; +private: + // We may eventually want to migrate to pimpl, especially when we support + // EDNS options. In this initial implementation, we keep it simple. + const uint8_t version_; + uint16_t udp_size_; + bool dnssec_aware_; +}; + +/// \brief Create a new \c EDNS object from a set of RR parameters, also +/// providing the extended RCODE value. +/// +/// This function is similar to the EDNS class constructor +/// \c EDNS::EDNS(const Name&, const RRClass&, const RRType&, const RRTTL&, const rdata::Rdata&) +/// but is different in that +/// - It dynamically creates a new object +/// - It returns (via a reference argument) the topmost 8 bits of the extended +/// RCODE encoded in the \c ttl. +/// +/// On success, \c extended_rcode will be updated with the 8-bit part of +/// the extended RCODE encoded in the TTL of the OPT RR. +/// +/// The intended usage of this function is to parse an OPT RR of an incoming +/// DNS message, while updating the RCODE of the message. +/// One common usage pattern is as follows: +/// +/// \code Message msg; +/// ... +/// uint8_t extended_rcode; +/// ConstEDNSPtr edns = ConstEDNSPtr(createEDNSFromRR(..., extended_rcode)); +/// rcode = Rcode(msg.getRcode().getCode(), extended_rcode); +/// \endcode +/// (although, like the \c EDNS constructor, normal applications wouldn't have +/// to use this function directly). +/// +/// This function provides the strong exception guarantee: Unless an +/// exception is thrown \c extended_code won't be modified. +/// +/// This function validates the given parameters and throws exceptions on +/// failure in the same way as the \c EDNS class constructor. +/// In addition, if memory allocation for the new object fails it throws the +/// corresponding standard exception. +/// +/// Note that this function returns a bare pointer to the newly allocated +/// object, not a shared pointer object enclosing the pointer. +/// The caller is responsible for deleting the object after the use of it +/// (typically, the caller would immediately encapsulate the returned pointer +/// in a shared pointer object, \c EDNSPtr or \c ConstEDNSPtr). +/// It returns a bare pointer so that it can be used where the use of a shared +/// pointer is impossible or not desirable. +/// +/// Note to developers: there is no strong technical reason why this function +/// cannot be a constructor of the \c EDNS class or even integrated into the +/// constructor. But we decided to make it a separate free function so that +/// constructors will be free from side effects (which is in itself a matter +/// of preference). +/// +/// \param name The owner name of the OPT RR. This must be the root name. +/// \param rrclass The RR class of the OPT RR. +/// \param rrtype This must specify the OPT RR type. +/// \param ttl The TTL of the OPT RR. +/// \param rdata The RDATA of the OPT RR. +/// \param extended_rcode A placeholder to store the topmost 8 bits of the +/// extended Rcode. +/// \return A pointer to the created \c EDNS object. +EDNS* createEDNSFromRR(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& ttl, + const rdata::Rdata& rdata, uint8_t& extended_rcode); + +/// \brief Insert the \c EDNS as a string into stream. +/// +/// This method convert \c edns into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param edns A reference to an \c EDNS object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const EDNS& edns); +} +} +#endif // EDNS_H diff --git a/src/lib/dns/exceptions.cc b/src/lib/dns/exceptions.cc new file mode 100644 index 0000000..e164348 --- /dev/null +++ b/src/lib/dns/exceptions.cc @@ -0,0 +1,26 @@ +// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dns/exceptions.h> +#include <dns/rcode.h> + +namespace isc { +namespace dns { + +const Rcode& +DNSMessageFORMERR::getRcode() const { + return (Rcode::FORMERR()); +} + +const Rcode& +DNSMessageBADVERS::getRcode() const { + return (Rcode::BADVERS()); +} + +} // end of namespace dns +} // end of namespace isc diff --git a/src/lib/dns/exceptions.h b/src/lib/dns/exceptions.h new file mode 100644 index 0000000..8c3477e --- /dev/null +++ b/src/lib/dns/exceptions.h @@ -0,0 +1,72 @@ +// Copyright (C) 2010-2024 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/. + +// XXX: we have another exceptions.h, so we need to use a prefix "DNS_" in +// the include guard. More preferably, we should define a consistent naming +// style for the header guide (e.g. module-name_file-name_H) throughout the +// package. + +#ifndef DNS_EXCEPTIONS_H +#define DNS_EXCEPTIONS_H + +#include <exceptions/exceptions.h> + +namespace isc { +namespace dns { + +/// +/// \brief A standard DNS module exception ...[TBD] +/// +class Rcode; // forward declaration + +class Exception : public isc::Exception { +public: + Exception(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// +/// \brief Base class for all sorts of text parse errors. +/// +class DNSTextError : public isc::dns::Exception { +public: + DNSTextError(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// \brief Base class for name parser exceptions. +/// +class NameParserException : public DNSTextError { +public: + NameParserException(const char* file, size_t line, const char* what) : + DNSTextError(file, line, what) {} +}; + +class DNSProtocolError : public isc::dns::Exception { +public: + DNSProtocolError(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} + virtual const Rcode& getRcode() const = 0; +}; + +class DNSMessageFORMERR : public DNSProtocolError { +public: + DNSMessageFORMERR(const char* file, size_t line, const char* what) : + DNSProtocolError(file, line, what) {} + virtual const Rcode& getRcode() const; +}; + +class DNSMessageBADVERS : public DNSProtocolError { +public: + DNSMessageBADVERS(const char* file, size_t line, const char* what) : + DNSProtocolError(file, line, what) {} + virtual const Rcode& getRcode() const; +}; + +} +} +#endif // DNS_EXCEPTIONS_H diff --git a/src/lib/dns/labelsequence.cc b/src/lib/dns/labelsequence.cc new file mode 100644 index 0000000..c0b8778 --- /dev/null +++ b/src/lib/dns/labelsequence.cc @@ -0,0 +1,468 @@ +// Copyright (C) 2012-2024 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 <dns/labelsequence.h> +#include <dns/name_internal.h> +#include <exceptions/exceptions.h> +#include <exceptions/isc_assert.h> + +#include <boost/functional/hash.hpp> + +#include <cstring> + +namespace isc { +namespace dns { + +LabelSequence::LabelSequence(const void* buf) { +#ifdef ENABLE_DEBUG + // In non-debug mode, dereferencing the null pointer further below + // will lead to a crash, so disabling this check is not + // unsafe. Except for a programming mistake, this case should not + // happen. + if (!buf) { + isc_throw(BadValue, + "Null pointer passed to LabelSequence constructor"); + } +#endif + + const uint8_t* bp = reinterpret_cast<const uint8_t*>(buf); + first_label_ = 0; + const uint8_t offsets_len = *bp++; + +#ifdef ENABLE_DEBUG + if (offsets_len == 0 || offsets_len > Name::MAX_LABELS) { + isc_throw(BadValue, + "Bad offsets len in serialized LabelSequence data: " + << static_cast<unsigned int>(offsets_len)); + } +#endif + + last_label_ = offsets_len - 1; + offsets_ = bp; + data_ = bp + offsets_len; + +#ifdef ENABLE_DEBUG + // Check the integrity on the offsets and the name data + const uint8_t* dp = data_; + for (size_t cur_offset = 0; cur_offset < offsets_len; ++cur_offset) { + if (dp - data_ != offsets_[cur_offset] || *dp > Name::MAX_LABELLEN) { + isc_throw(BadValue, + "Broken offset or name data in serialized " + "LabelSequence data"); + } + dp += (1 + *dp); + } +#endif +} + +LabelSequence::LabelSequence(const LabelSequence& src, + uint8_t buf[MAX_SERIALIZED_LENGTH]) { + size_t data_len; + const uint8_t *data = src.getData(&data_len); + std::memcpy(buf, data, data_len); + + for (size_t i = 0; i < src.getLabelCount(); ++i) { + buf[Name::MAX_WIRE + i] = src.offsets_[i + src.first_label_] - + src.offsets_[src.first_label_]; + } + + first_label_ = 0; + last_label_ = src.last_label_ - src.first_label_; + data_ = buf; + offsets_ = &buf[Name::MAX_WIRE]; +} + + +const uint8_t* +LabelSequence::getData(size_t *len) const { + *len = getDataLength(); + return (&data_[offsets_[first_label_]]); +} + +size_t +LabelSequence::getDataLength() const { + const size_t last_label_len = data_[offsets_[last_label_]] + 1; + return (offsets_[last_label_] - offsets_[first_label_] + last_label_len); +} + +size_t +LabelSequence::getSerializedLength() const { + return (1 + getLabelCount() + getDataLength()); +} + +namespace { +// Check if buf is not in the range of [bp, ep), which means +// - end of buffer is before bp, or +// - beginning of buffer is on or after ep +bool +isOutOfRange(const uint8_t* bp, const uint8_t* ep, + const uint8_t* buf, size_t buf_len) { + return (bp >= buf + buf_len || // end of buffer is before bp + ep <= buf); // beginning of buffer is on or after ep +} +} + +void +LabelSequence::serialize(void* buf, size_t buf_len) const { + const size_t expected_size = getSerializedLength(); + if (expected_size > buf_len) { + isc_throw(BadValue, "buffer too short for LabelSequence::serialize"); + } + + const size_t offsets_len = getLabelCount(); + isc_throw_assert(offsets_len < 256); // should be in the 8-bit range + + // Overridden check. Buffer shouldn't overwrap the offset of name data + // regions. + uint8_t* bp = reinterpret_cast<uint8_t*>(buf); + const size_t ndata_len = getDataLength(); + if (!isOutOfRange(offsets_, offsets_ + offsets_len, bp, buf_len) || + !isOutOfRange(data_, data_ + ndata_len, bp, buf_len)) { + isc_throw(BadValue, "serialize would break the source sequence"); + } + + *bp++ = offsets_len; + for (size_t i = 0; i < offsets_len; ++i) { + *bp++ = offsets_[first_label_ + i] - offsets_[first_label_]; + } + std::memcpy(bp, &data_[offsets_[first_label_]], ndata_len); + bp += ndata_len; + + isc_throw_assert(bp - reinterpret_cast<const uint8_t*>(buf) == expected_size); +} + +bool +LabelSequence::equals(const LabelSequence& other, bool case_sensitive) const { + size_t len, other_len; + const uint8_t* data = getData(&len); + const uint8_t* other_data = other.getData(&other_len); + + if (len != other_len) { + return (false); + } + if (case_sensitive) { + return (std::memcmp(data, other_data, len) == 0); + } + + // As long as the data was originally validated as (part of) a name, + // label length must never be a capital ascii character, so we can + // simply compare them after converting to lower characters. + for (size_t i = 0; i < len; ++i) { + const uint8_t ch = data[i]; + const uint8_t other_ch = other_data[i]; + if (isc::dns::name::internal::maptolower[ch] != + isc::dns::name::internal::maptolower[other_ch]) { + return (false); + } + } + return (true); +} + +NameComparisonResult +LabelSequence::compare(const LabelSequence& other, + bool case_sensitive) const { + // Determine the relative ordering under the DNSSEC order relation of + // 'this' and 'other', and also determine the hierarchical relationship + // of the labels. + + unsigned int nlabels = 0; + int l1 = getLabelCount(); + int l2 = other.getLabelCount(); + const int ldiff = static_cast<int>(l1) - static_cast<int>(l2); + unsigned int l = (ldiff < 0) ? l1 : l2; + + while (l > 0) { + --l; + --l1; + --l2; + size_t pos1 = offsets_[l1 + first_label_]; + size_t pos2 = other.offsets_[l2 + other.first_label_]; + unsigned int count1 = data_[pos1++]; + unsigned int count2 = other.data_[pos2++]; + + // We don't support any extended label types including now-obsolete + // bitstring labels. + isc_throw_assert(count1 <= Name::MAX_LABELLEN && count2 <= Name::MAX_LABELLEN); + + const int cdiff = static_cast<int>(count1) - static_cast<int>(count2); + unsigned int count = (cdiff < 0) ? count1 : count2; + + while (count > 0) { + const uint8_t label1 = data_[pos1]; + const uint8_t label2 = other.data_[pos2]; + int chdiff; + + if (case_sensitive) { + chdiff = static_cast<int>(label1) - static_cast<int>(label2); + } else { + chdiff = static_cast<int>( + isc::dns::name::internal::maptolower[label1]) - + static_cast<int>( + isc::dns::name::internal::maptolower[label2]); + } + + if (chdiff != 0) { + return (NameComparisonResult( + chdiff, nlabels, + nlabels == 0 ? NameComparisonResult::NONE : + NameComparisonResult::COMMONANCESTOR)); + } + --count; + ++pos1; + ++pos2; + } + if (cdiff != 0) { + return (NameComparisonResult( + cdiff, nlabels, + nlabels == 0 ? NameComparisonResult::NONE : + NameComparisonResult::COMMONANCESTOR)); + } + ++nlabels; + } + + if (ldiff < 0) { + return (NameComparisonResult(ldiff, nlabels, + NameComparisonResult::SUPERDOMAIN)); + } else if (ldiff > 0) { + return (NameComparisonResult(ldiff, nlabels, + NameComparisonResult::SUBDOMAIN)); + } + + return (NameComparisonResult(ldiff, nlabels, NameComparisonResult::EQUAL)); +} + +void +LabelSequence::stripLeft(size_t i) { + if (i >= getLabelCount()) { + isc_throw(OutOfRange, "Cannot strip to zero or less labels; " << i << + " (labelcount: " << getLabelCount() << ")"); + } + first_label_ += i; +} + +void +LabelSequence::stripRight(size_t i) { + if (i >= getLabelCount()) { + isc_throw(OutOfRange, "Cannot strip to zero or less labels; " << i << + " (labelcount: " << getLabelCount() << ")"); + } + last_label_ -= i; +} + +bool +LabelSequence::isAbsolute() const { + return (data_[offsets_[last_label_]] == 0); +} + +size_t +LabelSequence::getHash(bool case_sensitive) const { + size_t length; + const uint8_t* s = getData(&length); + if (length > 16) { + length = 16; + } + + size_t hash_val = 0; + while (length > 0) { + const uint8_t c = *s++; + boost::hash_combine(hash_val, case_sensitive ? c : + isc::dns::name::internal::maptolower[c]); + --length; + } + return (hash_val); +} + +std::string +LabelSequence::toRawText(bool omit_final_dot) const { + const uint8_t* np = &data_[offsets_[first_label_]]; + const uint8_t* np_end = np + getDataLength(); + + // use for integrity check + unsigned int labels = getLabelCount(); + // init with an impossible value to catch error cases in the end: + unsigned int count = Name::MAX_LABELLEN + 1; + + // result string: it will roughly have the same length as the wire format + // label sequence data. reserve that length to minimize reallocation. + std::string result; + result.reserve(getDataLength()); + + while (np != np_end) { + labels--; + count = *np++; + + if (count == 0) { + // We've reached the "final dot". If we've not dumped any + // character, the entire label sequence is the root name. + // In that case we don't omit the final dot. + if (!omit_final_dot || result.empty()) { + result.push_back('.'); + } + break; + } + + if (count <= Name::MAX_LABELLEN) { + isc_throw_assert(np_end - np >= count); + + if (!result.empty()) { + // just after a non-empty label. add a separating dot. + result.push_back('.'); + } + + while (count-- > 0) { + const uint8_t c = *np++; + result.push_back(c); + } + } else { + isc_throw(BadLabelType, "unknown label type in name data"); + } + } + + // We should be at the end of the data and have consumed all labels. + isc_throw_assert(np == np_end); + isc_throw_assert(labels == 0); + + return (result); +} + + +std::string +LabelSequence::toText(bool omit_final_dot) const { + const uint8_t* np = &data_[offsets_[first_label_]]; + const uint8_t* np_end = np + getDataLength(); + + // use for integrity check + unsigned int labels = getLabelCount(); + // init with an impossible value to catch error cases in the end: + unsigned int count = Name::MAX_LABELLEN + 1; + + // result string: it will roughly have the same length as the wire format + // label sequence data. reserve that length to minimize reallocation. + std::string result; + result.reserve(getDataLength()); + + while (np != np_end) { + labels--; + count = *np++; + + if (count == 0) { + // We've reached the "final dot". If we've not dumped any + // character, the entire label sequence is the root name. + // In that case we don't omit the final dot. + if (!omit_final_dot || result.empty()) { + result.push_back('.'); + } + break; + } + + if (count <= Name::MAX_LABELLEN) { + isc_throw_assert(np_end - np >= count); + + if (!result.empty()) { + // just after a non-empty label. add a separating dot. + result.push_back('.'); + } + + while (count-- > 0) { + const uint8_t c = *np++; + switch (c) { + case 0x22: // '"' + case 0x28: // '(' + case 0x29: // ')' + case 0x2E: // '.' + case 0x3B: // ';' + case 0x5C: // '\\' + // Special modifiers in zone files. + case 0x40: // '@' + case 0x24: // '$' + result.push_back('\\'); + result.push_back(c); + break; + default: + if (c > 0x20 && c < 0x7f) { + // append printable characters intact + result.push_back(c); + } else { + // encode non-printable characters in the form of \DDD + result.push_back(0x5c); + result.push_back(0x30 + ((c / 100) % 10)); + result.push_back(0x30 + ((c / 10) % 10)); + result.push_back(0x30 + (c % 10)); + } + } + } + } else { + isc_throw(BadLabelType, "unknown label type in name data"); + } + } + + // We should be at the end of the data and have consumed all labels. + isc_throw_assert(np == np_end); + isc_throw_assert(labels == 0); + + return (result); +} + +std::string +LabelSequence::toText() const { + return (toText(!isAbsolute())); +} + +void +LabelSequence::extend(const LabelSequence& labels, + uint8_t buf[MAX_SERIALIZED_LENGTH]) { + // collect data to perform steps before anything is changed + size_t label_count = last_label_ + 1; + // Since we may have been stripped, do not use getDataLength(), but + // calculate actual data size this labelsequence currently uses + size_t data_pos = offsets_[last_label_] + data_[offsets_[last_label_]] + 1; + + // If this labelsequence is absolute, virtually strip the root label. + if (isAbsolute()) { + data_pos--; + label_count--; + } + const size_t append_label_count = labels.getLabelCount(); + size_t data_len; + const uint8_t *data = labels.getData(&data_len); + + // Sanity checks + if (data_ != buf || offsets_ != &buf[Name::MAX_WIRE]) { + isc_throw(BadValue, + "extend() called with unrelated buffer"); + } + // Check MAX_LABELS before MAX_WIRE or it will be never reached + if (label_count + append_label_count > Name::MAX_LABELS) { + isc_throw(BadValue, + "extend() would exceed maximum number of labels"); + } + if (data_pos + data_len > Name::MAX_WIRE) { + isc_throw(BadValue, + "extend() would exceed maximum wire length"); + } + + // All seems to be reasonably ok, let's proceed. + std::memmove(&buf[data_pos], data, data_len); + + for (size_t i = 0; i < append_label_count; ++i) { + buf[Name::MAX_WIRE + label_count + i] = + data_pos + + labels.offsets_[i + labels.first_label_] - + labels.offsets_[labels.first_label_]; + } + last_label_ = label_count + append_label_count - 1; +} + +std::ostream& +operator<<(std::ostream& os, const LabelSequence& label_sequence) { + os << label_sequence.toText(); + return (os); +} + +} // end namespace dns +} // end namespace isc diff --git a/src/lib/dns/labelsequence.h b/src/lib/dns/labelsequence.h new file mode 100644 index 0000000..660b70d --- /dev/null +++ b/src/lib/dns/labelsequence.h @@ -0,0 +1,452 @@ +// Copyright (C) 2012-2024 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 LABELSEQUENCE_H +#define LABELSEQUENCE_H + +#include <dns/name.h> +#include <util/buffer.h> + +namespace isc { +namespace dns { + +/// \brief Light-weight Accessor to Name data. +/// +/// The purpose of this class is to easily match Names and parts of Names, +/// without needing to copy the underlying data on each label strip. +/// +/// It can only work on existing Name objects, or data as provided by the +/// Name object or another LabelSequence, and the data or Name MUST +/// remain in scope during the entire lifetime of its associated +/// LabelSequence(s). +/// +/// Upon creation of a LabelSequence, it records the offsets of the +/// labels in the wireformat data of the Name. When stripLeft() or +/// stripRight() is called on the LabelSequence, no changes in the +/// original data occur, but the internal pointers of the +/// LabelSequence are modified. +/// +/// LabelSequences can be compared to other LabelSequences, and their +/// data can be requested (which then points to part of the original +/// data of the original Name object). +class LabelSequence { + // Name calls the private toText(bool) method of LabelSequence. + friend std::string Name::toText(bool) const; + +public: + /// \brief Max possible size of serialized image generated by \c serialize + /// + /// A fixed length buffer of this size can be always passed to + /// \c serialize() safely. (But the application shouldn't use the + /// specific size value; it must use this constant variable). + static const size_t MAX_SERIALIZED_LENGTH = + Name::MAX_WIRE + Name::MAX_LABELS + 1; + + /// + /// \name Well-known LabelSequence constants + /// + //@{ + /// Wildcard label ("*") + static const LabelSequence& WILDCARD(); + //@} + + /// \brief Constructs a LabelSequence for the given name + /// + /// \note The associated Name MUST remain in scope during the lifetime + /// of this LabelSequence, since getData() refers to data from the + /// Name object (the only data the LabelSequence stores are pointers + /// to the labels in the Name object). + /// + /// \param name The Name to construct a LabelSequence for + explicit LabelSequence(const Name& name): + data_(&name.ndata_[0]), + offsets_(&name.offsets_[0]), + first_label_(0), + last_label_(name.getLabelCount() - 1) { + } + + /// \brief Constructor from serialized image. + /// + /// This constructor restores a \c LabelSequence object from a serialized + /// binary image previously generated by \c serialize(). Any other input + /// to this constructor will result in undefined behavior. + /// + /// The binary data passed to this constructor MUST remain in scope and + /// MUST NOT be modified during the lifetime of this LabelSequence. + /// + /// As long as the data were previously generated by a call to + /// \c serialize() on a valid \c LabelSequence object, this constructor + /// should succeed. While any other case is undefined, this constructor + /// may perform some validity checks internally for safety. Nevertheless, + /// applications must not rely on such checks. + /// + /// \param buf Pointer to the serialized image generated by \c serialize(). + explicit LabelSequence(const void* buf); + + /// \brief Construct 'extendable' LabelSequence + /// + /// This form of LabelSequence copies the data from the given + /// labelsequence into the given external buffer, which is subsequently + /// extendable by calling extend() + /// + /// The data is placed into the given buffer as follows: + /// - binary sequence of name data, starting at position 0, + /// length determined by source LabelSequence + /// - offsets, starting at position Name::MAX_WIRE, length + /// determined by source LabelSequence + /// The offsets are updated to be correct for the potentially partial + /// name data (as stripLeft() and stripRight may have been called on + /// the source LabelSequence). + /// + /// \note The given buf MUST remain in scope during the lifetime of + /// the LabelSequence created here. + /// \note The buffer should never be modified except through + /// calls to extend(). + /// \note Also, only associate the buffer with at most one + /// LabelSequence. Behaviour is undefined if two LabelSequences are + /// using the same buffer. + /// + /// \param src LabelSequence to copy the initial data from + /// \param buf external buffer to store this labelsequence's data in + LabelSequence(const LabelSequence& src, uint8_t buf[MAX_SERIALIZED_LENGTH]); + + /// \brief Copy constructor. + /// + /// \note The associated data MUST remain in scope during the lifetime + /// of this LabelSequence, since only the pointers are copied. + /// + /// \note No validation is done on the given data upon construction; + /// use with care. + /// + /// \param ls The LabelSequence to construct a LabelSequence from + LabelSequence(const LabelSequence& ls): + data_(ls.data_), + offsets_(ls.offsets_), + first_label_(ls.first_label_), + last_label_(ls.last_label_) { + } + + /// \brief Assignment operator. + /// + /// \note The associated data MUST remain in scope during the lifetime + /// of this LabelSequence, since only the pointers are copied. + /// + /// \note No validation is done on the given data upon construction; + /// use with care. + /// + /// \param other The LabelSequence to assign a LabelSequence from + LabelSequence& operator=(const LabelSequence& other) { + if (this != &other) { + // Not self-assignment. + data_ = other.data_; + offsets_ = other.offsets_; + first_label_ = other.first_label_; + last_label_ = other.last_label_; + } + return (*this); + } + + /// \brief Return the wire-format data for this LabelSequence + /// + /// The data is returned as a pointer to (the part of) the original + /// wireformat data, from either the original Name object, or the + /// raw data given in the constructor, and the given len value is + /// set to the number of octets that match this labelsequence. + /// + /// \note The data pointed to is only valid if the original Name + /// object or data is still in scope + /// + /// \param len Pointer to a size_t where the length of the data + /// will be stored (in number of octets) + /// \return Pointer to the wire-format data of this label sequence + const uint8_t* getData(size_t* len) const; + + /// \brief Return the length of the wire-format data of this LabelSequence + /// + /// This method returns the number of octets for the data that would + /// be returned by the \c getData() method. + /// + /// Note that the return value of this method is always positive. + /// Note also that if the return value of this method is 1, it means the + /// sequence consists of the null label, i.e., a single "dot", and vice + /// versa. + /// + /// \note The data pointed to is only valid if the original Name + /// object or data is still in scope + /// + /// \return The length of the data of the label sequence in octets. + size_t getDataLength() const; + + /// \brief Return the size of serialized image of the \c LabelSequence. + /// + /// This method calculates the size of necessary storage to store + /// serialized image of this \c LabelSequence (which would be dumped by + /// \c serialize()) and returns it. The size is in bytes. + /// + /// \throw none. + /// + /// \return The size of serialized image of the \c LabelSequence. + size_t getSerializedLength() const; + + /// \brief Serialize the \c LabelSequence object in to a buffer. + /// + /// This method dumps a serialized image of this \c LabelSequence + /// that would be restored by the corresponding constructor into the + /// given buffer. The buffer size must be at least equal to + /// the value returned by getSerializedLength() (it can be larger than + /// that). + /// + /// Be careful about where the buffer is located; due to the nature + /// of the buffer, it's quite possible that the memory region is being used + /// to construct another active \c LabelSequence. In such a case + /// the serialization would silently break that sequence object, and + /// it will be very difficult to identify the cause. This method + /// has minimal level checks to avoid such disruption: If the serialization + /// would break "this" \c LabelSequence object, it doesn't write anything + /// to the given buffer and throw a \c isc::BadValue exception. + /// + /// In general, it should be safe to call this method on a + /// \c LabelSequence object constructed from a \c Name object or + /// a copy of such \c LabelSequence. When you construct \c LabelSequence + /// from pre-serialized data, calling this method on it can be unsafe. + /// One safe (but a bit less efficient) way in such a case is to make + /// the source \c LabelSequence temporary and immediately create a + /// local copy using an explicit buffer, and call this method on the + /// latter: + /// \code + /// // don't do this, it's not safe (and would result in exception): + /// // LabelSequence(buf).serialize(buf, buf_len); + /// + /// // The following are the safe way: + /// uint8_t ext_buf[LabelSequence::MAX_SERIALIZED_LENGTH]; + /// LabelSequence seq(LabelSequence(buf), ext_buf); + /// ... (strip the labels, etc) + /// seq.serialize(buf, buf_len); // it's safe to override buf here + /// \endcode + /// + /// The serialized image would be as follows: + /// - olen: number of offsets (1 byte) + /// - binary sequence of offsets (olen bytes, verbatim copy of offsets_ + /// of this size) + /// - binary sequence of name data (length determined by itself, verbatim + /// copy of data_ of the corresponding size) + /// + /// Applications must use the resulting image as opaque value and must not + /// use it for other purposes than input to the corresponding constructor + /// to restore it. Application behavior that assumes the specific + /// organization of the image is not guaranteed. + /// + /// \throw isc::BadValue buf_len is too short (this method never throws + /// otherwise) or the serialization would override internal data of + /// of the source LabelSequence. + /// + /// \param buf Pointer to the placeholder to dump the serialized image + /// \param buf_len The size of available region in \c buf + void serialize(void* buf, size_t buf_len) const; + + /// \brief Compares two label sequences for equality. + /// + /// Performs a (optionally case-sensitive) comparison between this + /// LabelSequence and another LabelSequence for equality. + /// + /// \param other The LabelSequence to compare with + /// \param case_sensitive If true, comparison is case-sensitive + /// \return true if The label sequences consist are the same length, + /// and contain the same data. + bool equals(const LabelSequence& other, bool case_sensitive = false) const; + + /// \brief Compares two label sequences for equality (case ignored). + /// + /// This is equivalent to <code>this->equals(other)</code>. + /// + /// The operator version is convenient some specific cases such as in + /// unit tests. + bool operator==(const LabelSequence& other) const { + return (equals(other)); + } + + /// \brief Compares two label sequences. + /// + /// Performs a (optionally case-insensitive) comparison between this + /// LabelSequence and another LabelSequence. + /// + /// \param other The LabelSequence to compare with + /// \param case_sensitive If true, comparison is case-insensitive + /// \return a <code>NameComparisonResult</code> object representing the + /// comparison result. + NameComparisonResult compare(const LabelSequence& other, + bool case_sensitive = false) const; + + /// \brief Remove labels from the front of this LabelSequence + /// + /// \note No actual memory is changed, this operation merely updates the + /// internal pointers based on the offsets in the Name object. + /// + /// \exception OutOfRange if i is greater than or equal to the number + /// of labels currently pointed to by this LabelSequence + /// + /// \param i The number of labels to remove. + void stripLeft(size_t i); + + /// \brief Remove labels from the end of this LabelSequence + /// + /// \note No actual memory is changed, this operation merely updates the + /// internal pointers based on the offsets originally provided. + /// + /// \exception OutOfRange if i is greater than or equal to the number + /// of labels currently pointed to by this LabelSequence + /// + /// \param i The number of labels to remove. + void stripRight(size_t i); + + /// \brief Returns the current number of labels for this LabelSequence + /// + /// \return The number of labels + size_t getLabelCount() const { + return (last_label_ - first_label_ + 1); + } + + /// \brief Convert the LabelSequence to a string. + /// + /// This method returns a <code>std::string</code> object representing the + /// LabelSequence as a string. The returned string ends with a dot + /// '.' if the label sequence is absolute. + /// + /// This function assumes the underlying data is in proper + /// uncompressed wire format. If it finds an unexpected label + /// character including compression pointer, an exception of class + /// \c BadLabelType will be thrown. In addition, if resource + /// allocation for the result string fails, a corresponding standard + /// exception will be thrown. + /// + /// \return a string representation of the <code>LabelSequence</code>. + std::string toText() const; + + /// \brief Convert the LabelSequence to a string without escape sequences. + /// + /// The string returned will contain a single character value for any + /// escape sequences in the label(s). + /// + /// \param omit_final_dot whether to omit the trailing dot in the output. + /// \return a string representation of the <code>LabelSequence</code> + /// that does not contain escape sequences. + std::string toRawText(bool omit_final_dot) const; + + /// \brief Extend this LabelSequence with the given labelsequence + /// + /// The given labels are appended to the name data, and internal + /// offset data is updated accordingly. + /// + /// The data from the given LabelSequence is copied into the buffer + /// associated with this LabelSequence; the appended LabelSequence + /// (the 'labels' argument) can be released if it is not needed for + /// other operations anymore. + /// + /// If this LabelSequence is absolute, its root label will be stripped + /// before the given LabelSequence is appended; after extend(), + /// this LabelSequence will be absolute if, and only if, the appended + /// LabelSequence was. A side-effect of this property is that adding + /// the root label to an absolute LabelSequence has no effect (the + /// root label is stripped, then added again). + /// + /// Some minimal checking is done on the data, but internal integrity + /// is not assumed. Do NOT modify the given buffer except through calls + /// to this method, and do NOT call this method if the buffer is + /// associated to another LabelSequence (behaviour of the other + /// LabelSequence is undefined in that scenario). + /// + /// \exception BadValue If the buffer does not appear to be associated + /// with this LabelSequence, or if the maximum wire length or maximum + /// number of labels would be exceeded by this operation + /// + /// \param labels The labels to append to this LabelSequence + /// \param buf The buffer associated with this LabelSequence + void extend(const LabelSequence& labels, + uint8_t buf[MAX_SERIALIZED_LENGTH]); + +private: + /// \brief Convert the LabelSequence to a string. + /// + /// This method is a version of the zero-argument toText() method, + /// that accepts a <code>omit_final_dot</code> argument. The + /// returned string ends with a dot '.' if + /// <code>omit_final_dot</code> is <code>false</code>. + /// + /// This method is used as a helper for <code>Name::toText()</code> + /// only. + /// + /// \param omit_final_dot whether to omit the trailing dot in the output. + /// \return a string representation of the <code>LabelSequence</code>. + std::string toText(bool omit_final_dot) const; +public: + /// \brief Calculate a simple hash for the label sequence. + /// + /// This method calculates a hash value for the label sequence as binary + /// data. If \c case_sensitive is false, it ignores the case stored in + /// the labels; specifically, it normalizes the labels by converting all + /// upper case characters to lower case ones and calculates the hash value + /// for the result. + /// + /// This method is intended to provide a lightweight way to store a + /// relatively small number of label sequences in a hash table. + /// For this reason it only takes into account data up to 16 octets + /// (16 was derived from BIND 9's implementation). Also, the function does + /// not provide any unpredictability; a specific sequence will always have + /// the same hash value. It should therefore not be used in the context + /// where an untrusted third party can mount a denial of service attack by + /// forcing the application to create a very large number of label + /// sequences that have the same hash value and expected to be stored in + /// a hash table. + /// + /// \exception None + /// + /// \param case_sensitive + /// \return A hash value for this label sequence. + size_t getHash(bool case_sensitive) const; + + /// \brief Checks whether the label sequence is absolute + /// + /// \return true if the last label is the root label + bool isAbsolute() const; + +private: + const uint8_t* data_; // wire-format name data + const uint8_t* offsets_; // an array of offsets in data_ for the labels + size_t first_label_; // index of offsets_ for the first label + size_t last_label_; // index of offsets_ for the last label. + // can be equal to first_label_, but must not + // be smaller (the class ensures that) +}; + + +/// +/// \brief Insert the label sequence as a string into stream. +/// +/// This method convert the \c label_sequence into a string and inserts +/// it into the output stream \c os. +/// +/// This function overloads the global operator<< to behave as described in +/// ostream::operator<< but applied to \c LabelSequence objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param label_sequence The \c LabelSequence object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& +operator<<(std::ostream& os, const LabelSequence& label_sequence); + +inline const LabelSequence& +LabelSequence::WILDCARD() { + static const uint8_t wildcard_buf[4] = { 0x01, 0x00, 0x01, '*' }; + static const LabelSequence wild_ls(wildcard_buf); + return (wild_ls); +} + +} // end namespace dns +} // end namespace isc + +#endif diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc new file mode 100644 index 0000000..d4acac0 --- /dev/null +++ b/src/lib/dns/master_lexer.cc @@ -0,0 +1,608 @@ +// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <exceptions/isc_assert.h> +#include <dns/master_lexer.h> +#include <dns/master_lexer_inputsource.h> +#include <dns/master_lexer_state.h> + +#include <boost/lexical_cast.hpp> + +#include <bitset> +#include <limits> +#include <string> +#include <vector> + +namespace isc { +namespace dns { + +// The definition of SOURCE_SIZE_UNKNOWN. Note that we initialize it using +// a method of another library. Technically, this could trigger a static +// initialization fiasco. But in this particular usage it's very unlikely +// to happen because this value is expected to be used only as a return +// value of a MasterLexer's method, and its constructor needs definitions +// here. +const size_t MasterLexer::SOURCE_SIZE_UNKNOWN = + std::numeric_limits<size_t>::max(); + +namespace { +typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr; +} // end unnamed namespace +using namespace master_lexer_internal; + +struct MasterLexer::MasterLexerImpl { + MasterLexerImpl() : source_(0), token_(MasterToken::NOT_STARTED), + total_size_(0), popped_size_(0), + paren_count_(0), last_was_eol_(true), + has_previous_(false), + previous_paren_count_(0), + previous_was_eol_(false) { + separators_.set('\r'); + separators_.set('\n'); + separators_.set(' '); + separators_.set('\t'); + separators_.set('('); + separators_.set(')'); + separators_.set('"'); + esc_separators_.set('\r'); + esc_separators_.set('\n'); + } + + // A helper method to skip possible comments toward the end of EOL or EOF. + // commonly used by state classes. It returns the corresponding "end-of" + // character in case it's a comment; otherwise it simply returns the + // current character. + int skipComment(int c, bool escaped = false) { + if (c == ';' && !escaped) { + while (true) { + c = source_->getChar(); + if (c == '\n' || c == InputSource::END_OF_STREAM) { + return (c); + } + } + } + return (c); + } + + bool isTokenEnd(int c, bool escaped) { + // Special case of EOF (end of stream); this is not in the bitmaps + if (c == InputSource::END_OF_STREAM) { + return (true); + } + // In this implementation we only ensure the behavior for unsigned + // range of characters, so we restrict the range of the values up to + // 0x7f = 127 + return (escaped ? esc_separators_.test(c & 0x7f) : + separators_.test(c & 0x7f)); + } + + void setTotalSize() { + isc_throw_assert(source_); + if (total_size_ != SOURCE_SIZE_UNKNOWN) { + const size_t current_size = source_->getSize(); + if (current_size != SOURCE_SIZE_UNKNOWN) { + total_size_ += current_size; + } else { + total_size_ = SOURCE_SIZE_UNKNOWN; + } + } + } + + std::vector<InputSourcePtr> sources_; + InputSource* source_; // current source (null if sources_ is empty) + MasterToken token_; // currently recognized token (set by a state) + std::vector<char> data_; // placeholder for string data + + // Keep track of the total size of all sources and characters that have + // been read from sources already popped. + size_t total_size_; // accumulated size (# of chars) of sources + size_t popped_size_; // total size of sources that have been popped + + // These are used in states, and defined here only as a placeholder. + // The main lexer class does not need these members. + size_t paren_count_; // nest count of the parentheses + bool last_was_eol_; // whether the lexer just passed an end-of-line + + // Bitmaps that gives whether a given (positive) character should be + // considered a separator of a string/number token. The esc_ version + // is a subset of the other, excluding characters that can be ignored + // if escaped by a backslash. See isTokenEnd() for the bitmap size. + std::bitset<128> separators_; + std::bitset<128> esc_separators_; + + // These are to allow restoring state before previous token. + bool has_previous_; + size_t previous_paren_count_; + bool previous_was_eol_; +}; + +MasterLexer::MasterLexer() : impl_(new MasterLexerImpl()) { +} + +MasterLexer::~MasterLexer() { +} + +bool +MasterLexer::pushSource(const char* filename, std::string* error) { + if (!filename) { + isc_throw(InvalidParameter, + "null filename for MasterLexer::pushSource"); + } + try { + impl_->sources_.push_back(InputSourcePtr(new InputSource(filename))); + } catch (const InputSource::OpenError& ex) { + if (error) { + *error = ex.what(); + } + return (false); + } + + impl_->source_ = impl_->sources_.back().get(); + impl_->has_previous_ = false; + impl_->last_was_eol_ = true; + impl_->setTotalSize(); + return (true); +} + +void +MasterLexer::pushSource(std::istream& input) { + try { + impl_->sources_.push_back(InputSourcePtr(new InputSource(input))); + } catch (const InputSource::OpenError& ex) { + // Convert the "internal" exception to public one. + isc_throw(Unexpected, "Failed to push a stream to lexer: " << + ex.what()); + } + impl_->source_ = impl_->sources_.back().get(); + impl_->has_previous_ = false; + impl_->last_was_eol_ = true; + impl_->setTotalSize(); +} + +void +MasterLexer::popSource() { + if (impl_->sources_.empty()) { + isc_throw(InvalidOperation, + "MasterLexer::popSource on an empty source"); + } + impl_->popped_size_ += impl_->source_->getPosition(); + impl_->sources_.pop_back(); + impl_->source_ = impl_->sources_.empty() ? 0 : + impl_->sources_.back().get(); + impl_->has_previous_ = false; +} + +size_t +MasterLexer::getSourceCount() const { + return (impl_->sources_.size()); +} + +std::string +MasterLexer::getSourceName() const { + if (impl_->sources_.empty()) { + return (std::string()); + } + return (impl_->sources_.back()->getName()); +} + +size_t +MasterLexer::getSourceLine() const { + if (impl_->sources_.empty()) { + return (0); + } + return (impl_->sources_.back()->getCurrentLine()); +} + +size_t +MasterLexer::getTotalSourceSize() const { + return (impl_->total_size_); +} + +size_t +MasterLexer::getPosition() const { + size_t position = impl_->popped_size_; + for (auto const& src : impl_->sources_) { + position += src->getPosition(); + } + return (position); +} + +const MasterToken& +MasterLexer::getNextToken(Options options) { + if (!impl_->source_) { + isc_throw(isc::InvalidOperation, "No source to read tokens from"); + } + // Store the current state so we can restore it in ungetToken + impl_->previous_paren_count_ = impl_->paren_count_; + impl_->previous_was_eol_ = impl_->last_was_eol_; + impl_->source_->mark(); + impl_->has_previous_ = true; + // Reset the token now. This is to check a token was actually produced. + // This is debugging aid. + impl_->token_ = MasterToken(MasterToken::NO_TOKEN_PRODUCED); + // And get the token + + // This actually handles EOF internally too. + const State* state = State::start(*this, options); + if (state) { + state->handle(*this); + } + // Make sure a token was produced. Since this Can Not Happen, we assert + // here instead of throwing. + isc_throw_assert(impl_->token_.getType() != MasterToken::ERROR || + impl_->token_.getErrorCode() != MasterToken::NO_TOKEN_PRODUCED); + return (impl_->token_); +} + +namespace { +inline MasterLexer::Options +optionsForTokenType(MasterToken::Type expect) { + switch (expect) { + case MasterToken::STRING: + return (MasterLexer::NONE); + case MasterToken::QSTRING: + return (MasterLexer::QSTRING); + case MasterToken::NUMBER: + return (MasterLexer::NUMBER); + default: + isc_throw(InvalidParameter, + "expected type for getNextToken not supported: " << expect); + } +} +} + +const MasterToken& +MasterLexer::getNextToken(MasterToken::Type expect, bool eol_ok) { + // Get the next token, specifying an appropriate option corresponding to + // the expected type. The result should be set in impl_->token_. + getNextToken(optionsForTokenType(expect)); + + if (impl_->token_.getType() == MasterToken::ERROR) { + if (impl_->token_.getErrorCode() == MasterToken::NUMBER_OUT_OF_RANGE) { + ungetToken(); + } + throw LexerError(__FILE__, __LINE__, impl_->token_); + } + + const bool is_eol_like = + (impl_->token_.getType() == MasterToken::END_OF_LINE || + impl_->token_.getType() == MasterToken::END_OF_FILE); + if (eol_ok && is_eol_like) { + return (impl_->token_); + } + if (impl_->token_.getType() == MasterToken::STRING && + expect == MasterToken::QSTRING) { + return (impl_->token_); + } + if (impl_->token_.getType() != expect) { + ungetToken(); + if (is_eol_like) { + throw LexerError(__FILE__, __LINE__, + MasterToken(MasterToken::UNEXPECTED_END)); + } + isc_throw_assert(expect == MasterToken::NUMBER); + throw LexerError(__FILE__, __LINE__, + MasterToken(MasterToken::BAD_NUMBER)); + } + + return (impl_->token_); +} + +void +MasterLexer::ungetToken() { + if (impl_->has_previous_) { + impl_->has_previous_ = false; + impl_->source_->ungetAll(); + impl_->last_was_eol_ = impl_->previous_was_eol_; + impl_->paren_count_ = impl_->previous_paren_count_; + } else { + isc_throw(isc::InvalidOperation, "No token to unget ready"); + } +} + +namespace { +const char* const error_text[] = { + "lexer not started", // NOT_STARTED + "unbalanced parentheses", // UNBALANCED_PAREN + "unexpected end of input", // UNEXPECTED_END + "unbalanced quotes", // UNBALANCED_QUOTES + "no token produced", // NO_TOKEN_PRODUCED + "number out of range", // NUMBER_OUT_OF_RANGE + "not a valid number", // BAD_NUMBER + "unexpected quotes" // UNEXPECTED_QUOTES +}; +const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]); +} // end unnamed namespace + +std::string +MasterToken::getErrorText() const { + if (type_ != ERROR) { + isc_throw(InvalidOperation, + "MasterToken::getErrorText() for non error type"); + } + + // The class integrity ensures the following: + isc_throw_assert(val_.error_code_ < error_text_max_count); + return (error_text[val_.error_code_]); +} + +namespace master_lexer_internal { +// Below we implement state classes for state transitions of MasterLexer. +// Note that these need to be defined here so that they can refer to +// the details of MasterLexerImpl. + +bool +State::wasLastEOL(const MasterLexer& lexer) const { + return (lexer.impl_->last_was_eol_); +} + +const MasterToken& +State::getToken(const MasterLexer& lexer) const { + return (lexer.impl_->token_); +} + +size_t +State::getParenCount(const MasterLexer& lexer) const { + return (lexer.impl_->paren_count_); +} + +namespace { +class CRLF : public State { +public: + CRLF() {} + virtual ~CRLF() {} // see the base class for the destructor + virtual void handle(MasterLexer& lexer) const { + // We've just seen '\r'. If this is part of a sequence of '\r\n', + // we combine them as a single END-OF-LINE. Otherwise we treat the + // single '\r' as an EOL and continue tokenization from the character + // immediately after '\r'. One tricky case is that there's a comment + // between '\r' and '\n'. This implementation combines these + // characters and treats them as a single EOL (the behavior derived + // from BIND 9). Technically this may not be correct, but in practice + // the caller wouldn't distinguish this case from the case it has + // two EOLs, so we simplify the process. + const int c = getLexerImpl(lexer)->skipComment( + getLexerImpl(lexer)->source_->getChar()); + if (c != '\n') { + getLexerImpl(lexer)->source_->ungetChar(); + } + getLexerImpl(lexer)->token_ = MasterToken(MasterToken::END_OF_LINE); + getLexerImpl(lexer)->last_was_eol_ = true; + } +}; + +class String : public State { +public: + String() {} + virtual ~String() {} // see the base class for the destructor + virtual void handle(MasterLexer& lexer) const; +}; + +class QString : public State { +public: + QString() {} + virtual ~QString() {} // see the base class for the destructor + virtual void handle(MasterLexer& lexer) const; +}; + +class Number : public State { +public: + Number() {} + virtual ~Number() {} + virtual void handle(MasterLexer& lexer) const; +}; + +// We use a common instance of a each state in a singleton-like way to save +// construction overhead. They are not singletons in its strict sense as +// we don't prohibit direct construction of these objects. But that doesn't +// matter much anyway, because the definitions are completely hidden within +// this file. +const CRLF CRLF_STATE; +const String STRING_STATE; +const QString QSTRING_STATE; +const Number NUMBER_STATE; +} // end unnamed namespace + +const State& +State::getInstance(ID state_id) { + switch (state_id) { + case CRLF: + return (CRLF_STATE); + case String: + return (STRING_STATE); + case QString: + return (QSTRING_STATE); + case Number: + return (NUMBER_STATE); + } + + // This is a bug of the caller, and this method is only expected to be + // used by tests, so we just forcefully make it fail by asserting the + // condition. + isc_throw_assert(false); + return (STRING_STATE); // a dummy return, to silence some compilers. +} + +const State* +State::start(MasterLexer& lexer, MasterLexer::Options options) { + // define some shortcuts + MasterLexer::MasterLexerImpl& lexerimpl = *lexer.impl_; + size_t& paren_count = lexerimpl.paren_count_; + + // Note: the if-else in the loop is getting complicated. When we complete + // #2374, revisit the organization to see if we need a fundamental + // refactoring. + while (true) { + const int c = lexerimpl.skipComment(lexerimpl.source_->getChar()); + if (c == InputSource::END_OF_STREAM) { + lexerimpl.last_was_eol_ = false; + if (paren_count != 0) { + lexerimpl.token_ = MasterToken(MasterToken::UNBALANCED_PAREN); + paren_count = 0; // reset to 0; this helps in lenient mode. + return (0); + } + lexerimpl.token_ = MasterToken(MasterToken::END_OF_FILE); + return (0); + } else if (c == ' ' || c == '\t') { + // If requested and we are not in (), recognize the initial space. + if (lexerimpl.last_was_eol_ && paren_count == 0 && + (options & MasterLexer::INITIAL_WS) != 0) { + lexerimpl.last_was_eol_ = false; + lexerimpl.token_ = MasterToken(MasterToken::INITIAL_WS); + return (0); + } + } else if (c == '\n') { + lexerimpl.last_was_eol_ = true; + if (paren_count == 0) { // we don't recognize EOL if we are in () + lexerimpl.token_ = MasterToken(MasterToken::END_OF_LINE); + return (0); + } + } else if (c == '\r') { + if (paren_count == 0) { // check if we are in () (see above) + return (&CRLF_STATE); + } + } else if (c == '"') { + if ((options & MasterLexer::QSTRING) != 0) { + lexerimpl.last_was_eol_ = false; + return (&QSTRING_STATE); + } else { + lexerimpl.token_ = MasterToken(MasterToken::UNEXPECTED_QUOTES); + return (0); + } + } else if (c == '(') { + lexerimpl.last_was_eol_ = false; + ++paren_count; + } else if (c == ')') { + lexerimpl.last_was_eol_ = false; + if (paren_count == 0) { + lexerimpl.token_ = MasterToken(MasterToken::UNBALANCED_PAREN); + return (0); + } + --paren_count; + } else if ((options & MasterLexer::NUMBER) != 0 &&isdigit(c)) { + lexerimpl.last_was_eol_ = false; + // this character will be handled in the number state + lexerimpl.source_->ungetChar(); + return (&NUMBER_STATE); + } else { + // this character will be handled in the string state + lexerimpl.source_->ungetChar(); + lexerimpl.last_was_eol_ = false; + return (&STRING_STATE); + } + // no code should be here; we just continue the loop. + } +} + +void +String::handle(MasterLexer& lexer) const { + std::vector<char>& data = getLexerImpl(lexer)->data_; + data.clear(); + + bool escaped = false; + while (true) { + const int c = getLexerImpl(lexer)->skipComment( + getLexerImpl(lexer)->source_->getChar(), escaped); + + if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) { + getLexerImpl(lexer)->source_->ungetChar(); + // make sure it nul-terminated as a c-str (excluded from token + // data). + data.push_back('\0'); + getLexerImpl(lexer)->token_ = + MasterToken(&data.at(0), data.size() - 1); + return; + } + escaped = (c == '\\' && !escaped); + data.push_back(c); + } +} + +void +QString::handle(MasterLexer& lexer) const { + MasterToken& token = getLexerImpl(lexer)->token_; + std::vector<char>& data = getLexerImpl(lexer)->data_; + data.clear(); + + bool escaped = false; + while (true) { + const int c = getLexerImpl(lexer)->source_->getChar(); + if (c == InputSource::END_OF_STREAM) { + token = MasterToken(MasterToken::UNEXPECTED_END); + return; + } else if (c == '"') { + if (escaped) { + // found escaped '"'. overwrite the preceding backslash. + isc_throw_assert(!data.empty()); + escaped = false; + data.back() = '"'; + } else { + // make sure it nul-terminated as a c-str (excluded from token + // data). This also simplifies the case of an empty string. + data.push_back('\0'); + token = MasterToken(&data.at(0), data.size() - 1, true); + return; + } + } else if (c == '\n' && !escaped) { + getLexerImpl(lexer)->source_->ungetChar(); + token = MasterToken(MasterToken::UNBALANCED_QUOTES); + return; + } else { + escaped = (c == '\\' && !escaped); + data.push_back(c); + } + } +} + +void +Number::handle(MasterLexer& lexer) const { + MasterToken& token = getLexerImpl(lexer)->token_; + + // It may yet turn out to be a string, so we first + // collect all the data + bool digits_only = true; + std::vector<char>& data = getLexerImpl(lexer)->data_; + data.clear(); + bool escaped = false; + + while (true) { + const int c = getLexerImpl(lexer)->skipComment( + getLexerImpl(lexer)->source_->getChar(), escaped); + if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) { + getLexerImpl(lexer)->source_->ungetChar(); + // We need to close the string whether it's digits-only (for + // lexical_cast) or not (see String::handle()). + data.push_back('\0'); + if (digits_only) { + try { + const uint32_t number32 = + boost::lexical_cast<uint32_t, const char*>(&data[0]); + token = MasterToken(number32); + } catch (const boost::bad_lexical_cast&) { + // Since we already know we have only digits, + // range should be the only possible problem. + token = MasterToken(MasterToken::NUMBER_OUT_OF_RANGE); + } + } else { + token = MasterToken(&data.at(0), data.size() - 1); + } + return; + } + if (!isdigit(c)) { + digits_only = false; + } + escaped = (c == '\\' && !escaped); + data.push_back(c); + } +} + +} // namespace master_lexer_internal + +} // end of namespace dns +} // end of namespace isc diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h new file mode 100644 index 0000000..8a4a53f --- /dev/null +++ b/src/lib/dns/master_lexer.h @@ -0,0 +1,676 @@ +// Copyright (C) 2012-2024 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 MASTER_LEXER_H +#define MASTER_LEXER_H + +#include <dns/exceptions.h> + +#include <istream> +#include <string> + +#include <stdint.h> + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> + +namespace isc { +namespace dns { +namespace master_lexer_internal { +class State; +} + +/// \brief Tokens for \c MasterLexer +/// +/// This is a simple value-class encapsulating a type of a lexer token and +/// (if it has a value) its value. Essentially, the class provides +/// constructors corresponding to different types of tokens, and corresponding +/// getter methods. The type and value are fixed at the time of construction +/// and will never be modified throughout the lifetime of the object. +/// The getter methods are still provided to maximize the safety; an +/// application cannot refer to a value that is invalid for the type of token. +/// +/// This class is intentionally implemented as copyable and assignable +/// (using the default version of copy constructor and assignment operator), +/// but it's mainly for internal implementation convenience. Applications will +/// simply refer to Token object as a reference via the \c MasterLexer class. +class MasterToken { +public: + /// \brief Enumeration for token types + /// + /// \note At the time of initial implementation, all numeric tokens + /// that would be extracted from \c MasterLexer should be represented + /// as an unsigned 32-bit integer. If we see the need for larger integers + /// or negative numbers, we can then extend the token types. + enum Type { + END_OF_LINE, ///< End of line detected + END_OF_FILE, ///< End of file detected + INITIAL_WS, ///< White spaces at the beginning of a line after an + ///< end of line or at the beginning of file (if asked + // for detecting it) + NOVALUE_TYPE_MAX = INITIAL_WS, ///< Max integer corresponding to + /// no-value (type only) types. + /// Mainly for internal use. + STRING, ///< A single string + QSTRING, ///< A single string quoted by double-quotes ("). + NUMBER, ///< A decimal number (unsigned 32-bit) + ERROR ///< Error detected in getting a token + }; + + /// \brief Enumeration for lexer error codes + enum ErrorCode { + NOT_STARTED, ///< The lexer is just initialized and has no token + UNBALANCED_PAREN, ///< Unbalanced parentheses detected + UNEXPECTED_END, ///< The lexer reaches the end of line or file + /// unexpectedly + UNBALANCED_QUOTES, ///< Unbalanced quotations detected + NO_TOKEN_PRODUCED, ///< No token was produced. This means programmer + /// error and should never get out of the lexer. + NUMBER_OUT_OF_RANGE, ///< Number was out of range + BAD_NUMBER, ///< Number is expected but not recognized + UNEXPECTED_QUOTES, ///< Unexpected quotes character detected + MAX_ERROR_CODE ///< Max integer corresponding to valid error codes. + /// (excluding this one). Mainly for internal use. + }; + + /// \brief A simple representation of a range of a string. + /// + /// This is a straightforward pair of the start pointer of a string + /// and its length. The \c STRING and \c QSTRING types of tokens + /// will be primarily represented in this form. + /// + /// Any character can be stored in the valid range of the region. + /// In particular, there can be a nul character (\0) in the middle of + /// the region. So the usual string manipulation API may not work + /// as expected. + /// + /// The `MasterLexer` implementation ensures that there are at least + /// len + 1 bytes of valid memory region starting from beg, and that + /// beg[len] is \0. This means the application can use the bytes as a + /// validly nul-terminated C string if there is no intermediate nul + /// character. Note also that due to this property beg is always non + /// null; for an empty string len will be set to 0 and beg[0] is \0. + struct StringRegion { + const char* beg; ///< The start address of the string + size_t len; ///< The length of the string in bytes + }; + + /// \brief Constructor for non-value type of token. + /// + /// \throw InvalidParameter A value type token is specified. + /// \param type The type of the token. It must indicate a non-value + /// type (not larger than \c NOVALUE_TYPE_MAX). + explicit MasterToken(Type type) : type_(type) { + if (type > NOVALUE_TYPE_MAX) { + isc_throw(InvalidParameter, "Token per-type constructor " + "called with invalid type: " << type); + } + } + + /// \brief Constructor for string and quoted-string types of token. + /// + /// The optional \c quoted parameter specifies whether it's a quoted or + /// non quoted string. + /// + /// The string is specified as a pair of a pointer to the start address + /// and its length. Any character can be contained in any position of + /// the valid range (see \c StringRegion). + /// + /// When it's a quoted string, the quotation marks must be excluded + /// from the specified range. + /// + /// \param str_beg The start address of the string + /// \param str_len The size of the string in bytes + /// \param quoted true if it's a quoted string; false otherwise. + MasterToken(const char* str_beg, size_t str_len, bool quoted = false) : + type_(quoted ? QSTRING : STRING) { + val_.str_region_.beg = str_beg; + val_.str_region_.len = str_len; + } + + /// \brief Constructor for number type of token. + /// + /// \brief number An unsigned 32-bit integer corresponding to the token + /// value. + explicit MasterToken(uint32_t number) : type_(NUMBER) { + val_.number_ = number; + } + + /// \brief Constructor for error type of token. + /// + /// \throw InvalidParameter Invalid error code value is specified. + /// \brief error_code A pre-defined constant of \c ErrorCode. + explicit MasterToken(ErrorCode error_code) : type_(ERROR) { + if (!(error_code < MAX_ERROR_CODE)) { + isc_throw(InvalidParameter, "Invalid master lexer error code: " + << error_code); + } + val_.error_code_ = error_code; + } + + /// \brief Return the token type. + /// + /// \throw none + Type getType() const { + return (type_); + } + + /// \brief Return the value of a string-variant token. + /// + /// \throw InvalidOperation Called on a non string-variant types of token. + /// \return A reference to \c StringRegion corresponding to the string + /// token value. + const StringRegion& getStringRegion() const { + if (type_ != STRING && type_ != QSTRING) { + isc_throw(InvalidOperation, + "Token::getStringRegion() for non string-variant type"); + } + return (val_.str_region_); + } + + /// \brief Return the value of a string-variant token as a string object. + /// + /// Note that the underlying string may contain a nul (\0) character + /// in the middle. The returned string object will contain all characters + /// of the valid range of the underlying string. So some string + /// operations such as c_str() may not work as expected. + /// + /// \throw InvalidOperation Called on a non string-variant types of token. + /// \throw std::bad_alloc Resource allocation failure in constructing the + /// string object. + /// \return A std::string object corresponding to the string token value. + std::string getString() const { + std::string ret; + getString(ret); + return (ret); + } + + /// \brief Fill in a string with the value of a string-variant token. + /// + /// This is similar to the other version of \c getString(), but + /// the caller is supposed to pass a placeholder string object. + /// This will be more efficient if the caller uses the same + /// \c MasterLexer repeatedly and needs to get string token in the + /// form of a string object many times as this version could reuse + /// the existing internal storage of the passed string. + /// + /// Any existing content of the passed string will be removed. + /// + /// \throw InvalidOperation Called on a non string-variant types of token. + /// \throw std::bad_alloc Resource allocation failure in constructing the + /// string object. + /// + /// \param ret A string object to be filled with the token string. + void getString(std::string& ret) const { + if (type_ != STRING && type_ != QSTRING) { + isc_throw(InvalidOperation, + "Token::getString() for non string-variant type"); + } + ret.assign(val_.str_region_.beg, + val_.str_region_.beg + val_.str_region_.len); + } + + /// \brief Return the value of a string-variant token as a string object. + /// + /// \throw InvalidOperation Called on a non number type of token. + /// \return The integer corresponding to the number token value. + uint32_t getNumber() const { + if (type_ != NUMBER) { + isc_throw(InvalidOperation, + "Token::getNumber() for non number type"); + } + return (val_.number_); + } + + /// \brief Return the error code of a error type token. + /// + /// \throw InvalidOperation Called on a non error type of token. + /// \return The error code of the token. + ErrorCode getErrorCode() const { + if (type_ != ERROR) { + isc_throw(InvalidOperation, + "Token::getErrorCode() for non error type"); + } + return (val_.error_code_); + }; + + /// \brief Return a textual description of the error of a error type token. + /// + /// The returned string would be useful to produce a log message when + /// a zone file parser encounters an error. + /// + /// \throw InvalidOperation Called on a non error type of token. + /// \throw std::bad_alloc Resource allocation failure in constructing the + /// string object. + /// \return A string object that describes the meaning of the error. + std::string getErrorText() const; + +private: + Type type_; // this is not const so the class can be assignable + + // We use a union to represent different types of token values via the + // unified Token class. The class integrity should ensure valid operation + // on the union; getter methods should only refer to the member set at + // the construction. + union { + StringRegion str_region_; + uint32_t number_; + ErrorCode error_code_; + } val_; +}; + +/// \brief Tokenizer for parsing DNS master files. +/// +/// The \c MasterLexer class provides tokenize interfaces for parsing DNS +/// master files. It understands some special rules of master files as +/// defined in RFC 1035, such as comments, character escaping, and multi-line +/// data, and provides the user application with the actual data in a +/// more convenient form such as a std::string object. +/// +/// In order to support the $INCLUDE notation, this class is designed to be +/// able to operate on multiple files or input streams in the nested way. +/// The \c pushSource() and \c popSource() methods correspond to the push +/// and pop operations. +/// +/// While this class is public, it is less likely to be used by normal +/// applications; it's mainly expected to be used within this library, +/// specifically by the \c MasterLoader class and \c Rdata implementation +/// classes. +/// +/// \note The error handling policy of this class is slightly different from +/// that of other classes of this library. We generally throw an exception +/// for an invalid input, whether it's more likely to be a program error or +/// a "user error", which means an invalid input that comes from outside of +/// the library. But, this class returns an error code for some certain +/// types of user errors instead of throwing an exception. Such cases include +/// a syntax error identified by the lexer or a misspelled file name that +/// causes a system error at the time of open. This is based on the assumption +/// that the main user of this class is a parser of master files, where +/// we want to give an option to ignore some non fatal errors and continue +/// the parsing. This will be useful if it just performs overall error +/// checks on a master file. When the (immediate) caller needs to do explicit +/// error handling, exceptions are not that a useful tool for error reporting +/// because we cannot separate the normal and error cases anyway, which would +/// be one major advantage when we use exceptions. And, exceptions are +/// generally more expensive, either when it happens or just by being able +/// to handle with \c try and \c catch (depending on the underlying +/// implementation of the exception handling). For these reasons, some of +/// this class does not throw for an error that would be reported as an +/// exception in other classes. +class MasterLexer : public boost::noncopyable { + friend class master_lexer_internal::State; +public: + /// \brief Exception thrown when we fail to read from the input + /// stream or file. + class ReadError : public Unexpected { + public: + ReadError(const char* file, size_t line, const char* what) : + Unexpected(file, line, what) + {} + }; + + /// \brief Exception thrown from a wrapper version of + /// \c MasterLexer::getNextToken() for non fatal errors. + /// + /// See the method description for more details. + /// + /// The \c token_ member variable (read-only) is set to a \c MasterToken + /// object of type ERROR indicating the reason for the error. + class LexerError : public isc::dns::Exception { + public: + LexerError(const char* file, size_t line, MasterToken error_token) : + isc::dns::Exception(file, line, error_token.getErrorText().c_str()), + token_(error_token) + {} + const MasterToken token_; + }; + + /// \brief Special value for input source size meaning "unknown". + /// + /// This constant value will be used as a return value of + /// \c getTotalSourceSize() when the size of one of the pushed sources + /// is unknown. Note that this value itself is a valid integer in the + /// range of the type, so there's still a small possibility of + /// ambiguity. In practice, however, the value should be sufficiently + /// large that should eliminate the possibility. + static const size_t SOURCE_SIZE_UNKNOWN; + + /// \brief Options for getNextToken. + /// + /// A compound option, indicating multiple options are set, can be + /// specified using the logical OR operator (operator|()). + enum Options { + NONE = 0, ///< No option + INITIAL_WS = 1, ///< recognize begin-of-line spaces after an + ///< end-of-line + QSTRING = 2, ///< recognize quoted string + NUMBER = 4 ///< recognize numeric text as integer + }; + + /// \brief The constructor. + /// + /// \throw std::bad_alloc Internal resource allocation fails (rare case). + MasterLexer(); + + /// \brief The destructor. + /// + /// It internally closes any remaining input sources. + ~MasterLexer(); + + /// \brief Open a file and make it the current input source of MasterLexer. + /// + /// The opened file can be explicitly closed by the \c popSource() method; + /// if \c popSource() is not called within the lifetime of the + /// \c MasterLexer, it will be closed in the destructor. + /// + /// In the case possible system errors in opening the file (most likely + /// because of specifying a non-existent or unreadable file), it returns + /// false, and if the optional \c error parameter is non null, it will be + /// set to a description of the error (any existing content of the string + /// will be discarded). If opening the file succeeds, the given + /// \c error parameter will be intact. + /// + /// Note that this method has two styles of error reporting: one by + /// returning \c false (and setting \c error optionally) and the other + /// by throwing an exception. See the note for the class description + /// about the distinction. + /// + /// \throw InvalidParameter filename is null + /// \param filename A non null string specifying a master file + /// \param error If non null, a placeholder to set error description in + /// case of failure. + /// + /// \return true if pushing the file succeeds; false otherwise. + bool pushSource(const char* filename, std::string* error = 0); + + /// \brief Make the given stream the current input source of MasterLexer. + /// + /// The caller still holds the ownership of the passed stream; it's the + /// caller's responsibility to keep it valid as long as it's used in + /// \c MasterLexer or to release any resource for the stream after that. + /// The caller can explicitly tell \c MasterLexer to stop using the + /// stream by calling the \c popSource() method. + /// + /// The data in \c input must be complete at the time of this call. + /// The behavior of the lexer is undefined if the caller builds or adds + /// data in \c input after pushing it. + /// + /// Except for rare case system errors such as memory allocation failure, + /// this method is generally expected to be exception free. However, + /// it can still throw if it encounters an unexpected failure when it + /// tries to identify the "size" of the input source (see + /// \c getTotalSourceSize()). It's an unexpected result unless the + /// caller intentionally passes a broken stream; otherwise it would mean + /// some system-dependent unexpected behavior or possibly an internal bug. + /// In these cases it throws an \c Unexpected exception. Note that + /// this version of the method doesn't return a boolean unlike the + /// other version that takes a file name; since this failure is really + /// unexpected and can be critical, it doesn't make sense to give the + /// caller an option to continue (other than by explicitly catching the + /// exception). + /// + /// \throw Unexpected An unexpected failure happens in initialization. + /// + /// \param input An input stream object that produces textual + /// representation of DNS RRs. + void pushSource(std::istream& input); + + /// \brief Stop using the most recently opened input source (file or + /// stream). + /// + /// If it's a file, the previously opened file will be closed internally. + /// If it's a stream, \c MasterLexer will simply stop using + /// the stream; the caller can assume it will be never used in + /// \c MasterLexer thereafter. + /// + /// This method must not be called when there is no source pushed for + /// \c MasterLexer. This method is otherwise exception free. + /// + /// \throw isc::InvalidOperation Called with no pushed source. + void popSource(); + + /// \brief Get number of sources inside the lexer. + /// + /// This method never throws. + size_t getSourceCount() const; + + /// \brief Return the name of the current input source name. + /// + /// If it's a file, it will be the C string given at the corresponding + /// \c pushSource() call, that is, its filename. If it's a stream, it will + /// be formatted as \c "stream-%p" where \c %p is hex representation + /// of the address of the stream object. + /// + /// If there is no opened source at the time of the call, this method + /// returns an empty string. + /// + /// \throw std::bad_alloc Resource allocation failed for string + /// construction (rare case) + /// + /// \return A string representation of the current source (see the + /// description) + std::string getSourceName() const; + + /// \brief Return the input source line number. + /// + /// If there is an opened source, the return value will be a non-0 + /// integer indicating the line number of the current source where + /// the \c MasterLexer is currently working. The expected usage of + /// this value is to print a helpful error message when parsing fails + /// by specifically identifying the position of the error. + /// + /// If there is no opened source at the time of the call, this method + /// returns 0. + /// + /// \throw None + /// + /// \return The current line number of the source (see the description) + size_t getSourceLine() const; + + /// \brief Return the total size of pushed sources. + /// + /// This method returns the sum of the size of sources that have been + /// pushed to the lexer by the time of the call. It would give the + /// caller some hint about the amount of data the lexer is working on. + /// + /// The size of a normal file is equal to the file size at the time of + /// the source is pushed. The size of other type of input stream is + /// the size of the data available in the stream at the time of the + /// source is pushed. + /// + /// In some special cases, it's possible that the size of the file or + /// stream is unknown. It happens, for example, if the standard input + /// is associated with a pipe from the output of another process and it's + /// specified as an input source. If the size of some of the pushed + /// source is unknown, this method returns SOURCE_SIZE_UNKNOWN. + /// + /// The total size won't change when a source is popped. So the return + /// values of this method will monotonically increase or + /// \c SOURCE_SIZE_UNKNOWN; once it returns \c SOURCE_SIZE_UNKNOWN, + /// any subsequent call will also result in that value, by the above + /// definition. + /// + /// Before pushing any source, it returns 0. + /// + /// \throw None + size_t getTotalSourceSize() const; + + /// \brief Return the position of lexer in the pushed sources so far. + /// + /// This method returns the position in terms of the number of recognized + /// characters from all sources that have been pushed by the time of the + /// call. Conceptually, the position in a single source is the offset + /// from the beginning of the file or stream to the current "read cursor" + /// of the lexer. The return value of this method is the sum of the + /// positions in all the pushed sources. If any of the sources has + /// already been popped, the position of the source at the time of the + /// pop operation will be used for the calculation. + /// + /// If the lexer reaches the end for each of all the pushed sources, + /// the return value should be equal to that of \c getTotalSourceSize(). + /// It's generally expected that a source is popped when the lexer + /// reaches the end of the source. So, when the application of this + /// class parses all contents of all sources, possibly with multiple + /// pushes and pops, the return value of this method and + /// \c getTotalSourceSize() should be identical (unless the latter + /// returns SOURCE_SIZE_UNKNOWN). But this is not necessarily + /// guaranteed as the application can pop a source in the middle of + /// parsing it. + /// + /// Before pushing any source, it returns 0. + /// + /// The return values of this method and \c getTotalSourceSize() would + /// give the caller an idea of the progress of the lexer at the time of + /// the call. Note, however, that since it's not predictable whether + /// more sources will be pushed after the call, the progress determined + /// this way may not make much sense; it can only give an informational + /// hint of the progress. + /// + /// Note that the conceptual "read cursor" would move backward after a + /// call to \c ungetToken(), in which case this method will return a + /// smaller value. That is, unlike \c getTotalSourceSize(), return + /// values of this method may not always monotonically increase. + /// + /// \throw None + size_t getPosition() const; + + /// \brief Parse and return another token from the input. + /// + /// It reads a bit of the last opened source and produces another token + /// found in it. + /// + /// This method does not provide the strong exception guarantee. Generally, + /// if it throws, the object should not be used any more and should be + /// discarded. It was decided all the exceptions thrown from here are + /// serious enough that aborting the loading process is the only reasonable + /// recovery anyway, so the strong exception guarantee is not needed. + /// + /// \param options The options can be used to modify the tokenization. + /// The method can be made reporting things which are usually ignored + /// by this parameter. Multiple options can be passed at once by + /// bitwise or (eg. option1 | option 2). See description of available + /// options. + /// \return Next token found in the input. Note that the token refers to + /// some internal data in the lexer. It is valid only until + /// getNextToken or ungetToken is called. Also, the token becomes + /// invalid when the lexer is destroyed. + /// \throw isc::InvalidOperation in case the source is not available. This + /// may mean the pushSource() has not been called yet, or that the + /// current source has been read past the end. + /// \throw ReadError in case there's problem reading from the underlying + /// source (eg. I/O error in the file on the disk). + /// \throw std::bad_alloc in case allocation of some internal resources + /// or the token fail. + const MasterToken& getNextToken(Options options = NONE); + + /// \brief Parse the input for the expected type of token. + /// + /// This method is a wrapper of the other version, customized for the case + /// where a particular type of token is expected as the next one. + /// More specifically, it's intended to be used to get tokens for RDATA + /// fields. Since most RDATA types of fixed format, the token type is + /// often predictable and the method interface can be simplified. + /// + /// This method basically works as follows: it gets the type of the + /// expected token, calls the other version of \c getNextToken(Options), + /// and returns the token if it's of the expected type (due to the usage + /// assumption this should be normally the case). There are some non + /// trivial details though: + /// + /// - If the expected type is MasterToken::QSTRING, both quoted and + /// unquoted strings are recognized and returned. + /// - A string with quotation marks is not recognized as a + /// - MasterToken::STRING. You have to get it as a + /// - MasterToken::QSTRING. + /// - If the optional \c eol_ok parameter is \c true (very rare case), + /// MasterToken::END_OF_LINE and MasterToken::END_OF_FILE are recognized + /// and returned if they are found instead of the expected type of + /// token. + /// - If the next token is not of the expected type (including the case + /// a number is expected but it's out of range), ungetToken() is + /// internally called so the caller can re-read that token. + /// - If other types or errors (such as unbalanced parentheses) are + /// detected, the erroneous part isn't "ungotten"; the caller can + /// continue parsing after that part. + /// + /// In some very rare cases where the RDATA has an optional trailing field, + /// the \c eol_ok parameter would be set to \c true. This way the caller + /// can handle both cases (the field does or does not exist) by a single + /// call to this method. In all other cases \c eol_ok should be set to + /// \c false, and that is the default and can be omitted. + /// + /// Unlike the other version of \c getNextToken(Options), this method + /// throws an exception of type \c LexerError for non fatal errors such as + /// broken syntax or encountering an unexpected type of token. This way + /// the caller can write RDATA parser code without bothering to handle + /// errors for each field. For example, pseudo parser code for MX RDATA + /// would look like this: + /// \code + /// const uint32_t pref = + /// lexer.getNextToken(MasterToken::NUMBER).getNumber(); + /// // check if pref is the uint16_t range; no other check is needed. + /// const Name mx(lexer.getNextToken(MasterToken::STRING).getString()); + /// \endcode + /// + /// In the case where \c LexerError exception is thrown, it's expected + /// to be handled comprehensively for the parser of the RDATA or at a + /// higher layer. The \c token_ member variable of the corresponding + /// \c LexerError exception object stores a token of type + /// \c MasterToken::ERROR that indicates the reason for the error. + /// + /// Due to the specific intended usage of this method, only a subset + /// of \c MasterToken::Type values are acceptable for the \c expect + /// parameter: \c MasterToken::STRING, \c MasterToken::QSTRING, and + /// \c MasterToken::NUMBER. Specifying other values will result in + /// an \c InvalidParameter exception. + /// + /// \throw InvalidParameter The expected token type is not allowed for + /// this method. + /// \throw LexerError The lexer finds non fatal error or it finds an + /// \throw other Anything the other version of getNextToken() can throw. + /// + /// \param expect Expected type of token. Must be either STRING, QSTRING, + /// or NUMBER. + /// \param eol_ok \c true iff END_OF_LINE or END_OF_FILE is acceptable. + /// \return The expected type of token. + const MasterToken& getNextToken(MasterToken::Type expect, + bool eol_ok = false); + + /// \brief Return the last token back to the lexer. + /// + /// The method undoes the lasts call to getNextToken(). If you call the + /// getNextToken() again with the same options, it'll return the same + /// token. If the options are different, it may return a different token, + /// but it acts as if the previous getNextToken() was never called. + /// + /// It is possible to return only one token back in time (you can't call + /// ungetToken() twice in a row without calling getNextToken() in between + /// successfully). + /// + /// It does not work after change of source (by pushSource or popSource). + /// + /// \throw isc::InvalidOperation If called second time in a row or if + /// getNextToken() was not called since the last change of the source. + void ungetToken(); + +private: + struct MasterLexerImpl; + boost::shared_ptr<MasterLexerImpl> impl_; +}; + +/// \brief Operator to combine \c MasterLexer options +/// +/// This is a trivial shortcut so that compound options can be specified +/// in an intuitive way. +inline MasterLexer::Options +operator|(MasterLexer::Options o1, MasterLexer::Options o2) { + return (static_cast<MasterLexer::Options>( + static_cast<unsigned>(o1) | static_cast<unsigned>(o2))); +} + +} // namespace dns +} // namespace isc +#endif // MASTER_LEXER_H diff --git a/src/lib/dns/master_lexer_inputsource.cc b/src/lib/dns/master_lexer_inputsource.cc new file mode 100644 index 0000000..05cf633 --- /dev/null +++ b/src/lib/dns/master_lexer_inputsource.cc @@ -0,0 +1,220 @@ +// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/isc_assert.h> +#include <dns/master_lexer_inputsource.h> +#include <dns/master_lexer.h> + +#include <istream> +#include <iostream> +#include <cerrno> +#include <cstring> + +namespace isc { +namespace dns { +namespace master_lexer_internal { + +namespace { // unnamed namespace + +std::string +createStreamName(const std::istream& input_stream) { + std::stringstream ss; + ss << "stream-" << &input_stream; + return (ss.str()); +} + +size_t +getStreamSize(std::istream& is) { + errno = 0; // see below + is.seekg(0, std::ios_base::end); + if (is.bad()) { + // This means the istream has an integrity error. It doesn't make + // sense to continue from this point, so we treat it as a fatal error. + isc_throw(InputSource::OpenError, + "failed to seek end of input source"); + } else if (is.fail() || errno != 0) { + // This is an error specific to seekg(). There can be several + // reasons, but the most likely cause in this context is that the + // stream is associated with a special type of file such as a pipe. + // In this case, it's more likely that other main operations of + // the input source work fine, so we continue with just setting + // the stream size to "unknown". + // + // (At least some versions of) Solaris + SunStudio shows deviant + // behavior here: seekg() apparently calls lseek(2) internally, but + // even if it fails it doesn't set the error bits of istream. That will + // confuse the rest of this function, so, as a heuristic workaround + // we check errno and handle any non 0 value as fail(). + is.clear(); // clear this error not to confuse later ops. + return (MasterLexer::SOURCE_SIZE_UNKNOWN); + } + const std::streampos len = is.tellg(); + size_t ret = len; + if (len == static_cast<std::streampos>(-1)) { // cast for some compilers + if (!is.fail()) { + // tellg() returns -1 if istream::fail() would be true, but it's + // not guaranteed that it shouldn't be returned in other cases. + // In fact, with the combination of SunStudio and stlport, + // a stringstream created by the default constructor showed that + // behavior. We treat such cases as an unknown size. + ret = MasterLexer::SOURCE_SIZE_UNKNOWN; + } else { + isc_throw(InputSource::OpenError, "failed to get input size"); + } + } + is.seekg(0, std::ios::beg); + if (is.fail()) { + isc_throw(InputSource::OpenError, + "failed to seek beginning of input source"); + } + isc_throw_assert(len >= 0 || ret == MasterLexer::SOURCE_SIZE_UNKNOWN); + return (ret); +} + +} // end of unnamed namespace + +// Explicit definition of class static constant. The value is given in the +// declaration so it's not needed here. +const int InputSource::END_OF_STREAM; + +InputSource::InputSource(std::istream& input_stream) : + at_eof_(false), + line_(1), + saved_line_(line_), + buffer_pos_(0), + total_pos_(0), + name_(createStreamName(input_stream)), + input_(input_stream), + input_size_(getStreamSize(input_)) { +} + +namespace { +// A helper to initialize InputSource::input_ in the member initialization +// list. +std::istream& +openFileStream(std::ifstream& file_stream, const char* filename) { + errno = 0; + file_stream.open(filename); + if (file_stream.fail()) { + std::string error_txt("Error opening the input source file: "); + error_txt += filename; + if (errno != 0) { + error_txt += "; possible cause: "; + error_txt += std::strerror(errno); + } + isc_throw(InputSource::OpenError, error_txt); + } + + return (file_stream); +} +} + +InputSource::InputSource(const char* filename) : + at_eof_(false), + line_(1), + saved_line_(line_), + buffer_pos_(0), + total_pos_(0), + name_(filename), + input_(openFileStream(file_stream_, filename)), + input_size_(getStreamSize(input_)) { +} + +InputSource::~InputSource() { + if (file_stream_.is_open()) { + file_stream_.close(); + } +} + +int +InputSource::getChar() { + if (buffer_pos_ == buffer_.size()) { + // We may have reached EOF at the last call to + // getChar(). at_eof_ will be set then. We then simply return + // early. + if (at_eof_) { + return (END_OF_STREAM); + } + // We are not yet at EOF. Read from the stream. + const int c = input_.get(); + // Have we reached EOF now? If so, set at_eof_ and return early, + // but don't modify buffer_pos_ (which should still be equal to + // the size of buffer_). + if (input_.eof()) { + at_eof_ = true; + return (END_OF_STREAM); + } + // This has to come after the .eof() check as some + // implementations seem to check the eofbit also in .fail(). + if (input_.fail()) { + isc_throw(MasterLexer::ReadError, + "Error reading from the input stream: " << getName()); + } + buffer_.push_back(c); + } + + const int c = buffer_[buffer_pos_]; + ++buffer_pos_; + ++total_pos_; + if (c == '\n') { + ++line_; + } + + return (c); +} + +void +InputSource::ungetChar() { + if (at_eof_) { + at_eof_ = false; + } else if (buffer_pos_ == 0) { + isc_throw(UngetBeforeBeginning, + "Cannot skip before the start of buffer"); + } else { + --buffer_pos_; + --total_pos_; + if (buffer_[buffer_pos_] == '\n') { + --line_; + } + } +} + +void +InputSource::ungetAll() { + isc_throw_assert(total_pos_ >= buffer_pos_); + total_pos_ -= buffer_pos_; + buffer_pos_ = 0; + line_ = saved_line_; + at_eof_ = false; +} + +void +InputSource::saveLine() { + saved_line_ = line_; +} + +void +InputSource::compact() { + if (buffer_pos_ == buffer_.size()) { + buffer_.clear(); + } else { + buffer_.erase(buffer_.begin(), buffer_.begin() + buffer_pos_); + } + + buffer_pos_ = 0; +} + +void +InputSource::mark() { + saveLine(); + compact(); +} + +} // namespace master_lexer_internal +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/master_lexer_inputsource.h b/src/lib/dns/master_lexer_inputsource.h new file mode 100644 index 0000000..2d6a613 --- /dev/null +++ b/src/lib/dns/master_lexer_inputsource.h @@ -0,0 +1,185 @@ +// Copyright (C) 2012-2024 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 DNS_INPUTSOURCE_H +#define DNS_INPUTSOURCE_H + +#include <exceptions/exceptions.h> + +#include <boost/noncopyable.hpp> + +#include <iostream> +#include <fstream> +#include <string> +#include <vector> + +namespace isc { +namespace dns { +namespace master_lexer_internal { + +/// \brief An input source that is used internally by MasterLexer. +/// +/// This is a helper internal class for MasterLexer, and represents +/// state of a single source of the entire zone data to be +/// parsed. Normally this means the master zone file, but MasterLexer +/// can have multiple InputSources if $INCLUDE is used. The source can +/// also be generic input stream (std::istream). +/// +/// This class is not meant for public use. We also enforce that +/// instances are non-copyable. +class InputSource : boost::noncopyable { +public: + /// \brief Returned by getChar() when end of stream is reached. + /// + /// \note C++ allows a static const class member of an integral type to + /// be used without explicit definition as long as its address isn't + /// required. But, since this is a public member variable and we cannot + /// assume how it's used, we give a definition in the implementation. + static const int END_OF_STREAM = -1; + + /// \brief Exception thrown when ungetChar() is made to go before + /// the start of buffer. + struct UngetBeforeBeginning : public OutOfRange { + UngetBeforeBeginning(const char* file, size_t line, const char* what) : + OutOfRange(file, line, what) + {} + }; + + /// \brief Exception thrown when we fail to open the input file. + struct OpenError : public Unexpected { + OpenError(const char* file, size_t line, const char* what) : + Unexpected(file, line, what) + {} + }; + + /// \brief Constructor which takes an input stream. The stream is + /// read-from, but it is not closed. + /// + /// \throws OpenError If the data size of the input stream cannot be + /// detected. + explicit InputSource(std::istream& input_stream); + + /// \brief Constructor which takes a filename to read from. The + /// associated file stream is managed internally. + /// + /// \throws OpenError when opening the input file fails or the size of + /// the file cannot be detected. + explicit InputSource(const char* filename); + + /// \brief Destructor + ~InputSource(); + + /// \brief Returns a name for the InputSource. Typically this is the + /// filename, but if the InputSource was constructed for an + /// \c std::istream, it returns a name in the format "stream-%p". + const std::string& getName() const { + return (name_); + } + + /// \brief Returns the size of the input source in bytes. + /// + /// If the size is unknown, it returns \c MasterLexer::SOURCE_SIZE_UNKNOWN. + /// + /// See \c MasterLexer::getTotalSourceSize() for the definition of + /// the size of sources and for when the size can be unknown. + /// + /// \throw None + size_t getSize() const { + return (input_size_); + } + + /// \brief Returns the current read position in the input source. + /// + /// This method returns the position of the character that was last + /// retrieved from the source. Unless some characters have been + /// "ungotten" by \c ungetChar() or \c ungetAll(), this value is equal + /// to the number of calls to \c getChar() until it reaches the + /// END_OF_STREAM. Note that the position of the first character in + /// the source is 1. At the point of the last character, the return value + /// of this method should be equal to that of \c getSize(), and + /// recognizing END_OF_STREAM doesn't increase the position. + /// + /// If \c ungetChar() or \c ungetAll() is called, the position is + /// decreased by the number of "ungotten" characters. So the return + /// values may not always monotonically increase. + /// + /// \throw None + size_t getPosition() const { + return (total_pos_); + } + + /// \brief Returns if the input source is at end of file. + bool atEOF() const { + return (at_eof_); + } + + /// \brief Returns the current line number being read. + size_t getCurrentLine() const { + return (line_); + } + + /// \brief Saves the current line being read. Later, when + /// \c ungetAll() is called, it skips back to the last-saved line. + /// + /// TODO: Please make this method private if it is unused after the + /// MasterLexer implementation is complete (and only \c mark() is + /// used instead). + void saveLine(); + + /// Removes buffered content before the current location in the + /// \c InputSource. It's not possible to \c ungetChar() after this, + /// unless we read more data using \c getChar(). + /// + /// TODO: Please make this method private if it is unused after the + /// MasterLexer implementation is complete (and only \c mark() is + /// used instead). + void compact(); + + /// Calls \c saveLine() and \c compact() in sequence. + void mark(); + + /// \brief Returns a single character from the input source. If end + /// of file is reached, \c END_OF_STREAM is returned. + /// + /// \throws MasterLexer::ReadError when reading from the input stream or + /// file fails. + int getChar(); + + /// \brief Skips backward a single character in the input + /// source. The last-read character is unget. + /// + /// \throws UngetBeforeBeginning if we go backwards past the start + /// of reading, or backwards past the last time compact() was + /// called. + void ungetChar(); + + /// Forgets what was read, and skips back to the position where + /// \c compact() was last called. If \c compact() was not called, it + /// skips back to where reading started. If \c saveLine() was called + /// previously, it sets the current line number to the line number + /// saved. + void ungetAll(); + +private: + bool at_eof_; + size_t line_; + size_t saved_line_; + + std::vector<char> buffer_; + size_t buffer_pos_; + size_t total_pos_; + + const std::string name_; + std::ifstream file_stream_; + std::istream& input_; + const size_t input_size_; +}; + +} // namespace master_lexer_internal +} // namespace dns +} // namespace isc + +#endif // DNS_INPUTSOURCE_H diff --git a/src/lib/dns/master_lexer_state.h b/src/lib/dns/master_lexer_state.h new file mode 100644 index 0000000..2165e84 --- /dev/null +++ b/src/lib/dns/master_lexer_state.h @@ -0,0 +1,134 @@ +// Copyright (C) 2012-2024 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 MASTER_LEXER_STATE_H +#define MASTER_LEXER_STATE_H + +#include <dns/master_lexer.h> + +namespace isc { +namespace dns { + +namespace master_lexer_internal { + +/// \brief Tokenization state for \c MasterLexer. +/// +/// This is a base class of classes that represent various states of a single +/// tokenization session of \c MasterLexer, i.e., the states used for a +/// single call to \c MasterLexer::getNextToken(). +/// +/// It follows the convention of the state design pattern: each derived class +/// corresponds to a specific state, and the state transition takes place +/// through the virtual method named \c handle(). The \c handle() method +/// takes the main \c MasterLexer object that holds all necessary internal +/// context, and updates it as necessary; each \c State derived class is +/// completely stateless. +/// +/// The initial transition takes place in a static method of the base class, +/// \c start(). This is mainly for implementation convenience; we need to +/// pass options given to \c MasterLexer::getNextToken() for the initial +/// state, so it makes more sense to separate the interface for the transition +/// from the initial state. +/// +/// If the whole lexer transition is completed within start(), it sets the +/// identified token and returns null; otherwise it returns a pointer to +/// an object of a specific state class that completes the session +/// on the call of handle(). +/// +/// As is usual in the state design pattern, the \c State class is made +/// a friend class of \c MasterLexer and can refer to its internal details. +/// This is intentional; essentially its a part of \c MasterLexer and +/// is defined as a separate class only for implementation clarity and better +/// testability. It's defined in a publicly visible header, but that's only +/// for testing purposes. No normal application or even no other classes of +/// this library are expected to use this class. +class State { +public: + /// \brief Virtual destructor. + /// + /// In our usage this actually doesn't matter, but some compilers complain + /// about it and we need to silence them. + virtual ~State() {} + + /// \brief Begin state transitions to get the next token. + /// + /// This is the first method that \c MasterLexer needs to call for a + /// tokenization session. The lexer passes a reference to itself + /// and options given in \c getNextToken(). + /// + /// \throw MasterLexer::ReadError Unexpected I/O error + /// \throw std::bad_alloc Internal resource allocation failure + /// + /// \param lexer The lexer object that holds the main context. + /// \param options The options passed to getNextToken(). + /// \return A pointer to the next state object or null if the transition + /// is completed. + static const State* start(MasterLexer& lexer, + MasterLexer::Options options); + + /// \brief Handle the process of one specific state. + /// + /// This method is expected to be called on the object returned by + /// start(). In the usual state transition design pattern, it would + /// return the next state. But as we noticed, we never have another + /// state, so we simplify it by not returning anything instead of + /// returning null every time. + /// + /// \throw MasterLexer::ReadError Unexpected I/O error + /// \throw std::bad_alloc Internal resource allocation failure + /// + /// \param lexer The lexer object that holds the main context. + virtual void handle(MasterLexer& lexer) const = 0; + + /// \brief Types of states. + /// + /// Specific states are basically hidden within the implementation, + /// but we'd like to allow tests to examine them, so we provide + /// a way to get an instance of a specific state. + enum ID { + CRLF, ///< Just seen a carriage-return character + String, ///< Handling a string token + QString, ///< Handling a quoted string token + Number ///< Handling a number + }; + + /// \brief Returns a \c State instance of the given state. + /// + /// This is provided only for testing purposes so tests can check + /// the behavior of each state separately. \c MasterLexer shouldn't + /// need this method. + static const State& getInstance(ID state_id); + + /// \name Read-only accessors for testing purposes. + /// + /// These allow tests to inspect some selected portion of the internal + /// states of \c MasterLexer. These shouldn't be used except for testing + /// purposes. + ///@{ + bool wasLastEOL(const MasterLexer& lexer) const; + const MasterToken& getToken(const MasterLexer& lexer) const; + size_t getParenCount(const MasterLexer& lexer) const; + ///@} + +protected: + /// \brief An accessor to the internal implementation class of + /// \c MasterLexer. + /// + /// This is provided for specific derived classes as they are not direct + /// friends of \c MasterLexer. + /// + /// \param lexer The lexer object that holds the main context. + /// \return A pointer to the implementation class object of the given + /// lexer. This is never null. + MasterLexer::MasterLexerImpl* getLexerImpl(MasterLexer& lexer) const { + return (lexer.impl_.get()); + } +}; + +} // namespace master_lexer_internal +} // namespace dns +} // namespace isc +#endif // MASTER_LEXER_STATE_H diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc new file mode 100644 index 0000000..e771bd3 --- /dev/null +++ b/src/lib/dns/master_loader.cc @@ -0,0 +1,742 @@ +// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/isc_assert.h> +#include <dns/master_loader.h> +#include <dns/master_lexer.h> +#include <dns/name.h> +#include <dns/rdataclass.h> +#include <dns/rrttl.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/rdata.h> + +#include <boost/format.hpp> +#include <boost/algorithm/string/predicate.hpp> // for iequals +#include <boost/scoped_ptr.hpp> +#include <boost/shared_ptr.hpp> + +#include <string> +#include <memory> +#include <vector> + +#include <cstdio> // for sscanf() + +using std::string; +using std::unique_ptr; +using std::vector; +using std::pair; +using boost::algorithm::iequals; +using boost::shared_ptr; + +namespace isc { +namespace dns { + +namespace { + +// An internal exception, used to control the code flow in case of errors. +// It is thrown during the loading and caught later, not to be propagated +// outside of the file. +class InternalException : public isc::Exception { +public: + InternalException(const char* filename, size_t line, const char* what) : + Exception(filename, line, what) + {} +}; + +} // end unnamed namespace + +/// \brief Private implementation class for the \c MasterLoader +/// +/// This class is used internally by the \c MasterLoader and is not +/// publicly visible. It is present to avoid polluting the public API +/// with internal implementation details of the \c MasterLoader. +// cppcheck-suppress noConstructor +class MasterLoader::MasterLoaderImpl { +public: + /// \brief Constructor. + /// + /// \param master_file Path to the file to load. + /// \param zone_origin The origin of zone to be expected inside + /// the master file. Currently unused, but it is expected to + /// be used for some validation. + /// \param zone_class The class of zone to be expected inside the + /// master file. + /// \param callbacks The callbacks by which it should report problems. + /// Usually, the callback carries a filename and line number of the + /// input where the problem happens. There's a special case of empty + /// filename and zero line in case the opening of the top-level master + /// file fails. + /// \param add_callback The callback which would be called with each + /// loaded RR. + /// \param options Options for the parsing, which is bitwise-or of + /// the Options values or DEFAULT. If the MANY_ERRORS option is + /// included, the parser tries to continue past errors. If it + /// is not included, it stops at first encountered error. + /// \throw std::bad_alloc when there's not enough memory. + MasterLoaderImpl(const char* master_file, + const Name& zone_origin, + const RRClass& zone_class, + const MasterLoaderCallbacks& callbacks, + const AddRRCallback& add_callback, + MasterLoader::Options options) : + lexer_(), + zone_origin_(zone_origin), + active_origin_(zone_origin), + zone_class_(zone_class), + callbacks_(callbacks), + add_callback_(add_callback), + options_(options), + master_file_(master_file), + initialized_(false), + ok_(true), + many_errors_((options & MANY_ERRORS) != 0), + previous_name_(false), + complete_(false), + seen_error_(false), + warn_rfc1035_ttl_(true), + rr_count_(0) { + } + + /// \brief Wrapper around \c MasterLexer::pushSource() (file version) + /// + /// This method is used as a wrapper around the lexer's + /// \c pushSource() to also save the current origin and the last + /// seen name (to be restored upon \c popSource()). It also calls + /// \c pushSource(). See \c doInclude() implementation for more + /// details. + /// + /// \param filename Path to the file to push as a new source. + /// \param current_origin The current origin name to save. + void pushSource(const std::string& filename, const Name& current_origin) { + std::string error; + if (!lexer_.pushSource(filename.c_str(), &error)) { + if (initialized_) { + isc_throw(InternalException, error.c_str()); + } else { + // Top-level file + reportError("", 0, error); + ok_ = false; + } + } + // Store the current status, so we can recover it upon popSource + include_info_.push_back(IncludeInfo(current_origin, last_name_)); + initialized_ = true; + previous_name_ = false; + } + + /// \brief Wrapper around \c MasterLexer::pushSource() (stream version) + /// + /// Similar to \c pushSource(). This method need not save the + /// current origin as it is not used with $INCLUDE processing. + /// + /// \param stream The input stream to use as a new source. + void pushStreamSource(std::istream& stream) { + lexer_.pushSource(stream); + initialized_ = true; + } + + /// \brief Implementation of \c MasterLoader::loadIncremental() + /// + /// See \c MasterLoader::loadIncremental() for details. + bool loadIncremental(size_t count_limit); + + /// \brief Return the total size of the input sources pushed so + /// far. See \c MasterLexer::getTotalSourceSize(). + size_t getSize() const { + return (lexer_.getTotalSourceSize()); + } + + /// \brief Return the line number being parsed in the pushed input + /// sources. See \c MasterLexer::getPosition(). + size_t getPosition() const { + return (lexer_.getPosition()); + } + +private: + /// \brief Report an error using the callbacks that were supplied + /// during \c MasterLoader construction. Note that this method also + /// throws \c MasterLoaderError exception if necessary, so the + /// caller need not throw it. + void reportError(const std::string& filename, size_t line, + const std::string& reason) { + seen_error_ = true; + callbacks_.error(filename, line, reason); + if (!many_errors_) { + // In case we don't have the lenient mode, every error is fatal + // and we throw + ok_ = false; + complete_ = true; + isc_throw(MasterLoaderError, reason.c_str()); + } + } + + /// \brief Wrapper around \c MasterLexer::popSource() + /// + /// This method is used as a wrapper around the lexer's + /// \c popSource() to also restore the current origin and the last + /// seen name (at time of push). It also calls \c popSource(). See + /// \c doInclude() implementation for more details. + bool popSource() { + if (lexer_.getSourceCount() == 1) { + return (false); + } + lexer_.popSource(); + // Restore original origin and last seen name + + // We move in tandem, there's an extra item included during the + // initialization, so we can never run out of them + isc_throw_assert(!include_info_.empty()); + const IncludeInfo& info(include_info_.back()); + active_origin_ = info.first; + last_name_ = info.second; + include_info_.pop_back(); + previous_name_ = false; + return (true); + } + + /// \brief Get a string token. Handle it as error if it is not string. + const string getString() { + lexer_.getNextToken(MasterToken::STRING).getString(string_token_); + return (string_token_); + } + + /// \brief Parse the initial token at the beginning of a line in a + /// master file (or stream). + /// + /// A helper method of \c loadIncremental(), parsing the first token + /// of a new line. If it looks like an RR, detect its owner name + /// and return a string token for the next field of the RR. + /// + /// Otherwise, return either \c END_OF_LINE or \c END_OF_FILE token + /// depending on whether the loader continues to the next line or + /// completes the load, respectively. Other corner cases including + /// $-directive handling is done here. + /// + /// For unexpected errors, it throws an exception, which will be + /// handled in \c loadIncremental(). + MasterToken handleInitialToken(); + + /// \brief Process the $ORIGIN directive. + void doOrigin(bool is_optional) { + // Parse and create the new origin. It is relative to the previous + // one. + const MasterToken& + name_tok(lexer_.getNextToken(MasterToken::QSTRING, is_optional)); + + if (name_tok.getType() == MasterToken::QSTRING || + name_tok.getType() == MasterToken::STRING) { + + const MasterToken::StringRegion& + name_string(name_tok.getStringRegion()); + active_origin_ = Name(name_string.beg, name_string.len, + &active_origin_); + if (name_string.len > 0 && + name_string.beg[name_string.len - 1] != '.') { + callbacks_.warning(lexer_.getSourceName(), + lexer_.getSourceLine(), + "The new origin is relative, did you really" + " mean " + active_origin_.toText() + "?"); + } + } else { + // If it is not optional, we must not get anything but + // a string token. + isc_throw_assert(is_optional); + + // We return the newline there. This is because we want to + // behave the same if there is or isn't the name, leaving the + // newline there. + lexer_.ungetToken(); + } + } + + /// \brief Process the $INCLUDE directive. + void doInclude() { + // First, get the filename to include + const string + filename(lexer_.getNextToken(MasterToken::QSTRING).getString()); + + // There optionally can be an origin, that applies before the include. + // We need to save the currently active origin before calling + // doOrigin(), because it would update active_origin_ while we need + // to pass the active origin before recognizing the new origin to + // pushSource. Note: RFC 1035 is not really clear on this: it reads + // "regardless of changes... within the included file", but the new + // origin is not really specified "within the included file". + // Nevertheless, this behavior is probably more likely to be the + // intent of the RFC, and it's compatible with BIND 9. + const Name current_origin = active_origin_; + doOrigin(true); + + pushSource(filename, current_origin); + } + + /// \brief Parse RR fields (TTL, CLASS and TYPE). + /// + /// A helper method for \c loadIncremental(). It parses part of an + /// RR until it finds the RR type field. If TTL or RR class is + /// specified before the RR type, it also recognizes and validates + /// them. + /// + /// \param explicit_ttl will be set to true if this method finds a + /// valid TTL field. + /// \param rrparam_token Pass the current (parsed) token here. + RRType parseRRParams(bool& explicit_ttl, MasterToken rrparam_token) { + // Find TTL, class and type. Both TTL and class are + // optional and may occur in any order if they exist. TTL + // and class come before type which must exist. + // + // [<TTL>] [<class>] <type> <RDATA> + // [<class>] [<TTL>] <type> <RDATA> + + // named-signzone outputs TTL first, so try parsing it in order + // first. + if (setCurrentTTL(rrparam_token.getString())) { + explicit_ttl = true; + rrparam_token = lexer_.getNextToken(MasterToken::STRING); + } else { + // If it's not a TTL here, continue and try again + // after the RR class below. + } + + boost::scoped_ptr<RRClass> rrclass + (RRClass::createFromText(rrparam_token.getString())); + if (rrclass) { + if (*rrclass != zone_class_) { + isc_throw(InternalException, "Class mismatch: " << *rrclass << + " vs. " << zone_class_); + } + rrparam_token = lexer_.getNextToken(MasterToken::STRING); + } + + // If we couldn't parse TTL earlier in the stream (above), try + // again at current location. + if (!explicit_ttl && setCurrentTTL(rrparam_token.getString())) { + explicit_ttl = true; + rrparam_token = lexer_.getNextToken(MasterToken::STRING); + } + + // Return the current string token's value as the RRType. + return (RRType(rrparam_token.getString())); + } + + /// \brief Check and limit TTL to maximum value. + /// + /// Upper limit check when recognizing a specific TTL value from the + /// zone file ($TTL, the RR's TTL field, or the SOA minimum). RFC2181 + /// Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff), + /// and prohibits transmitting a TTL field exceeding this range. We + /// guarantee that by limiting the value at the time of zone + /// parsing/loading, following what BIND 9 does. Resetting it to 0 + /// at this point may not be exactly what the RFC states (depending on + /// the meaning of 'received'), but the end result would be the same (i.e., + /// the guarantee on transmission). Again, we follow the BIND 9's behavior + /// here. + /// + /// \param ttl the TTL to check. If it is larger than the maximum + /// allowed, it is set to 0. + /// \param post_parsing should be true iff this method is called + /// after parsing the entire RR and the lexer is positioned at the + /// next line. It's just for calculating the accurate source line + /// when callback is necessary. + void limitTTL(RRTTL& ttl, bool post_parsing) { + if (ttl > RRTTL::MAX_TTL()) { + const size_t src_line = lexer_.getSourceLine() - + (post_parsing ? 1 : 0); + callbacks_.warning(lexer_.getSourceName(), src_line, + "TTL " + ttl.toText() + " > MAXTTL, " + "setting to 0 per RFC2181"); + ttl = RRTTL(0); + } + } + + /// \brief Set/reset the default TTL. + /// + /// This should be from either $TTL or SOA minimum TTL (it's the + /// caller's responsibility; this method doesn't care about where it + /// comes from). See \c limitTTL() for parameter post_parsing. + void setDefaultTTL(const RRTTL& ttl, bool post_parsing) { + assignTTL(default_ttl_, ttl); + limitTTL(*default_ttl_, post_parsing); + } + + /// \brief Try to set/reset the current TTL from candidate TTL text. + /// + /// It's possible it that the text does not actually represent a TTL + /// (which is not immediately considered an error). Returns \c true + /// iff it's recognized as a valid TTL (and only in which case the + /// current TTL is set). + /// + /// \param ttl_txt The text to parse as a TTL. + /// \return true if a TTL was parsed (and set as the current TTL). + bool setCurrentTTL(const string& ttl_txt) { + // We use the factory version instead of RRTTL constructor as we + // need to expect cases where ttl_txt does not actually represent a TTL + // but an RR class or type. + RRTTL* rrttl = RRTTL::createFromText(ttl_txt); + if (rrttl) { + current_ttl_.reset(rrttl); + limitTTL(*current_ttl_, false); + return (true); + } + return (false); + } + + /// \brief Determine the TTL of the current RR based on the given + /// parsing context. + /// + /// \c explicit_ttl is true iff the TTL is explicitly specified for that RR + /// (in which case current_ttl_ is set to that TTL). + /// \c rrtype is the type of the current RR, and \c rdata is its RDATA. They + /// only matter if the type is SOA and no available TTL is known. In this + /// case the minimum TTL of the SOA will be used as the TTL of that SOA + /// and the default TTL for subsequent RRs. + const RRTTL& getCurrentTTL(bool explicit_ttl, const RRType& rrtype, + const rdata::ConstRdataPtr& rdata) { + // We've completed parsing the full of RR, and the lexer is already + // positioned at the next line. If we need to call callback, + // we need to adjust the line number. + const size_t current_line = lexer_.getSourceLine() - 1; + + if (!current_ttl_ && !default_ttl_) { + if (rrtype == RRType::SOA()) { + callbacks_.warning(lexer_.getSourceName(), current_line, + "no TTL specified; " + "using SOA MINTTL instead"); + const uint32_t ttl_val = + dynamic_cast<const rdata::generic::SOA&>(*rdata). + getMinimum(); + setDefaultTTL(RRTTL(ttl_val), true); + assignTTL(current_ttl_, *default_ttl_); + } else { + // On catching the exception we'll try to reach EOL again, + // so we need to unget it now. + lexer_.ungetToken(); + throw InternalException(__FILE__, __LINE__, + "no TTL specified; load rejected"); + } + } else if (!explicit_ttl && default_ttl_) { + assignTTL(current_ttl_, *default_ttl_); + } else if (!explicit_ttl && warn_rfc1035_ttl_) { + // Omitted (class and) TTL values are default to the last + // explicitly stated values (RFC 1035, Sec. 5.1). + callbacks_.warning(lexer_.getSourceName(), current_line, + "using RFC1035 TTL semantics; default to the " + "last explicitly stated TTL"); + warn_rfc1035_ttl_ = false; // we only warn about this once + } + isc_throw_assert(current_ttl_); + return (*current_ttl_); + } + + /// \brief Handle a $DIRECTIVE + /// + /// This method is called when a $DIRECTIVE is encountered in the + /// input stream. + void handleDirective(const char* directive, size_t length) { + if (iequals(directive, "INCLUDE")) { + doInclude(); + } else if (iequals(directive, "ORIGIN")) { + doOrigin(false); + eatUntilEOL(true); + } else if (iequals(directive, "TTL")) { + setDefaultTTL(RRTTL(getString()), false); + eatUntilEOL(true); + } else { + isc_throw(InternalException, "Unknown directive '" << + string(directive, directive + length) << "'"); + } + } + + /// \brief Skip tokens until end-of-line. + void eatUntilEOL(bool reportExtra) { + // We want to continue. Try to read until the end of line + for (;;) { + const MasterToken& token(lexer_.getNextToken()); + switch (token.getType()) { + case MasterToken::END_OF_FILE: + callbacks_.warning(lexer_.getSourceName(), + lexer_.getSourceLine(), + "File does not end with newline"); + // We don't pop here. The End of file will stay there, + // and we'll handle it in the next iteration of + // loadIncremental properly. + return; + case MasterToken::END_OF_LINE: + // Found the end of the line. Good. + return; + default: + // Some other type of token. + if (reportExtra) { + reportExtra = false; + reportError(lexer_.getSourceName(), + lexer_.getSourceLine(), + "Extra tokens at the end of line"); + } + break; + } + } + } + + /// \brief Assign the right RRTTL's value to the left RRTTL. If one + /// doesn't exist in the scoped_ptr, make a new RRTTL copy of the + /// right argument. + static void assignTTL(boost::scoped_ptr<RRTTL>& left, const RRTTL& right) { + if (!left) { + left.reset(new RRTTL(right)); + } else { + *left = right; + } + } + +private: + MasterLexer lexer_; + const Name zone_origin_; + Name active_origin_; // The origin used during parsing + // (modifiable by $ORIGIN) + shared_ptr<Name> last_name_; // Last seen name (for INITIAL_WS handling) + const RRClass zone_class_; + MasterLoaderCallbacks callbacks_; + const AddRRCallback add_callback_; + boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when + // unspecified. If null no default + // is known. + boost::scoped_ptr<RRTTL> current_ttl_; // The TTL used most recently. + // Initially unset. Once set + // always stores a valid + // RRTTL. + const MasterLoader::Options options_; + const std::string master_file_; + std::string string_token_; + bool initialized_; + bool ok_; // Is it OK to continue loading? + const bool many_errors_; // Are many errors allowed (or should we abort + // on the first) + // Some info about the outer files from which we include. + // The first one is current origin, the second is the last seen name + // in that file. + typedef pair<Name, shared_ptr<Name> > IncludeInfo; + vector<IncludeInfo> include_info_; + bool previous_name_; // True if there was a previous name in this file + // (false at the beginning or after an $INCLUDE line) + +public: + bool complete_; // All work done. + bool seen_error_; // Was there at least one error during the + // load? + bool warn_rfc1035_ttl_; // should warn if implicit TTL determination + // from the previous RR is used. + size_t rr_count_; // number of RRs successfully loaded +}; + +MasterToken +MasterLoader::MasterLoaderImpl::handleInitialToken() { + const MasterToken& initial_token = + lexer_.getNextToken(MasterLexer::QSTRING | MasterLexer::INITIAL_WS); + + // The most likely case is INITIAL_WS, and then string/qstring. We + // handle them first. + if (initial_token.getType() == MasterToken::INITIAL_WS) { + const MasterToken& next_token = lexer_.getNextToken(); + if (next_token.getType() == MasterToken::END_OF_LINE) { + return (next_token); // blank line + } else if (next_token.getType() == MasterToken::END_OF_FILE) { + lexer_.ungetToken(); // handle it in the next iteration. + eatUntilEOL(true); // effectively warn about the unexpected EOF. + return (MasterToken(MasterToken::END_OF_LINE)); + } + + // This means the same name as previous. + if (!last_name_) { + isc_throw(InternalException, "No previous name to use in " + "place of initial whitespace"); + } else if (!previous_name_) { + callbacks_.warning(lexer_.getSourceName(), lexer_.getSourceLine(), + "Owner name omitted around $INCLUDE, the result " + "might not be as expected"); + } + return (next_token); + } else if (initial_token.getType() == MasterToken::STRING || + initial_token.getType() == MasterToken::QSTRING) { + // If it is name (or directive), handle it. + const MasterToken::StringRegion& + name_string(initial_token.getStringRegion()); + + if (name_string.len > 0 && name_string.beg[0] == '$') { + // This should have either thrown (and the error handler + // will read up until the end of line) or read until the + // end of line. + + // Exclude the $ from the string on this point. + handleDirective(name_string.beg + 1, name_string.len - 1); + // So, get to the next line, there's nothing more interesting + // in this one. + return (MasterToken(MasterToken::END_OF_LINE)); + } + + // This should be an RR, starting with an owner name. Construct the + // name, and some string token should follow. + last_name_.reset(new Name(name_string.beg, name_string.len, + &active_origin_)); + previous_name_ = true; + return (lexer_.getNextToken(MasterToken::STRING)); + } + + switch (initial_token.getType()) { // handle less common cases + case MasterToken::END_OF_FILE: + if (!popSource()) { + return (initial_token); + } else { + // We try to read a token from the popped source + // So continue to the next line of that source, but first, make + // sure the source is at EOL + eatUntilEOL(true); + return (MasterToken(MasterToken::END_OF_LINE)); + } + case MasterToken::END_OF_LINE: + return (initial_token); // empty line + case MasterToken::ERROR: + // Error token here. + isc_throw(InternalException, initial_token.getErrorText()); + default: + // Some other token (what could that be?) + isc_throw(InternalException, "Parser got confused (unexpected " + "token " << initial_token.getType() << ")"); + } +} + +bool +MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) { + if (count_limit == 0) { + isc_throw(isc::InvalidParameter, "Count limit set to 0"); + } + if (complete_) { + isc_throw(isc::InvalidOperation, + "Trying to load when already loaded"); + } + if (!initialized_) { + pushSource(master_file_, active_origin_); + } + size_t count = 0; + while (ok_ && count < count_limit) { + try { + const MasterToken next_token = handleInitialToken(); + if (next_token.getType() == MasterToken::END_OF_FILE) { + return (true); // we are done + } else if (next_token.getType() == MasterToken::END_OF_LINE) { + continue; // nothing more to do in this line + } + // We are going to parse an RR, have known the owner name, + // and are now seeing the next string token in the rest of the RR. + isc_throw_assert(next_token.getType() == MasterToken::STRING); + + bool explicit_ttl = false; + const RRType rrtype = parseRRParams(explicit_ttl, next_token); + // TODO: Check if it is SOA, it should be at the origin. + + const rdata::RdataPtr rdata = + rdata::createRdata(rrtype, zone_class_, lexer_, + &active_origin_, options_, callbacks_); + + // In case we get null, it means there was error creating + // the Rdata. The errors should have been reported by + // callbacks_ already. We need to decide if we want to continue + // or not. + if (rdata) { + add_callback_(*last_name_, zone_class_, rrtype, + getCurrentTTL(explicit_ttl, rrtype, rdata), + rdata); + // Good, we loaded another one + ++count; + ++rr_count_; + } else { + seen_error_ = true; + if (!many_errors_) { + ok_ = false; + complete_ = true; + // We don't have the exact error here, but it was reported + // by the error callback. + isc_throw(MasterLoaderError, "Invalid RR data"); + } + } + } catch (const isc::dns::DNSTextError& e) { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + e.what()); + eatUntilEOL(false); + } catch (const MasterLexer::ReadError& e) { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + e.what()); + eatUntilEOL(false); + } catch (const MasterLexer::LexerError& e) { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + e.what()); + eatUntilEOL(false); + } catch (const InternalException& e) { + reportError(lexer_.getSourceName(), lexer_.getSourceLine(), + e.what()); + eatUntilEOL(false); + } + } + // When there was a fatal error and ok is false, we say we are done. + return (!ok_); +} + +MasterLoader::MasterLoader(const char* master_file, + const Name& zone_origin, + const RRClass& zone_class, + const MasterLoaderCallbacks& callbacks, + const AddRRCallback& add_callback, + Options options) { + if (!add_callback) { + isc_throw(isc::InvalidParameter, "Empty add RR callback"); + } + impl_.reset(new MasterLoaderImpl(master_file, zone_origin, + zone_class, callbacks, add_callback, options)); +} + +MasterLoader::MasterLoader(std::istream& stream, + const Name& zone_origin, + const RRClass& zone_class, + const MasterLoaderCallbacks& callbacks, + const AddRRCallback& add_callback, + Options options) { + if (!add_callback) { + isc_throw(isc::InvalidParameter, "Empty add RR callback"); + } + impl_.reset(new MasterLoaderImpl("", zone_origin, zone_class, + callbacks, add_callback, options)); + impl_->pushStreamSource(stream); +} + +MasterLoader::~MasterLoader() { +} + +bool +MasterLoader::loadIncremental(size_t count_limit) { + const bool result = impl_->loadIncremental(count_limit); + impl_->complete_ = result; + return (result); +} + +bool +MasterLoader::loadedSuccessfully() const { + return (impl_->complete_ && !impl_->seen_error_); +} + +size_t +MasterLoader::getSize() const { + return (impl_->getSize()); +} + +size_t +MasterLoader::getPosition() const { + return (impl_->getPosition()); +} + +} // end namespace dns +} // end namespace isc diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h new file mode 100644 index 0000000..3cb8aff --- /dev/null +++ b/src/lib/dns/master_loader.h @@ -0,0 +1,186 @@ +// Copyright (C) 2012-2024 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 MASTER_LOADER_H +#define MASTER_LOADER_H + +#include <dns/exceptions.h> +#include <dns/master_loader_callbacks.h> + +#include <boost/noncopyable.hpp> + +#include <memory> + +namespace isc { +namespace dns { + +class Name; +class RRClass; + +/// \brief Error while loading by MasterLoader without specifying the +/// MANY_ERRORS option. +class MasterLoaderError : public isc::Exception { +public: + MasterLoaderError(const char* file, size_t line, const char* what) : + Exception(file, line, what) + {} +}; + +/// \brief A class able to load DNS master files +/// +/// This class is able to produce a stream of RRs from a master file. +/// It is able to load all of the master file at once, or by blocks +/// incrementally. +/// +/// It reports the loaded RRs and encountered errors by callbacks. +class MasterLoader : boost::noncopyable { +public: + /// \brief Options how the parsing should work. + enum Options { + DEFAULT = 0, ///< Nothing special. + MANY_ERRORS = 1 ///< Lenient mode (see documentation of MasterLoader + /// constructor). + }; + + /// \brief Constructor + /// + /// This creates a master loader and provides it with all + /// relevant information. + /// + /// Except for the exceptions listed below, the constructor doesn't + /// throw. Most errors (like non-existent master file) are reported + /// by the callbacks during load() or loadIncremental(). + /// + /// \param master_file Path to the file to load. + /// \param zone_origin The origin of zone to be expected inside + /// the master file. Currently unused, but it is expected to + /// be used for some validation. + /// \param zone_class The class of zone to be expected inside the + /// master file. + /// \param callbacks The callbacks by which it should report problems. + /// Usually, the callback carries a filename and line number of the + /// input where the problem happens. There's a special case of empty + /// filename and zero line in case the opening of the top-level master + /// file fails. + /// \param add_callback The callback which would be called with each + /// loaded RR. + /// \param options Options for the parsing, which is bitwise-or of + /// the Options values or DEFAULT. If the MANY_ERRORS option is + /// included, the parser tries to continue past errors. If it + /// is not included, it stops at first encountered error. + /// \throw std::bad_alloc when there's not enough memory. + /// \throw isc::InvalidParameter if add_callback is empty. + MasterLoader(const char* master_file, + const Name& zone_origin, + const RRClass& zone_class, + const MasterLoaderCallbacks& callbacks, + const AddRRCallback& add_callback, + Options options = DEFAULT); + + /// \brief Constructor from a stream + /// + /// This is a constructor very similar to the previous one. The only + /// difference is it doesn't take a filename, but an input stream + /// to read the data from. It is expected to be mostly used in tests, + /// but it is public as it may possibly be useful for other currently + /// unknown purposes. + MasterLoader(std::istream& input, + const Name& zone_origin, + const RRClass& zone_class, + const MasterLoaderCallbacks& callbacks, + const AddRRCallback& add_callback, + Options options = DEFAULT); + + /// \brief Destructor + ~MasterLoader(); + + /// \brief Load some RRs + /// + /// This method loads at most count_limit RRs and reports them. In case + /// an error (either fatal or without MANY_ERRORS) or end of file is + /// encountered, they may be less. + /// + /// \param count_limit Upper limit on the number of RRs loaded. + /// \return In case it stops because of the count limit, it returns false. + /// It returns true if the loading is done. + /// \throw isc::InvalidOperation when called after loading was done + /// already. + /// \throw MasterLoaderError when there's an error in the input master + /// file and the MANY_ERRORS is not specified. It never throws this + /// in case MANY_ERRORS is specified. + bool loadIncremental(size_t count_limit); + + /// \brief Load everything + /// + /// This simply calls loadIncremental until the loading is done. + /// \throw isc::InvalidOperation when called after loading was done + /// already. + /// \throw MasterLoaderError when there's an error in the input master + /// file and the MANY_ERRORS is not specified. It never throws this + /// in case MANY_ERRORS is specified. + void load() { + while (!loadIncremental(1000)) { // 1000 = arbitrary largish number + // Body intentionally left blank + } + } + + /// \brief Was the loading successful? + /// + /// \return true if and only if the loading was complete (after a call of + /// load or after loadIncremental returned true) and there was no + /// error. In other cases, return false. + /// \note While this method works even before the loading is complete (by + /// returning false in that case), it is meant to be called only after + /// finishing the load. + bool loadedSuccessfully() const; + + /// \brief Return the total size of the zone files and streams. + /// + /// This method returns the size of the source of the zone to be loaded + /// (master zone files or streams) that is known at the time of the call. + /// For a zone file, it's the size of the file; for a stream, it's the + /// size of the data (in bytes) available at the start of the load. + /// If separate zone files are included via the $INCLUDE directive, the + /// sum of the sizes of these files are added. + /// + /// If the loader is constructed with a stream, the size can be + /// "unknown" as described for \c MasterLexer::getTotalSourceSize(). + /// In this case this method always returns + /// \c MasterLexer::SOURCE_SIZE_UNKNOWN. + /// + /// If the loader is constructed with a zone file, this method + /// initially returns 0. So until either \c load() or \c loadIncremental() + /// is called, the value is meaningless. + /// + /// Note that when the source includes separate files, this method + /// cannot take into account the included files that the loader has not + /// recognized at the time of call. So it's possible that this method + /// returns different values at different times of call. + /// + /// \throw None + size_t getSize() const; + + /// \brief Return the position of the loader in zone. + /// + /// This method returns a conceptual "position" of the loader in the + /// zone to be loaded. Specifically, it returns the total number of + /// characters contained in the zone files and streams and recognized + /// by the loader. Before starting the load it returns 0; on successful + /// completion it will be equal to the return value of \c getSize() + /// (unless the latter returns \c MasterLexer::SOURCE_SIZE_UNKNOWN). + /// + /// \throw None + size_t getPosition() const; + +private: + class MasterLoaderImpl; + std::unique_ptr<MasterLoaderImpl> impl_; +}; + +} // end namespace dns +} // end namespace isc + +#endif // MASTER_LOADER_H diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h new file mode 100644 index 0000000..3f5be01 --- /dev/null +++ b/src/lib/dns/master_loader_callbacks.h @@ -0,0 +1,121 @@ +// Copyright (C) 2012-2024 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 MASTER_LOADER_CALLBACKS_H +#define MASTER_LOADER_CALLBACKS_H + +#include <exceptions/exceptions.h> + +#include <boost/shared_ptr.hpp> +#include <functional> +#include <string> + +namespace isc { +namespace dns { +class Name; +class RRClass; +class RRType; +class RRTTL; +namespace rdata { +class Rdata; +typedef boost::shared_ptr<Rdata> RdataPtr; +} + +/// \brief Type of callback to add a RR. +/// +/// This type of callback is used by the loader to report another loaded +/// RR. The Rdata is no longer preserved by the loader and is fully +/// owned by the callback. +/// +/// \param name The domain name where the RR belongs. +/// \param rrclass The class of the RR. +/// \param rrtype Type of the RR. +/// \param rrttl Time to live of the RR. +/// \param rdata The actual carried data of the RR. +typedef std::function<void(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& rrttl, + const rdata::RdataPtr& rdata)> + AddRRCallback; + +/// \brief Set of issue callbacks for a loader. +/// +/// This holds a set of callbacks by which a loader (such as MasterLoader) +/// can report loaded RRsets, errors and other unusual conditions. +/// +/// All the callbacks must be set. +class MasterLoaderCallbacks { +public: + /// \brief Type of one callback to report problems. + /// + /// This is the type of one callback used to report an unusual + /// condition or error. + /// + /// \param source_name The name of the source where the problem happened. + /// This is usually a file name. + /// \param source_line Position of the problem, counted in lines from the + /// beginning of the source. + /// \param reason Human readable description of what happened. + typedef std::function<void(const std::string& source_name, + size_t source_line, + const std::string& reason)> IssueCallback; + + /// \brief Constructor + /// + /// Initializes the callbacks. + /// + /// \param error The error callback to use. + /// \param warning The warning callback to use. + /// \throw isc::InvalidParameter if any of the callbacks is empty. + MasterLoaderCallbacks(const IssueCallback& error, + const IssueCallback& warning) : + error_(error), + warning_(warning) { + if (!error_ || !warning_) { + isc_throw(isc::InvalidParameter, + "Empty function passed as callback"); + } + } + + /// \brief Call callback for serious errors + /// + /// This is called whenever there's a serious problem which makes the data + /// being loaded unusable. Further processing may or may not happen after + /// this (for example to detect further errors), but the data should not + /// be used. + /// + /// It calls whatever was passed to the error parameter to the constructor. + /// + /// If the caller of the loader wants to abort, it is possible to throw + /// from the callback, which aborts the load. + void error(const std::string& source_name, size_t source_line, + const std::string& reason) const { + error_(source_name, source_line, reason); + } + + /// \brief Call callback for potential problems + /// + /// This is called whenever a minor problem is discovered. This might mean + /// the data is completely OK, it just looks suspicious. + /// + /// It calls whatever was passed to the warn parameter to the constructor. + /// + /// The loading will continue after the callback. If the caller wants to + /// abort (which is probably not a very good idea, since warnings + /// may be false positives), it is possible to throw from inside the + /// callback. + void warning(const std::string& source_name, size_t source_line, + const std::string& reason) const { + warning_(source_name, source_line, reason); + } +private: + const IssueCallback error_; + const IssueCallback warning_; +}; + +} +} + +#endif // MASTER_LOADER_CALLBACKS_H diff --git a/src/lib/dns/message.cc b/src/lib/dns/message.cc new file mode 100644 index 0000000..913240c --- /dev/null +++ b/src/lib/dns/message.cc @@ -0,0 +1,1167 @@ +// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dns/edns.h> +#include <dns/exceptions.h> +#include <dns/message.h> +#include <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/opcode.h> +#include <dns/rcode.h> +#include <dns/question.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/rrttl.h> +#include <dns/rrset.h> +#include <dns/tsig.h> +#include <util/buffer.h> + +#include <stdint.h> +#include <algorithm> +#include <cassert> +#include <string> +#include <sstream> +#include <vector> +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> + +using namespace isc::dns::rdata; +using namespace isc::util; + +using namespace std; +using boost::lexical_cast; + +namespace isc { +namespace dns { + +namespace { +// protocol constants +const size_t HEADERLEN = 12; + +const unsigned int OPCODE_MASK = 0x7800; +const unsigned int OPCODE_SHIFT = 11; +const unsigned int RCODE_MASK = 0x000f; + +// This diagram shows the wire-format representation of the 2nd 16 bits of +// the DNS header section, which contain all defined flag bits. +// +// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +// |QR| Opcode |AA|TC|RD|RA| |AD|CD| RCODE | +// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +// 1 0 0 0| 0 1 1 1| 1 0 1 1| 0 0 0 0| +// 0x8 0x7 0xb 0x0 +// +// This mask covers all the flag bits, and those bits only. +// Note: we reject a "flag" the is not covered by this mask in some of the +// public methods. This means our current definition is not fully extendable; +// applications cannot introduce a new flag bit temporarily without modifying +// the source code. +const unsigned int HEADERFLAG_MASK = 0x87b0; + +// This is a set of flag bits that should be preserved when building a reply +// from a request. +// Note: we assume the specific definition of HEADERFLAG_xx. We may change +// the definition in future, in which case we need to adjust this definition, +// too (see also the description about the Message::HeaderFlag type). +const uint16_t MESSAGE_REPLYPRESERVE = (Message::HEADERFLAG_RD | + Message::HEADERFLAG_CD); + +const char* const sectiontext[] = { + "QUESTION", + "ANSWER", + "AUTHORITY", + "ADDITIONAL" +}; +} + +class MessageImpl { +public: + MessageImpl(Message::Mode mode); + // Open issues: should we rather have a header in wire-format + // for efficiency? + Message::Mode mode_; + qid_t qid_; + + // We want to use null for [op,r]code_ to mean the code being not + // correctly parsed or set. We store the real code object in + // xxcode_placeholder_ and have xxcode_ refer to it when the object + // is valid. + const Rcode* rcode_; + Rcode rcode_placeholder_; + const Opcode* opcode_; + Opcode opcode_placeholder_; + + uint16_t flags_; // wire-format representation of header flags. + + bool header_parsed_; + static const unsigned int NUM_SECTIONS = 4; // TODO: revisit this design + int counts_[NUM_SECTIONS]; // TODO: revisit this definition + vector<QuestionPtr> questions_; + vector<RRsetPtr> rrsets_[NUM_SECTIONS]; + ConstEDNSPtr edns_; + ConstTSIGRecordPtr tsig_rr_; + + // RRsetsSorter* sorter_; : TODO + + void init(); + void setOpcode(const Opcode& opcode); + void setRcode(const Rcode& rcode); + int parseQuestion(InputBuffer& buffer); + int parseSection(const Message::Section section, InputBuffer& buffer, + Message::ParseOptions options); + void addRR(Message::Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype, + const RRTTL& ttl, ConstRdataPtr rdata, + Message::ParseOptions options); + // There are also times where an RR needs to be added that + // represents an empty RRset. There is no Rdata in that case + void addRR(Message::Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype, + const RRTTL& ttl, Message::ParseOptions options); + void addEDNS(Message::Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype, + const RRTTL& ttl, const Rdata& rdata); + void addTSIG(Message::Section section, unsigned int count, + const InputBuffer& buffer, size_t start_position, + const Name& name, const RRClass& rrclass, + const RRTTL& ttl, const Rdata& rdata); + void toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx); +}; + +/// @brief Pointer to the @ref MessageImpl object. +typedef boost::shared_ptr<MessageImpl> MessageImplPtr; + +MessageImpl::MessageImpl(Message::Mode mode) : + mode_(mode), + rcode_placeholder_(Rcode(0)), // for placeholders the value doesn't matter + opcode_placeholder_(Opcode(0)) { + init(); +} + +void +MessageImpl::init() { + flags_ = 0; + qid_ = 0; + rcode_ = 0; + opcode_ = 0; + edns_ = EDNSPtr(); + tsig_rr_ = ConstTSIGRecordPtr(); + + for (int i = 0; i < NUM_SECTIONS; ++i) { + counts_[i] = 0; + } + + header_parsed_ = false; + questions_.clear(); + rrsets_[Message::SECTION_ANSWER].clear(); + rrsets_[Message::SECTION_AUTHORITY].clear(); + rrsets_[Message::SECTION_ADDITIONAL].clear(); +} + +void +MessageImpl::setOpcode(const Opcode& opcode) { + opcode_placeholder_ = opcode; + opcode_ = &opcode_placeholder_; +} + +void +MessageImpl::setRcode(const Rcode& rcode) { + rcode_placeholder_ = rcode; + rcode_ = &rcode_placeholder_; +} + +namespace { +// This helper class is used by MessageImpl::toWire() to render a set of +// RRsets of a specific section of message to a given MessageRenderer. +// +// A RenderSection object is expected to be used with a QuestionIterator or +// SectionIterator. Its operator() is called for each RRset as the iterator +// iterates over the corresponding section, and it renders the RRset to +// the given MessageRenderer, while counting the number of RRs (note: not +// RRsets) successfully rendered. If the MessageRenderer reports the need +// for truncation (via its isTruncated() method), the RenderSection object +// stops rendering further RRsets. In addition, unless partial_ok (given on +// construction) is true, it removes any RRs that are partially rendered +// from the MessageRenderer. +// +// On the completion of rendering the entire section, the owner of the +// RenderSection object can get the number of rendered RRs via the +// getTotalCount() method. +template <typename T> +struct RenderSection { + RenderSection(AbstractMessageRenderer& renderer, const bool partial_ok) : + counter_(0), renderer_(renderer), partial_ok_(partial_ok), + truncated_(false) { + } + + void operator()(const T& entry) { + // If it's already truncated, ignore the rest of the section. + if (truncated_) { + return; + } + const size_t pos0 = renderer_.getLength(); + counter_ += entry->toWire(renderer_); + if (renderer_.isTruncated()) { + truncated_ = true; + if (!partial_ok_) { + // roll back to the end of the previous RRset. + renderer_.trim(renderer_.getLength() - pos0); + } + } + } + + unsigned int getTotalCount() { + return (counter_); + } + + unsigned int counter_; + + AbstractMessageRenderer& renderer_; + + const bool partial_ok_; + + bool truncated_; +}; +} + +void +MessageImpl::toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx) { + if (mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "Message rendering attempted in non render mode"); + } + if (!rcode_) { + isc_throw(InvalidMessageOperation, + "Message rendering attempted without Rcode set"); + } + if (!opcode_) { + isc_throw(InvalidMessageOperation, + "Message rendering attempted without Opcode set"); + } + + // Reserve the space for TSIG (if needed) so that we can handle truncation + // case correctly later when that happens. orig_xxx variables remember + // some configured parameters of renderer in case they are needed in + // truncation processing below. + const size_t tsig_len = (tsig_ctx ? tsig_ctx->getTSIGLength() : 0); + const size_t orig_msg_len_limit = renderer.getLengthLimit(); + const AbstractMessageRenderer::CompressMode orig_compress_mode = + renderer.getCompressMode(); + + // We are going to skip soon, so we need to clear the renderer + // But we'll leave the length limit and the compress mode intact + // (or shortened in case of TSIG) + renderer.clear(); + renderer.setCompressMode(orig_compress_mode); + + if (tsig_len > 0) { + if (tsig_len > orig_msg_len_limit) { + isc_throw(InvalidParameter, "Failed to render DNS message: " + "too small limit for a TSIG (" << + orig_msg_len_limit << ")"); + } + renderer.setLengthLimit(orig_msg_len_limit - tsig_len); + } else { + renderer.setLengthLimit(orig_msg_len_limit); + } + + // reserve room for the header + if (renderer.getLengthLimit() < HEADERLEN) { + isc_throw(InvalidParameter, "Failed to render DNS message: " + "too small limit for a Header"); + } + renderer.skip(HEADERLEN); + + uint16_t qdcount = + for_each(questions_.begin(), questions_.end(), + RenderSection<QuestionPtr>(renderer, false)).getTotalCount(); + + // TODO: sort RRsets in each section based on configuration policy. + uint16_t ancount = 0; + if (!renderer.isTruncated()) { + ancount = + for_each(rrsets_[Message::SECTION_ANSWER].begin(), + rrsets_[Message::SECTION_ANSWER].end(), + RenderSection<RRsetPtr>(renderer, true)).getTotalCount(); + } + uint16_t nscount = 0; + if (!renderer.isTruncated()) { + nscount = + for_each(rrsets_[Message::SECTION_AUTHORITY].begin(), + rrsets_[Message::SECTION_AUTHORITY].end(), + RenderSection<RRsetPtr>(renderer, true)).getTotalCount(); + } + uint16_t arcount = 0; + if (renderer.isTruncated()) { + flags_ |= Message::HEADERFLAG_TC; + } else { + arcount = + for_each(rrsets_[Message::SECTION_ADDITIONAL].begin(), + rrsets_[Message::SECTION_ADDITIONAL].end(), + RenderSection<RRsetPtr>(renderer, false)).getTotalCount(); + } + + // Add EDNS OPT RR if necessary. Basically, we add it only when EDNS + // has been explicitly set. However, if the RCODE would require it and + // no EDNS has been set we generate a temporary local EDNS and use it. + if (!renderer.isTruncated()) { + ConstEDNSPtr local_edns = edns_; + if (!local_edns && rcode_->getExtendedCode() != 0) { + local_edns = ConstEDNSPtr(new EDNS()); + } + if (local_edns) { + arcount += local_edns->toWire(renderer, rcode_->getExtendedCode()); + } + } + + // If we're adding a TSIG to a truncated message, clear all RRsets + // from the message except for the question before adding the TSIG. + // If even (some of) the question doesn't fit, don't include it. + if (tsig_ctx && renderer.isTruncated()) { + renderer.clear(); + renderer.setLengthLimit(orig_msg_len_limit - tsig_len); + renderer.setCompressMode(orig_compress_mode); + renderer.skip(HEADERLEN); + qdcount = for_each(questions_.begin(), questions_.end(), + RenderSection<QuestionPtr>(renderer, + false)).getTotalCount(); + ancount = 0; + nscount = 0; + arcount = 0; + } + + // Adjust the counter buffer. + // XXX: these may not be equal to the number of corresponding entries + // in rrsets_[] or questions_ if truncation occurred or an EDNS OPT RR + // was inserted. This is not good, and we should revisit the entire + // design. + counts_[Message::SECTION_QUESTION] = qdcount; + counts_[Message::SECTION_ANSWER] = ancount; + counts_[Message::SECTION_AUTHORITY] = nscount; + counts_[Message::SECTION_ADDITIONAL] = arcount; + + // fill in the header + size_t header_pos = 0; + renderer.writeUint16At(qid_, header_pos); + header_pos += sizeof(uint16_t); + + uint16_t codes_and_flags = + (opcode_->getCode() << OPCODE_SHIFT) & OPCODE_MASK; + codes_and_flags |= (rcode_->getCode() & RCODE_MASK); + codes_and_flags |= (flags_ & HEADERFLAG_MASK); + renderer.writeUint16At(codes_and_flags, header_pos); + header_pos += sizeof(uint16_t); + // TODO: should avoid repeated pattern + renderer.writeUint16At(qdcount, header_pos); + header_pos += sizeof(uint16_t); + renderer.writeUint16At(ancount, header_pos); + header_pos += sizeof(uint16_t); + renderer.writeUint16At(nscount, header_pos); + header_pos += sizeof(uint16_t); + renderer.writeUint16At(arcount, header_pos); + + // Add TSIG, if necessary, at the end of the message. + if (tsig_ctx) { + // Release the reserved space in the renderer. + renderer.setLengthLimit(orig_msg_len_limit); + + const int tsig_count = + tsig_ctx->sign(qid_, renderer.getData(), + renderer.getLength())->toWire(renderer); + if (tsig_count != 1) { + isc_throw(Unexpected, "Failed to render a TSIG RR"); + } + + // update the ARCOUNT for the TSIG RR. Note that for a sane DNS + // message arcount should never overflow to 0. + renderer.writeUint16At(++arcount, header_pos); + } +} + +Message::Message(Mode mode) : + impl_(new MessageImpl(mode)) { +} + +bool +Message::getHeaderFlag(const HeaderFlag flag) const { + if (flag == 0 || (flag & ~HEADERFLAG_MASK) != 0) { + isc_throw(InvalidParameter, + "Message::getHeaderFlag:: Invalid flag is specified: " << + "0x" << std::hex << flag); + } + return ((impl_->flags_ & flag) != 0); +} + +void +Message::setHeaderFlag(const HeaderFlag flag, const bool on) { + if (impl_->mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "setHeaderFlag performed in non-render mode"); + } + if (flag == 0 || (flag & ~HEADERFLAG_MASK) != 0) { + isc_throw(InvalidParameter, + "Message::getHeaderFlag:: Invalid flag is specified: " << + "0x" << std::hex << static_cast<int>(flag)); + } + if (on) { + impl_->flags_ |= flag; + } else { + impl_->flags_ &= ~flag; + } +} + +qid_t +Message::getQid() const { + return (impl_->qid_); +} + +void +Message::setQid(qid_t qid) { + if (impl_->mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "setQid performed in non-render mode"); + } + impl_->qid_ = qid; +} + +const Rcode& +Message::getRcode() const { + if (!impl_->rcode_) { + isc_throw(InvalidMessageOperation, "getRcode attempted before set"); + } + return (*impl_->rcode_); +} + +void +Message::setRcode(const Rcode& rcode) { + if (impl_->mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "setRcode performed in non-render mode"); + } + impl_->setRcode(rcode); +} + +const Opcode& +Message::getOpcode() const { + if (!impl_->opcode_) { + isc_throw(InvalidMessageOperation, "getOpcode attempted before set"); + } + return (*impl_->opcode_); +} + +void +Message::setOpcode(const Opcode& opcode) { + if (impl_->mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "setOpcode performed in non-render mode"); + } + impl_->setOpcode(opcode); +} + +ConstEDNSPtr +Message::getEDNS() const { + return (impl_->edns_); +} + +void +Message::setEDNS(ConstEDNSPtr edns) { + if (impl_->mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "setEDNS performed in non-render mode"); + } + impl_->edns_ = edns; +} + +const TSIGRecord* +Message::getTSIGRecord() const { + if (impl_->mode_ != Message::PARSE) { + isc_throw(InvalidMessageOperation, + "getTSIGRecord performed in non-parse mode"); + } + + return (impl_->tsig_rr_.get()); +} + +unsigned int +Message::getRRCount(const Section section) const { + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + return (impl_->counts_[section]); +} + +void +Message::addRRset(const Section section, RRsetPtr rrset) { + if (!rrset) { + isc_throw(InvalidParameter, + "null RRset is given to Message::addRRset"); + } + if (impl_->mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "addRRset performed in non-render mode"); + } + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + + impl_->rrsets_[section].push_back(rrset); + impl_->counts_[section] += rrset->getRdataCount(); + impl_->counts_[section] += rrset->getRRsigDataCount(); +} + +bool +Message::hasRRset(const Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype) const { + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + + for (auto const& r : impl_->rrsets_[section]) { + if (r->getClass() == rrclass && + r->getType() == rrtype && + r->getName() == name) { + return (true); + } + } + + return (false); +} + +bool +Message::hasRRset(const Section section, const RRsetPtr& rrset) const { + return (hasRRset(section, rrset->getName(), + rrset->getClass(), rrset->getType())); +} + +bool +Message::removeRRset(const Section section, RRsetIterator& iterator) { + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + + bool removed = false; + for (auto i = impl_->rrsets_[section].begin(); i != impl_->rrsets_[section].end(); ++i) { + if (((*i)->getName() == (*iterator)->getName()) && + ((*i)->getClass() == (*iterator)->getClass()) && + ((*i)->getType() == (*iterator)->getType())) { + + // Found the matching RRset so remove it & ignore rest + impl_->counts_[section] -= (*iterator)->getRdataCount(); + impl_->counts_[section] -= (*iterator)->getRRsigDataCount(); + impl_->rrsets_[section].erase(i); + removed = true; + break; + } + } + + return (removed); +} + +void +Message::clearSection(const Section section) { + if (impl_->mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "clearSection performed in non-render mode"); + } + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + if (section == Message::SECTION_QUESTION) { + impl_->questions_.clear(); + } else { + impl_->rrsets_[section].clear(); + } + impl_->counts_[section] = 0; +} + +void +Message::addQuestion(const QuestionPtr question) { + if (impl_->mode_ != Message::RENDER) { + isc_throw(InvalidMessageOperation, + "addQuestion performed in non-render mode"); + } + + impl_->questions_.push_back(question); + ++impl_->counts_[SECTION_QUESTION]; +} + +void +Message::addQuestion(const Question& question) { + addQuestion(QuestionPtr(new Question(question))); +} + +void +Message::toWire(AbstractMessageRenderer& renderer, TSIGContext* tsig_ctx) { + impl_->toWire(renderer, tsig_ctx); +} + +void +Message::parseHeader(InputBuffer& buffer) { + if (impl_->mode_ != Message::PARSE) { + isc_throw(InvalidMessageOperation, + "Message parse attempted in non parse mode"); + } + + if (impl_->header_parsed_) { + return; + } + + if ((buffer.getLength() - buffer.getPosition()) < HEADERLEN) { + isc_throw(MessageTooShort, "Malformed DNS message (short length): " + << buffer.getLength() - buffer.getPosition()); + } + + impl_->qid_ = buffer.readUint16(); + const uint16_t codes_and_flags = buffer.readUint16(); + impl_->setOpcode(Opcode((codes_and_flags & OPCODE_MASK) >> OPCODE_SHIFT)); + impl_->setRcode(Rcode(codes_and_flags & RCODE_MASK)); + impl_->flags_ = (codes_and_flags & HEADERFLAG_MASK); + impl_->counts_[SECTION_QUESTION] = buffer.readUint16(); + impl_->counts_[SECTION_ANSWER] = buffer.readUint16(); + impl_->counts_[SECTION_AUTHORITY] = buffer.readUint16(); + impl_->counts_[SECTION_ADDITIONAL] = buffer.readUint16(); + + impl_->header_parsed_ = true; +} + +void +Message::fromWire(InputBuffer& buffer, ParseOptions options) { + if (impl_->mode_ != Message::PARSE) { + isc_throw(InvalidMessageOperation, + "Message parse attempted in non parse mode"); + } + + // Clear any old parsed data + clear(Message::PARSE); + + buffer.setPosition(0); + parseHeader(buffer); + + impl_->counts_[SECTION_QUESTION] = impl_->parseQuestion(buffer); + impl_->counts_[SECTION_ANSWER] = + impl_->parseSection(SECTION_ANSWER, buffer, options); + impl_->counts_[SECTION_AUTHORITY] = + impl_->parseSection(SECTION_AUTHORITY, buffer, options); + impl_->counts_[SECTION_ADDITIONAL] = + impl_->parseSection(SECTION_ADDITIONAL, buffer, options); +} + +int +MessageImpl::parseQuestion(InputBuffer& buffer) { + unsigned int added = 0; + + for (unsigned int count = 0; + count < counts_[Message::SECTION_QUESTION]; + ++count) { + const Name name(buffer); + + if ((buffer.getLength() - buffer.getPosition()) < + 2 * sizeof(uint16_t)) { + isc_throw(DNSMessageFORMERR, "Question section too short: " << + (buffer.getLength() - buffer.getPosition()) << " bytes"); + } + const RRType rrtype(buffer.readUint16()); + const RRClass rrclass(buffer.readUint16()); + + // XXX: need a duplicate check. We might also want to have an + // optimized algorithm that requires the question section contain + // exactly one RR. + + questions_.push_back(QuestionPtr(new Question(name, rrclass, rrtype))); + ++added; + } + + return (added); +} + +namespace { +struct MatchRR { + MatchRR(const Name& name, const RRType& rrtype, const RRClass& rrclass) : + name_(name), rrtype_(rrtype), rrclass_(rrclass) { + } + + bool operator()(const RRsetPtr& rrset) const { + return (rrset->getType() == rrtype_ && + rrset->getClass() == rrclass_ && + rrset->getName() == name_); + } + + const Name& name_; + + const RRType& rrtype_; + + const RRClass& rrclass_; +}; +} + +// Note about design decision: +// we need some type specific processing here, including EDNS and TSIG. +// how much we should generalize/hardcode the special logic is subject +// to discussion. In terms of modularity it would be ideal to introduce +// an abstract class (say "MessageAttribute") and let other such +// concrete notions as EDNS or TSIG inherit from it. Then we would +// just do: +// message->addAttribute(rrtype, rrclass, buffer); +// to create and attach type-specific concrete object to the message. +// +// A major downside of this approach is, as usual, complexity due to +// indirection and performance penalty. Also, it may not be so easy +// to separate the processing logic because in many cases we'll need +// parse context for which the message class is responsible (e.g. +// to check the EDNS OPT RR only appears in the additional section, +// and appears only once). +// +// Another point to consider is that we may not need so many special +// types other than EDNS and TSIG (and when and if we implement it, +// SIG(0)); newer optional attributes of the message would more likely +// be standardized as new flags or options of EDNS. If that's the case, +// introducing an abstract class with all the overhead and complexity +// may not make much sense. +// +// Conclusion: don't over-generalize type-specific logic for now. +// introduce separate concrete classes, and move context-independent +// logic to that class; processing logic dependent on parse context +// is hardcoded here. +int +MessageImpl::parseSection(const Message::Section section, + InputBuffer& buffer, Message::ParseOptions options) { + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + + unsigned int added = 0; + + for (unsigned int count = 0; count < counts_[section]; ++count) { + // We need to remember the start position for TSIG processing + const size_t start_position = buffer.getPosition(); + + const Name name(buffer); + + // buffer must store at least RR TYPE, RR CLASS, TTL, and RDLEN. + if ((buffer.getLength() - buffer.getPosition()) < + 3 * sizeof(uint16_t) + sizeof(uint32_t)) { + isc_throw(DNSMessageFORMERR, sectiontext[section] << + " section too short: " << + (buffer.getLength() - buffer.getPosition()) << " bytes"); + } + + const RRType rrtype(buffer.readUint16()); + const RRClass rrclass(buffer.readUint16()); + const RRTTL ttl(buffer.readUint32()); + const size_t rdlen = buffer.readUint16(); + + // If class is ANY or NONE, rdlength may be zero, to signal + // an empty RRset. + // (the class check must be done to differentiate from RRTypes + // that can have zero length rdata + if ((rrclass == RRClass::ANY() || rrclass == RRClass::NONE()) && + rdlen == 0) { + addRR(section, name, rrclass, rrtype, ttl, options); + ++added; + continue; + } + ConstRdataPtr rdata = createRdata(rrtype, rrclass, buffer, rdlen); + + if (rrtype == RRType::OPT()) { + addEDNS(section, name, rrclass, rrtype, ttl, *rdata); + } else if (rrtype == RRType::TSIG()) { + addTSIG(section, count, buffer, start_position, name, rrclass, ttl, + *rdata); + } else { + addRR(section, name, rrclass, rrtype, ttl, rdata, options); + ++added; + } + } + + return (added); +} + +void +MessageImpl::addRR(Message::Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype, + const RRTTL& ttl, ConstRdataPtr rdata, + Message::ParseOptions options) { + if ((options & Message::PRESERVE_ORDER) == 0) { + vector<RRsetPtr>::iterator it = + find_if(rrsets_[section].begin(), rrsets_[section].end(), + MatchRR(name, rrtype, rrclass)); + if (it != rrsets_[section].end()) { + (*it)->setTTL(min((*it)->getTTL(), ttl)); + (*it)->addRdata(rdata); + return; + } + } + RRsetPtr rrset(new RRset(name, rrclass, rrtype, ttl)); + rrset->addRdata(rdata); + rrsets_[section].push_back(rrset); +} + +void +MessageImpl::addRR(Message::Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype, + const RRTTL& ttl, Message::ParseOptions options) { + if ((options & Message::PRESERVE_ORDER) == 0) { + vector<RRsetPtr>::iterator it = + find_if(rrsets_[section].begin(), rrsets_[section].end(), + MatchRR(name, rrtype, rrclass)); + if (it != rrsets_[section].end()) { + (*it)->setTTL(min((*it)->getTTL(), ttl)); + return; + } + } + RRsetPtr rrset(new RRset(name, rrclass, rrtype, ttl)); + rrsets_[section].push_back(rrset); +} + +void +MessageImpl::addEDNS(Message::Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype, + const RRTTL& ttl, const Rdata& rdata) { + if (section != Message::SECTION_ADDITIONAL) { + isc_throw(DNSMessageFORMERR, + "EDNS OPT RR found in an invalid section"); + } + if (edns_) { + isc_throw(DNSMessageFORMERR, "multiple EDNS OPT RR found"); + } + + uint8_t extended_rcode; + edns_ = ConstEDNSPtr(createEDNSFromRR(name, rrclass, rrtype, ttl, rdata, + extended_rcode)); + setRcode(Rcode(rcode_->getCode(), extended_rcode)); +} + +void +MessageImpl::addTSIG(Message::Section section, unsigned int count, + const InputBuffer& buffer, size_t start_position, + const Name& name, const RRClass& rrclass, + const RRTTL& ttl, const Rdata& rdata) { + if (section != Message::SECTION_ADDITIONAL) { + isc_throw(DNSMessageFORMERR, + "TSIG RR found in an invalid section"); + } + if (count != counts_[section] - 1) { + isc_throw(DNSMessageFORMERR, "TSIG RR is not the last record"); + } + // This check will never fail as the multiple TSIG RR case is + // caught before by the not the last record check... + if (tsig_rr_) { + isc_throw(DNSMessageFORMERR, "multiple TSIG RRs found"); + } + tsig_rr_ = ConstTSIGRecordPtr(new TSIGRecord(name, rrclass, + ttl, rdata, + buffer.getPosition() - + start_position)); +} + +namespace { +template <typename T> +struct SectionFormatter { + SectionFormatter(const Message::Section section, string& output) : + section_(section), output_(output) { + } + + void operator()(const T& entry) { + if (section_ == Message::SECTION_QUESTION) { + output_ += ";"; + output_ += entry->toText(); + output_ += "\n"; + } else { + output_ += entry->toText(); + } + } + + const Message::Section section_; + + string& output_; +}; +} + +string +Message::toText() const { + if (!impl_->rcode_) { + isc_throw(InvalidMessageOperation, + "Message::toText() attempted without Rcode set"); + } + if (!impl_->opcode_) { + isc_throw(InvalidMessageOperation, + "Message::toText() attempted without Opcode set"); + } + + string s; + + s += ";; ->>HEADER<<- opcode: " + impl_->opcode_->toText(); + // for simplicity we don't consider extended rcode (unlike BIND9) + s += ", status: " + impl_->rcode_->toText(); + s += ", id: " + boost::lexical_cast<string>(impl_->qid_); + s += "\n;; flags:"; + if (getHeaderFlag(HEADERFLAG_QR)) { + s += " qr"; + } + if (getHeaderFlag(HEADERFLAG_AA)) { + s += " aa"; + } + if (getHeaderFlag(HEADERFLAG_TC)) { + s += " tc"; + } + if (getHeaderFlag(HEADERFLAG_RD)) { + s += " rd"; + } + if (getHeaderFlag(HEADERFLAG_RA)) { + s += " ra"; + } + if (getHeaderFlag(HEADERFLAG_AD)) { + s += " ad"; + } + if (getHeaderFlag(HEADERFLAG_CD)) { + s += " cd"; + } + + // for simplicity, don't consider the update case for now + s += "; QUERY: " + // note: not "QUESTION" to be compatible with BIND 9 dig + lexical_cast<string>(impl_->counts_[SECTION_QUESTION]); + s += ", ANSWER: " + + lexical_cast<string>(impl_->counts_[SECTION_ANSWER]); + s += ", AUTHORITY: " + + lexical_cast<string>(impl_->counts_[SECTION_AUTHORITY]); + + unsigned int arcount = impl_->counts_[SECTION_ADDITIONAL]; + if (impl_->edns_) { + ++arcount; + } + if (impl_->tsig_rr_) { + ++arcount; + } + s += ", ADDITIONAL: " + lexical_cast<string>(arcount) + "\n"; + + if (impl_->edns_) { + s += "\n;; OPT PSEUDOSECTION:\n"; + s += impl_->edns_->toText(); + } + + if (!impl_->questions_.empty()) { + s += "\n;; " + + string(sectiontext[SECTION_QUESTION]) + " SECTION:\n"; + for_each(impl_->questions_.begin(), impl_->questions_.end(), + SectionFormatter<QuestionPtr>(SECTION_QUESTION, s)); + } + if (!impl_->rrsets_[SECTION_ANSWER].empty()) { + s += "\n;; " + + string(sectiontext[SECTION_ANSWER]) + " SECTION:\n"; + for_each(impl_->rrsets_[SECTION_ANSWER].begin(), + impl_->rrsets_[SECTION_ANSWER].end(), + SectionFormatter<RRsetPtr>(SECTION_ANSWER, s)); + } + if (!impl_->rrsets_[SECTION_AUTHORITY].empty()) { + s += "\n;; " + + string(sectiontext[SECTION_AUTHORITY]) + " SECTION:\n"; + for_each(impl_->rrsets_[SECTION_AUTHORITY].begin(), + impl_->rrsets_[SECTION_AUTHORITY].end(), + SectionFormatter<RRsetPtr>(SECTION_AUTHORITY, s)); + } + if (!impl_->rrsets_[SECTION_ADDITIONAL].empty()) { + s += "\n;; " + + string(sectiontext[SECTION_ADDITIONAL]) + + " SECTION:\n"; + for_each(impl_->rrsets_[SECTION_ADDITIONAL].begin(), + impl_->rrsets_[SECTION_ADDITIONAL].end(), + SectionFormatter<RRsetPtr>(SECTION_ADDITIONAL, s)); + } + + if (impl_->tsig_rr_) { + s += "\n;; TSIG PSEUDOSECTION:\n"; + s += impl_->tsig_rr_->toText(); + } + + return (s); +} + +void +Message::clear(Mode mode) { + impl_->init(); + impl_->mode_ = mode; +} + +void +Message::appendSection(const Section section, const Message& source) { + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + + if (section == SECTION_QUESTION) { + for (auto qi = source.beginQuestion(); qi != source.endQuestion(); ++qi) { + addQuestion(*qi); + } + } else { + for (auto rrsi = source.beginSection(section); rrsi != source.endSection(section); ++rrsi) { + addRRset(section, *rrsi); + } + } +} + +void +Message::makeResponse() { + if (impl_->mode_ != Message::PARSE) { + isc_throw(InvalidMessageOperation, + "makeResponse() is performed in non-parse mode"); + } + + impl_->mode_ = Message::RENDER; + + impl_->edns_ = EDNSPtr(); + impl_->flags_ &= MESSAGE_REPLYPRESERVE; + setHeaderFlag(HEADERFLAG_QR, true); + + impl_->rrsets_[SECTION_ANSWER].clear(); + impl_->counts_[SECTION_ANSWER] = 0; + impl_->rrsets_[SECTION_AUTHORITY].clear(); + impl_->counts_[SECTION_AUTHORITY] = 0; + impl_->rrsets_[SECTION_ADDITIONAL].clear(); + impl_->counts_[SECTION_ADDITIONAL] = 0; +} + +/// +/// Template version of Section Iterator +/// +template <typename T> +struct SectionIteratorImpl { + SectionIteratorImpl(const typename vector<T>::const_iterator& it) : + it_(it) { + } + + typename vector<T>::const_iterator it_; +}; + +template <typename T> +SectionIterator<T>::SectionIterator(const SectionIteratorImpl<T>& impl) { + impl_ = new SectionIteratorImpl<T>(impl.it_); +} + +template <typename T> +SectionIterator<T>::~SectionIterator() { + delete impl_; +} + +template <typename T> +SectionIterator<T>::SectionIterator(const SectionIterator<T>& source) : + impl_(new SectionIteratorImpl<T>(source.impl_->it_)) { +} + +template <typename T> +void +SectionIterator<T>::operator=(const SectionIterator<T>& source) { + if (impl_ == source.impl_) { + return; + } + SectionIteratorImpl<T>* newimpl = + new SectionIteratorImpl<T>(source.impl_->it_); + delete impl_; + impl_ = newimpl; +} + +template <typename T> +SectionIterator<T>& +SectionIterator<T>::operator++() { + ++(impl_->it_); + return (*this); +} + +template <typename T> +SectionIterator<T> +SectionIterator<T>::operator++(int) { + SectionIterator<T> tmp(*this); + ++(*this); + return (tmp); +} + +template <typename T> +const T& +SectionIterator<T>::operator*() const { + return (*(impl_->it_)); +} + +template <typename T> +const T* +SectionIterator<T>::operator->() const { + return (&(operator*())); +} + +template <typename T> +bool +SectionIterator<T>::operator==(const SectionIterator<T>& other) const { + return (impl_->it_ == other.impl_->it_); +} + +template <typename T> +bool +SectionIterator<T>::operator!=(const SectionIterator<T>& other) const { + return (impl_->it_ != other.impl_->it_); +} + +/// +/// We need to explicitly instantiate these template classes because these +/// are public classes but defined in this implementation file. +/// +template class SectionIterator<QuestionPtr>; +template class SectionIterator<RRsetPtr>; + +namespace { +typedef SectionIteratorImpl<QuestionPtr> QuestionIteratorImpl; +typedef SectionIteratorImpl<RRsetPtr> RRsetIteratorImpl; +} + +/// +/// Question iterator +/// +const QuestionIterator +Message::beginQuestion() const { + return (QuestionIterator(QuestionIteratorImpl(impl_->questions_.begin()))); +} + +const QuestionIterator +Message::endQuestion() const { + return (QuestionIterator(QuestionIteratorImpl(impl_->questions_.end()))); +} + +/// +/// RRsets iterators +/// +const SectionIterator<RRsetPtr> +Message::beginSection(const Section section) const { + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + if (section == SECTION_QUESTION) { + isc_throw(InvalidMessageSection, + "RRset iterator is requested for question"); + } + + return (RRsetIterator(RRsetIteratorImpl(impl_->rrsets_[section].begin()))); +} + +const SectionIterator<RRsetPtr> +Message::endSection(const Section section) const { + if (static_cast<int>(section) >= MessageImpl::NUM_SECTIONS) { + isc_throw(OutOfRange, "Invalid message section: " << static_cast<int>(section)); + } + if (section == SECTION_QUESTION) { + isc_throw(InvalidMessageSection, + "RRset iterator is requested for question"); + } + + return (RRsetIterator(RRsetIteratorImpl(impl_->rrsets_[section].end()))); +} + +ostream& +operator<<(ostream& os, const Message& message) { + return (os << message.toText()); +} +} // end of namespace dns +} // end of namespace isc diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h new file mode 100644 index 0000000..be5d0af --- /dev/null +++ b/src/lib/dns/message.h @@ -0,0 +1,688 @@ +// Copyright (C) 2009-2024 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 MESSAGE_H +#define MESSAGE_H + +#include <stdint.h> + +#include <iterator> +#include <string> +#include <ostream> + +#include <dns/exceptions.h> +#include <util/buffer.h> + +#include <dns/edns.h> +#include <dns/question.h> +#include <dns/rrset.h> + +namespace isc { +namespace dns { +class TSIGContext; +class TSIGRecord; + +/// +/// \brief A standard DNS module exception that is thrown if a wire format +/// message parser encounters a short length of data that don't even contain +/// the full header section. +/// +class MessageTooShort : public isc::dns::Exception { +public: + MessageTooShort(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if a section iterator +/// is being constructed for an incompatible section. Specifically, this +/// happens RRset iterator is being constructed for a Question section. +/// +class InvalidMessageSection : public isc::dns::Exception { +public: + InvalidMessageSection(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if a \c Message +/// class method is called that is prohibited for the current mode of +/// the message. +/// +class InvalidMessageOperation : public isc::dns::Exception { +public: + InvalidMessageOperation(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if a UDP buffer size +/// smaller than the standard default maximum (DEFAULT_MAX_UDPSIZE) is +/// being specified for the message. +/// +class InvalidMessageUDPSize : public isc::dns::Exception { +public: + InvalidMessageUDPSize(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +typedef uint16_t qid_t; + +class AbstractMessageRenderer; +class Message; +class MessageImpl; +class Opcode; +class Rcode; + +template <typename T> +struct SectionIteratorImpl; + +/// \c SectionIterator is a templated class to provide standard-compatible +/// iterators for Questions and RRsets for a given DNS message section. +/// The template parameter is either \c QuestionPtr (for the question section) +/// or \c RRsetPtr (for the answer, authority, or additional section). +template <typename T> +class SectionIterator { +public: + // Aliases used to enable iterator behavior on this class + using iterator_category = std::input_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using reference = T&; + + SectionIterator() : impl_(0) { + } + SectionIterator(const SectionIteratorImpl<T>& impl); + ~SectionIterator(); + SectionIterator(const SectionIterator<T>& source); + void operator=(const SectionIterator<T>& source); + SectionIterator<T>& operator++(); + SectionIterator<T> operator++(int); + const T& operator*() const; + const T* operator->() const; + bool operator==(const SectionIterator<T>& other) const; + bool operator!=(const SectionIterator<T>& other) const; +private: + SectionIteratorImpl<T>* impl_; +}; + +typedef SectionIterator<QuestionPtr> QuestionIterator; +typedef SectionIterator<RRsetPtr> RRsetIterator; + +class MessageImpl; +/// @brief Pointer to the @ref MessageImpl object. +typedef boost::shared_ptr<MessageImpl> MessageImplPtr; + +/// \brief The \c Message class encapsulates a standard DNS message. +/// +/// Details of the design and interfaces of this class are still in flux. +/// Here are some notes about the current design. +/// +/// Since many realistic DNS applications deal with messages, message objects +/// will be frequently used, and can be performance sensitive. To minimize +/// the performance overhead of constructing and destructing the objects, +/// this class is designed to be reusable. The \c clear() method is provided +/// for this purpose. +/// +/// A \c Message class object is in either the \c PARSE or the \c RENDER mode. +/// A \c PARSE mode object is intended to be used to convert wire-format +/// message data into a complete \c Message object. +/// A \c RENDER mode object is intended to be used to convert a \c Message +/// object into wire-format data. +/// Some of the method functions of this class are limited to a specific mode. +/// In general, "set" type operations are only allowed for \c RENDER mode +/// objects. +/// The initial mode must be specified on construction, and can be changed +/// through some method functions. +/// +/// This class uses the "pimpl" idiom, and hides detailed implementation +/// through the \c impl_ pointer. Since a \c Message object is expected to +/// be reused, the construction overhead of this approach should be acceptable. +/// +/// Open issues (among other things): +/// - We may want to provide an "iterator" for all RRsets/RRs for convenience. +/// This will be for applications that do not care about performance much, +/// so the implementation can only be moderately efficient. +/// - We may want to provide a "find" method for a specified type +/// of RR in the message. +class Message { +public: + /// Constants to specify the operation mode of the \c Message. + enum Mode { + PARSE = 0, ///< Parse mode (handling an incoming message) + RENDER = 1 ///< Render mode (building an outgoing message) + }; + + /// \brief Constants for flag bit fields of a DNS message header. + /// + /// Only the defined constants are valid where a header flag is required + /// in this library (e.g., in \c Message::setHeaderFlag()). + /// Since these are enum constants, however, an invalid value could be + /// passed via casting without an error at compilation time. + /// It is generally the callee's responsibility to check and reject invalid + /// values. + /// Of course, applications shouldn't pass invalid values even if the + /// callee does not perform proper validation; the result in such usage + /// is undefined. + /// + /// In the current implementation, the defined values happen to be + /// a 16-bit integer with one bit being set corresponding to the + /// specified flag in the second 16 bits of the DNS Header section + /// in order to make the internal implementation simpler. + /// For example, \c HEADERFLAG_QR is defined to be 0x8000 as the QR + /// bit is the most significant bit of the second 16 bits of the header. + /// However, applications should not assume this coincidence and + /// must solely use the enum representations. + /// Any usage based on the assumption of the underlying values is invalid + /// and the result is undefined. + /// + /// Likewise, bit wise operations such as AND or OR on the flag values + /// are invalid and are not guaranteed to work, even if it could compile + /// with casting. + /// For example, the following code will compile: + /// \code const uint16_t combined_flags = + /// static_cast<uint16_t>(Message::HEADERFLAG_AA) | + /// static_cast<uint16_t>(Message::HEADERFLAG_CD); + /// message->setHeaderFlag(static_cast<Message::HeaderFlag>(combined_flags)); + /// \endcode + /// and (with the current definition) happens to work as if it were + /// validly written as follows: + /// \code message->setHeaderFlag(Message::HEADERFLAG_AA); + /// message->setHeaderFlag(Message::HEADERFLAG_CD); + /// \endcode + /// But the former notation is invalid and may not work in future versions. + /// We did not try to prohibit such usage at compilation time, e.g., by + /// introducing a separately defined class considering the balance + /// between the complexity and advantage, but hopefully the cast notation + /// is sufficiently ugly to prevent proliferation of the usage. + enum HeaderFlag { + HEADERFLAG_QR = 0x8000, ///< Query (if cleared) or response (if set) + HEADERFLAG_AA = 0x0400, ///< Authoritative answer + HEADERFLAG_TC = 0x0200, ///< Truncation + HEADERFLAG_RD = 0x0100, ///< Recursion desired + HEADERFLAG_RA = 0x0080, ///< Recursion available + HEADERFLAG_AD = 0x0020, ///< Authentic %data (RFC4035) + HEADERFLAG_CD = 0x0010 ///< DNSSEC checking disabled (RFC4035) + }; + + /// \brief Constants to specify sections of a DNS message. + /// + /// The sections are those defined in RFC 1035 excluding the Header + /// section; the fields of the Header section are accessed via specific + /// methods of the \c Message class (e.g., \c getQid()). + /// + /// <b>Open Design Issue:</b> + /// In the current implementation the values for the constants are + /// sorted in the order of appearance in DNS messages, i.e., + /// from %Question to Additional. + /// So, for example, + /// code <code>section >= Message::SECTION_AUTHORITY</code> can be + /// used to do something in or after the Authority section. + /// This would be convenient, but it is not clear if it's really a good + /// idea to rely on relationship between the underlying values of enum + /// constants. At the moment, applications are discouraged to rely on + /// this implementation detail. We will see if such usage is sufficiently + /// common to officially support it. + /// + /// Note also that since we don't define \c operator++ for this enum, + /// the following code intending to iterate over all sections will + /// \b not compile: + /// \code for (Section s; s <= SECTION_ADDITIONAL; ++s) { // ++s undefined + /// // do something + /// } \endcode + /// This is intentional at this moment, and we'll see if we need to allow + /// that as we have more experiences with this library. + /// + /// <b>Future Extension:</b> We'll probably also define constants for + /// the section names used in dynamic updates in future versions. + enum Section { + SECTION_QUESTION = 0, ///< %Question section + SECTION_ANSWER = 1, ///< Answer section + SECTION_AUTHORITY = 2, ///< Authority section + SECTION_ADDITIONAL = 3 ///< Additional section + }; + + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are + /// intentionally defined as private. + /// The intended use case wouldn't require copies of a \c Message object; + /// once created, it would normally be expected to be reused, changing the + /// mode from \c PARSE to \c RENDER, and vice versa. + //@{ +public: + /// \brief The constructor. + /// The mode of the message is specified by the \c mode parameter. + Message(Mode mode); + /// \brief The destructor. + ~Message() = default; +private: + Message(const Message& source); + Message& operator=(const Message& source); + //@} +public: + /// \brief Return whether the specified header flag bit is set in the + /// header section. + /// + /// This method is basically exception free, but if + /// \c flag is not a valid constant of the \c HeaderFlag type, + /// an exception of class \c InvalidParameter will be thrown. + /// + /// \param flag The header flag constant to test. + /// \return \c true if the specified flag is set; otherwise \c false. + bool getHeaderFlag(const HeaderFlag flag) const; + + /// \brief Set or clear the specified header flag bit in the header + /// section. + /// + /// The optional parameter \c on indicates the operation mode, + /// set or clear; if it's \c true the corresponding flag will be set; + /// otherwise the flag will be cleared. + /// In either case the original state of the flag does not affect the + /// operation; for example, if a flag is already set and the "set" + /// operation is attempted, it effectively results in no operation. + /// + /// The parameter \c on can be omitted, in which case a value of \c true + /// (i.e., set operation) will be assumed. + /// This is based on the observation that the flag would have to be set + /// in the vast majority of the cases where an application needs to + /// use this method. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + /// + /// If \c flag is not a valid constant of the \c HeaderFlag type, + /// an exception of class \c InvalidParameter will be thrown. + /// + /// \param flag The header flag constant to set or clear. + /// \param on If \c true the flag will be set; otherwise the flag will be + /// cleared. + void setHeaderFlag(const HeaderFlag flag, const bool on = true); + + /// \brief Return the query ID given in the header section of the message. + qid_t getQid() const; + + /// \brief Set the query ID of the header section of the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + void setQid(qid_t qid); + + /// \brief Return the Response Code of the message. + /// + /// This includes extended codes specified by an EDNS OPT RR (when + /// included). In the \c PARSE mode, if the received message contains + /// an EDNS OPT RR, the corresponding extended code is identified and + /// returned. + /// + /// The message must have been properly parsed (in the case of the + /// \c PARSE mode) or an \c Rcode has been set (in the case of the + /// \c RENDER mode) beforehand. Otherwise, an exception of class + /// \c InvalidMessageOperation will be thrown. + const Rcode& getRcode() const; + + /// \brief Set the Response Code of the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + /// + /// If the specified code is an EDNS extended RCODE, an EDNS OPT RR will be + /// included in the message. + void setRcode(const Rcode& rcode); + + /// \brief Return the OPCODE given in the header section of the message. + /// + /// The message must have been properly parsed (in the case of the + /// \c PARSE mode) or an \c Opcode has been set (in the case of the + /// \c RENDER mode) beforehand. Otherwise, an exception of class + /// \c InvalidMessageOperation will be thrown. + const Opcode& getOpcode() const; + + /// \brief Set the OPCODE of the header section of the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + void setOpcode(const Opcode& opcode); + + /// \brief Return, if any, the EDNS associated with the message. + /// + /// This method never throws an exception. + /// + /// \return A shared pointer to the EDNS. This will be a null shared + /// pointer if the message is not associated with EDNS. + ConstEDNSPtr getEDNS() const; + + /// \brief Set EDNS for the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + /// + /// \param edns A shared pointer to an \c EDNS object to be set in + /// \c Message. + void setEDNS(ConstEDNSPtr edns); + + /// \brief Return, if any, the TSIG record contained in the received + /// message. + /// + /// Currently, this method is only intended to return a TSIG record + /// for an incoming message built via the \c fromWire() method in the + /// PARSE mode. A call to this method in the RENDER mode is invalid and + /// result in an exception. Also, calling this method is meaningless + /// unless \c fromWire() is performed. + /// + /// The returned pointer is valid only during the lifetime of the + /// \c Message object and until \c clear() is called. The \c Message + /// object retains the ownership of \c TSIGRecord; the caller must not + /// try to delete it. + /// + /// \exception InvalidMessageOperation Message is not in the PARSE mode. + /// + /// \return A pointer to the stored \c TSIGRecord or null. + const TSIGRecord* getTSIGRecord() const; + + /// \brief Returns the number of RRs contained in the given section. + /// + /// In the \c PARSE mode, the returned value may not be identical to + /// the actual number of RRs of the incoming message that is parsed. + /// The \c Message class handles some "meta" RRs such as EDNS OPT RR + /// separately. This method doesn't include such RRs. + /// Also, a future version of the parser will detect and unify duplicate + /// RRs (which should be rare in practice though), in which case + /// the stored RRs in the \c Message object will be fewer than the RRs + /// originally contained in the incoming message. + /// + /// Likewise, in the \c RENDER mode, even if \c EDNS is set in the + /// \c Message, this method doesn't count the corresponding OPT RR + /// in the Additional section. + /// + /// This method is basically exception free, but if + /// \c section is not a valid constant of the \c Section type, + /// an exception of class \c OutOfRange will be thrown. + /// + /// \param section The section in the message where RRs should be + /// counted. + /// \return The number of RRs stored in the specified section of the + /// message. + unsigned int getRRCount(const Section section) const; + + /// \brief Return an iterator corresponding to the beginning of the + /// Question section of the message. + const QuestionIterator beginQuestion() const; + + /// \brief Return an iterator corresponding to the end of the + /// Question section of the message. + const QuestionIterator endQuestion() const; + + /// \brief Return an iterator corresponding to the beginning of the + /// given section (other than Question) of the message. + /// + /// \c section must be a valid constant of the \c Section type; + /// otherwise, an exception of class \c OutOfRange will be thrown. + const RRsetIterator beginSection(const Section section) const; + + /// \brief Return an iterator corresponding to the end of the + /// given section (other than Question) of the message. + /// + /// \c section must be a valid constant of the \c Section type; + /// otherwise, an exception of class \c OutOfRange will be thrown. + const RRsetIterator endSection(const Section section) const; + + /// \brief Add a (pointer like object of) Question to the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + void addQuestion(QuestionPtr question); + + /// \brief Add a (pointer like object of) Question to the message. + /// + /// This version internally creates a \c QuestionPtr object from the + /// given \c question and calls the other version of this method. + /// So this is inherently less efficient, but is provided because this + /// form may be more intuitive and may make more sense for performance + /// insensitive applications. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + void addQuestion(const Question& question); + + /// \brief Add a (pointer like object of) RRset to the given section + /// of the message. + /// + /// Note that \c addRRset() does not currently check for duplicate + /// data before inserting RRsets. The caller is responsible for + /// checking for these (see \c hasRRset() below). + /// + /// \throw InvalidParameter rrset is null + /// \throw InvalidMessageOperation The message is not in the \c RENDER + /// mode. + /// \throw OutOfRange \c section doesn't specify a valid \c Section value. + /// + /// \param section The message section to which the rrset is to be added + /// \param rrset The rrset to be added. Must not be null. + void addRRset(const Section section, RRsetPtr rrset); + + /// \brief Determine whether the given section already has an RRset + /// matching the given name, RR class and RR type. + /// + /// \c section must be a valid constant of the \c Section type; + /// otherwise, an exception of class \c OutOfRange will be thrown. + /// + /// This should probably be extended to be a "find" method that returns + /// a matching RRset if found. + bool hasRRset(const Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype) const; + + /// \brief Determine whether the given section already has an RRset + /// matching the one pointed to by the argument + /// + /// \c section must be a valid constant of the \c Section type; + /// otherwise, an exception of class \c OutOfRange will be thrown. + bool hasRRset(const Section section, const RRsetPtr& rrset) const; + + /// \brief Remove RRSet from Message + /// + /// Removes the RRset identified by the section iterator from the message. + /// Note: if,.for some reason, the RRset is duplicated in the section, only + /// one occurrence is removed. + /// + /// If the operation is successful, all iterators into the section are + /// invalidated. + /// + /// \param section Section to which the iterator belongs + /// \param iterator Iterator pointing to the element to be removed + /// + /// \return true if the element was removed, false if the iterator was not + /// found in the specified section. + bool removeRRset(const Section section, RRsetIterator& iterator); + + /// \brief Remove all RRSets from the given Section + /// + /// This method is only allowed in the \c RENDER mode, and the given + /// section must be valid. + /// + /// \throw InvalidMessageOperation Message is not in the \c RENDER mode + /// \throw OutOfRange The specified section is not valid + /// + /// \param section Section to remove all rrsets from + void clearSection(const Section section); + + // The following methods are not currently implemented. + //void removeQuestion(QuestionPtr question); + // notyet: + //void addRR(const Section section, const RR& rr); + //void removeRR(const Section section, const RR& rr); + + /// \brief Clear the message content (if any) and reinitialize it in the + /// specified mode. + void clear(Mode mode); + + /// \brief Adds all rrsets from the source the given section in the + /// source message to the same section of this message + /// + /// \param section the section to append + /// \param source The source Message + void appendSection(const Section section, const Message& source); + + /// \brief Prepare for making a response from a request. + /// + /// This will clear the DNS header except those fields that should be kept + /// for the response, and clear answer and the following sections. + /// See also dns_message_reply() of BIND9. + void makeResponse(); + + /// \brief Convert the Message to a string. + /// + /// At least \c Opcode and \c Rcode must be validly set in the \c Message + /// (as a result of parse in the \c PARSE mode or by explicitly setting + /// in the \c RENDER mode); otherwise, an exception of + /// class \c InvalidMessageOperation will be thrown. + std::string toText() const; + + /// \brief Render the message in wire formant into a message renderer + /// object with (or without) TSIG. + /// + /// This \c Message must be in the \c RENDER mode and both \c Opcode and + /// \c Rcode must have been set beforehand; otherwise, an exception of + /// class \c InvalidMessageOperation will be thrown. + /// + /// If a non-null \c tsig_ctx is passed, it will also add a TSIG RR + /// with (in many cases) the TSIG MAC for the message along with the + /// given TSIG context (\c tsig_ctx). The TSIG RR will be placed at + /// the end of \c renderer. The \c TSIGContext at \c tsig_ctx will + /// be updated based on the fact it was used for signing and with + /// the latest MAC. + /// + /// \exception InvalidMessageOperation The message is not in the Render + /// mode, or either Rcode or Opcode is not set. + /// \exception InvalidParameter The allowable limit of \c renderer is too + /// small for a TSIG or the Header section. Note that this shouldn't + /// happen with parameters as defined in the standard protocols, + /// so it's more likely a program bug. + /// \exception Unexpected Rendering the TSIG RR fails. The implementation + /// internally makes sure this doesn't happen, so if that ever occurs + /// it should mean a bug either in the TSIG context or in the renderer + /// implementation. + /// + /// \note The renderer's internal buffers and data are automatically + /// cleared, keeping the length limit and the compression mode intact. + /// In case truncation is triggered, the renderer is cleared completely. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// \param tsig_ctx A TSIG context that is to be used for signing the + /// message + void toWire(AbstractMessageRenderer& renderer, + TSIGContext* tsig_ctx = 0); + + /// Parse options. + /// + /// describe PRESERVE_ORDER: note doesn't affect EDNS or TSIG. + /// + /// The option values are used as a parameter for \c fromWire(). + /// These are values of a bitmask type. Bitwise operations can be + /// performed on these values to express compound options. + enum ParseOptions { + PARSE_DEFAULT = 0, ///< The default options + PRESERVE_ORDER = 1 ///< Preserve RR order and don't combine them + }; + + /// \brief Parse the header section of the \c Message. + /// + /// NOTE: If the header has already been parsed by a previous call + /// to this method, this method simply returns (i.e., it does not + /// read from the \c buffer). + void parseHeader(isc::util::InputBuffer& buffer); + + /// \brief (Re)build a \c Message object from wire-format data. + /// + /// This method parses the given wire format data to build a + /// complete Message object. On success, the values of the header section + /// fields can be accessible via corresponding get methods, and the + /// question and following sections can be accessible via the + /// corresponding iterators. If the message contains an EDNS or TSIG, + /// they can be accessible via \c getEDNS() and \c getTSIGRecord(), + /// respectively. + /// + /// This \c Message must be in the \c PARSE mode. + /// + /// This method performs strict validation on the given message based + /// on the DNS protocol specifications. If the given message data is + /// invalid, this method throws an exception (see the exception list). + /// + /// By default, this method combines RRs of the same name, RR type and + /// RR class in a section into a single RRset, even if they are interleaved + /// with a different type of RR (though it would be a rare case in + /// practice). If the \c PRESERVE_ORDER option is specified, it handles + /// each RR separately, in the appearing order, and converts it to a + /// separate RRset (so this RRset should contain exactly one Rdata). + /// This mode will be necessary when the higher level protocol is + /// ordering conscious. For example, in AXFR and IXFR, the position of + /// the SOA RRs are crucial. + /// + /// \exception InvalidMessageOperation \c Message is in the RENDER mode + /// \exception DNSMessageFORMERR The given message data is syntactically + /// \exception MessageTooShort The given data is shorter than a valid + /// header section + /// \exception std::bad_alloc Memory allocation failure + /// \exception Others \c Name, \c Rdata, and \c EDNS classes can also throw + /// + /// \param buffer A input buffer object that stores the wire + /// data. This method reads from position 0 in the passed buffer. + /// \param options Parse options + void fromWire(isc::util::InputBuffer& buffer, ParseOptions options = PARSE_DEFAULT); + + /// + /// \name Protocol constants + /// + //@{ + /// \brief The default maximum size of UDP DNS messages that don't cause + /// truncation. + /// + /// With EDNS the maximum size can be increased per message. + static const uint16_t DEFAULT_MAX_UDPSIZE = 512; + + /// \brief The default maximum size of UDP DNS messages we can handle + static const uint16_t DEFAULT_MAX_EDNS0_UDPSIZE = 4096; + //@} + +private: + MessageImplPtr impl_; +}; + +/// \brief Pointer-like type pointing to a \c Message +/// +/// This type is expected to be used as an argument in asynchronous +/// callback functions. The internal reference-counting will ensure that +/// that ongoing state information will not be lost if the object +/// that originated the asynchronous call falls out of scope. +typedef boost::shared_ptr<Message> MessagePtr; +typedef boost::shared_ptr<const Message> ConstMessagePtr; + +/// Insert the \c Message as a string into stream. +/// +/// This method convert \c message into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param message A \c Message object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const Message& message); + +} // namespace dns +} // namespace isc + +#endif // MESSAGE_H diff --git a/src/lib/dns/messagerenderer.cc b/src/lib/dns/messagerenderer.cc new file mode 100644 index 0000000..4e1dfea --- /dev/null +++ b/src/lib/dns/messagerenderer.cc @@ -0,0 +1,393 @@ +// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <exceptions/isc_assert.h> +#include <dns/name.h> +#include <dns/name_internal.h> +#include <dns/labelsequence.h> +#include <dns/messagerenderer.h> +#include <util/buffer.h> + +#include <boost/array.hpp> +#include <boost/static_assert.hpp> +#include <limits> +#include <cassert> +#include <vector> + +using namespace isc::util; +using isc::dns::name::internal::maptolower; + +using namespace std; + +namespace isc { +namespace dns { + +namespace { // hide internal-only names from the public namespaces +/// +/// \brief The \c OffsetItem class represents a pointer to a name +/// rendered in the internal buffer for the \c MessageRendererImpl object. +/// +/// A \c MessageRendererImpl object maintains a set of \c OffsetItem +/// objects in a hash table, and searches the table for the position of the +/// longest match (ancestor) name against each new name to be rendered into +/// the buffer. +struct OffsetItem { + OffsetItem(size_t hash, size_t pos, size_t len) : + hash_(hash), pos_(pos), len_(len) { + } + + /// The hash value for the stored name calculated by LabelSequence.getHash. + /// This will help make name comparison in \c NameCompare more efficient. + size_t hash_; + + /// The position (offset from the beginning) in the buffer where the + /// name starts. + uint16_t pos_; + + /// The length of the corresponding sequence (which is a domain name). + uint16_t len_; +}; + +/// \brief The \c NameCompare class is a functor that checks equality +/// between the name corresponding to an \c OffsetItem object and the name +/// consists of labels represented by a \c LabelSequence object. +/// +/// Template parameter CASE_SENSITIVE determines whether to ignore the case +/// of the names. This policy doesn't change throughout the lifetime of +/// this object, so we separate these using template to avoid unnecessary +/// condition check. +template <bool CASE_SENSITIVE> +struct NameCompare { + /// \brief Constructor + /// + /// \param buffer The buffer for rendering used in the caller renderer + /// \param name_buf An input buffer storing the wire-format data of the + /// name to be newly rendered (and only that data). + /// \param hash The hash value for the name. + NameCompare(const OutputBuffer& buffer, InputBuffer& name_buf, + size_t hash) : + buffer_(&buffer), name_buf_(&name_buf), hash_(hash) { + } + + bool operator()(const OffsetItem& item) const { + // Trivial inequality check. If either the hash or the total length + // doesn't match, the names are obviously different. + if (item.hash_ != hash_ || item.len_ != name_buf_->getLength()) { + return (false); + } + + // Compare the name data, character-by-character. + // item_pos keeps track of the position in the buffer corresponding to + // the character to compare. item_label_len is the number of + // characters in the labels where the character pointed by item_pos + // belongs. When it reaches zero, nextPosition() identifies the + // position for the subsequent label, taking into account name + // compression, and resets item_label_len to the length of the new + // label. + name_buf_->setPosition(0); // buffer can be reused, so reset position + uint16_t item_pos = item.pos_; + uint16_t item_label_len = 0; + for (size_t i = 0; i < item.len_; ++i, ++item_pos) { + item_pos = nextPosition(*buffer_, item_pos, item_label_len); + const uint8_t ch1 = (*buffer_)[item_pos]; + const uint8_t ch2 = name_buf_->readUint8(); + if (CASE_SENSITIVE) { + if (ch1 != ch2) { + return (false); + } + } else { + if (maptolower[ch1] != maptolower[ch2]) { + return (false); + } + } + } + + return (true); + } + +private: + static uint16_t nextPosition(const OutputBuffer& buffer, + uint16_t pos, uint16_t& llen) { + if (llen == 0) { + size_t i = 0; + + while ((buffer[pos] & Name::COMPRESS_POINTER_MARK8) == + Name::COMPRESS_POINTER_MARK8) { + pos = (buffer[pos] & ~Name::COMPRESS_POINTER_MARK8) * + 256 + buffer[pos + 1]; + + // This loop should stop as long as the buffer has been + // constructed validly and the search/insert argument is based + // on a valid name, which is an assumption for this class. + // But we'll abort if a bug could cause an infinite loop. + i += 2; + isc_throw_assert(i < Name::MAX_WIRE); + } + llen = buffer[pos]; + } else { + --llen; + } + return (pos); + } + + const OutputBuffer* buffer_; + InputBuffer* name_buf_; + const size_t hash_; +}; +} + +/// +/// \brief The \c MessageRendererImpl class is the actual implementation of +/// \c MessageRenderer. +/// +/// The implementation is hidden from applications. We can refer to specific +/// members of this class only within the implementation source file. +/// +/// It internally holds a hash table for OffsetItem objects corresponding +/// to portions of names rendered in this renderer. The offset information +/// is used to compress subsequent names to be rendered. +struct MessageRenderer::MessageRendererImpl { + // The size of hash buckets and number of hash entries per bucket for + // which space is preallocated and kept reserved for subsequent rendering + // to provide better performance. These values are derived from the + // BIND 9 implementation that uses a similar hash table. + static const size_t BUCKETS = 64; + static const size_t RESERVED_ITEMS = 16; + static const uint16_t NO_OFFSET = 65535; // used as a marker of 'not found' + + /// \brief Constructor + MessageRendererImpl() : + msglength_limit_(512), truncated_(false), + compress_mode_(MessageRenderer::CASE_INSENSITIVE) { + // Reserve some spaces for hash table items. + for (size_t i = 0; i < BUCKETS; ++i) { + table_[i].reserve(RESERVED_ITEMS); + } + } + + uint16_t findOffset(const OutputBuffer& buffer, InputBuffer& name_buf, + size_t hash, bool case_sensitive) const { + // Find a matching entry, if any. We use some heuristics here: often + // the same name appears consecutively (like repeating the same owner + // name for a single RRset), so in case there's a collision in the + // bucket it will be more likely to find it in the tail side of the + // bucket. + const size_t bucket_id = hash % BUCKETS; + vector<OffsetItem>::const_reverse_iterator found; + if (case_sensitive) { + found = find_if(table_[bucket_id].rbegin(), + table_[bucket_id].rend(), + NameCompare<true>(buffer, name_buf, hash)); + } else { + found = find_if(table_[bucket_id].rbegin(), + table_[bucket_id].rend(), + NameCompare<false>(buffer, name_buf, hash)); + } + if (found != table_[bucket_id].rend()) { + return (found->pos_); + } + return (NO_OFFSET); + } + + void addOffset(size_t hash, size_t offset, size_t len) { + table_[hash % BUCKETS].push_back(OffsetItem(hash, offset, len)); + } + + // The hash table for the (offset + position in the buffer) entries + vector<OffsetItem> table_[BUCKETS]; + /// The maximum length of rendered data that can fit without + /// truncation. + uint16_t msglength_limit_; + /// A boolean flag that indicates truncation has occurred while rendering + /// the data. + bool truncated_; + /// The name compression mode. + CompressMode compress_mode_; + + // Placeholder for hash values as they are calculated in writeName(). + // Note: we may want to make it a local variable of writeName() if it + // works more efficiently. + boost::array<size_t, Name::MAX_LABELS> seq_hashes_; +}; + +MessageRenderer::MessageRenderer() : + AbstractMessageRenderer(), + impl_(new MessageRendererImpl()) { +} + +MessageRenderer::~MessageRenderer() { +} + +void +MessageRenderer::clear() { + AbstractMessageRenderer::clear(); + impl_->msglength_limit_ = 512; + impl_->truncated_ = false; + impl_->compress_mode_ = CASE_INSENSITIVE; + + // Clear the hash table. We reserve the minimum space for possible + // subsequent use of the renderer. + for (size_t i = 0; i < MessageRendererImpl::BUCKETS; ++i) { + if (impl_->table_[i].size() > MessageRendererImpl::RESERVED_ITEMS) { + // Trim excessive capacity: swap ensures the new capacity is only + // reasonably large for the reserved space. + vector<OffsetItem> new_table; + new_table.reserve(MessageRendererImpl::RESERVED_ITEMS); + new_table.swap(impl_->table_[i]); + } + impl_->table_[i].clear(); + } +} + +size_t +MessageRenderer::getLengthLimit() const { + return (impl_->msglength_limit_); +} + +void +MessageRenderer::setLengthLimit(const size_t len) { + impl_->msglength_limit_ = len; +} + +bool +MessageRenderer::isTruncated() const { + return (impl_->truncated_); +} + +void +MessageRenderer::setTruncated() { + impl_->truncated_ = true; +} + +MessageRenderer::CompressMode +MessageRenderer::getCompressMode() const { + return (impl_->compress_mode_); +} + +void +MessageRenderer::setCompressMode(const CompressMode mode) { + if (getLength() != 0) { + isc_throw(isc::InvalidParameter, + "compress mode cannot be changed during rendering"); + } + impl_->compress_mode_ = mode; +} + +void +MessageRenderer::writeName(const LabelSequence& ls, const bool compress) { + LabelSequence sequence(ls); + const size_t nlabels = sequence.getLabelCount(); + size_t data_len; + const uint8_t* data; + + // Find the offset in the offset table whose name gives the longest + // match against the name to be rendered. + size_t nlabels_uncomp; + uint16_t ptr_offset = MessageRendererImpl::NO_OFFSET; + const bool case_sensitive = (impl_->compress_mode_ == + MessageRenderer::CASE_SENSITIVE); + for (nlabels_uncomp = 0; nlabels_uncomp < nlabels; ++nlabels_uncomp) { + if (nlabels_uncomp > 0) { + sequence.stripLeft(1); + } + + data = sequence.getData(&data_len); + if (data_len == 1) { // trailing dot. + ++nlabels_uncomp; + break; + } + // write with range check for safety + impl_->seq_hashes_.at(nlabels_uncomp) = + sequence.getHash(impl_->compress_mode_); + InputBuffer name_buf(data, data_len); + ptr_offset = impl_->findOffset(getBuffer(), name_buf, + impl_->seq_hashes_[nlabels_uncomp], + case_sensitive); + if (ptr_offset != MessageRendererImpl::NO_OFFSET) { + break; + } + } + + // Record the current offset before updating the offset table + size_t offset = getLength(); + // Write uncompress part: + if (nlabels_uncomp > 0 || !compress) { + LabelSequence uncomp_sequence(ls); + if (compress && nlabels > nlabels_uncomp) { + // If there's compressed part, strip off that part. + uncomp_sequence.stripRight(nlabels - nlabels_uncomp); + } + data = uncomp_sequence.getData(&data_len); + writeData(data, data_len); + } + // And write compression pointer if available: + if (compress && ptr_offset != MessageRendererImpl::NO_OFFSET) { + ptr_offset |= Name::COMPRESS_POINTER_MARK16; + writeUint16(ptr_offset); + } + + // Finally, record the offset and length for each uncompressed sequence + // in the hash table. The renderer's buffer has just stored the + // corresponding data, so we use the rendered data to get the length + // of each label of the names. + size_t seqlen = ls.getDataLength(); + for (size_t i = 0; i < nlabels_uncomp; ++i) { + const uint8_t label_len = getBuffer()[offset]; + if (label_len == 0) { // offset for root doesn't need to be stored. + break; + } + if (offset > Name::MAX_COMPRESS_POINTER) { + break; + } + // Store the tuple of <hash, offset, len> to the table. Note that we + // already know the hash value for each name. + impl_->addOffset(impl_->seq_hashes_[i], offset, seqlen); + offset += (label_len + 1); + seqlen -= (label_len + 1); + } +} + +void +MessageRenderer::writeName(const Name& name, const bool compress) { + const LabelSequence ls(name); + writeName(ls, compress); +} + +AbstractMessageRenderer::AbstractMessageRenderer() : + local_buffer_(0), buffer_(&local_buffer_) { +} + +void +AbstractMessageRenderer::setBuffer(OutputBuffer* buffer) { + if (buffer && buffer_->getLength() != 0) { + isc_throw(isc::InvalidParameter, + "MessageRenderer buffer cannot be set when in use"); + } + if (!buffer && buffer_ == &local_buffer_) { + isc_throw(isc::InvalidParameter, + "Default MessageRenderer buffer cannot be reset"); + } + + if (!buffer) { + // Reset to the default buffer, then clear other internal resources. + // The order is important; we need to keep the used buffer intact. + buffer_ = &local_buffer_; + clear(); + } else { + buffer_ = buffer; + } +} + +void +AbstractMessageRenderer::clear() { + buffer_->clear(); +} + +} +} diff --git a/src/lib/dns/messagerenderer.h b/src/lib/dns/messagerenderer.h new file mode 100644 index 0000000..4b105cd --- /dev/null +++ b/src/lib/dns/messagerenderer.h @@ -0,0 +1,391 @@ +// Copyright (C) 2009-2024 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 MESSAGERENDERER_H +#define MESSAGERENDERER_H + +#include <util/buffer.h> + +#include <boost/noncopyable.hpp> + +#include <memory> + +namespace isc { +namespace dns { +// forward declarations +class Name; +class LabelSequence; + +/// \brief The \c AbstractMessageRenderer class is an abstract base class +/// that provides common interfaces for rendering a DNS message into a buffer +/// in wire format. +/// +/// A specific derived class of \c AbstractMessageRenderer (we call it +/// a renderer class hereafter) is simply responsible for name compression at +/// least in the current design. A renderer class object (conceptually) +/// manages the positions of names rendered in some sort of buffer and uses +/// that information to render subsequent names with compression. +/// +/// A renderer class is mainly intended to be used as a helper for a more +/// comprehensive \c Message class internally; normal applications won't have +/// to care about details of this class. +/// +/// By default any (derived) renderer class object is associated with +/// an internal buffer, and subsequent write operations will be performed +/// on that buffer. The rendering result can be retrieved via the +/// \c getData() method. +/// +/// If an application wants a separate buffer can be (normally temporarily) +/// set for rendering operations via the \c setBuffer() method. In that case, +/// it is generally expected that all rendering operations are performed via +/// that object. If the application modifies the buffer in +/// parallel with the renderer, the result will be undefined. +/// +/// Note to developers: we introduced a separate class for name compression +/// because previous benchmark with BIND9 showed compression affects overall +/// response performance very much. By having a separate class dedicated for +/// this purpose, we'll be able to change the internal implementation of name +/// compression in the future without affecting other part of the API and +/// implementation. +/// +/// In addition, by introducing a class hierarchy from +/// \c AbstractMessageRenderer, we allow an application to use a customized +/// renderer class for specific purposes. For example, a high performance +/// DNS server may want to use an optimized renderer class assuming some +/// specific underlying data representation. +/// +/// \note Some functions (like writeUint8) are not virtual. It is because +/// it is hard to imagine any version of message renderer that would +/// do anything else than just putting the data into a buffer, so we +/// provide a default implementation and having them virtual would only +/// hurt the performance with no real gain. If it would happen a different +/// implementation is really needed, we can make them virtual in future. +/// The only one that is virtual is writeName and it's because this +/// function is much more complicated, therefore there's a lot of space +/// for different implementations or different behavior. +class AbstractMessageRenderer { +public: + /// \brief Compression mode constants. + /// + /// The \c CompressMode enum type represents the name compression mode + /// for renderer classes. + /// \c CASE_INSENSITIVE means compress names in case-insensitive manner; + /// \c CASE_SENSITIVE means compress names in case-sensitive manner. + /// By default, a renderer compresses names in case-insensitive + /// manner. + /// Compression mode can be dynamically modified by the + /// \c setCompressMode() method. + /// The mode can be changed even in the middle of rendering, although this + /// is not an intended usage. In this case the names already compressed + /// are intact; only names being compressed after the mode change are + /// affected by the change. + /// If a renderer class object is reinitialized by the \c clear() + /// method, the compression mode will be reset to the default, which is + /// \c CASE_INSENSITIVE + /// + /// One specific case where case-sensitive compression is required is + /// AXFR as described in draft-ietf-dnsext-axfr-clarify. A primary + /// authoritative DNS server implementation using this API would specify + /// \c CASE_SENSITIVE before rendering outgoing AXFR messages. + /// + enum CompressMode { + CASE_INSENSITIVE, //!< Compress names case-insensitive manner (default) + CASE_SENSITIVE //!< Compress names case-sensitive manner + }; +protected: + /// + /// \name Constructors and Destructor + //@{ + /// \brief The default constructor. + /// + /// This is intentionally defined as \c protected as this base class should + /// never be instantiated (except as part of a derived class). + AbstractMessageRenderer(); + +public: + /// \brief The destructor. + virtual ~AbstractMessageRenderer() {} + //@} +protected: + /// \brief Return the output buffer we render into. + const isc::util::OutputBuffer& getBuffer() const { return (*buffer_); } + isc::util::OutputBuffer& getBuffer() { return (*buffer_); } +private: + /// \brief Local (default) buffer to store data. + isc::util::OutputBuffer local_buffer_; + + /// \brief Buffer to store data. + /// + /// Note that the class interface ensures this pointer is never null; + /// it either refers to \c local_buffer_ or to an application-supplied + /// buffer by \c setBuffer(). + /// + /// It was decided that there's no need to have this in every subclass, + /// at least not now, and this reduces code size and gives compiler a + /// better chance to optimize. + isc::util::OutputBuffer* buffer_; +public: + /// + /// \name Getter Methods + /// + //@{ + /// \brief Return a pointer to the head of the data stored in the internal + /// buffer. + /// + /// This method works exactly same as the same method of the \c OutputBuffer + /// class; all notes for \c OutputBuffer apply. + const void* getData() const { + return (buffer_->getData()); + } + + /// \brief Return the length of data written in the internal buffer. + size_t getLength() const { + return (buffer_->getLength()); + } + + /// \brief Return whether truncation has occurred while rendering. + /// + /// Once the return value of this method is \c true, it doesn't make sense + /// to try rendering more data, although this class itself doesn't reject + /// the attempt. + /// + /// This method never throws an exception. + /// + /// \return true if truncation has occurred; otherwise \c false. + virtual bool isTruncated() const = 0; + + /// \brief Return the maximum length of rendered data that can fit in the + /// corresponding DNS message without truncation. + /// + /// This method never throws an exception. + /// + /// \return The maximum length in bytes. + virtual size_t getLengthLimit() const = 0; + + /// \brief Return the compression mode of the renderer class object. + /// + /// This method never throws an exception. + /// + /// \return The current compression mode. + virtual CompressMode getCompressMode() const = 0; + //@} + + /// + /// \name Setter Methods + /// + //@{ + /// \brief Set or reset a temporary output buffer. + /// + /// This method can be used for an application that manages an output + /// buffer separately from the message renderer and wants to keep reusing + /// the renderer. When the renderer is associated with the default buffer + /// and the given pointer is non null, the given buffer will be + /// (temporarily) used for subsequent message rendering; if the renderer + /// is associated with a temporary buffer and the given pointer is null, + /// the renderer will be reset with the default buffer. In the latter + /// case any additional resources (possibly specific to a derived renderer + /// class) will be cleared, but the temporary buffer is kept as the latest + /// state (which would normally store the rendering result). + /// + /// This method imposes some restrictions to prevent accidental misuse + /// that could cause disruption such as dereferencing an invalid object. + /// First, a temporary buffer must not be set when the associated buffer + /// is in use, that is, any data are stored in the buffer. Also, the + /// default buffer cannot be "reset"; when null is specified a temporary + /// buffer must have been set beforehand. If these conditions aren't met + /// an isc::InvalidParameter exception will be thrown. This method is + /// exception free otherwise. + /// + /// \throw isc::InvalidParameter A restrictions of the method usage isn't + /// met. + /// + /// \param buffer A pointer to a temporary output buffer or null for reset + /// it. + void setBuffer(isc::util::OutputBuffer* buffer); + + /// \brief Mark the renderer to indicate truncation has occurred while + /// rendering. + /// + /// This method never throws an exception. + virtual void setTruncated() = 0; + + /// \brief Set the maximum length of rendered data that can fit in the + /// corresponding DNS message without truncation. + /// + /// This method never throws an exception. + /// + /// \param len The maximum length in bytes. + virtual void setLengthLimit(size_t len) = 0; + + /// \brief Set the compression mode of the renderer class object. + /// + /// This method never throws an exception. + /// + /// \param mode A \c CompressMode value representing the compression mode. + virtual void setCompressMode(CompressMode mode) = 0; + //@} + + /// + /// \name Methods for writing data into the internal buffer. + /// + //@{ + /// \brief Insert a specified length of gap at the end of the buffer. + /// + /// The caller should not assume any particular value to be inserted. + /// This method is provided as a shortcut to make a hole in the buffer + /// that is to be filled in later, e.g, by \ref writeUint16At(). + /// + /// \param len The length of the gap to be inserted in bytes. + void skip(size_t len) { + buffer_->skip(len); + } + + /// \brief Trim the specified length of data from the end of the internal + /// buffer. + /// + /// This method is provided for such cases as DNS message truncation. + /// + /// The specified length must not exceed the current data size of the + /// buffer; otherwise an exception of class \c isc::OutOfRange will + /// be thrown. + /// + /// \param len The length of data that should be trimmed. + void trim(size_t len) { + buffer_->trim(len); + } + + /// \brief Clear the internal buffer and other internal resources. + /// + /// This method can be used to re-initialize and reuse the renderer + /// without constructing a new one. + virtual void clear(); + + /// \brief Write an unsigned 8-bit integer into the internal buffer. + /// + /// \param data The 8-bit integer to be written into the internal buffer. + void writeUint8(const uint8_t data) { + buffer_->writeUint8(data); + } + + /// \brief Write an unsigned 16-bit integer in host byte order into the + /// internal buffer in network byte order. + /// + /// \param data The 16-bit integer to be written into the buffer. + void writeUint16(uint16_t data) { + buffer_->writeUint16(data); + } + + /// \brief Write an unsigned 16-bit integer in host byte order at the + /// specified position of the internal buffer in network byte order. + /// + /// The buffer must have a sufficient room to store the given data at the + /// given position, that is, <code>pos + 2 < getLength()</code>; + /// otherwise an exception of class \c isc::OutOfRange will be thrown. + /// Note also that this method never extends the internal buffer. + /// + /// \param data The 16-bit integer to be written into the internal buffer. + /// \param pos The beginning position in the buffer to write the data. + void writeUint16At(uint16_t data, size_t pos) { + buffer_->writeUint16At(data, pos); + } + + /// \brief Write an unsigned 32-bit integer in host byte order into the + /// internal buffer in network byte order. + /// + /// \param data The 32-bit integer to be written into the buffer. + void writeUint32(uint32_t data) { + buffer_->writeUint32(data); + } + + /// \brief Copy an arbitrary length of data into the internal buffer + /// of the renderer object. + /// + /// No conversion on the copied data is performed. + /// + /// \param data A pointer to the data to be copied into the internal buffer. + /// \param len The length of the data in bytes. + void writeData(const void *data, size_t len) { + buffer_->writeData(data, len); + } + + /// \brief Write a \c Name object into the internal buffer in wire format, + /// with or without name compression. + /// + /// If the optional parameter \c compress is \c true, this method tries to + /// compress the \c name if possible, searching the entire message that has + /// been rendered. Otherwise name compression is omitted. Its default + /// value is \c true. + /// + /// Note: even if \c compress is \c true, the position of the \c name (and + /// possibly its ancestor names) in the message is recorded and may be used + /// for compressing subsequent names. + /// + /// \param name A \c Name object to be written. + /// \param compress A boolean indicating whether to enable name + /// compression. + virtual void writeName(const Name& name, bool compress = true) = 0; + + /// \brief Write a \c LabelSequence object into the internal buffer + /// in wire format, with or without name compression. + /// + /// This is the same as the other version, which takes \c Name instead + /// of \c LabelSequence, except for the parameter type. The passed + /// \c LabelSequence must be absolute. + /// + /// \param ls A \c LabelSequence object to be written. + /// \param compress A boolean indicating whether to enable name + /// compression. + virtual void writeName(const LabelSequence& ls, bool compress = true) = 0; + //@} +}; + +/// The \c MessageRenderer is a concrete derived class of +/// \c AbstractMessageRenderer as a general purpose implementation of the +/// renderer interfaces. +/// +/// A \c MessageRenderer object is constructed with a \c OutputBuffer +/// object, which is the buffer into which the rendered %data will be written. +/// Normally the buffer is expected to be empty on construction, but it doesn't +/// have to be so; the renderer object will start rendering from the +/// end of the buffer at the time of construction. However, if the +/// pre-existing portion of the buffer contains DNS names, these names won't +/// be considered for name compression. +class MessageRenderer : public AbstractMessageRenderer, + public boost::noncopyable { // Can crash if copied +public: + using AbstractMessageRenderer::CASE_INSENSITIVE; + using AbstractMessageRenderer::CASE_SENSITIVE; + + MessageRenderer(); + + virtual ~MessageRenderer(); + virtual bool isTruncated() const; + virtual size_t getLengthLimit() const; + virtual CompressMode getCompressMode() const; + virtual void setTruncated(); + virtual void setLengthLimit(size_t len); + + /// This implementation does not allow this call in the middle of + /// rendering (i.e. after at least one name is rendered) due to + /// restriction specific to the internal implementation. Such attempts + /// will result in an \c isc::InvalidParameter exception. + /// + /// This shouldn't be too restrictive in practice; there's no known + /// practical case for such a mixed compression policy in a single + /// message. + virtual void setCompressMode(CompressMode mode); + + virtual void clear(); + virtual void writeName(const Name& name, bool compress = true); + virtual void writeName(const LabelSequence& ls, bool compress = true); + +private: + struct MessageRendererImpl; + std::unique_ptr<MessageRendererImpl> impl_; +}; +} +} +#endif // MESSAGERENDERER_H diff --git a/src/lib/dns/name.cc b/src/lib/dns/name.cc new file mode 100644 index 0000000..ac48205 --- /dev/null +++ b/src/lib/dns/name.cc @@ -0,0 +1,720 @@ +// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/isc_assert.h> +#include <dns/name.h> +#include <dns/name_internal.h> +#include <dns/messagerenderer.h> +#include <dns/labelsequence.h> + +#include <cctype> +#include <iterator> +#include <functional> +#include <vector> +#include <iostream> +#include <algorithm> + +using namespace isc::util; +using namespace isc::dns::name::internal; + +using namespace std; + +namespace isc { +namespace dns { + +namespace { +/// +/// These are shortcut arrays for efficient character conversion. +/// digitvalue converts a digit character to the corresponding integer. +/// maptolower convert uppercase alphabets to their lowercase counterparts. +/// We once used a helper non-local static object to avoid hardcoding the +/// array members, but we then realized it's susceptible to static +/// initialization order fiasco: Since these constants are used in a Name +/// constructor, a non-local static Name object defined in another translation +/// unit than this file may not be initialized correctly. +/// There are several ways to address this issue, but in this specific case +/// we chose the naive but simple hardcoding approach. +/// +/// These definitions are derived from BIND 9's libdns module. +/// Note: we could use the standard tolower() function instead of the +/// maptolower array, but a benchmark indicated that the private array could +/// improve the performance of message rendering (which internally uses the +/// array heavily) about 27%. Since we want to achieve very good performance +/// for message rendering in some cases, we'll keep using it. +const signed char digitvalue[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 48 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 64 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128 + -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, // 256 +}; +} + +namespace name { +namespace internal { +const uint8_t maptolower[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // ..., 'A' - 'G' + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, // 'H' - 'O' + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // 'P' - 'W' + 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, // 'X' - 'Z', ... + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; +} // end of internal +} // end of name + +namespace { +/// +/// Textual name parser states. +/// +typedef enum { + ft_init = 0, // begin of the name + ft_start, // begin of a label + ft_ordinary, // parsing an ordinary label + ft_initialescape, // just found '\' + ft_escape, // begin of handling a '\'-escaped sequence + ft_escdecimal // parsing a '\DDD' octet. +} ft_state; + +// The parser of name from a string. It is a template, because +// some parameters are used with two different types, while others +// are private type aliases. +template<class Iterator, class Offsets, class Data> +void +stringParse(Iterator s, Iterator send, bool downcase, Offsets& offsets, + Data& ndata) +{ + const Iterator orig_s(s); + // + // Initialize things to make the compiler happy; they're not required. + // + unsigned int digits = 0; + unsigned int value = 0; + unsigned int count = 0; + + // + // Set up the state machine. + // + bool done = false; + bool is_root = false; + const bool empty = s == send; + ft_state state = ft_init; + + // Prepare the output buffers. + offsets.reserve(Name::MAX_LABELS); + offsets.push_back(0); + ndata.reserve(Name::MAX_WIRE); + + // should we refactor this code using, e.g, the state pattern? Probably + // not at this point, as this is based on proved code (derived from BIND9) + // and it's less likely that we'll have more variations in the domain name + // syntax. If this ever happens next time, we should consider refactor + // the code, rather than adding more states and cases below. + while (ndata.size() < Name::MAX_WIRE && s != send && !done) { + unsigned char c = *s++; + + switch (state) { + case ft_init: + // + // Is this the root name? + // + if (c == '.') { + if (s != send) { + isc_throw(EmptyLabel, + "non terminating empty label in " << + string(orig_s, send)); + } + is_root = true; + } else if (c == '@' && s == send) { + // handle a single '@' as the root name. + is_root = true; + } + + if (is_root) { + ndata.push_back(0); + done = true; + break; + } + + // FALLTHROUGH + case ft_start: + ndata.push_back(0); // placeholder for the label length field + count = 0; + if (c == '\\') { + state = ft_initialescape; + break; + } + state = ft_ordinary; + isc_throw_assert(ndata.size() < Name::MAX_WIRE); + // FALLTHROUGH + case ft_ordinary: + if (c == '.') { + if (count == 0) { + isc_throw(EmptyLabel, + "duplicate period in " << string(orig_s, send)); + } + ndata.at(offsets.back()) = count; + offsets.push_back(ndata.size()); + if (s == send) { + ndata.push_back(0); + done = true; + } + state = ft_start; + } else if (c == '\\') { + state = ft_escape; + } else { + if (++count > Name::MAX_LABELLEN) { + isc_throw(TooLongLabel, + "label is too long in " << string(orig_s, send)); + } + ndata.push_back(downcase ? maptolower[c] : c); + } + break; + case ft_initialescape: + if (c == '[') { + // This looks like a bitstring label, which was deprecated. + // Intentionally drop it. + isc_throw(BadLabelType, + "invalid label type in " << string(orig_s, send)); + } + // FALLTHROUGH + case ft_escape: + if (!isdigit(c & 0xff)) { + if (++count > Name::MAX_LABELLEN) { + isc_throw(TooLongLabel, + "label is too long in " << string(orig_s, send)); + } + ndata.push_back(downcase ? maptolower[c] : c); + state = ft_ordinary; + break; + } + digits = 0; + value = 0; + state = ft_escdecimal; + // FALLTHROUGH + case ft_escdecimal: + if (!isdigit(c & 0xff)) { + isc_throw(BadEscape, + "mixture of escaped digit and non-digit in " + << string(orig_s, send)); + } + value *= 10; + value += digitvalue[c]; + digits++; + if (digits == 3) { + if (value > 255) { + isc_throw(BadEscape, + "escaped decimal is too large in " + << string(orig_s, send)); + } + if (++count > Name::MAX_LABELLEN) { + isc_throw(TooLongLabel, + "label is too long in " << string(orig_s, send)); + } + ndata.push_back(downcase ? maptolower[value] : value); + state = ft_ordinary; + } + break; + default: + // impossible case + isc_throw_assert(false); + } + } + + if (!done) { // no trailing '.' was found. + if (ndata.size() == Name::MAX_WIRE) { + isc_throw(TooLongName, + "name is too long for termination in " << + string(orig_s, send)); + } + isc_throw_assert(s == send); + if (state != ft_ordinary) { + isc_throw(IncompleteName, + "incomplete textual name in " << + (empty ? "<empty>" : string(orig_s, send))); + } + if (state == ft_ordinary) { + isc_throw_assert(count != 0); + ndata.at(offsets.back()) = count; + + offsets.push_back(ndata.size()); + // add a trailing \0 + ndata.push_back('\0'); + } + } +} + +} + +Name::Name(const std::string &namestring, bool downcase) { + // Prepare inputs for the parser + const std::string::const_iterator s = namestring.begin(); + const std::string::const_iterator send = namestring.end(); + + // Prepare outputs + NameOffsets offsets; + NameString ndata; + + // To the parsing + stringParse(s, send, downcase, offsets, ndata); + + // And get the output + labelcount_ = offsets.size(); + isc_throw_assert(labelcount_ > 0 && labelcount_ <= Name::MAX_LABELS); + ndata_.assign(ndata.data(), ndata.size()); + length_ = ndata_.size(); + offsets_.assign(offsets.begin(), offsets.end()); +} + +Name::Name(const char* namedata, size_t data_len, const Name* origin, + bool downcase) { + // Check validity of data + if (!namedata || data_len == 0) { + isc_throw(isc::InvalidParameter, + "No data provided to Name constructor"); + } + // If the last character is not a dot, it is a relative to origin. + // It is safe to check now, we know there's at least one character. + const bool absolute = (namedata[data_len - 1] == '.'); + // If we are not absolute, we need the origin to complete the name. + if (!absolute && !origin) { + isc_throw(MissingNameOrigin, + "No origin available and name is relative"); + } + // Prepare inputs for the parser + const char* end = namedata + data_len; + + // Prepare outputs + NameOffsets offsets; + NameString ndata; + + // Do the actual parsing + stringParse(namedata, end, downcase, offsets, ndata); + + // Get the output + labelcount_ = offsets.size(); + isc_throw_assert(labelcount_ > 0 && labelcount_ <= Name::MAX_LABELS); + ndata_.assign(ndata.data(), ndata.size()); + length_ = ndata_.size(); + offsets_.assign(offsets.begin(), offsets.end()); + + if (!absolute) { + // Now, extend the data with the ones from origin. But eat the + // last label (the empty one). + + // Drop the last character of the data (the \0) and append a copy of + // the origin's data + ndata_.erase(ndata_.end() - 1); + ndata_.append(origin->ndata_); + + // Do a similar thing with offsets. However, we need to move them + // so they point after the prefix we parsed before. + size_t offset = offsets_.back(); + offsets_.pop_back(); + size_t offset_count = offsets_.size(); + offsets_.insert(offsets_.end(), origin->offsets_.begin(), + origin->offsets_.end()); + for (auto it(offsets_.begin() + offset_count); it != offsets_.end(); ++it) { + *it += offset; + } + + // Adjust sizes. + length_ = ndata_.size(); + labelcount_ = offsets_.size(); + + // And check the sizes are OK. + if (labelcount_ > Name::MAX_LABELS || length_ > Name::MAX_WIRE) { + isc_throw(TooLongName, "Combined name is too long"); + } + } +} + +namespace { +/// +/// Wire-format name parser states. +/// +typedef enum { + fw_start = 0, // beginning of a label + fw_ordinary, // inside an ordinary (non compressed) label + fw_newcurrent // beginning of a compression pointer +} fw_state; +} + +Name::Name(InputBuffer& buffer, bool downcase) { + NameOffsets offsets; + offsets.reserve(Name::MAX_LABELS); + + /* + * Initialize things to make the compiler happy; they're not required. + */ + unsigned int n = 0; + + // + // Set up. + // + bool done = false; + unsigned int nused = 0; + bool seen_pointer = false; + fw_state state = fw_start; + + unsigned int cused = 0; // Bytes of compressed name data used + unsigned int current = buffer.getPosition(); + unsigned int pos_begin = current; + unsigned int biggest_pointer = current; + + // Make the compiler happy; this is not required. + // XXX: bad style in that we initialize it with a dummy value and define + // it far from where it's used. But alternatives seemed even worse. + unsigned int new_current = 0; + + // + // Note: The following code is not optimized for speed, but + // rather for correctness. Speed will be addressed in the future. + // + while (current < buffer.getLength() && !done) { + unsigned int c = buffer.readUint8(); + current++; + if (!seen_pointer) { + cused++; + } + + switch (state) { + case fw_start: + if (c <= MAX_LABELLEN) { + offsets.push_back(nused); + if (nused + c + 1 > Name::MAX_WIRE) { + isc_throw(DNSMessageFORMERR, "wire name is too long: " + << nused + c + 1 << " bytes"); + } + nused += c + 1; + ndata_.push_back(c); + if (c == 0) { + done = true; + } + n = c; + state = fw_ordinary; + } else if ((c & COMPRESS_POINTER_MARK8) == COMPRESS_POINTER_MARK8) { + // + // Ordinary 14-bit pointer. + // + new_current = c & ~COMPRESS_POINTER_MARK8; + n = 1; + state = fw_newcurrent; + } else { + // this case includes local compression pointer, which hasn't + // been standardized. + isc_throw(DNSMessageFORMERR, "unknown label character: " << c); + } + break; + case fw_ordinary: + if (downcase) { + c = maptolower[c]; + } + ndata_.push_back(c); + if (--n == 0) { + state = fw_start; + } + break; + case fw_newcurrent: + new_current *= 256; + new_current += c; + if (--n != 0) { + break; + } + if (new_current >= biggest_pointer) { + isc_throw(DNSMessageFORMERR, + "bad compression pointer (out of range): " << + new_current); + } + biggest_pointer = new_current; + current = new_current; + buffer.setPosition(current); + seen_pointer = true; + state = fw_start; + break; + default: + isc_throw_assert(false); + } + } + + if (!done) { + isc_throw(DNSMessageFORMERR, "incomplete wire-format name"); + } + + labelcount_ = offsets.size(); + length_ = nused; + offsets_.assign(offsets.begin(), offsets.end()); + buffer.setPosition(pos_begin + cused); +} + +void +Name::toWire(OutputBuffer& buffer) const { + buffer.writeData(ndata_.data(), ndata_.size()); +} + +void +Name::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeName(*this); +} + +std::string +Name::toText(bool omit_final_dot) const { + LabelSequence ls(*this); + return (ls.toText(omit_final_dot)); +} + +std::string +Name::toRawText(bool omit_final_dot) const { + LabelSequence ls(*this); + return (ls.toRawText(omit_final_dot)); +} + +NameComparisonResult +Name::compare(const Name& other) const { + const LabelSequence ls1(*this); + const LabelSequence ls2(other); + return (ls1.compare(ls2)); +} + +bool +Name::equals(const Name& other) const { + if (length_ != other.length_ || labelcount_ != other.labelcount_) { + return (false); + } + + for (unsigned int l = labelcount_, pos = 0; l > 0; --l) { + uint8_t count = ndata_[pos]; + if (count != other.ndata_[pos]) { + return (false); + } + ++pos; + + while (count-- > 0) { + uint8_t label1 = ndata_[pos]; + uint8_t label2 = other.ndata_[pos]; + + if (maptolower[label1] != maptolower[label2]) { + return (false); + } + ++pos; + } + } + + return (true); +} + +bool +Name::leq(const Name& other) const { + return (compare(other).getOrder() <= 0); +} + +bool +Name::geq(const Name& other) const { + return (compare(other).getOrder() >= 0); +} + +bool +Name::lthan(const Name& other) const { + return (compare(other).getOrder() < 0); +} + +bool +Name::gthan(const Name& other) const { + return (compare(other).getOrder() > 0); +} + +bool +Name::isWildcard() const { + return (length_ >= 2 && ndata_[0] == 1 && ndata_[1] == '*'); +} + +Name +Name::concatenate(const Name& suffix) const { + isc_throw_assert(length_ > 0 && suffix.length_ > 0); + isc_throw_assert(labelcount_ > 0 && suffix.labelcount_ > 0); + + unsigned int length = length_ + suffix.length_ - 1; + if (length > Name::MAX_WIRE) { + isc_throw(TooLongName, "names are too long to concatenate"); + } + + Name retname; + retname.ndata_.reserve(length); + retname.ndata_.assign(ndata_, 0, length_ - 1); + retname.ndata_.insert(retname.ndata_.end(), + suffix.ndata_.begin(), suffix.ndata_.end()); + isc_throw_assert(retname.ndata_.size() == length); + retname.length_ = length; + + // + // Setup the offsets vector. Copy the offsets of this (prefix) name, + // excluding that for the trailing dot, and append the offsets of the + // suffix name with the additional offset of the length of the prefix. + // + unsigned int labels = labelcount_ + suffix.labelcount_ - 1; + isc_throw_assert(labels <= Name::MAX_LABELS); + retname.offsets_.reserve(labels); + retname.offsets_.assign(&offsets_[0], &offsets_[0] + labelcount_ - 1); + transform(suffix.offsets_.begin(), suffix.offsets_.end(), + back_inserter(retname.offsets_), + [this] (char x) { return (x + length_ - 1); }); + isc_throw_assert(retname.offsets_.size() == labels); + retname.labelcount_ = labels; + + return (retname); +} + +Name +Name::reverse() const { + Name retname; + // + // Set up offsets: The size of the string and number of labels will + // be the same in as in the original. + // + retname.offsets_.reserve(labelcount_); + retname.ndata_.reserve(length_); + + // Copy the original name, label by label, from tail to head. + NameOffsets::const_reverse_iterator rit0 = offsets_.rbegin(); + NameOffsets::const_reverse_iterator rit1 = rit0 + 1; + NameString::const_iterator n0 = ndata_.begin(); + retname.offsets_.push_back(0); + while (rit1 != offsets_.rend()) { + retname.ndata_.append(n0 + *rit1, n0 + *rit0); + retname.offsets_.push_back(retname.ndata_.size()); + ++rit0; + ++rit1; + } + retname.ndata_.push_back(0); + + retname.labelcount_ = labelcount_; + retname.length_ = length_; + + return (retname); +} + +Name +Name::split(const unsigned int first, const unsigned int n) const { + if (n == 0 || n > labelcount_ || first > labelcount_ - n) { + isc_throw(OutOfRange, "Name::split: invalid split range"); + } + + Name retname; + // If the specified range doesn't include the trailing dot, we need one + // more label for that. + unsigned int newlabels = (first + n == labelcount_) ? n : n + 1; + + // + // Set up offsets: copy the corresponding range of the original offsets + // with subtracting an offset of the prefix length. + // + retname.offsets_.reserve(newlabels); + transform(offsets_.begin() + first, offsets_.begin() + first + newlabels, + back_inserter(retname.offsets_), + [&](char x) { return (x - offsets_[first]); }); + + // + // Set up the new name. At this point the tail of the new offsets specifies + // the position of the trailing dot, which should be equal to the length of + // the extracted portion excluding the dot. First copy that part from the + // original name, and append the trailing dot explicitly. + // + retname.ndata_.reserve(retname.offsets_.back() + 1); + retname.ndata_.assign(ndata_, offsets_[first], retname.offsets_.back()); + retname.ndata_.push_back(0); + + retname.length_ = retname.ndata_.size(); + retname.labelcount_ = retname.offsets_.size(); + isc_throw_assert(retname.labelcount_ == newlabels); + + return (retname); +} + +Name +Name::split(const unsigned int level) const { + if (level >= getLabelCount()) { + isc_throw(OutOfRange, "invalid level for name split (" << level + << ") for name " << *this); + } + + return (split(level, getLabelCount() - level)); +} + +Name& +Name::downcase() { + unsigned int nlen = length_; + unsigned int labels = labelcount_; + unsigned int pos = 0; + + while (labels > 0 && nlen > 0) { + --labels; + --nlen; + + // we assume a valid name, and do abort() if the assumption fails + // rather than throwing an exception. + unsigned int count = ndata_.at(pos++); + isc_throw_assert(count <= MAX_LABELLEN); + isc_throw_assert(nlen >= count); + + while (count > 0) { + ndata_.at(pos) = + maptolower[ndata_.at(pos)]; + ++pos; + --nlen; + --count; + } + } + + return (*this); +} + +std::ostream& +operator<<(std::ostream& os, const Name& name) { + os << name.toText(); + return (os); +} + +} +} diff --git a/src/lib/dns/name.h b/src/lib/dns/name.h new file mode 100644 index 0000000..1a64f19 --- /dev/null +++ b/src/lib/dns/name.h @@ -0,0 +1,758 @@ +// Copyright (C) 2009-2024 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 NAME_H +#define NAME_H + +#include <util/buffer.h> +#include <dns/messagerenderer.h> + +#include <stdint.h> + +#include <string> +#include <vector> + +#include <dns/exceptions.h> + +namespace isc { +namespace dns { +/// +/// \brief A standard DNS module exception that is thrown if the name parser +/// encounters an empty label in the middle of a name. +/// +class EmptyLabel : public NameParserException { +public: + EmptyLabel(const char* file, size_t line, const char* what) : + NameParserException(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if the name parser +/// encounters too long a name. +/// +class TooLongName : public NameParserException { +public: + TooLongName(const char* file, size_t line, const char* what) : + NameParserException(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if the name parser +/// encounters too long a label. +/// +class TooLongLabel : public NameParserException { +public: + TooLongLabel(const char* file, size_t line, const char* what) : + NameParserException(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if the name parser +/// encounters an obsolete or incomplete label type. In effect "obsolete" only +/// applies to bitstring labels, which would begin with "\[". Incomplete cases +/// include an incomplete escaped sequence such as "\12". +/// +class BadLabelType : public NameParserException { +public: + BadLabelType(const char* file, size_t line, const char* what) : + NameParserException(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if the name parser +/// fails to decode a back-slash escaped sequence. +/// +class BadEscape : public NameParserException { +public: + BadEscape(const char* file, size_t line, const char* what) : + NameParserException(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if the name parser +/// finds the input (string or wire-format %data) is incomplete. +/// +/// An attempt of constructing a name from an empty string will trigger this +/// exception. +/// +class IncompleteName : public NameParserException { +public: + IncompleteName(const char* file, size_t line, const char* what) : + NameParserException(file, line, what) {} +}; + +/// \brief Thrown when origin is null and is needed. +/// +/// The exception is thrown when the Name constructor for master file +/// is used, the provided data is relative and the origin parameter is +/// set to null. +class MissingNameOrigin : public NameParserException { +public: + MissingNameOrigin(const char* file, size_t line, const char* what) : + NameParserException(file, line, what) {} +}; + +/// +/// This is a supplemental class used only as a return value of +/// Name::compare() and LabelSequence::compare(). +/// It encapsulate a tuple of the comparison: ordering, number of common +/// labels, and relationship as follows: +/// - ordering: relative ordering under the DNSSEC order relation +/// - labels: the number of common significant labels of the two names or +/// two label sequences being compared +/// - relationship: see NameComparisonResult::NameRelation +/// +/// Note that the ordering is defined for two label sequences that have no +/// hierarchical relationship (in which case the relationship will be NONE). +/// For example, two non absolute (or "relative") sequences "example.com" and +/// "example.org" have no hierarchical relationship, and the former should be +/// sorted before (i.e. "smaller") than the latter. +class NameComparisonResult { +public: + /// The relation of two names under comparison. + /// Its semantics for the case of + /// <code>name1->compare(name2)</code> (where name1 and name2 are instances + /// of the \c Name or \c LabelSequence class) is as follows: + /// - SUPERDOMAIN: name1 properly contains name2; name2 is a proper + /// subdomain of name1 + /// - SUBDOMAIN: name1 is a proper subdomain of name2 + /// - EQUAL: name1 and name2 are equal + /// - COMMONANCESTOR: name1 and name2 share a common ancestor + /// - NONE: There's no hierarchical relationship between name1 and name2 + /// + /// Note that there's always a hierarchical relationship between any two + /// names since all names (not generic label sequences) are absolute and + /// they at least share the trailing empty label. + /// So, for example, the relationship between "com." and "net." is + /// "commonancestor". The relationship of "NONE" can only happen for + /// comparison between two label sequences (\c LabelSequence objects); + /// usually only SUPERDOMAIN, SUBDOMAIN or EQUAL are important relationship + /// between two names. + /// + /// When two \c LabelSequence objects are compared, it's generally expected + /// they are either both absolute or both non absolute; if one is absolute + /// and the other is not, the resulting relationship will be NONE. + enum NameRelation { + SUPERDOMAIN = 0, + SUBDOMAIN = 1, + EQUAL = 2, + COMMONANCESTOR = 3, + NONE = 4 + }; + + /// + /// \name Constructors and Destructor + /// + //@{ + /// \brief Constructor from a comparison tuple + /// + /// This constructor simply initializes the object in the straightforward + /// way. + NameComparisonResult(int order, unsigned int nlabels, + NameRelation relation) : + order_(order), nlabels_(nlabels), relation_(relation) {} + //@} + + /// + /// \name Getter Methods + /// + //@{ + /// Returns the ordering of the comparison result + int getOrder() const { return (order_); } + /// Returns the number of common labels of the comparison result + unsigned int getCommonLabels() const { return (nlabels_); } + /// Returns the NameRelation of the comparison result + NameRelation getRelation() const { return (relation_); } + //@} +private: + int order_; + unsigned int nlabels_; + NameRelation relation_; +}; + +/// +/// The \c Name class encapsulates DNS names. +/// +/// It provides interfaces to construct a name from string or wire-format %data, +/// transform a name into a string or wire-format %data, compare two names, get +/// access to various properties of a name, etc. +/// +/// Notes to developers: Internally, a name object maintains the name %data +/// in wire format as an instance of \c std::string. Since many string +/// implementations adopt copy-on-write %data sharing, we expect this approach +/// will make copying a name less expensive in typical cases. If this is +/// found to be a significant performance bottleneck later, we may reconsider +/// the internal representation or perhaps the API. +/// +/// A name object also maintains a vector of offsets (\c offsets_ member), +/// each of which is the offset to a label of the name: The n-th element of +/// the vector specifies the offset to the n-th label. For example, if the +/// object represents "www.example.com", the elements of the offsets vector +/// are 0, 4, 12, and 16. Note that the offset to the trailing dot (16) is +/// included. In the BIND9 DNS library from which this implementation is +/// derived, the offsets are optional, probably due to performance +/// considerations (in fact, offsets can always be calculated from the name +/// %data, and in that sense are redundant). In our implementation, however, +/// we always build and maintain the offsets. We believe we need more low +/// level, specialized %data structure and interface where we really need to +/// pursue performance, and would rather keep this generic API and +/// implementation simpler. +/// +/// While many other DNS APIs introduce an "absolute or relative" +/// attribute of names as defined in RFC1035, names are always "absolute" in +/// the initial design of this API. +/// In fact, separating absolute and relative would confuse API users +/// unnecessarily. For example, it's not so intuitive to consider the +/// comparison result of an absolute name with a relative name. +/// We've looked into how the concept of absolute names is used in BIND9, +/// and found that in many cases names are generally absolute. +/// The only reasonable case of separating absolute and relative is in a master +/// file parser, where a relative name must be a complete name with an "origin" +/// name, which must be absolute. So, in this initial design, we chose a +/// simpler approach: the API generally handles names as absolute; when we +/// introduce a parser of master files, we'll introduce the notion of relative +/// names as a special case. +/// +class Name { + // LabelSequences use knowledge about the internal data structure + // of this class for efficiency (they use the offsets_ vector and + // the ndata_ string) + friend class LabelSequence; + + /// + /// \name Constructors and Destructor + /// + //@{ +private: + /// \brief Name data string + typedef std::basic_string<uint8_t> NameString; + /// \brief Name offsets type + typedef std::vector<uint8_t> NameOffsets; + + /// The default constructor + /// + /// This is used internally in the class implementation, but at least at + /// the moment defined as private because it will construct an incomplete + /// object in that it doesn't have any labels. We may reconsider this + /// design choice as we see more applications of the class. + Name() : length_(0), labelcount_(0) {} +public: + /// Constructor from a string + /// + /// If the given string does not represent a valid DNS name, an exception + /// of class \c EmptyLabel, \c TooLongLabel, \c BadLabelType, \c BadEscape, + /// \c TooLongName, or \c IncompleteName will be thrown. + /// In addition, if resource allocation for the new name fails, a + /// corresponding standard exception will be thrown. + /// + /// \param namestr A string representation of the name to be constructed. + /// \param downcase Whether to convert upper case alphabets to lower case. + explicit Name(const std::string& namestr, bool downcase = false); + + /// \brief Constructor for master file parser + /// + /// This acts similar to the above. But the data is passed as raw C-string + /// instead of wrapped-up C++ std::string. + /// + /// Also, when the origin is non-null and the name_data is not ending with + /// a dot, it is considered relative and the origin is appended to it. + /// + /// If the name_data is equal to "@", the content of origin is copied. + /// + /// \param name_data The raw data of the name. + /// \param data_len How many bytes in name_data is valid and considered + /// part of the name. + /// \param origin If non-null, it is taken as the origin to complete + /// relative names. + /// \param downcase Whether to convert upper case letters to lower case. + /// \throw NameParserException or any of its descendants in case the + /// input data is invalid. + /// \throw isc::InvalidParameter In case name_data is null or + /// data_len is 0. + /// \throw std::bad_alloc In case allocation fails. + /// \note This constructor is specially designed for the use of master + /// file parser. It mimics the behaviour of names in the master file + /// and accepts raw data. It is not recommended to be used by anything + /// else. + /// \todo Should we make it private and the parser a friend, to hide the + /// constructor? + Name(const char* name_data, size_t data_len, const Name* origin, + bool downcase = false); + + /// Constructor from wire-format %data. + /// + /// The \c buffer parameter normally stores a complete DNS message + /// containing the name to be constructed. The current read position of + /// the buffer points to the head of the name. + /// + /// The input %data may or may not be compressed; if it's compressed, this + /// method will automatically decompress it. + /// + /// If the given %data does not represent a valid DNS name, an exception + /// of class \c DNSMessageFORMERR will be thrown. + /// In addition, if resource allocation for the new name fails, a + /// corresponding standard exception will be thrown. + /// + /// \param buffer A buffer storing the wire format %data. + /// \param downcase Whether to convert upper case alphabets to lower case. + explicit Name(isc::util::InputBuffer& buffer, bool downcase = false); + /// + /// We use the default copy constructor intentionally. + //@} + /// We use the default copy assignment operator intentionally. + /// + + /// + /// \name Getter Methods + /// + //@{ + /// \brief Provides one-byte name %data in wire format at the specified + /// position. + /// + /// This method returns the unsigned 8-bit value of wire-format \c Name + /// %data at the given position from the head. + /// + /// For example, if \c n is a \c Name object for "example.com", + /// \c n.at(3) would return \c 'a', and \c n.at(7) would return \c 'e'. + /// Note that \c n.at(0) would be 7 (decimal), the label length of + /// "example", instead of \c 'e', because it returns a %data portion + /// in wire-format. Likewise, \c n.at(8) would return 3 (decimal) + /// instead of <code>'.'</code> + /// + /// This method would be useful for an application to examine the + /// wire-format name %data without dumping the %data into a buffer, + /// which would involve %data copies and would be less efficient. + /// One common usage of this method would be something like this: + /// \code for (size_t i = 0; i < name.getLength(); ++i) { + /// uint8_t c = name.at(i); + /// // do something with c + /// } \endcode + /// + /// Parameter \c pos must be in the valid range of the name %data, that is, + /// must be less than \c Name.getLength(). Otherwise, an exception of + /// class \c OutOfRange will be thrown. + /// This method never throws an exception in other ways. + /// + /// \param pos The position in the wire format name %data to be returned. + /// \return An unsigned 8-bit integer corresponding to the name %data + /// at the position of \c pos. + uint8_t at(size_t pos) const + { + if (pos >= length_) { + isc_throw(OutOfRange, "Out of range access in Name::at()"); + } + return (ndata_[pos]); + } + + /// \brief Gets the length of the <code>Name</code> in its wire format. + /// + /// This method never throws an exception. + /// + /// \return the length (the number of octets in wire format) of the + /// <code>Name</code> + size_t getLength() const { return (length_); } + + /// \brief Returns the number of labels contained in the <code>Name</code>. + /// + /// Note that an empty label (corresponding to a trailing '.') is counted + /// as a single label, so the return value of this method must be >0. + /// + /// This method never throws an exception. + /// + /// \return the number of labels + unsigned int getLabelCount() const { return (labelcount_); } + //@} + + /// + /// \name Converter methods + /// + //@{ + /// \brief Convert the Name to a string. + /// + /// This method returns a <code>std::string</code> object representing the + /// Name as a string. Unless <code>omit_final_dot</code> is + /// <code>true</code>, the returned string ends with a dot '.'; the default + /// is <code>false</code>. The default value of this parameter is + /// <code>true</code>; converted names will have a trailing dot by default. + /// + /// This function assumes the name is in proper uncompressed wire format. + /// If it finds an unexpected label character including compression pointer, + /// an exception of class \c BadLabelType will be thrown. + /// In addition, if resource allocation for the result string fails, a + /// corresponding standard exception will be thrown. + // + /// \param omit_final_dot whether to omit the trailing dot in the output. + /// \return a string representation of the <code>Name</code>. + std::string toText(bool omit_final_dot = false) const; + + /// \brief Convert the LabelSequence to a string without escape sequences. + /// + /// The string returned will contain a single character value for any + /// escape sequences in the label(s). + /// + /// \param omit_final_dot whether to omit the trailing dot in the output. + /// \return a string representation of the <code>LabelSequence</code> + /// that does not contain escape sequences. Default value is false. + std::string toRawText(bool omit_final_dot = false) const; + + /// \brief Render the <code>Name</code> in the wire format with compression. + /// + /// This method dumps the Name in wire format with help of \c renderer, + /// which encapsulates output buffer and name compression algorithm to + /// render the name. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + void toWire(AbstractMessageRenderer& renderer) const; + + /// \brief Render the <code>Name</code> in the wire format without + /// compression. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. This can be avoided by preallocating + /// a sufficient size of \c buffer. Specifically, if + /// <code>buffer.getCapacity() - buffer.getLength() >= Name::MAX_WIRE</code> + /// then this method should not throw an exception. + /// + /// \param buffer An output buffer to store the wire %data. + void toWire(isc::util::OutputBuffer& buffer) const; + //@} + + /// + /// \name Comparison methods + /// + //@{ + /// \brief Compare two <code>Name</code>s. + /// + /// This method compares the <code>Name</code> and <code>other</code> and + /// returns the result in the form of a <code>NameComparisonResult</code> + /// object. + /// + /// Note that this is case-insensitive comparison. + /// + /// This method never throws an exception. + /// + /// \param other the right-hand operand to compare against. + /// \return a <code>NameComparisonResult</code> object representing the + /// comparison result. + NameComparisonResult compare(const Name& other) const; + +public: + /// \brief Return true iff two names are equal. + /// + /// Semantically this could be implemented based on the result of the + /// \c compare() method, but the actual implementation uses different code + /// that simply performs character-by-character comparison (case + /// insensitive for the name label parts) on the two names. This is because + /// it would be much faster and the simple equality check would be pretty + /// common. + /// + /// This method never throws an exception. + /// + /// \param other the <code>Name</code> object to compare against. + /// \return true if the two names are equal; otherwise false. + bool equals(const Name& other) const; + + /// Same as equals() + bool operator==(const Name& other) const { return (equals(other)); } + + /// \brief Return true iff two names are not equal. + /// + /// This method simply negates the result of \c equal() method, and in that + /// sense it's redundant. The separate method is provided just for + /// convenience. + bool nequals(const Name& other) const { return (!(equals(other))); } + + /// Same as nequals() + bool operator!=(const Name& other) const { return (nequals(other)); } + + /// \brief Less-than or equal comparison for Name against <code>other</code> + /// + /// The comparison is based on the result of the \c compare() method. + /// + /// This method never throws an exception. + /// + /// \param other the <code>Name</code> object to compare against. + /// \return true if <code>compare(other).getOrder() <= 0</code>; + /// otherwise false. + bool leq(const Name& other) const; + + /// Same as leq() + bool operator<=(const Name& other) const { return (leq(other)); } + + /// \brief Greater-than or equal comparison for Name against + /// <code>other</code> + /// + /// The comparison is based on the result of the \c compare() method. + /// + /// This method never throws an exception. + /// + /// \param other the <code>Name</code> object to compare against. + /// \return true if <code>compare(other).getOrder() >= 0</code>; + /// otherwise false. + bool geq(const Name& other) const; + + /// Same as geq() + bool operator>=(const Name& other) const { return (geq(other)); } + + /// \brief Less-than comparison for Name against <code>other</code> + /// + /// The comparison is based on the result of the \c compare() method. + /// + /// This method never throws an exception. + /// + /// \param other the <code>Name</code> object to compare against. + /// \return true if <code>compare(other).getOrder() < 0</code>; + /// otherwise false. + bool lthan(const Name& other) const; + + /// Same as lthan() + bool operator<(const Name& other) const { return (lthan(other)); } + + /// \brief Greater-than comparison for Name against <code>other</code> + /// + /// The comparison is based on the result of the \c compare() method. + //// + /// This method never throws an exception. + /// + /// \param other the <code>Name</code> object to compare against. + /// \return true if <code>compare(other).getOrder() > 0</code>; + /// otherwise false. + bool gthan(const Name& other) const; + + /// Same as gthan() + bool operator>(const Name& other) const { return (gthan(other)); } + //@} + + /// + /// \name Transformer methods + /// + //@{ + /// \brief Extract a specified subpart of Name. + /// + /// <code>name.split(first, n)</code> constructs a new name starting from + /// the <code>first</code>-th label of the \c name, and subsequent \c n + /// labels including the \c first one. Since names in this current + /// implementation are always "absolute", if the specified range doesn't + /// contain the trailing dot of the original \c name, then a dot will be + /// appended to the resulting name. As a result, the number of labels + /// will be <code>n + 1</code>, rather than \c n. For example, + /// when \c n is <code>Name("www.example.com")</code>, + /// both <code>n.split(1, 2)</code> and <code>n.split(1, 3)</code> + /// will produce a name corresponding to "example.com.", which has 3 labels. + /// Note also that labels are counted from 0, and so <code>first = 1</code> + /// in this example specified the label "example", not "www". + /// + /// Parameter \c n must be larger than 0, and the range specified by + /// \c first and \c n must not exceed the valid range of the original name; + /// otherwise, an exception of class \c OutOfRange will be thrown. + /// + /// Note to developers: we may want to have different versions (signatures) + /// of this method. For example, we want to split the Name based on a given + /// suffix name. + /// + /// \param first The start position (in labels) of the extracted name + /// \param n Number of labels of the extracted name + /// \return A new Name object based on the Name containing <code>n</code> + /// labels including and following the <code>first</code> label. + Name split(unsigned int first, unsigned int n) const; + + /// \brief Extract a specified super domain name of Name. + /// + /// This function constructs a new \c Name object that is a super domain + /// of \c this name. + /// The new name is \c level labels upper than \c this name. + /// For example, when \c name is www.example.com, + /// <code>name.split(1)</code> will return a \c Name object for example.com. + /// \c level can be 0, in which case this method returns a copy of + /// \c this name. + /// The possible maximum value for \c level is + /// <code>this->getLabelCount()-1</code>, in which case this method + /// returns a root name. + /// + /// One common expected usage of this method is to iterate over super + /// domains of a given name, label by label, as shown in the following + /// sample code: + /// \code // if name is www.example.com... + /// for (int i = 0; i < name.getLabelCount(); ++i) { + /// Name upper_name(name.split(i)); + /// // upper_name'll be www.example.com., example.com., com., and then . + /// } + /// \endcode + /// + /// \c level must be smaller than the number of labels of \c this name; + /// otherwise an exception of class \c OutOfRange will be thrown. + /// In addition, if resource allocation for the new name fails, a + /// corresponding standard exception will be thrown. + /// + /// Note to developers: probably as easily imagined, this method is a + /// simple wrapper to one usage of the other + /// <code>split(unsigned int, unsigned int) const</code> method and is + /// redundant in some sense. + /// We provide the "redundant" method for convenience, however, because + /// the expected usage shown above seems to be common, and the parameters + /// to the other \c split(unsigned int, unsigned int) const to implement + /// it may not be very intuitive. + /// + /// We are also aware that it is generally discouraged to add a public + /// member function that could be implemented using other member functions. + /// We considered making it a non member function, but we could not come + /// up with an intuitive function name to represent the specific service. + /// Some other developers argued, probably partly because of the + /// counter intuitive function name, a different signature of \c split + /// would be better to improve code readability. + /// While that may be a matter of personal preference, we accepted the + /// argument. One major goal of public APIs like this is wider acceptance + /// from internal/external developers, so unless there is a clear advantage + /// it would be better to respect the preference of the API users. + /// + /// Since this method doesn't have to be a member function in other way, + /// it is intentionally implemented only using public interfaces of the + /// \c Name class; it doesn't refer to private members of the class even if + /// it could. + /// This way we hope we can avoid damaging the class encapsulation, + /// which is a major drawback of public member functions. + /// As such if and when this "method" has to be extended, it should be + /// implemented without the privilege of being a member function unless + /// there is a very strong reason to do so. In particular a minor + /// performance advantage shouldn't justify that approach. + /// + /// \param level The number of labels to be removed from \c this name to + /// create the super domain name. + /// (0 <= \c level < <code>this->getLabelCount()</code>) + /// \return A new \c Name object to be created. + Name split(unsigned int level) const; + + /// \brief Reverse the labels of a name + /// + /// This method reverses the labels of a name. For example, if + /// \c this is "www.example.com.", this method will return + /// "com.example.www." (This is useful because DNSSEC sort order + /// is equivalent to a lexical sort of label-reversed names.) + Name reverse() const; + + /// \brief Concatenate two names. + /// + /// This method appends \c suffix to \c this Name. The trailing dot of + /// \c this Name will be removed. For example, if \c this is "www." + /// and \c suffix is "example.com.", a successful return of this method + /// will be a name of "www.example.com." + /// + ///The resulting length of the concatenated name must not exceed + /// \c Name::MAX_WIRE; otherwise an exception of class + /// \c TooLongName will be thrown. + /// + /// \param suffix a Name object to be appended to the Name. + /// \return a new Name object concatenating \c suffix to \c this Name. + Name concatenate(const Name& suffix) const; + + /// \brief Downcase all upper case alphabet characters in the name. + /// + /// This method modifies the calling object so that it can perform the + /// conversion as fast as possible and can be exception free. + /// + /// The return value of this version of \c downcase() is a reference to + /// the calling object (i.e., \c *this) so that the caller can use the + /// result of downcasing in a single line. For example, if variable + /// \c n is a \c Name class object possibly containing upper case + /// characters, and \c b is an \c OutputBuffer class object, then the + /// following code will dump the name in wire format to \c b with + /// downcasing upper case characters: + /// + /// \code n.downcase().toWire(b); \endcode + /// + /// Since this method modifies the calling object, a \c const name object + /// cannot call it. If \c n is a \c const Name class object, it must first + /// be copied to a different object and the latter must be used for the + /// downcase modification. + /// + /// \return A reference to the calling object with being downcased. + Name& downcase(); + //@} + + /// + /// \name Testing methods + /// + //@{ + /// \brief Test if this is a wildcard name. + /// + /// \return \c true if the least significant label of this Name is + /// <code>'*'</code>; otherwise \c false. + bool isWildcard() const; + //@} + + /// + /// \name Protocol constants + /// + //@{ + /// \brief Max allowable length of domain names. + static const size_t MAX_WIRE = 255; + + /// \brief Max allowable labels of domain names. + /// + /// This is <code>ceil(MAX_WIRE / 2)</code>, and is equal to the number of + /// labels of name "a.a.a.a....a." (127 "a"'s and trailing dot). + static const size_t MAX_LABELS = 128; + + /// \brief Max allowable length of labels of a domain name. + static const size_t MAX_LABELLEN = 63; + + /// \brief Max possible pointer value for name compression. + /// + /// This is the highest number of 14-bit unsigned integer. Name compression + /// pointers are identified as a 2-byte value starting with the upper two + /// bit being 11. + static const uint16_t MAX_COMPRESS_POINTER = 0x3fff; + /// \brief A 8-bit masked value indicating a start of compression pointer. + static const uint16_t COMPRESS_POINTER_MARK8 = 0xc0; + /// \brief A 16-bit masked value indicating a start of compression pointer. + static const uint16_t COMPRESS_POINTER_MARK16 = 0xc000; + //@} + + /// + /// \name Well-known name constants + /// + //@{ + /// \brief Root name (i.e. "."). + static const Name& ROOT_NAME(); + //@} + +private: + NameString ndata_; + NameOffsets offsets_; + unsigned int length_; + unsigned int labelcount_; +}; + +inline const Name& +Name::ROOT_NAME() { + static Name root_name("."); + return (root_name); +} + +/// +/// \brief Insert the name as a string into stream. +/// +/// This method convert the \c name into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global operator<< to behave as described in +/// ostream::operator<< but applied to \c Name objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param name The \c Name object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& +operator<<(std::ostream& os, const Name& name); + +} +} +#endif // NAME_H diff --git a/src/lib/dns/name_internal.h b/src/lib/dns/name_internal.h new file mode 100644 index 0000000..5cf001c --- /dev/null +++ b/src/lib/dns/name_internal.h @@ -0,0 +1,31 @@ +// Copyright (C) 2012-2024 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 NAME_INTERNAL_H +#define NAME_INTERNAL_H + +// This is effectively a "private" namespace for the Name class implementation, +// but exposed publicly so the definitions in it can be shared with other +// modules of the library (as of its introduction, used by LabelSequence and +// MessageRenderer). It's not expected to be used even by normal applications. +// This header file is therefore not expected to be installed as part of the +// library. +// +// Note: if it turns out that we need this shortcut for many other places +// we may even want to make it expose to other Kea modules, but for now +// we'll keep it semi-private (note also that except for very performance +// sensitive applications the standard std::tolower() function should be just +// sufficient). +namespace isc { +namespace dns { +namespace name { +namespace internal { +extern const uint8_t maptolower[]; +} // end of internal +} // end of name +} // end of dns +} // end of isc +#endif // NAME_INTERNAL_H diff --git a/src/lib/dns/opcode.cc b/src/lib/dns/opcode.cc new file mode 100644 index 0000000..c6e051a --- /dev/null +++ b/src/lib/dns/opcode.cc @@ -0,0 +1,62 @@ +// Copyright (C) 2010-2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <string> +#include <ostream> + +#include <exceptions/exceptions.h> + +#include <dns/opcode.h> + +using namespace std; + +namespace isc { +namespace dns { +namespace { +const char *opcodetext[] = { + "QUERY", + "IQUERY", + "STATUS", + "RESERVED3", + "NOTIFY", + "UPDATE", + "RESERVED6", + "RESERVED7", + "RESERVED8", + "RESERVED9", + "RESERVED10", + "RESERVED11", + "RESERVED12", + "RESERVED13", + "RESERVED14", + "RESERVED15" +}; + +// OPCODEs are 4-bit values. So 15 is the highest code. +const uint8_t MAX_OPCODE = 15; +} + +Opcode::Opcode(const uint8_t code) : code_(static_cast<CodeValue>(code)) { + if (code > MAX_OPCODE) { + isc_throw(OutOfRange, + "DNS Opcode is too large to construct: " + << static_cast<unsigned>(code)); + } +} + +string +Opcode::toText() const { + return (opcodetext[code_]); +} + +ostream& +operator<<(std::ostream& os, const Opcode& opcode) { + return (os << opcode.toText()); +} +} +} diff --git a/src/lib/dns/opcode.h b/src/lib/dns/opcode.h new file mode 100644 index 0000000..f92dcde --- /dev/null +++ b/src/lib/dns/opcode.h @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2010-2024 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 <stdint.h> + +#include <ostream> + +#ifndef OPCODE_H +#define OPCODE_H + +namespace isc { +namespace dns { + +/// \brief The \c Opcode class objects represent standard OPCODEs +/// of the header section of DNS messages as defined in RFC1035. +/// +/// This is a straightforward value class encapsulating the OPCODE code +/// values. Since OPCODEs are 4-bit integers that are used in limited +/// places and it's unlikely that new code values will be assigned, we could +/// represent them as simple integers (via constant variables or enums). +/// However, we define a separate class so that we can benefit from C++ +/// type safety as much as possible. For convenience we also provide +/// an enum type for standard OPCDE values, but it is generally advisable +/// to handle OPCODEs through this class. In fact, public interfaces of +/// this library uses this class to pass or return OPCODEs instead of the +/// bare code values. +class Opcode { +public: + /// Constants for standard OPCODE values. + enum CodeValue { + QUERY_CODE = 0, ///< 0: Standard query (RFC1035) + IQUERY_CODE = 1, ///< 1: Inverse query (RFC1035) + STATUS_CODE = 2, ///< 2: Server status request (RFC1035) + RESERVED3_CODE = 3, ///< 3: Reserved for future use (RFC1035) + NOTIFY_CODE = 4, ///< 4: Notify (RFC1996) + UPDATE_CODE = 5, ///< 5: Dynamic update (RFC2136) + RESERVED6_CODE = 6, ///< 6: Reserved for future use (RFC1035) + RESERVED7_CODE = 7, ///< 7: Reserved for future use (RFC1035) + RESERVED8_CODE = 8, ///< 8: Reserved for future use (RFC1035) + RESERVED9_CODE = 9, ///< 9: Reserved for future use (RFC1035) + RESERVED10_CODE = 10, ///< 10: Reserved for future use (RFC1035) + RESERVED11_CODE = 11, ///< 11: Reserved for future use (RFC1035) + RESERVED12_CODE = 12, ///< 12: Reserved for future use (RFC1035) + RESERVED13_CODE = 13, ///< 13: Reserved for future use (RFC1035) + RESERVED14_CODE = 14, ///< 14: Reserved for future use (RFC1035) + RESERVED15_CODE = 15 ///< 15: Reserved for future use (RFC1035) + }; + + /// \name Constructors and Destructor + /// + /// We use the default versions of destructor, copy constructor, + /// and assignment operator. + /// + /// The default constructor is hidden as a result of defining the other + /// constructors. This is intentional; we don't want to allow an + /// \c Opcode object to be constructed with an invalid state. + //@{ + /// \brief Constructor from the code value. + /// + /// Since OPCODEs are 4-bit values, parameters larger than 15 are invalid. + /// If \c code is larger than 15 an exception of class \c isc::OutOfRange + /// will be thrown. + /// + /// \param code The underlying code value of the \c Opcode. + explicit Opcode(const uint8_t code); + //@} + + /// \brief Returns the \c Opcode code value. + /// + /// This method never throws an exception. + /// + /// \return The underlying code value corresponding to the \c Opcode. + CodeValue getCode() const { + return (code_); + } + + /// \brief Return true iff two Opcodes are equal. + /// + /// Two Opcodes are equal iff their type codes are equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c Opcode object to compare against. + /// \return true if the two Opcodes are equal; otherwise false. + bool equals(const Opcode& other) const { + return (code_ == other.code_); + } + + /// \brief Same as \c equals(). + bool operator==(const Opcode& other) const { + return (equals(other)); + } + + /// \brief Return true iff two Opcodes are not equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c Opcode object to compare against. + /// \return true if the two Opcodes are not equal; otherwise false. + bool nequals(const Opcode& other) const { + return (code_ != other.code_); + } + + /// \brief Same as \c nequals(). + bool operator!=(const Opcode& other) const { + return (nequals(other)); + } + + /// \brief Convert the \c Opcode to a string. + /// + /// This method returns a string representation of the "mnemonic' used + /// for the enum and constant objects. For example, the string for + /// code value 0 is "QUERY", etc. + /// + /// If resource allocation for the string fails, a corresponding standard + /// exception will be thrown. + /// + /// \return A string representation of the \c Opcode. + std::string toText() const; + + /// A constant object for the QUERY Opcode. + static const Opcode& QUERY(); + + /// A constant object for the IQUERY Opcode. + static const Opcode& IQUERY(); + + /// A constant object for the STATUS Opcode. + static const Opcode& STATUS(); + + /// A constant object for a reserved (code 3) Opcode. + static const Opcode& RESERVED3(); + + /// A constant object for the NOTIFY Opcode. + static const Opcode& NOTIFY(); + + /// A constant object for the UPDATE Opcode. + static const Opcode& UPDATE(); + + /// A constant object for a reserved (code 6) Opcode. + static const Opcode& RESERVED6(); + + /// A constant object for a reserved (code 7) Opcode. + static const Opcode& RESERVED7(); + + /// A constant object for a reserved (code 8) Opcode. + static const Opcode& RESERVED8(); + + /// A constant object for a reserved (code 9) Opcode. + static const Opcode& RESERVED9(); + + /// A constant object for a reserved (code 10) Opcode. + static const Opcode& RESERVED10(); + + /// A constant object for a reserved (code 11) Opcode. + static const Opcode& RESERVED11(); + + /// A constant object for a reserved (code 12) Opcode. + static const Opcode& RESERVED12(); + + /// A constant object for a reserved (code 13) Opcode. + static const Opcode& RESERVED13(); + + /// A constant object for a reserved (code 14) Opcode. + static const Opcode& RESERVED14(); + + /// A constant object for a reserved (code 15) Opcode. + static const Opcode& RESERVED15(); +private: + CodeValue code_; +}; + +inline const Opcode& +Opcode::QUERY() { + static Opcode c(0); + return (c); +} + +inline const Opcode& +Opcode::IQUERY() { + static Opcode c(1); + return (c); +} + +inline const Opcode& +Opcode::STATUS() { + static Opcode c(2); + return (c); +} + +inline const Opcode& +Opcode::RESERVED3() { + static Opcode c(3); + return (c); +} + +inline const Opcode& +Opcode::NOTIFY() { + static Opcode c(4); + return (c); +} + +inline const Opcode& +Opcode::UPDATE() { + static Opcode c(5); + return (c); +} + +inline const Opcode& +Opcode::RESERVED6() { + static Opcode c(6); + return (c); +} + +inline const Opcode& +Opcode::RESERVED7() { + static Opcode c(7); + return (c); +} + +inline const Opcode& +Opcode::RESERVED8() { + static Opcode c(8); + return (c); +} + +inline const Opcode& +Opcode::RESERVED9() { + static Opcode c(9); + return (c); +} + +inline const Opcode& +Opcode::RESERVED10() { + static Opcode c(10); + return (c); +} + +inline const Opcode& +Opcode::RESERVED11() { + static Opcode c(11); + return (c); +} + +inline const Opcode& +Opcode::RESERVED12() { + static Opcode c(12); + return (c); +} + +inline const Opcode& +Opcode::RESERVED13() { + static Opcode c(13); + return (c); +} + +inline const Opcode& +Opcode::RESERVED14() { + static Opcode c(14); + return (c); +} + +inline const Opcode& +Opcode::RESERVED15() { + static Opcode c(15); + return (c); +} + +/// \brief Insert the \c Opcode as a string into stream. +/// +/// This method convert \c opcode into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param opcode A reference to an \c Opcode object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const Opcode& opcode); +} +} +#endif // OPCODE_H diff --git a/src/lib/dns/question.cc b/src/lib/dns/question.cc new file mode 100644 index 0000000..af52842 --- /dev/null +++ b/src/lib/dns/question.cc @@ -0,0 +1,81 @@ +// Copyright (C) 2010-2024 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 <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/question.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <util/buffer.h> + +#include <iostream> +#include <string> + +using namespace isc::util; + +using namespace std; + +namespace isc { +namespace dns { +Question::Question(InputBuffer& buffer) : + name_(buffer), rrtype_(0), rrclass_(0) { + // In theory, we could perform this in the member initialization list, + // and it would be a little bit more efficient. We don't do this, however, + // because the initialization ordering is crucial (type must be first) + // and the ordering in the initialization list depends on the appearance + // order of member variables. It's fragile to rely on such an implicit + // dependency, so we make the initialization order explicit. + rrtype_ = RRType(buffer); + rrclass_ = RRClass(buffer); +} + +std::string +Question::toText(bool newline) const { + std::string r(name_.toText() + " " + rrclass_.toText() + " " + + rrtype_.toText()); + if (newline) { + r.append("\n"); + } + + return (r); +} + +uint32_t +Question::toWire(OutputBuffer& buffer) const { + name_.toWire(buffer); + rrtype_.toWire(buffer); + rrclass_.toWire(buffer); // number of "entries", which is always 1 + + return (1); +} + +uint32_t +Question::toWire(AbstractMessageRenderer& renderer) const { + const size_t pos0 = renderer.getLength(); + + renderer.writeName(name_); + rrtype_.toWire(renderer); + rrclass_.toWire(renderer); + + // Make sure the renderer has a room for the question + if (renderer.getLength() > renderer.getLengthLimit()) { + renderer.trim(renderer.getLength() - pos0); + renderer.setTruncated(); + return (0); + } + + return (1); // number of "entries" +} + +ostream& +operator<<(std::ostream& os, const Question& question) { + os << question.toText(); + return (os); +} +} +} diff --git a/src/lib/dns/question.h b/src/lib/dns/question.h new file mode 100644 index 0000000..efd6c3f --- /dev/null +++ b/src/lib/dns/question.h @@ -0,0 +1,286 @@ +// Copyright (C) 2010-2024 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 QUESTION_H +#define QUESTION_H + +#include <iostream> +#include <string> + +#include <boost/shared_ptr.hpp> + +#include <dns/name.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> + +namespace isc { +namespace dns { +class Question; + +/// \brief A pointer-like type pointing to an \c Question object. +typedef boost::shared_ptr<Question> QuestionPtr; + +/// \brief A pointer-like type pointing to an (immutable) \c Question object. +typedef boost::shared_ptr<const Question> ConstQuestionPtr; + +/// \brief The \c Question class encapsulates the common search key of DNS +/// lookup, consisting of owner name, RR type and RR class. +/// +/// The primarily intended use case of this class is an entry of the question +/// section of DNS messages. +/// This could also be used as a general purpose lookup key, e.g., in a +/// custom implementation of DNS database. +/// +/// In this initial implementation, the \c Question class is defined as +/// a <em>concrete class</em>; it's not expected to be inherited by +/// a user-defined customized class. +/// It may be worth noting that it's different from the design of the +/// RRset classes (\c AbstractRRset and its derived classes). +/// The RRset classes form an inheritance hierarchy from the base abstract +/// class. +/// This may look odd in that an "RRset" and "Question" are similar from the +/// protocol point of view: Both are used as a semantics unit of DNS messages; +/// both share the same set of components (name, RR type and RR class). +/// +/// In fact, BIND9 didn't introduce a separate data structure for Questions, +/// and use the same \c "rdataset" structure for both RRsets and Questions. +/// We could take the same approach, but chose to adopt the different design. +/// One reason for that is because a Question and an RRset are still +/// different, and a Question might not be cleanly defined, e.g., if it were +/// a derived class of some "RRset-like" class. +/// For example, we couldn't give a reasonable semantics for \c %getTTL() or +/// \c %setTTL() methods for a Question, since it's not associated with the +/// TTL. +/// In fact, the BIND9 implementation ended up often separating the case where +/// a \c "rdataset" is from the Question section of a DNS message and the +/// case where it comes from other sections. +/// If we cannot treat them completely transparently anyway, separating the +/// class (type) would make more sense because we can exploit compilation +/// time type checks. +/// +/// On the other hand, we do not expect a strong need for customizing the +/// \c Question class, unlike the RRset. +/// Handling the "Question" section of a DNS message is relatively a +/// simple work comparing to RRset-involved operations, so a unified +/// straightforward implementation should suffice for any use cases +/// including performance sensitive ones. +/// +/// We may, however, still want to have a customized version of Question +/// for, e.g, highly optimized behavior, and may revisit this design choice +/// as we have more experience with this implementation. +/// +/// One disadvantage of defining RRsets and Questions as unrelated classes +/// is that we cannot handle them in a polymorphic way. +/// For example, we might want to iterate over DNS message sections and +/// render the data in the wire format, whether it's an RRset or a Question. +/// If a \c Question were a derived class of some common RRset-like class, +/// we could do this by calling <code>rrset_or_question->%toWire()</code>. +/// But the actual design doesn't allow this approach, which may require +/// duplicate code for almost the same operation. +/// To mitigate this problem, we intentionally used the same names +/// with the same signature for some common methods of \c Question and +/// \c AbstractRRset such as \c %getName() or \c %toWire(). +/// So the user class may use a template function that is applicable to both +/// \c Question and \c RRset to avoid writing duplicate code logic. +class Question { + /// + /// \name Constructors and Destructor + /// + /// We use the default versions of destructor, copy constructor, + /// and assignment operator. + /// + /// The default constructor is hidden as a result of defining the other + /// constructors. This is intentional; we don't want to allow a + /// \c Question object to be constructed with an invalid state. + //@{ +public: + /// \brief Constructor from wire-format data. + /// + /// It simply constructs a set of \c Name, \c RRType, and \c RRClass + /// object from the \c buffer in this order, and constructs a + /// \c Question object in a straightforward way. + /// + /// It may throw an exception if the construction of these component + /// classes fails. + /// + /// \param buffer A buffer storing the wire format data. + Question(isc::util::InputBuffer& buffer); + + /// \brief Constructor from fixed parameters of the \c Question. + /// + /// This constructor is basically expected to be exception free, but + /// copying the name may involve resource allocation, and if it fails + /// the corresponding standard exception will be thrown. + /// + /// \param name The owner name of the \c Question. + /// \param rrclass The RR class of the \c Question. + /// \param rrtype The RR type of the \c Question. + Question(const Name& name, const RRClass& rrclass, const RRType& rrtype) : + name_(name), rrtype_(rrtype), rrclass_(rrclass) { + } + //@} + + /// + /// \name Getter Methods + /// + //@{ + /// \brief Returns the owner name of the \c Question. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c Name class object corresponding to the + /// \c Question owner name. + const Name& getName() const { + return (name_); + } + + /// \brief Returns the RR Class of the \c Question. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRClass class object corresponding to the + /// RR class of the \c Question. + const RRType& getType() const { + return (rrtype_); + } + + /// \brief Returns the RR Type of the \c Question. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRType class object corresponding to the + /// RR type of the \c Question. + const RRClass& getClass() const { + return (rrclass_); + } + //@} + + /// + /// \name Converter Methods + /// + //@{ + /// \brief Convert the Question to a string. + /// + /// When \c newline argument is \c true, this method terminates the + /// resulting string with a trailing newline character (following + /// the BIND9 convention). + /// + /// This method simply calls the \c %toText() methods of the corresponding + /// \c Name, \c RRType and \c RRClass classes for this \c Question, and + /// these methods may throw an exception. + /// In particular, if resource allocation fails, a corresponding standard + /// exception will be thrown. + /// + /// \param newline Whether to add a trailing newline. If true, a + /// trailing newline is added. If false, no trailing newline is + /// added. + /// + /// \return A string representation of the \c Question. + std::string toText(bool newline = false) const; + + /// \brief Render the Question in the wire format with name compression. + /// + /// This method simply calls the \c %toWire() methods of the corresponding + /// \c Name, \c RRType and \c RRClass classes for this \c Question, and + /// these methods may throw an exception. + /// In particular, if resource allocation fails, a corresponding standard + /// exception will be thrown. + /// + /// This method returns 1, which is the number of "questions" contained + /// in the \c Question. + /// This is a meaningless value, but is provided to be consistent with + /// the corresponding method of \c AbstractRRset (see the detailed + /// class description). + /// + /// The owner name will be compressed if possible, although it's an + /// unlikely event in practice because the Question section a DNS + /// message normally doesn't contain multiple question entries and + /// it's located right after the Header section. + /// Nevertheless, \c renderer records the information of the owner name + /// so that it can be pointed by other RRs in other sections (which is + /// more likely to happen). + /// + /// It could be possible, though very rare in practice, that + /// an attempt to render a Question may cause truncation + /// (when the Question section contains a large number of entries). + /// In such a case this method avoid the rendering and indicate the + /// truncation in the \c renderer. This method returns 0 in this case. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// + /// \return 1 on success; 0 if it causes truncation + uint32_t toWire(AbstractMessageRenderer& renderer) const; + + /// \brief Render the Question in the wire format without name compression. + /// + /// This method behaves like the render version except it doesn't compress + /// the owner name. + /// See \c toWire(AbstractMessageRenderer& renderer)const. + /// + /// \param buffer An output buffer to store the wire data. + /// \return 1 + uint32_t toWire(isc::util::OutputBuffer& buffer) const; + //@} + + /// + /// \name Comparison Operators + /// + //@{ + /// A "less than" operator is needed for this class so it can + /// function as an index to std::map. + bool operator <(const Question& rhs) const { + return (rrclass_ < rhs.rrclass_ || + (rrclass_ == rhs.rrclass_ && + (rrtype_ < rhs.rrtype_ || + (rrtype_ == rhs.rrtype_ && (name_ < rhs.name_))))); + } + + /// Equality operator. Primarily used to compare the question section in + /// a response to that in the query. + /// + /// \param rhs Question to compare against + /// \return true if name, class and type are equal, false otherwise + bool operator==(const Question& rhs) const { + return ((rrclass_ == rhs.rrclass_) && (rrtype_ == rhs.rrtype_) && + (name_ == rhs.name_)); + } + + /// Inequality operator. Primarily used to compare the question section in + /// a response to that in the query. + /// + /// \param rhs Question to compare against + /// \return true if one or more of the name, class and type do not match, + /// false otherwise. + bool operator!=(const Question& rhs) const { + return (!operator==(rhs)); + } + //@} + +private: + Name name_; + RRType rrtype_; + RRClass rrclass_; +}; + +/// \brief Insert the \c Question as a string into stream. +/// +/// This method convert the \c question into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global \c operator<< to behave as described in +/// \c %ostream::%operator<< but applied to Question objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param question A reference to a \c Question object output by the +/// operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const Question& question); +} // end of namespace dns +} // end of namespace isc +#endif // QUESTION_H diff --git a/src/lib/dns/rcode.cc b/src/lib/dns/rcode.cc new file mode 100644 index 0000000..34d93ce --- /dev/null +++ b/src/lib/dns/rcode.cc @@ -0,0 +1,95 @@ +// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <string> +#include <sstream> +#include <ostream> + +#include <exceptions/exceptions.h> + +#include <dns/rcode.h> + +using namespace std; + +namespace isc { +namespace dns { +namespace { +// This diagram shows the wire-format representation of the 12-bit extended +// form RCODEs and its relationship with implementation specific parameters. +// +// 0 3 11 15 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |UNUSED | EXTENDED-RCODE | RCODE | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// <= EXTRCODE_SHIFT (4 bits) +const unsigned int EXTRCODE_SHIFT = 4; +const unsigned int RCODE_MASK = 0x000f; + + +// EDNS-extended RCODEs are 12-bit unsigned integers. 0xfff is the highest. +const uint16_t MAX_RCODE = 0xfff; + +const char* const rcodetext[] = { + "NOERROR", + "FORMERR", + "SERVFAIL", + "NXDOMAIN", + "NOTIMP", + "REFUSED", + "YXDOMAIN", + "YXRRSET", + "NXRRSET", + "NOTAUTH", + "NOTZONE", + "RESERVED11", + "RESERVED12", + "RESERVED13", + "RESERVED14", + "RESERVED15", + "BADVERS" +}; +} + +Rcode::Rcode(const uint16_t code) : code_(code) { + if (code_ > MAX_RCODE) { + isc_throw(OutOfRange, "Rcode is too large to construct"); + } +} + +Rcode::Rcode(const uint8_t code, const uint8_t extended_code) : + code_((extended_code << EXTRCODE_SHIFT) | (code & RCODE_MASK)) +{ + if (code > RCODE_MASK) { + isc_throw(OutOfRange, + "Base Rcode is too large to construct: " + << static_cast<unsigned int>(code)); + } +} + +uint8_t +Rcode::getExtendedCode() const { + return (code_ >> EXTRCODE_SHIFT); +} + +string +Rcode::toText() const { + if (code_ < sizeof(rcodetext) / sizeof (const char*)) { + return (rcodetext[code_]); + } + + ostringstream oss; + oss << code_; + return (oss.str()); +} + +ostream& +operator<<(std::ostream& os, const Rcode& rcode) { + return (os << rcode.toText()); +} +} +} diff --git a/src/lib/dns/rcode.h b/src/lib/dns/rcode.h new file mode 100644 index 0000000..391456c --- /dev/null +++ b/src/lib/dns/rcode.h @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2010-2024 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 <stdint.h> + +#include <ostream> + +#ifndef RCODE_H +#define RCODE_H + +namespace isc { +namespace dns { + +/// \brief DNS Response Codes (RCODEs) class. +/// +/// The \c Rcode class objects represent standard Response Codes +/// (RCODEs) of the header section of DNS messages, and extended response +/// codes as defined in the EDNS specification. +/// +/// Originally RCODEs were defined as 4-bit integers (RFC1035), and then +/// extended to 12 bits as part of the %EDNS specification (RFC2671). +/// This API uses the 12-bit version of the definition from the beginning; +/// applications don't have to aware of the original definition except when +/// dealing with the wire-format representation of the %EDNS OPT RR +/// (which is rare). +/// +/// Like the \c Opcode class, Rcodes could be represented as bare integers, +/// but we define a separate class to benefit from C++ type safety. +/// +/// For convenience we also provide +/// an enum type for pre-defined RCODE values, but it is generally advisable +/// to handle RCODEs through this class. In fact, public interfaces of +/// this library uses this class to pass or return RCODEs instead of the +/// bare code values. +class Rcode { +public: + /// Constants for pre-defined RCODE values. + enum CodeValue { + NOERROR_CODE = 0, ///< 0: No error (RFC1035) + FORMERR_CODE = 1, ///< 1: Format error (RFC1035) + SERVFAIL_CODE = 2, ///< 2: Server failure (RFC1035) + NXDOMAIN_CODE = 3, ///< 3: Name Error (RFC1035) + NOTIMP_CODE = 4, ///< 4: Not Implemented (RFC1035) + REFUSED_CODE = 5, ///< 5: Refused (RFC1035) + YXDOMAIN_CODE = 6, ///< 6: Name unexpectedly exists (RFC2136) + YXRRSET_CODE = 7, ///< 7: RRset unexpectedly exists (RFC2136) + NXRRSET_CODE = 8, ///< 8: RRset should exist but not (RFC2136) + NOTAUTH_CODE = 9, ///< 9: Server isn't authoritative (RFC2136) + NOTZONE_CODE = 10, ///< 10: Name is not within the zone (RFC2136) + RESERVED11_CODE = 11, ///< 11: Reserved for future use (RFC1035) + RESERVED12_CODE = 12, ///< 12: Reserved for future use (RFC1035) + RESERVED13_CODE = 13, ///< 13: Reserved for future use (RFC1035) + RESERVED14_CODE = 14, ///< 14: Reserved for future use (RFC1035) + RESERVED15_CODE = 15, ///< 15: Reserved for future use (RFC1035) + BADVERS_CODE = 16 ///< 16: EDNS version not implemented (RFC2671) + }; + + /// \name Constructors and Destructor + /// + /// We use the default versions of destructor, copy constructor, + /// and assignment operator. + /// + /// The default constructor is hidden as a result of defining the other + /// constructors. This is intentional; we don't want to allow an + /// \c Rcode object to be constructed with an invalid state. + //@{ + /// \brief Constructor from the code value. + /// + /// Since RCODEs are 12-bit values, parameters larger than 0xfff are + /// invalid. + /// If \c code is larger than 0xfff an exception of class + /// \c isc::OutOfRange will be thrown. + /// + /// \param code The underlying 12-bit code value of the \c Rcode. + explicit Rcode(const uint16_t code); + + /// \brief Constructor from a pair of base and extended parts of code. + /// + /// This constructor takes two parameters, one for the lower 4 bits of + /// the code value, the other for the upper 8 bits, and combines them + /// to build a complete 12-bit code value. + /// + /// The first parameter, \c code, is the lower 4 bits, and therefore must + /// not exceed 15. Otherwise, an exception of class + /// \c isc::OutOfRange will be thrown. + /// + /// This version of constructor is provided specifically for constructing + /// an Rcode from a DNS header and an %EDNS OPT RR. Normal applications + /// won't have to use this constructor. + /// + /// \param code The lower 4 bits of the underlying code value. + /// \param extended_code The upper 8 bits of the underlying code value. + Rcode(const uint8_t code, const uint8_t extended_code); + //@} + + /// \brief Returns the \c Rcode code value. + /// + /// This method never throws an exception. + /// + /// \return The underlying code value corresponding to the \c Rcode. + uint16_t getCode() const { + return (code_); + } + + /// \brief Returns the upper 8-bit of the \c Rcode code value. + /// + /// Normal applications won't have to use this method. This is provided + /// in case the upper 8 bits are necessary for the EDNS protocol + /// processing. + /// + /// This method never throws an exception. + /// + /// \return The upper 8-bit of the underlying code value. + uint8_t getExtendedCode() const; + + /// \brief Return true iff two Rcodes are equal. + /// + /// Two Rcodes are equal iff their type codes are equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c Rcode object to compare against. + /// \return true if the two Rcodes are equal; otherwise false. + bool equals(const Rcode& other) const { + return (code_ == other.code_); + } + + /// \brief Same as \c equals(). + bool operator==(const Rcode& other) const { + return (equals(other)); + } + + /// \brief Return true iff two Rcodes are not equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c Rcode object to compare against. + /// \return true if the two Rcodes are not equal; otherwise false. + bool nequals(const Rcode& other) const { + return (code_ != other.code_); + } + + /// \brief Same as \c nequals(). + bool operator!=(const Rcode& other) const { + return (nequals(other)); + } + + /// \brief Convert the \c Rcode to a string. + /// + /// For pre-defined code values (see Rcode::CodeValue), + /// this method returns a string representation of the "mnemonic' used + /// for the enum and constant objects. For example, the string for + /// code value 0 is "NOERROR", etc. + /// For other code values it returns a string representation of the decimal + /// number of the value, e.g. "32", "100", etc. + /// + /// If resource allocation for the string fails, a corresponding standard + /// exception will be thrown. + /// + /// \return A string representation of the \c Rcode. + std::string toText() const; + + /// A constant object for the NOERROR Rcode (see \c Rcode::NOERROR_CODE). + static const Rcode& NOERROR(); + + /// A constant object for the FORMERR Rcode (see \c Rcode::FORMERR_CODE). + static const Rcode& FORMERR(); + + /// A constant object for the SERVFAIL Rcode (see \c Rcode::SERVFAIL_CODE). + static const Rcode& SERVFAIL(); + + /// A constant object for the NXDOMAIN Rcode (see \c Rcode::NXDOMAIN_CODE). + static const Rcode& NXDOMAIN(); + + /// A constant object for the NOTIMP Rcode (see \c Rcode::NOTIMP_CODE). + static const Rcode& NOTIMP(); + + /// A constant object for the REFUSED Rcode (see \c Rcode::REFUSED_CODE). + static const Rcode& REFUSED(); + + /// A constant object for the YXDOMAIN Rcode (see \c Rcode::YXDOMAIN_CODE). + static const Rcode& YXDOMAIN(); + + /// A constant object for the YXRRSET Rcode (see \c Rcode::YXRRSET_CODE). + static const Rcode& YXRRSET(); + + /// A constant object for the NXRRSET Rcode (see \c Rcode::NXRRSET_CODE). + static const Rcode& NXRRSET(); + + /// A constant object for the NOTAUTH Rcode (see \c Rcode::NOTAUTH_CODE). + static const Rcode& NOTAUTH(); + + /// A constant object for the NOTZONE Rcode (see \c Rcode::NOTZONE_CODE). + static const Rcode& NOTZONE(); + + /// A constant object for a reserved (code 11) Rcode. + /// (see \c Rcode::RESERVED11_CODE). + static const Rcode& RESERVED11(); + + /// A constant object for a reserved (code 12) Rcode. + /// (see \c Rcode::RESERVED12_CODE). + static const Rcode& RESERVED12(); + + /// A constant object for a reserved (code 13) Rcode. + /// (see \c Rcode::RESERVED13_CODE). + static const Rcode& RESERVED13(); + + /// A constant object for a reserved (code 14) Rcode. + /// (see \c Rcode::RESERVED14_CODE). + static const Rcode& RESERVED14(); + + /// A constant object for a reserved (code 15) Rcode. + /// (see \c Rcode::RESERVED15_CODE). + static const Rcode& RESERVED15(); + + /// A constant object for the BADVERS Rcode (see \c Rcode::BADVERS_CODE). + static const Rcode& BADVERS(); +private: + uint16_t code_; +}; + +inline const Rcode& +Rcode::NOERROR() { + static Rcode c(0); + return (c); +} + +inline const Rcode& +Rcode::FORMERR() { + static Rcode c(1); + return (c); +} + +inline const Rcode& +Rcode::SERVFAIL() { + static Rcode c(2); + return (c); +} + +inline const Rcode& +Rcode::NXDOMAIN() { + static Rcode c(3); + return (c); +} + +inline const Rcode& +Rcode::NOTIMP() { + static Rcode c(4); + return (c); +} + +inline const Rcode& +Rcode::REFUSED() { + static Rcode c(5); + return (c); +} + +inline const Rcode& +Rcode::YXDOMAIN() { + static Rcode c(6); + return (c); +} + +inline const Rcode& +Rcode::YXRRSET() { + static Rcode c(7); + return (c); +} + +inline const Rcode& +Rcode::NXRRSET() { + static Rcode c(8); + return (c); +} + +inline const Rcode& +Rcode::NOTAUTH() { + static Rcode c(9); + return (c); +} + +inline const Rcode& +Rcode::NOTZONE() { + static Rcode c(10); + return (c); +} + +inline const Rcode& +Rcode::RESERVED11() { + static Rcode c(11); + return (c); +} + +inline const Rcode& +Rcode::RESERVED12() { + static Rcode c(12); + return (c); +} + +inline const Rcode& +Rcode::RESERVED13() { + static Rcode c(13); + return (c); +} + +inline const Rcode& +Rcode::RESERVED14() { + static Rcode c(14); + return (c); +} + +inline const Rcode& +Rcode::RESERVED15() { + static Rcode c(15); + return (c); +} + +inline const Rcode& +Rcode::BADVERS() { + static Rcode c(16); + return (c); +} + +/// \brief Insert the \c Rcode as a string into stream. +/// +/// This method convert \c rcode into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param rcode A reference to an \c Rcode object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const Rcode& rcode); +} +} +#endif // RCODE_H diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc new file mode 100644 index 0000000..b175dfa --- /dev/null +++ b/src/lib/dns/rdata.cc @@ -0,0 +1,388 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <exceptions/isc_assert.h> +#include <dns/name.h> +#include <dns/messagerenderer.h> +#include <dns/master_lexer.h> +#include <dns/rdata.h> +#include <dns/rrparamregistry.h> +#include <dns/rrtype.h> +#include <util/buffer.h> +#include <util/encode/encode.h> + +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> +#include <algorithm> +#include <cctype> +#include <string> +#include <sstream> +#include <iomanip> +#include <ios> +#include <ostream> +#include <vector> +#include <stdint.h> +#include <string.h> + +using namespace isc::util; + +using namespace std; +using boost::lexical_cast; + +namespace isc { +namespace dns { +namespace rdata { + +uint16_t +Rdata::getLength() const { + OutputBuffer obuffer(0); + + toWire(obuffer); + + return (obuffer.getLength()); +} + +// XXX: we need to specify std:: for string to help doxygen match the +// function signature with that given in the header file. +RdataPtr +createRdata(const RRType& rrtype, const RRClass& rrclass, + const std::string& rdata_string) { + return (RRParamRegistry::getRegistry().createRdata(rrtype, rrclass, + rdata_string)); +} + +RdataPtr +createRdata(const RRType& rrtype, const RRClass& rrclass, + InputBuffer& buffer, size_t len) { + if (len > MAX_RDLENGTH) { + isc_throw(InvalidRdataLength, "RDLENGTH too large"); + } + + size_t old_pos = buffer.getPosition(); + + RdataPtr rdata = + RRParamRegistry::getRegistry().createRdata(rrtype, rrclass, buffer, + len); + + if (buffer.getPosition() - old_pos != len) { + isc_throw(InvalidRdataLength, "RDLENGTH mismatch: " << + buffer.getPosition() - old_pos << " != " << len); + } + + return (rdata); +} + +RdataPtr +createRdata(const RRType& rrtype, const RRClass& rrclass, const Rdata& source) { + return (RRParamRegistry::getRegistry().createRdata(rrtype, rrclass, + source)); +} + +namespace { +void +fromtextError(bool& error_issued, const MasterLexer& lexer, + MasterLoaderCallbacks& callbacks, + const MasterToken* token, const char* reason) { + // Don't be too noisy if there are many issues for single RDATA + if (error_issued) { + return; + } + error_issued = true; + + if (!token) { + callbacks.error(lexer.getSourceName(), lexer.getSourceLine(), + "createRdata from text failed: " + string(reason)); + return; + } + + switch (token->getType()) { + case MasterToken::STRING: + case MasterToken::QSTRING: + callbacks.error(lexer.getSourceName(), lexer.getSourceLine(), + "createRdata from text failed near '" + + token->getString() + "': " + string(reason)); + break; + case MasterToken::ERROR: + callbacks.error(lexer.getSourceName(), lexer.getSourceLine(), + "createRdata from text failed: " + + token->getErrorText()); + break; + default: + // This case shouldn't happen based on how we use MasterLexer in + // createRdata(), so we could assert() that here. But since it + // depends on detailed behavior of other classes, we treat the case + // in a bit less harsh way. + isc_throw(Unexpected, "bug: createRdata() saw unexpected token type"); + } +} +} + +RdataPtr +createRdata(const RRType& rrtype, const RRClass& rrclass, + MasterLexer& lexer, const Name* origin, + MasterLoader::Options options, + MasterLoaderCallbacks& callbacks) { + RdataPtr rdata; + + bool error_issued = false; + try { + rdata = RRParamRegistry::getRegistry().createRdata( + rrtype, rrclass, lexer, origin, options, callbacks); + } catch (const MasterLexer::LexerError& error) { + fromtextError(error_issued, lexer, callbacks, &error.token_, ""); + } catch (const Exception& ex) { + // Catching all isc::Exception is too broad, but right now we don't + // have better granularity. When we complete #2518 we can make this + // finer. + fromtextError(error_issued, lexer, callbacks, 0, ex.what()); + } + // Other exceptions mean a serious implementation bug or fatal system + // error; it doesn't make sense to catch and try to recover from them + // here. Just propagate. + + // Consume to end of line / file. + // Call callback via fromtextError once if there was an error. + do { + const MasterToken& token = lexer.getNextToken(); + switch (token.getType()) { + case MasterToken::END_OF_LINE: + return (rdata); + case MasterToken::END_OF_FILE: + callbacks.warning(lexer.getSourceName(), lexer.getSourceLine(), + "file does not end with newline"); + return (rdata); + default: + rdata.reset(); // we'll return null + fromtextError(error_issued, lexer, callbacks, &token, + "extra input text"); + // Continue until we see EOL or EOF + } + } while (true); + + // We shouldn't reach here + isc_throw_assert(false); + return (RdataPtr()); // add explicit return to silence some compilers +} + +int +compareNames(const Name& n1, const Name& n2) { + size_t len1 = n1.getLength(); + size_t len2 = n2.getLength(); + size_t cmplen = min(len1, len2); + + for (size_t i = 0; i < cmplen; ++i) { + uint8_t c1 = tolower(n1.at(i)); + uint8_t c2 = tolower(n2.at(i)); + if (c1 < c2) { + return (-1); + } else if (c1 > c2) { + return (1); + } + } + + return ((len1 == len2) ? 0 : (len1 < len2) ? -1 : 1); +} + +namespace generic { +struct GenericImpl { + GenericImpl(const vector<uint8_t>& data) : data_(data) {} + vector<uint8_t> data_; +}; + +Generic::Generic(InputBuffer& buffer, size_t rdata_len) { + if (rdata_len > MAX_RDLENGTH) { + isc_throw(InvalidRdataLength, "RDLENGTH too large"); + } + + vector<uint8_t> data(rdata_len); + if (rdata_len > 0) { + buffer.readData(&data[0], rdata_len); + } + + impl_.reset(new GenericImpl(data)); +} + +std::unique_ptr<GenericImpl> +Generic::constructFromLexer(MasterLexer& lexer) { + const MasterToken& token = lexer.getNextToken(MasterToken::STRING); + if (token.getString() != "\\#") { + isc_throw(InvalidRdataText, + "Missing the special token (\\#) for " + "unknown RDATA encoding"); + } + + // Initialize with an absurd value. + uint32_t rdlen = 65536; + + try { + rdlen = lexer.getNextToken(MasterToken::NUMBER).getNumber(); + } catch (const MasterLexer::LexerError&) { + isc_throw(InvalidRdataLength, + "Unknown RDATA length is invalid"); + } + + if (rdlen > 65535) { + isc_throw(InvalidRdataLength, + "Unknown RDATA length is out of range: " << rdlen); + } + + vector<uint8_t> data; + + if (rdlen > 0) { + string hex_txt; + string hex_part; + // Whitespace is allowed within hex data, so read to the end of input. + while (true) { + const MasterToken& token = + lexer.getNextToken(MasterToken::STRING, true); + if ((token.getType() == MasterToken::END_OF_FILE) || + (token.getType() == MasterToken::END_OF_LINE)) { + // Unget the last read token as createRdata() expects us + // to leave it at the end-of-line or end-of-file when we + // return. + lexer.ungetToken(); + break; + } + token.getString(hex_part); + hex_txt.append(hex_part); + } + + try { + encode::decodeHex(hex_txt, data); + } catch (const isc::BadValue& ex) { + isc_throw(InvalidRdataText, + "Invalid hex encoding of generic RDATA: " << ex.what()); + } + } + + if (data.size() != rdlen) { + isc_throw(InvalidRdataLength, + "Size of unknown RDATA hex data doesn't match RDLENGTH: " + << data.size() << " vs. " << rdlen); + } + + return (std::unique_ptr<GenericImpl>(new GenericImpl(data))); +} + +Generic::Generic(const std::string& rdata_string) { + try { + std::istringstream ss(rdata_string); + MasterLexer lexer; + lexer.pushSource(ss); + + impl_ = constructFromLexer(lexer); + + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, "extra input text for unknown RDATA: " + << rdata_string); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, "Failed to construct unknown RDATA " + "from '" << rdata_string << "': " << ex.what()); + } +} + +Generic::Generic(MasterLexer& lexer, const Name*, + MasterLoader::Options, + MasterLoaderCallbacks&) { + impl_ = constructFromLexer(lexer); +} + +Generic::~Generic() { +} + +Generic::Generic(const Generic& source) : + Rdata(), impl_(new GenericImpl(*source.impl_)) { +} + +Generic& +// Our check is better than the usual if (this == &source), +// but cppcheck doesn't recognize it. +// cppcheck-suppress operatorEqToSelf +Generic::operator=(const Generic& source) { + if (impl_ == source.impl_) { + return (*this); + } + + impl_.reset(new GenericImpl(*source.impl_)); + + return (*this); +} + +namespace { +class UnknownRdataDumper { +public: + UnknownRdataDumper(ostringstream& oss) : oss_(&oss) {} + void operator()(const unsigned char d) + { + *oss_ << setw(2) << static_cast<unsigned int>(d); + } +private: + ostringstream* oss_; +}; +} + +string +Generic::toText() const { + ostringstream oss; + + oss << "\\# " << impl_->data_.size() << " "; + oss.fill('0'); + oss << right << hex; + for_each(impl_->data_.begin(), impl_->data_.end(), UnknownRdataDumper(oss)); + + return (oss.str()); +} + +void +Generic::toWire(OutputBuffer& buffer) const { + buffer.writeData(&impl_->data_[0], impl_->data_.size()); +} + +void +Generic::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeData(&impl_->data_[0], impl_->data_.size()); +} + +namespace { +inline int +compare_internal(const GenericImpl& lhs, const GenericImpl& rhs) { + size_t this_len = lhs.data_.size(); + size_t other_len = rhs.data_.size(); + size_t len = (this_len < other_len) ? this_len : other_len; + int cmp; + + // TODO: is there a need to check len - should we just assert? + // (Depends if it is possible for rdata to have zero length) + if ((len != 0) && + ((cmp = memcmp(&lhs.data_[0], &rhs.data_[0], len)) != 0)) { + return (cmp); + } else { + return ((this_len == other_len) ? 0 : + (this_len < other_len) ? -1 : 1); + } +} +} + +int +Generic::compare(const Rdata& other) const { + const Generic& other_rdata = dynamic_cast<const Generic&>(other); + + return (compare_internal(*impl_, *other_rdata.impl_)); +} + +std::ostream& +operator<<(std::ostream& os, const Generic& rdata) { + return (os << rdata.toText()); +} +} // end of namespace generic + +} // end of namespace rdata +} +} diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h new file mode 100644 index 0000000..af294f0 --- /dev/null +++ b/src/lib/dns/rdata.h @@ -0,0 +1,575 @@ +// Copyright (C) 2010-2024 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 RDATA_H +#define RDATA_H + +#include <dns/master_lexer.h> +#include <dns/master_loader.h> +#include <dns/master_loader_callbacks.h> +#include <dns/exceptions.h> +#include <util/buffer.h> + +#include <boost/shared_ptr.hpp> + +#include <memory> +#include <stdint.h> + +namespace isc { +namespace dns { +class AbstractMessageRenderer; +class RRType; +class RRClass; +class Name; + +namespace rdata { + +/// +/// \brief A standard DNS module exception that is thrown if RDATA parser +/// encounters an invalid or inconsistent data length. +/// +class InvalidRdataLength : public DNSTextError { +public: + InvalidRdataLength(const char* file, size_t line, const char* what) : + DNSTextError(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if RDATA parser +/// fails to recognize a given textual representation. +/// +class InvalidRdataText : public DNSTextError { +public: + InvalidRdataText(const char* file, size_t line, const char* what) : + DNSTextError(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if RDATA parser +/// encounters a character-string (as defined in RFC1035) exceeding +/// the maximum allowable length (\c MAX_CHARSTRING_LEN). +/// +class CharStringTooLong : public DNSTextError { +public: + CharStringTooLong(const char* file, size_t line, const char* what) : + DNSTextError(file, line, what) {} +}; + +// Forward declaration to define RdataPtr. +class Rdata; + +/// +/// The \c RdataPtr type is a pointer-like type, pointing to an +/// object of some concrete derived class of \c Rdata. +/// +typedef boost::shared_ptr<Rdata> RdataPtr; +typedef boost::shared_ptr<const Rdata> ConstRdataPtr; + +/// \brief Possible maximum length of RDATA, which is the maximum unsigned +/// 16 bit value. +const size_t MAX_RDLENGTH = 65535; + +/// \brief The maximum allowable length of character-string containing in +/// RDATA as defined in RFC1035, not including the 1-byte length field. +const unsigned int MAX_CHARSTRING_LEN = 255; + +/// \brief The \c Rdata class is an abstract base class that provides +/// a set of common interfaces to manipulate concrete RDATA objects. +/// +/// Generally, a separate derived class directly inherited from the base +/// \c Rdata class is defined for each well known RDATA. +/// Each of such classes will define the common logic based on the +/// corresponding protocol standard. +/// +/// Since some types of RRs are class specific and the corresponding RDATA +/// may have different semantics (e.g. type A for class IN and type A for +/// class CH have different representations and semantics), we separate +/// \c Rdata derived classes for such RR types in different namespaces. +/// The namespace of types specific to a class is named the lower-cased class +/// name; for example, RDATA of class IN-specific types are defined in the +/// \c in namespace, and RDATA of class CH-specific types are defined in +/// the \c ch namespace, and so on. +/// The derived classes are named using the RR type name (upper cased) such as +/// \c A or \c AAAA. +/// Thus RDATA of type A RR for class IN and CH are defined as \c in::A and +/// \c ch::A, respectively. +/// Many other RR types are class independent; the derived \c Rdata classes +/// for such RR types are defined in the \c generic namespace. Examples are +/// \c generic::NS and \c generic::SOA. +/// +/// If applications need to refer to these derived classes, it is generally +/// recommended to prepend at least some part of the namespace because the +/// same class name can be used in different namespaces. +/// So, instead of doing +/// \code using namespace isc::dns::rdata::in; +/// A& rdata_type_a; \endcode +/// it is advisable to prepend at least \c in from the namespace: +/// \code using namespace isc::dns::rdata; +/// in::A& rdata_type_a; \endcode +/// +/// In many cases, however, an application doesn't have to care about such +/// derived classes. +/// For instance, to parse an incoming DNS message an application wouldn't +/// have to perform type specific operation unless the application is +/// specifically concerned about a particular type. +/// So, this API generally handles \c Rdata in a polymorphic way through +/// a pointer or reference to this base abstract class. +class Rdata { + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are intentionally + /// defined as private. Concrete classes should generally specialize their + /// own versions of these methods. + //@{ +protected: + /// The default constructor. + /// + /// This is intentionally defined as \c protected as this base class should + /// never be instantiated (except as part of a derived class). In many + /// cases, the derived class wouldn't define a public default constructor + /// either, because an \c Rdata object without concrete data isn't + /// meaningful. + Rdata() {} +private: + Rdata(const Rdata& source); + void operator=(const Rdata& source); +public: + /// The destructor. + virtual ~Rdata() {}; + //@} + + /// + /// \name Converter methods + /// + //@{ + /// \brief Convert an \c Rdata to a string. + /// + /// This method returns a \c std::string object representing the \c Rdata. + /// + /// This is a pure virtual method without the definition; the actual + /// representation is specific to each derived concrete class and + /// should be explicitly defined in the derived class. + /// + /// \return A string representation of \c Rdata. + virtual std::string toText() const = 0; + + /// \brief Render the \c Rdata in the wire format into a buffer. + /// + /// This is a pure virtual method without the definition; the actual + /// conversion is specific to each derived concrete class and + /// should be explicitly defined in the derived class. + /// + /// \param buffer An output buffer to store the wire data. + virtual void toWire(isc::util::OutputBuffer& buffer) const = 0; + + /// \brief Render the \c Rdata in the wire format into a + /// \c MessageRenderer object. + /// + /// This is a pure virtual method without the definition; the actual + /// conversion is specific to each derived concrete class and + /// should be explicitly defined in the derived class. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer in which the \c Rdata is to be stored. + virtual void toWire(AbstractMessageRenderer& renderer) const = 0; + //@} + + /// + /// \name Comparison method + /// + //@{ + /// \brief Compare two instances of \c Rdata. + /// + /// This method compares \c this and the \c other Rdata objects + /// in terms of the DNSSEC sorting order as defined in RFC4034, and returns + /// the result as an integer. + /// + /// This is a pure virtual method without the definition; the actual + /// comparison logic is specific to each derived concrete class and + /// should be explicitly defined in the derived class. + /// + /// Specific implementations of this method must confirm that \c this + /// and the \c other are objects of the same concrete derived class of + /// \c Rdata. This is normally done by \c dynamic_cast in the + /// implementation. It also means if the assumption isn't met + /// an exception of class \c std::bad_cast will be thrown. + /// + /// Here is an implementation choice: instead of relying on + /// \c dynamic_cast, we could first convert the data into wire-format + /// and compare the pair as opaque data. This would be more polymorphic, + /// but might involve significant overhead, especially for a large size + /// of RDATA. + /// + /// \param other the right-hand operand to compare against. + /// \return < 0 if \c this would be sorted before \c other. + /// \return 0 if \c this is identical to \c other in terms of sorting order. + /// \return > 0 if \c this would be sorted after \c other. + virtual int compare(const Rdata& other) const = 0; + //@} + + /// \brief Get the wire format length of an Rdata. + /// + /// IMPLEMENTATION NOTE: Currently this base class implementation is + /// non-optimal as it renders the wire data to a buffer and returns + /// the buffer's length. What would perform better is to add + /// implementations of \c getLength() method to every RDATA + /// type. This is why this method is virtual. Once all Rdata types + /// have \c getLength() implementations, this base class + /// implementation must be removed and the method should become a + /// pure interface. + /// + /// \return The length of the wire format representation of the + /// RDATA. + virtual uint16_t getLength() const; +}; + +namespace generic { + +/// \brief The \c GenericImpl class is the actual implementation of the +/// \c generic::Generic class. +/// +/// The implementation is hidden from applications. This approach requires +/// dynamic memory allocation on construction, copy, or assignment, but +/// we believe it should be acceptable as "unknown" RDATA should be pretty +/// rare. +struct GenericImpl; + +/// \brief The \c generic::Generic class represents generic "unknown" RDATA. +/// +/// This class is used as a placeholder for all non well-known type of RDATA. +/// By definition, the stored data is regarded as opaque binary without +/// assuming any structure. +class Generic : public Rdata { +public: + /// + /// \name Constructors, Assignment Operator and Destructor. + /// + //@{ + /// \brief Constructor from a string. + /// + /// This method constructs a \c generic::Generic object from a textual + /// representation as defined in RFC3597. + /// + /// If \c rdata_string isn't a valid textual representation of this type + /// of RDATA, an exception of class \c InvalidRdataText or + /// \c InvalidRdataLength will be thrown. + /// If resource allocation to store the data fails, a corresponding standard + /// exception will be thrown. + /// + /// \param rdata_string A string of textual representation of generic + /// RDATA. + explicit Generic(const std::string& rdata_string); + + /// + /// \brief Constructor from wire-format data. + /// + /// The \c buffer parameter normally stores a complete DNS message + /// containing the generic RDATA to be constructed. + /// The current read position of the buffer points to the head of the + /// data. + /// + /// This method reads \c rdata_len bytes from the \c buffer, and internally + /// stores the data as an opaque byte sequence. + /// + /// \c rdata_len must not exceed \c MAX_RDLENGTH; otherwise, an exception + /// of class \c InvalidRdataLength will be thrown. + /// If resource allocation to hold the data fails, a corresponding + /// standard exception will be thrown; if the \c buffer doesn't + /// contain \c rdata_len bytes of unread data, an exception of + /// class \c isc::OutOfRange will be thrown. + /// + /// \param buffer A reference to an \c InputBuffer object storing the + /// \c Rdata to parse. + /// \param rdata_len The length in buffer of the \c Rdata. In bytes. + Generic(isc::util::InputBuffer& buffer, size_t rdata_len); + + /// \brief Constructor from master lexer. + /// + Generic(MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + + /// + /// \brief The destructor. + virtual ~Generic(); + /// + /// \brief The copy constructor. + /// + /// If resource allocation to copy the data fails, a corresponding standard + /// exception will be thrown. + /// + /// \param source A reference to a \c generic::Generic object to copy from. + Generic(const Generic& source); + + /// + /// \brief The assignment operator. + /// + /// If resource allocation to copy the data fails, a corresponding standard + /// exception will be thrown. + /// + /// \param source A reference to a \c generic::Generic object to copy from. + Generic& operator=(const Generic& source); + //@} + + /// + /// \name Converter methods + /// + //@{ + /// \brief Convert an \c generic::Generic object to a string. + /// + /// This method converts a generic "unknown" RDATA object into a textual + /// representation of such unknown data as defined in RFC3597. + /// + /// If resource allocation to copy the data fails, a corresponding standard + /// exception will be thrown. + /// + /// \return A string representation of \c generic::Generic. + virtual std::string toText() const; + + /// + /// \brief Render the \c generic::Generic in the wire format into a buffer. + /// + /// This will require \c rdata_len bytes of remaining capacity in the + /// \c buffer. If this is not the case and resource allocation for the + /// necessary memory space fails, a corresponding standard exception will + /// be thrown. + /// + /// \param buffer An output buffer to store the wire data. + virtual void toWire(isc::util::OutputBuffer& buffer) const; + + /// \brief Render the \c generic::Generic in the wire format into a + /// \c MessageRenderer object. + /// + /// This will require \c rdata_len bytes of remaining capacity in the + /// \c buffer. If this is not the case and resource allocation for the + /// necessary memory space fails, a corresponding standard exception will + /// be thrown. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer in which the \c Generic object is to be stored. + virtual void toWire(AbstractMessageRenderer& renderer) const; + //@} + + /// + /// \name Comparison method + /// + //@{ + /// \brief Compare two instances of \c generic::Generic objects. + /// + /// As defined in RFC4034, this method simply compares the wire-format + /// representations of the two objects as left-justified unsigned octet + /// sequences. + /// + /// The object referenced by \c other must have been instantiated as + /// a c generic::Generic class object; otherwise, an exception of class + /// \c std::bad_cast will be thrown. + /// Note that the comparison is RR type/class agnostic: this method doesn't + /// check whether the two \c Rdata objects to compare are of the comparable + /// RR type/class. For example, \c this object may come from an \c RRset + /// of \c RRType x, and the \c other may come from a different \c RRset + /// of \c RRType y (where x != y). This situation would be considered a + /// bug, but this method cannot detect this type of error. + /// The caller must ensure this condition. + /// + /// \param other the right-hand operand to compare against. + /// \return < 0 if \c this would be sorted before \c other. + /// \return 0 if \c this is identical to \c other in terms of sorting order. + /// \return > 0 if \c this would be sorted after \c other. + virtual int compare(const Rdata& other) const; + //@} + +private: + std::unique_ptr<GenericImpl> constructFromLexer(MasterLexer& lexer); + + std::unique_ptr<GenericImpl> impl_; +}; + +/// +/// \brief Insert the name as a string into stream. +/// +/// This method convert the \c rdata into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global \c operator<< to behave as described in +/// \c ostream::operator<< but applied to \c generic::Generic Rdata objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param rdata The \c Generic object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const Generic& rdata); +} // end of namespace "generic" + +// +// Non class-member functions related to Rdata +// + +/// +/// \name Parameterized Polymorphic RDATA Factories +/// +/// This set of global functions provide a unified interface to create an +/// \c Rdata object in a parameterized polymorphic way, +/// that is, these functions take a pair of \c RRType and \c RRClass +/// objects and data specific to that pair, and create an object of +/// the corresponding concrete derived class of \c Rdata. +/// +/// These will be useful when parsing/constructing a DNS message or +/// parsing a master file, where information for a specific type of RDATA +/// is given but the resulting object, once created, should better be used +/// in a polymorphic way. +/// +/// For example, if a master file parser encounters an NS RR +/// \verbatim example.com. 3600 IN NS ns.example.com.\endverbatim +/// it stores the text fragments "IN", "NS", and "ns.example.com." in +/// \c std::string objects \c class_txt, \c type_txt, and \c nsname_txt, +/// respectively, then it would create a new \c RdataPtr object as follows: +/// \code RdataPtr rdata = createRdata(RRType(type_txt), RRClass(class_txt), +/// nsname_txt); \endcode +/// On success, \c rdata will point to an object of the \c generic::NS class +/// that internally holds a domain name of "ns.example.com." +/// +/// Internally, these functions uses the corresponding +/// \c RRParamRegistry::createRdata methods of the \c RRParamRegistry. +/// See also the description on these methods for related notes. +//@{ +/// \brief Create RDATA of a given pair of RR type and class from a string. +/// +/// This method creates from a string an \c Rdata object of the given pair +/// of RR type and class. +/// +/// \param rrtype An \c RRType object specifying the type/class pair. +/// \param rrclass An \c RRClass object specifying the type/class pair. +/// \param rdata_string A string of textual representation of the \c Rdata. +/// \return An \c RdataPtr object pointing to the created \c Rdata +/// object. +RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass, + const std::string& rdata_string); + +/// \brief Create RDATA of a given pair of RR type and class from +/// wire-format data. +/// +/// This method creates from wire-format binary data an \c Rdata object +/// of the given pair of RR type and class. +/// +/// \c len must not exceed the protocol defined maximum value, \c MAX_RDLENGTH; +/// otherwise, an exception of class \c InvalidRdataLength will be thrown. +/// +/// In some cases, the length of the RDATA is determined without the +/// information of \c len. For example, the RDATA length of an IN/A RR +/// must always be 4. If \c len is not equal to the actual length in such +/// cases, an exception of class InvalidRdataLength will be thrown. +/// +/// \param rrtype An \c RRType object specifying the type/class pair. +/// \param rrclass An \c RRClass object specifying the type/class pair. +/// \param buffer A reference to an \c InputBuffer object storing the +/// \c Rdata to parse. +/// \param len The length in buffer of the \c Rdata. In bytes. +/// \return An \c RdataPtr object pointing to the created \c Rdata +/// object. +RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass, + isc::util::InputBuffer& buffer, size_t len); + +/// \brief Create RDATA of a given pair of RR type and class, copying +/// of another RDATA of same kind. +/// +/// This method creates an \c Rdata object of the given pair of +/// RR type and class, copying the content of the given \c Rdata, +/// \c source. +/// +/// \param rrtype An \c RRType object specifying the type/class pair. +/// \param rrclass An \c RRClass object specifying the type/class pair. +/// \param source A reference to an \c Rdata object whose content +/// is to be copied to the created \c Rdata object. +/// \return An \c RdataPtr object pointing to the created +/// \c Rdata object. +RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass, + const Rdata& source); + +/// \brief Create RDATA of a given pair of RR type and class using the +/// master lexer. +/// +/// This is a more generic form of factory from textual RDATA, and is mainly +/// intended to be used internally by the master file parser (\c MasterLoader) +/// of this library. +/// +/// The \c lexer is expected to be at the beginning of textual RDATA of the +/// specified type and class. This function (and its underlying Rdata +/// implementations) extracts necessary tokens from the lexer and constructs +/// the RDATA from them. +/// +/// Due to the intended usage of this version, this function handles error +/// cases quite differently from other versions. It internally catches +/// most of syntax and semantics errors of the input (reported as exceptions), +/// calls the corresponding callback specified by the \c callbacks parameters, +/// and returns a null smart pointer. If the caller rather wants to get +/// an exception in these cases, it can pass a callback that internally +/// throws on error. Some critical exceptions such as \c std::bad_alloc are +/// still propagated to the upper layer as it doesn't make sense to try +/// recovery from such a situation within this function. +/// +/// Whether or not the creation succeeds, this function updates the lexer +/// until it reaches either the end of line or file, starting from the end of +/// the RDATA text (or the point of failure if the parsing fails in the +/// middle of it). The caller can therefore assume it's ready for reading +/// the next data (which is normally a subsequent RR in the zone file) on +/// return, whether or not this function succeeds. +/// +/// \param rrtype An \c RRType object specifying the type/class pair. +/// \param rrclass An \c RRClass object specifying the type/class pair. +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +/// \param origin If non null, specifies the origin of any domain name fields +/// of the RDATA that are non absolute. +/// \param options Master loader options controlling how to deal with errors +/// or non critical issues in the parsed RDATA. +/// \param callbacks Callback to be called when an error or non critical issue +/// is found. +/// \return An \c RdataPtr object pointing to the created +/// \c Rdata object. Will be null if parsing fails. +RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass, + MasterLexer& lexer, const Name* origin, + MasterLoader::Options options, + MasterLoaderCallbacks& callbacks); + +//@} + +/// +/// \brief Gives relative ordering of two names in terms of DNSSEC RDATA +/// ordering. +/// +/// This method compares two names as defined in Sections 6.2 and 6.3 of +/// RFC4034: Comparing two names in their canonical form +/// (i.e., converting upper case ASCII characters to lower ones) and +/// as a left-justified unsigned octet sequence. Note that the ordering is +/// different from that for owner names. For example, "a.example" should be +/// sorted before "example" as RDATA, but the ordering is the opposite when +/// compared as owner names. +/// +/// Normally, applications would not need this function directly. +/// This is mostly an internal helper function for \c Rdata related classes +/// to implement their \c compare() method. +/// This function is publicly open, however, for the convenience of +/// external developers who want to implement new or experimental RR types. +/// +/// This function never throws an exception as long as the given names are +/// valid \c Name objects. +/// +/// Additional note about applicability: In fact, BIND9's similar function, +/// \c dns_name_rdatacompare(), is only used in rdata implementations and +/// for testing purposes. +/// +/// \param n1,n2 \c Name class objects to compare. +/// \return -1 if \c n1 would be sorted before \c n2. +/// \return 0 if \c n1 is identical to \c n2 in terms of sorting order. +/// \return 1 if \c n1 would be sorted after \c n2. +/// +int compareNames(const Name& n1, const Name& n2); + +} // end of namespace "rdata" +} +} +#endif // RDATA_H diff --git a/src/lib/dns/rdataclass.cc b/src/lib/dns/rdataclass.cc new file mode 100644 index 0000000..b2d05f5 --- /dev/null +++ b/src/lib/dns/rdataclass.cc @@ -0,0 +1,2462 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <exceptions/isc_assert.h> +#include <dns/exceptions.h> +#include <dns/master_lexer.h> +#include <dns/master_loader.h> +#include <dns/master_loader_callbacks.h> +#include <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/rcode.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrtype.h> +#include <dns/time_utils.h> +#include <dns/tsigkey.h> +#include <dns/tsigerror.h> +#include <dns/txt_like.h> +#include <util/buffer.h> +#include <util/encode/encode.h> +#include <util/buffer.h> + +#include <cerrno> +#include <cstring> +#include <iomanip> +#include <iostream> +#include <string> +#include <sstream> +#include <vector> + +#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards +#include <stdio.h> +#include <stdint.h> +#include <sys/socket.h> // for AF_INET/AF_INET6 +#include <time.h> + +#include <boost/lexical_cast.hpp> +#include <boost/shared_ptr.hpp> + +using namespace isc::util; +using namespace isc::util::encode; +using namespace isc::dns; +using isc::dns::rdata::generic::detail::createNameFromLexer; + +using namespace std; +using boost::lexical_cast; + +namespace isc { +namespace dns { +namespace rdata { +namespace any { + +// straightforward representation of TSIG RDATA fields +struct TSIGImpl { + TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge, + vector<uint8_t>& mac, uint16_t original_id, uint16_t error, + vector<uint8_t>& other_data) : + algorithm_(algorithm), time_signed_(time_signed), fudge_(fudge), + mac_(mac), original_id_(original_id), error_(error), + other_data_(other_data) { + } + TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge, + size_t macsize, const void* mac, uint16_t original_id, + uint16_t error, size_t other_len, const void* other_data) : + algorithm_(algorithm), time_signed_(time_signed), fudge_(fudge), + mac_(static_cast<const uint8_t*>(mac), + static_cast<const uint8_t*>(mac) + macsize), + original_id_(original_id), error_(error), + other_data_(static_cast<const uint8_t*>(other_data), + static_cast<const uint8_t*>(other_data) + other_len) { + } + template <typename Output> + void toWireCommon(Output& output) const; + + const Name algorithm_; + const uint64_t time_signed_; + const uint16_t fudge_; + const vector<uint8_t> mac_; + const uint16_t original_id_; + const uint16_t error_; + const vector<uint8_t> other_data_; +}; + +// helper function for string and lexer constructors +std::unique_ptr<TSIGImpl> +TSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) { + const Name& algorithm = + createNameFromLexer(lexer, origin ? origin : &Name::ROOT_NAME()); + const Name& canonical_algorithm_name = + (algorithm == TSIGKey::HMACMD5_SHORT_NAME()) ? + TSIGKey::HMACMD5_NAME() : algorithm; + + const string& time_txt = + lexer.getNextToken(MasterToken::STRING).getString(); + uint64_t time_signed; + try { + time_signed = boost::lexical_cast<uint64_t>(time_txt); + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidRdataText, "Invalid TSIG Time"); + } + if ((time_signed >> 48) != 0) { + isc_throw(InvalidRdataText, "TSIG Time out of range"); + } + + const uint32_t fudge = lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (fudge > 0xffff) { + isc_throw(InvalidRdataText, "TSIG Fudge out of range"); + } + const uint32_t macsize = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (macsize > 0xffff) { + isc_throw(InvalidRdataText, "TSIG MAC Size out of range"); + } + + const string& mac_txt = (macsize > 0) ? + lexer.getNextToken(MasterToken::STRING).getString() : ""; + vector<uint8_t> mac; + decodeBase64(mac_txt, mac); + if (mac.size() != macsize) { + isc_throw(InvalidRdataText, "TSIG MAC Size and data are inconsistent"); + } + + const uint32_t orig_id = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (orig_id > 0xffff) { + isc_throw(InvalidRdataText, "TSIG Original ID out of range"); + } + + const string& error_txt = + lexer.getNextToken(MasterToken::STRING).getString(); + uint32_t error = 0; + // XXX: In the initial implementation we hardcode the mnemonics. + // We'll soon generalize this. + if (error_txt == "NOERROR") { + error = Rcode::NOERROR_CODE; + } else if (error_txt == "BADSIG") { + error = TSIGError::BAD_SIG_CODE; + } else if (error_txt == "BADKEY") { + error = TSIGError::BAD_KEY_CODE; + } else if (error_txt == "BADTIME") { + error = TSIGError::BAD_TIME_CODE; + } else if (error_txt == "BADMODE") { + error = TSIGError::BAD_MODE_CODE; + } else if (error_txt == "BADNAME") { + error = TSIGError::BAD_NAME_CODE; + } else if (error_txt == "BADALG") { + error = TSIGError::BAD_ALG_CODE; + } else if (error_txt == "BADTRUNC") { + error = TSIGError::BAD_TRUNC_CODE; + } else { + /// we cast to uint32_t and range-check, because casting directly to + /// uint16_t will convert negative numbers to large positive numbers + try { + error = boost::lexical_cast<uint32_t>(error_txt); + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidRdataText, "Invalid TSIG Error"); + } + if (error > 0xffff) { + isc_throw(InvalidRdataText, "TSIG Error out of range"); + } + } + + const uint32_t otherlen = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (otherlen > 0xffff) { + isc_throw(InvalidRdataText, "TSIG Other Len out of range"); + } + const string otherdata_txt = (otherlen > 0) ? + lexer.getNextToken(MasterToken::STRING).getString() : ""; + vector<uint8_t> other_data; + decodeBase64(otherdata_txt, other_data); + if (other_data.size() != otherlen) { + isc_throw(InvalidRdataText, + "TSIG Other Data length does not match Other Len"); + } + // RFC2845 says Other Data is "empty unless Error == BADTIME". + // However, we don't enforce that. + + return (std::unique_ptr<TSIGImpl>(new TSIGImpl(canonical_algorithm_name, time_signed, fudge, mac, + orig_id, error, other_data))); +} + +/// \brief Constructor from string. +/// +/// The given string must represent a valid TSIG RDATA. There can be extra +/// space characters at the beginning or end of the text (which are simply +/// ignored), but other extra text, including a new line, will make the +/// construction fail with an exception. +/// +/// \c tsig_str must be formatted as follows: +/// \code <Algorithm Name> <Time Signed> <Fudge> <MAC Size> [<MAC>] +/// <Original ID> <Error> <Other Len> [<Other Data>] +/// \endcode +/// +/// Note that, since the Algorithm Name field is defined to be "in domain name +/// syntax", but it is not actually a domain name, it does not have to be +/// fully qualified. +/// +/// The Error field is an unsigned 16-bit decimal integer or a valid mnemonic +/// as specified in RFC2845. Currently, "NOERROR", "BADSIG", "BADKEY", and +/// "BADTIME" are supported (case sensitive). In future versions other +/// representations that are compatible with the DNS RCODE may be supported. +/// +/// The MAC and Other Data fields are base-64 encoded strings that do not +/// contain space characters. +/// If the MAC Size field is 0, the MAC field must not appear in \c tsig_str. +/// If the Other Len field is 0, the Other Data field must not appear in +/// \c tsig_str. +/// The decoded data of the MAC field is MAC Size bytes of binary stream. +/// The decoded data of the Other Data field is Other Len bytes of binary +/// stream. +/// +/// An example of valid string is: +/// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode +/// In this example Other Data is missing because Other Len is 0. +/// +/// Note that RFC2845 does not define the standard presentation format +/// of %TSIG RR, so the above syntax is implementation specific. +/// This is, however, compatible with the format acceptable to BIND 9's +/// RDATA parser. +/// +/// \throw Others Exception from the Name constructors. +/// \throw InvalidRdataText if any fields are out of their valid range, +/// or are incorrect. +/// \throw BadValue if MAC or Other Data is not validly encoded in base-64. +/// +/// \param tsig_str A string containing the RDATA to be created +TSIG::TSIG(const std::string& tsig_str) { + try { + std::istringstream ss(tsig_str); + MasterLexer lexer; + lexer.pushSource(ss); + + impl_ = constructFromLexer(lexer, 0); + + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, + "Extra input text for TSIG: " << tsig_str); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, + "Failed to construct TSIG from '" << tsig_str << "': " + << ex.what()); + } +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual +/// representation of an TSIG RDATA. +/// +/// See \c TSIG::TSIG(const std::string&) for description of the +/// expected RDATA fields. +/// +/// \throw MasterLexer::LexerError General parsing error such as +/// missing field. +/// \throw InvalidRdataText if any fields are out of their valid range, +/// or are incorrect. +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +TSIG::TSIG(MasterLexer& lexer, const Name* origin, + MasterLoader::Options, MasterLoaderCallbacks&) { + impl_ = constructFromLexer(lexer, origin); +} + +/// \brief Constructor from wire-format data. +/// +/// When a read operation on \c buffer fails (e.g., due to a corrupted +/// message) a corresponding exception from the \c InputBuffer class will +/// be thrown. +/// If the wire-format data does not begin with a valid domain name, +/// a corresponding exception from the \c Name class will be thrown. +/// In addition, this constructor internally involves resource allocation, +/// and if it fails a corresponding standard exception will be thrown. +/// +/// According to RFC3597, the Algorithm field must be a non compressed form +/// of domain name. But this implementation accepts a %TSIG RR even if that +/// field is compressed. +/// +/// \param buffer A buffer storing the wire format data. +/// \param rdata_len The length of the RDATA in bytes, normally expected +/// to be the value of the RDLENGTH field of the corresponding RR. +/// But this constructor does not use this parameter; if necessary, the caller +/// must check consistency between the length parameter and the actual +/// RDATA length. +TSIG::TSIG(InputBuffer& buffer, size_t) { + Name algorithm(buffer); + + uint8_t time_signed_buf[6]; + buffer.readData(time_signed_buf, sizeof(time_signed_buf)); + const uint64_t time_signed = + (static_cast<uint64_t>(time_signed_buf[0]) << 40 | + static_cast<uint64_t>(time_signed_buf[1]) << 32 | + static_cast<uint64_t>(time_signed_buf[2]) << 24 | + static_cast<uint64_t>(time_signed_buf[3]) << 16 | + static_cast<uint64_t>(time_signed_buf[4]) << 8 | + static_cast<uint64_t>(time_signed_buf[5])); + + const uint16_t fudge = buffer.readUint16(); + + const uint16_t mac_size = buffer.readUint16(); + vector<uint8_t> mac(mac_size); + if (mac_size > 0) { + buffer.readData(&mac[0], mac_size); + } + + const uint16_t original_id = buffer.readUint16(); + const uint16_t error = buffer.readUint16(); + + const uint16_t other_len = buffer.readUint16(); + vector<uint8_t> other_data(other_len); + if (other_len > 0) { + buffer.readData(&other_data[0], other_len); + } + + const Name& canonical_algorithm_name = + (algorithm == TSIGKey::HMACMD5_SHORT_NAME()) ? + TSIGKey::HMACMD5_NAME() : algorithm; + impl_.reset(new TSIGImpl(canonical_algorithm_name, time_signed, fudge, mac, + original_id, error, other_data)); +} + +TSIG::TSIG(const Name& algorithm, uint64_t time_signed, uint16_t fudge, + uint16_t mac_size, const void* mac, uint16_t original_id, + uint16_t error, uint16_t other_len, const void* other_data) { + // Time Signed is a 48-bit value. + if ((time_signed >> 48) != 0) { + isc_throw(OutOfRange, "TSIG Time Signed is too large: " << + time_signed); + } + if ((mac_size == 0 && mac) || (mac_size > 0 && !mac)) { + isc_throw(InvalidParameter, "TSIG MAC size and data inconsistent"); + } + if ((other_len == 0 && other_data) || (other_len > 0 && !other_data)) { + isc_throw(InvalidParameter, + "TSIG Other data length and data inconsistent"); + } + const Name& canonical_algorithm_name = + (algorithm == TSIGKey::HMACMD5_SHORT_NAME()) ? + TSIGKey::HMACMD5_NAME() : algorithm; + impl_.reset(new TSIGImpl(canonical_algorithm_name, time_signed, fudge, mac_size, + mac, original_id, error, other_len, other_data)); +} + +/// \brief The copy constructor. +/// +/// It internally allocates a resource, and if it fails a corresponding +/// standard exception will be thrown. +/// This constructor never throws an exception otherwise. +TSIG::TSIG(const TSIG& source) : Rdata(), impl_(new TSIGImpl(*source.impl_)) { +} + +TSIG& +TSIG::operator=(const TSIG& source) { + if (this == &source) { + return (*this); + } + + impl_.reset(new TSIGImpl(*source.impl_)); + + return (*this); +} + +TSIG::~TSIG() { +} + +/// \brief Convert the \c TSIG to a string. +/// +/// The output of this method is formatted as described in the "from string" +/// constructor (\c TSIG(const std::string&))). +/// +/// If internal resource allocation fails, a corresponding +/// standard exception will be thrown. +/// +/// \return A \c string object that represents the \c TSIG object. +std::string +TSIG::toText() const { + string result; + + result += impl_->algorithm_.toText() + " " + + lexical_cast<string>(impl_->time_signed_) + " " + + lexical_cast<string>(impl_->fudge_) + " " + + lexical_cast<string>(impl_->mac_.size()) + " "; + if (!impl_->mac_.empty()) { + result += encodeBase64(impl_->mac_) + " "; + } + result += lexical_cast<string>(impl_->original_id_) + " "; + result += TSIGError(impl_->error_).toText() + " "; + result += lexical_cast<string>(impl_->other_data_.size()); + if (!impl_->other_data_.empty()) { + result += " " + encodeBase64(impl_->other_data_); + } + + return (result); +} + +// Common sequence of toWire() operations used for the two versions of +// toWire(). +template <typename Output> +void +TSIGImpl::toWireCommon(Output& output) const { + output.writeUint16(time_signed_ >> 32); + output.writeUint32(time_signed_ & 0xffffffff); + output.writeUint16(fudge_); + const uint16_t mac_size = mac_.size(); + output.writeUint16(mac_size); + if (mac_size > 0) { + output.writeData(&mac_[0], mac_size); + } + output.writeUint16(original_id_); + output.writeUint16(error_); + const uint16_t other_len = other_data_.size(); + output.writeUint16(other_len); + if (other_len > 0) { + output.writeData(&other_data_[0], other_len); + } +} + +/// \brief Render the \c TSIG in the wire format without name compression. +/// +/// If internal resource allocation fails, a corresponding +/// standard exception will be thrown. +/// This method never throws an exception otherwise. +/// +/// \param buffer An output buffer to store the wire data. +void +TSIG::toWire(OutputBuffer& buffer) const { + impl_->algorithm_.toWire(buffer); + impl_->toWireCommon<OutputBuffer>(buffer); +} + +/// \brief Render the \c TSIG in the wire format with taking into account +/// compression. +/// +/// As specified in RFC3597, the Algorithm field (a domain name) will not +/// be compressed. However, the domain name could be a target of compression +/// of other compressible names (though pretty unlikely), the offset +/// information of the algorithm name may be recorded in \c renderer. +/// +/// If internal resource allocation fails, a corresponding +/// standard exception will be thrown. +/// This method never throws an exception otherwise. +/// +/// \param renderer DNS message rendering context that encapsulates the +/// output buffer and name compression information. +void +TSIG::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeName(impl_->algorithm_, false); + impl_->toWireCommon<AbstractMessageRenderer>(renderer); +} + +// A helper function commonly used for TSIG::compare(). +int +vectorComp(const vector<uint8_t>& v1, const vector<uint8_t>& v2) { + const size_t this_size = v1.size(); + const size_t other_size = v2.size(); + if (this_size != other_size) { + return (this_size < other_size ? -1 : 1); + } + if (this_size > 0) { + return (memcmp(&v1[0], &v2[0], this_size)); + } + return (0); +} + +/// \brief Compare two instances of \c TSIG RDATA. +/// +/// This method compares \c this and the \c other \c TSIG objects +/// in terms of the DNSSEC sorting order as defined in RFC4034, and returns +/// the result as an integer. +/// +/// This method is expected to be used in a polymorphic way, and the +/// parameter to compare against is therefore of the abstract \c Rdata class. +/// However, comparing two \c Rdata objects of different RR types +/// is meaningless, and \c other must point to a \c TSIG object; +/// otherwise, the standard \c bad_cast exception will be thrown. +/// This method never throws an exception otherwise. +/// +/// \param other the right-hand operand to compare against. +/// \return < 0 if \c this would be sorted before \c other. +/// \return 0 if \c this is identical to \c other in terms of sorting order. +/// \return > 0 if \c this would be sorted after \c other. +int +TSIG::compare(const Rdata& other) const { + const TSIG& other_tsig = dynamic_cast<const TSIG&>(other); + + const int ncmp = compareNames(impl_->algorithm_, + other_tsig.impl_->algorithm_); + if (ncmp != 0) { + return (ncmp); + } + + if (impl_->time_signed_ != other_tsig.impl_->time_signed_) { + return (impl_->time_signed_ < other_tsig.impl_->time_signed_ ? -1 : 1); + } + if (impl_->fudge_ != other_tsig.impl_->fudge_) { + return (impl_->fudge_ < other_tsig.impl_->fudge_ ? -1 : 1); + } + const int vcmp = vectorComp(impl_->mac_, other_tsig.impl_->mac_); + if (vcmp != 0) { + return (vcmp); + } + if (impl_->original_id_ != other_tsig.impl_->original_id_) { + return (impl_->original_id_ < other_tsig.impl_->original_id_ ? -1 : 1); + } + if (impl_->error_ != other_tsig.impl_->error_) { + return (impl_->error_ < other_tsig.impl_->error_ ? -1 : 1); + } + return (vectorComp(impl_->other_data_, other_tsig.impl_->other_data_)); +} + +const Name& +TSIG::getAlgorithm() const { + return (impl_->algorithm_); +} + +uint64_t +TSIG::getTimeSigned() const { + return (impl_->time_signed_); +} + +uint16_t +TSIG::getFudge() const { + return (impl_->fudge_); +} + +uint16_t +TSIG::getMACSize() const { + return (impl_->mac_.size()); +} + +const void* +TSIG::getMAC() const { + if (!impl_->mac_.empty()) { + return (&impl_->mac_[0]); + } else { + return (0); + } +} + +uint16_t +TSIG::getOriginalID() const { + return (impl_->original_id_); +} + +uint16_t +TSIG::getError() const { + return (impl_->error_); +} + +uint16_t +TSIG::getOtherLen() const { + return (impl_->other_data_.size()); +} + +const void* +TSIG::getOtherData() const { + if (!impl_->other_data_.empty()) { + return (&impl_->other_data_[0]); + } else { + return (0); + } +} + +} // end of namespace "any" + +namespace ch { + +A::A(const std::string&) { + // TBD +} + +A::A(MasterLexer&, const Name*, + MasterLoader::Options, MasterLoaderCallbacks&) { + // TBD +} + +A::A(InputBuffer&, size_t) { + // TBD +} + +A::A(const A&) : Rdata() { + // TBD +} + +void +A::toWire(OutputBuffer&) const { + // TBD +} + +void +A::toWire(AbstractMessageRenderer&) const { + // TBD +} + +string +A::toText() const { + // TBD + isc_throw(InvalidRdataText, "Not implemented yet"); +} + +int +A::compare(const Rdata&) const { + // TBD + return (0); +} + +} // end of namespace "ch" + +namespace generic { + +/// \brief Constructor from string. +/// +/// The given string must represent a valid NS RDATA. There can be extra +/// space characters at the beginning or end of the text (which are simply +/// ignored), but other extra text, including a new line, will make the +/// construction fail with an exception. +/// +/// The NSDNAME must be absolute since there's no parameter that +/// specifies the origin name; if it is not absolute, \c +/// MissingNameOrigin exception will be thrown. These must not be +/// represented as a quoted string. +/// +/// \throw Others Exception from the Name and RRTTL constructors. +/// \throw InvalidRdataText Other general syntax errors. +NS::NS(const std::string& namestr) : + // Fill in dummy name and replace them soon below. + nsname_(Name::ROOT_NAME()) { + try { + std::istringstream ss(namestr); + MasterLexer lexer; + lexer.pushSource(ss); + + nsname_ = createNameFromLexer(lexer, 0); + + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, "extra input text for NS: " + << namestr); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, "Failed to construct NS from '" << + namestr << "': " << ex.what()); + } +} + +NS::NS(InputBuffer& buffer, size_t) : + nsname_(buffer) { + // we don't need rdata_len for parsing. if necessary, the caller will + // check consistency. +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual +/// representation of an NS RDATA. The NSDNAME field can be +/// non-absolute if \c origin is non-null, in which case \c origin is +/// used to make it absolute. It must not be represented as a quoted +/// string. +/// +/// \throw MasterLexer::LexerError General parsing error such as missing field. +/// \throw Other Exceptions from the Name and RRTTL constructors if +/// construction of textual fields as these objects fail. +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +/// \param origin If non null, specifies the origin of NSDNAME when it +/// is non-absolute. +NS::NS(MasterLexer& lexer, const Name* origin, + MasterLoader::Options, MasterLoaderCallbacks&) : + nsname_(createNameFromLexer(lexer, origin)) { +} + +NS::NS(const NS& other) : + Rdata(), nsname_(other.nsname_) { +} + +void +NS::toWire(OutputBuffer& buffer) const { + nsname_.toWire(buffer); +} + +void +NS::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeName(nsname_); +} + +string +NS::toText() const { + return (nsname_.toText()); +} + +int +NS::compare(const Rdata& other) const { + const NS& other_ns = dynamic_cast<const NS&>(other); + + return (compareNames(nsname_, other_ns.nsname_)); +} + +const Name& +NS::getNSName() const { + return (nsname_); +} + +/// \brief Constructor. +OPT::PseudoRR::PseudoRR(uint16_t code, + boost::shared_ptr<std::vector<uint8_t> >& data) : + code_(code), + data_(data) { +} + +uint16_t +OPT::PseudoRR::getCode() const { + return (code_); +} + +const uint8_t* +OPT::PseudoRR::getData() const { + return (&(*data_)[0]); +} + +uint16_t +OPT::PseudoRR::getLength() const { + return (data_->size()); +} + +struct OPTImpl { + OPTImpl() : + rdlength_(0) { + } + + uint16_t rdlength_; + std::vector<OPT::PseudoRR> pseudo_rrs_; +}; + +/// \brief Default constructor. +OPT::OPT() : + impl_(new OPTImpl()) { +} + +/// \brief Constructor from string. +/// +/// This constructor cannot be used, and always throws an exception. +/// +/// \throw InvalidRdataText OPT RR cannot be constructed from text. +OPT::OPT(const std::string&) { + isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text"); +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// This constructor cannot be used, and always throws an exception. +/// +/// \throw InvalidRdataText OPT RR cannot be constructed from text. +OPT::OPT(MasterLexer&, const Name*, + MasterLoader::Options, MasterLoaderCallbacks&) { + isc_throw(InvalidRdataText, "OPT RR cannot be constructed from text"); +} + +OPT::OPT(InputBuffer& buffer, size_t rdata_len) { + impl_.reset(new OPTImpl()); + + while (true) { + if (rdata_len == 0) { + break; + } + + if (rdata_len < 4) { + isc_throw(InvalidRdataLength, + "Pseudo OPT RR record too short: " + << rdata_len << " bytes"); + } + + const uint16_t option_code = buffer.readUint16(); + const uint16_t option_length = buffer.readUint16(); + rdata_len -= 4; + + if (static_cast<uint16_t>(impl_->rdlength_ + option_length) < + impl_->rdlength_) { + isc_throw(InvalidRdataText, + "Option length " << option_length + << " would overflow OPT RR RDLEN (currently " + << impl_->rdlength_ << ")."); + } + + if (rdata_len < option_length) { + isc_throw(InvalidRdataLength, "Corrupt pseudo OPT RR record"); + } + + boost::shared_ptr<std::vector<uint8_t> > + option_data(new std::vector<uint8_t>(option_length)); + buffer.readData(&(*option_data)[0], option_length); + impl_->pseudo_rrs_.push_back(PseudoRR(option_code, option_data)); + impl_->rdlength_ += option_length; + rdata_len -= option_length; + } +} + +OPT::OPT(const OPT& other) : + Rdata(), impl_(new OPTImpl(*other.impl_)) { +} + +OPT& +OPT::operator=(const OPT& source) { + if (this == &source) { + return (*this); + } + + impl_.reset(new OPTImpl(*source.impl_)); + + return (*this); +} + +OPT::~OPT() { +} + +std::string +OPT::toText() const { + isc_throw(isc::InvalidOperation, + "OPT RRs do not have a presentation format"); +} + +void +OPT::toWire(OutputBuffer& buffer) const { + for (auto const& pseudo_rr : impl_->pseudo_rrs_) { + buffer.writeUint16(pseudo_rr.getCode()); + const uint16_t length = pseudo_rr.getLength(); + buffer.writeUint16(length); + if (length > 0) { + buffer.writeData(pseudo_rr.getData(), length); + } + } +} + +void +OPT::toWire(AbstractMessageRenderer& renderer) const { + for (auto const& pseudo_rr : impl_->pseudo_rrs_) { + renderer.writeUint16(pseudo_rr.getCode()); + const uint16_t length = pseudo_rr.getLength(); + renderer.writeUint16(length); + if (length > 0) { + renderer.writeData(pseudo_rr.getData(), length); + } + } +} + +int +OPT::compare(const Rdata&) const { + isc_throw(isc::InvalidOperation, + "It is meaningless to compare a set of OPT pseudo RRs; " + "they have unspecified order"); + return (0); +} + +void +OPT::appendPseudoRR(uint16_t code, const uint8_t* data, uint16_t length) { + // See if it overflows 16-bit length field. We only worry about the + // pseudo-RR length here, not the whole message length (which should + // be checked and enforced elsewhere). + if (static_cast<uint16_t>(impl_->rdlength_ + length) < + impl_->rdlength_) { + isc_throw(isc::InvalidParameter, + "Option length " << length + << " would overflow OPT RR RDLEN (currently " + << impl_->rdlength_ << ")."); + } + + boost::shared_ptr<std::vector<uint8_t> > + option_data(new std::vector<uint8_t>(length)); + if (length != 0) { + std::memcpy(&(*option_data)[0], data, length); + } + impl_->pseudo_rrs_.push_back(PseudoRR(code, option_data)); + impl_->rdlength_ += length; +} + +const std::vector<OPT::PseudoRR>& +OPT::getPseudoRRs() const { + return (impl_->pseudo_rrs_); +} + +/// \brief Constructor from string. +/// +/// The given string must represent a valid PTR RDATA. There can be +/// extra space characters at the beginning or end of the text (which +/// are simply ignored), but other extra text, including a new line, +/// will make the construction fail with an exception. +/// +/// The PTRDNAME must be absolute since there's no parameter that +/// specifies the origin name; if it is not absolute, \c +/// MissingNameOrigin exception will be thrown. These must not be +/// represented as a quoted string. +/// +/// \throw Others Exception from the Name and RRTTL constructors. +/// \throw InvalidRdataText Other general syntax errors. +PTR::PTR(const std::string& type_str) : + // Fill in dummy name and replace them soon below. + ptr_name_(Name::ROOT_NAME()) { + try { + std::istringstream ss(type_str); + MasterLexer lexer; + lexer.pushSource(ss); + + ptr_name_ = createNameFromLexer(lexer, 0); + + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, "extra input text for PTR: " + << type_str); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, "Failed to construct PTR from '" << + type_str << "': " << ex.what()); + } +} + +PTR::PTR(InputBuffer& buffer, size_t) : + ptr_name_(buffer) { + // we don't need rdata_len for parsing. if necessary, the caller will + // check consistency. +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual +/// representation of a PTR RDATA. The PTRDNAME field can be +/// non-absolute if \c origin is non-null, in which case \c origin is +/// used to make it absolute. It must not be represented as a quoted +/// string. +/// +/// \throw MasterLexer::LexerError General parsing error such as missing field. +/// \throw Other Exceptions from the Name and RRTTL constructors if +/// construction of textual fields as these objects fail. +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +/// \param origin If non null, specifies the origin of PTRDNAME when it +/// is non-absolute. +PTR::PTR(MasterLexer& lexer, const Name* origin, + MasterLoader::Options, MasterLoaderCallbacks&) : + ptr_name_(createNameFromLexer(lexer, origin)) { +} + +PTR::PTR(const PTR& source) : + Rdata(), ptr_name_(source.ptr_name_) { +} + +std::string +PTR::toText() const { + return (ptr_name_.toText()); +} + +void +PTR::toWire(OutputBuffer& buffer) const { + ptr_name_.toWire(buffer); +} + +void +PTR::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeName(ptr_name_); +} + +int +PTR::compare(const Rdata& other) const { + // The compare method normally begins with this dynamic cast. + const PTR& other_ptr = dynamic_cast<const PTR&>(other); + + return (compareNames(ptr_name_, other_ptr.ptr_name_)); + +} + +const Name& +PTR::getPTRName() const { + return (ptr_name_); +} + +namespace { +// This is the minimum necessary length of all wire-format RRSIG RDATA: +// - two 8-bit fields (algorithm and labels) +// - two 16-bit fields (covered and tag) +// - three 32-bit fields (original TTL, expire and inception) +const size_t RRSIG_MINIMUM_LEN = 2 * sizeof(uint8_t) + 2 * sizeof(uint16_t) + + 3 * sizeof(uint32_t); +} + +struct RRSIGImpl { + // straightforward representation of RRSIG RDATA fields + RRSIGImpl(const RRType& covered, uint8_t algorithm, uint8_t labels, + uint32_t originalttl, uint32_t timeexpire, + uint32_t timeinception, uint16_t tag, const Name& signer, + const vector<uint8_t>& signature) : + covered_(covered), algorithm_(algorithm), labels_(labels), + originalttl_(originalttl), timeexpire_(timeexpire), + timeinception_(timeinception), tag_(tag), signer_(signer), + signature_(signature) { + } + + const RRType covered_; + uint8_t algorithm_; + uint8_t labels_; + uint32_t originalttl_; + uint32_t timeexpire_; + uint32_t timeinception_; + uint16_t tag_; + const Name signer_; + const vector<uint8_t> signature_; +}; + +// helper function for string and lexer constructors +std::unique_ptr<RRSIGImpl> +RRSIG::constructFromLexer(MasterLexer& lexer, const Name* origin) { + const RRType covered(lexer.getNextToken(MasterToken::STRING).getString()); + const uint32_t algorithm = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (algorithm > 0xff) { + isc_throw(InvalidRdataText, "RRSIG algorithm out of range"); + } + const uint32_t labels = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (labels > 0xff) { + isc_throw(InvalidRdataText, "RRSIG labels out of range"); + } + const uint32_t originalttl = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + const uint32_t timeexpire = + timeFromText32(lexer.getNextToken(MasterToken::STRING).getString()); + const uint32_t timeinception = + timeFromText32(lexer.getNextToken(MasterToken::STRING).getString()); + const uint32_t tag = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (tag > 0xffff) { + isc_throw(InvalidRdataText, "RRSIG key tag out of range"); + } + const Name& signer = createNameFromLexer(lexer, origin); + + string signature_txt; + string signature_part; + // Whitespace is allowed within base64 text, so read to the end of input. + while (true) { + const MasterToken& token = + lexer.getNextToken(MasterToken::STRING, true); + if ((token.getType() == MasterToken::END_OF_FILE) || + (token.getType() == MasterToken::END_OF_LINE)) { + break; + } + token.getString(signature_part); + signature_txt.append(signature_part); + } + lexer.ungetToken(); + + vector<uint8_t> signature; + // missing signature is okay + if (signature_txt.size() > 0) { + decodeBase64(signature_txt, signature); + } + + return (std::unique_ptr<RRSIGImpl>(new RRSIGImpl(covered, + algorithm, + labels, + originalttl, + timeexpire, + timeinception, + static_cast<uint16_t>(tag), + signer, + signature))); +} + +/// \brief Constructor from string. +/// +/// The given string must represent a valid RRSIG RDATA. There can be extra +/// space characters at the beginning or end of the text (which are simply +/// ignored), but other extra text, including a new line, will make the +/// construction fail with an exception. +/// +/// The Signer's Name must be absolute since there's no parameter that +/// specifies the origin name; if this is not absolute, \c MissingNameOrigin +/// exception will be thrown. This must not be represented as a quoted +/// string. +/// +/// See the construction that takes \c MasterLexer for other fields. +/// +/// \throw Others Exception from the Name constructor. +/// \throw InvalidRdataText Other general syntax errors. +RRSIG::RRSIG(const std::string& rrsig_str) { + // We use unique_ptr here because if there is an exception in this + // constructor, the destructor is not called and there could be a + // leak of the RRSIGImpl that constructFromLexer() returns. + boost::shared_ptr<RRSIGImpl> impl_ptr; + + try { + std::istringstream iss(rrsig_str); + MasterLexer lexer; + lexer.pushSource(iss); + + impl_ = constructFromLexer(lexer, 0); + + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, "extra input text for RRSIG: " + << rrsig_str); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, "Failed to construct RRSIG from '" << + rrsig_str << "': " << ex.what()); + } +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual representation +/// of an RRSIG RDATA. The Signer's Name fields can be non absolute if \c +/// origin is non null, in which case \c origin is used to make it absolute. +/// This must not be represented as a quoted string. +/// +/// The Original TTL field is a valid decimal representation of an unsigned +/// 32-bit integer. Note that alternate textual representations of \c RRTTL, +/// such as "1H" for 3600 seconds, are not allowed here. +/// +/// \throw MasterLexer::LexerError General parsing error such as missing field. +/// \throw Other Exceptions from the Name constructor if +/// construction of textual fields as these objects fail. +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +/// \param origin If non null, specifies the origin of Signer's Name when +/// it is non absolute. +RRSIG::RRSIG(MasterLexer& lexer, const Name* origin, + MasterLoader::Options, MasterLoaderCallbacks&) { + impl_ = constructFromLexer(lexer, origin); +} + +RRSIG::RRSIG(InputBuffer& buffer, size_t rdata_len) { + size_t pos = buffer.getPosition(); + + if (rdata_len < RRSIG_MINIMUM_LEN) { + isc_throw(InvalidRdataLength, "RRSIG too short"); + } + + RRType covered(buffer); + uint8_t algorithm = buffer.readUint8(); + uint8_t labels = buffer.readUint8(); + uint32_t originalttl = buffer.readUint32(); + uint32_t timeexpire = buffer.readUint32(); + uint32_t timeinception = buffer.readUint32(); + uint16_t tag = buffer.readUint16(); + Name signer(buffer); + + // rdata_len must be sufficiently large to hold non empty signature data. + if (rdata_len <= buffer.getPosition() - pos) { + isc_throw(InvalidRdataLength, "RRSIG too short"); + } + rdata_len -= (buffer.getPosition() - pos); + + vector<uint8_t> signature(rdata_len); + buffer.readData(&signature[0], rdata_len); + + impl_.reset(new RRSIGImpl(covered, algorithm, labels, + originalttl, timeexpire, timeinception, tag, + signer, signature)); +} + +RRSIG::RRSIG(const RRSIG& source) : + Rdata(), impl_(new RRSIGImpl(*source.impl_)) { +} + +RRSIG& +RRSIG::operator=(const RRSIG& source) { + if (this == &source) { + return (*this); + } + + impl_.reset(new RRSIGImpl(*source.impl_)); + + return (*this); +} + +RRSIG::~RRSIG() { +} + +string +RRSIG::toText() const { + return (impl_->covered_.toText() + + " " + boost::lexical_cast<string>(static_cast<int>(impl_->algorithm_)) + + " " + boost::lexical_cast<string>(static_cast<int>(impl_->labels_)) + + " " + boost::lexical_cast<string>(impl_->originalttl_) + + " " + timeToText32(impl_->timeexpire_) + + " " + timeToText32(impl_->timeinception_) + + " " + boost::lexical_cast<string>(impl_->tag_) + + " " + impl_->signer_.toText() + + " " + encodeBase64(impl_->signature_)); +} + +void +RRSIG::toWire(OutputBuffer& buffer) const { + impl_->covered_.toWire(buffer); + buffer.writeUint8(impl_->algorithm_); + buffer.writeUint8(impl_->labels_); + buffer.writeUint32(impl_->originalttl_); + buffer.writeUint32(impl_->timeexpire_); + buffer.writeUint32(impl_->timeinception_); + buffer.writeUint16(impl_->tag_); + impl_->signer_.toWire(buffer); + buffer.writeData(&impl_->signature_[0], impl_->signature_.size()); +} + +void +RRSIG::toWire(AbstractMessageRenderer& renderer) const { + impl_->covered_.toWire(renderer); + renderer.writeUint8(impl_->algorithm_); + renderer.writeUint8(impl_->labels_); + renderer.writeUint32(impl_->originalttl_); + renderer.writeUint32(impl_->timeexpire_); + renderer.writeUint32(impl_->timeinception_); + renderer.writeUint16(impl_->tag_); + renderer.writeName(impl_->signer_, false); + renderer.writeData(&impl_->signature_[0], impl_->signature_.size()); +} + +int +RRSIG::compare(const Rdata& other) const { + const RRSIG& other_rrsig = dynamic_cast<const RRSIG&>(other); + + if (impl_->covered_.getCode() != other_rrsig.impl_->covered_.getCode()) { + return (impl_->covered_.getCode() < + other_rrsig.impl_->covered_.getCode() ? -1 : 1); + } + if (impl_->algorithm_ != other_rrsig.impl_->algorithm_) { + return (impl_->algorithm_ < other_rrsig.impl_->algorithm_ ? -1 : 1); + } + if (impl_->labels_ != other_rrsig.impl_->labels_) { + return (impl_->labels_ < other_rrsig.impl_->labels_ ? -1 : 1); + } + if (impl_->originalttl_ != other_rrsig.impl_->originalttl_) { + return (impl_->originalttl_ < other_rrsig.impl_->originalttl_ ? + -1 : 1); + } + if (impl_->timeexpire_ != other_rrsig.impl_->timeexpire_) { + return (impl_->timeexpire_ < other_rrsig.impl_->timeexpire_ ? + -1 : 1); + } + if (impl_->timeinception_ != other_rrsig.impl_->timeinception_) { + return (impl_->timeinception_ < other_rrsig.impl_->timeinception_ ? + -1 : 1); + } + if (impl_->tag_ != other_rrsig.impl_->tag_) { + return (impl_->tag_ < other_rrsig.impl_->tag_ ? -1 : 1); + } + + int cmp = compareNames(impl_->signer_, other_rrsig.impl_->signer_); + if (cmp != 0) { + return (cmp); + } + + size_t this_len = impl_->signature_.size(); + size_t other_len = other_rrsig.impl_->signature_.size(); + size_t cmplen = min(this_len, other_len); + cmp = memcmp(&impl_->signature_[0], &other_rrsig.impl_->signature_[0], + cmplen); + if (cmp != 0) { + return (cmp); + } else { + return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1); + } +} + +const RRType& +RRSIG::typeCovered() const { + return (impl_->covered_); +} + +SOA::SOA(InputBuffer& buffer, size_t) : + mname_(buffer), rname_(buffer) { + // we don't need rdata_len for parsing. if necessary, the caller will + // check consistency. + buffer.readData(numdata_, sizeof(numdata_)); +} + +namespace { +void +fillParameters(MasterLexer& lexer, uint8_t numdata[20]) { + // Copy serial, refresh, retry, expire, minimum. We accept the extended + // TTL-compatible style for the latter four. + OutputBuffer buffer(20); + buffer.writeUint32(lexer.getNextToken(MasterToken::NUMBER).getNumber()); + for (int i = 0; i < 4; ++i) { + buffer.writeUint32(RRTTL(lexer.getNextToken(MasterToken::STRING). + getString()).getValue()); + } + memcpy(numdata, buffer.getData(), buffer.getLength()); +} +} + +/// \brief Constructor from string. +/// +/// The given string must represent a valid SOA RDATA. There can be extra +/// space characters at the beginning or end of the text (which are simply +/// ignored), but other extra text, including a new line, will make the +/// construction fail with an exception. +/// +/// The MNAME and RNAME must be absolute since there's no parameter that +/// specifies the origin name; if these are not absolute, \c MissingNameOrigin +/// exception will be thrown. These must not be represented as a quoted +/// string. +/// +/// See the construction that takes \c MasterLexer for other fields. +/// +/// \throw Others Exception from the Name and RRTTL constructors. +/// \throw InvalidRdataText Other general syntax errors. +SOA::SOA(const std::string& soastr) : + // Fill in dummy name and replace them soon below. + mname_(Name::ROOT_NAME()), rname_(Name::ROOT_NAME()) { + try { + std::istringstream ss(soastr); + MasterLexer lexer; + lexer.pushSource(ss); + + mname_ = createNameFromLexer(lexer, 0); + rname_ = createNameFromLexer(lexer, 0); + fillParameters(lexer, numdata_); + + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, "extra input text for SOA: " + << soastr); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, "Failed to construct SOA from '" << + soastr << "': " << ex.what()); + } +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual representation +/// of an SOA RDATA. The MNAME and RNAME fields can be non absolute if +/// \c origin is non null, in which case \c origin is used to make them +/// absolute. These must not be represented as a quoted string. +/// +/// The REFRESH, RETRY, EXPIRE, and MINIMUM fields can be either a valid +/// decimal representation of an unsigned 32-bit integer or other +/// valid textual representation of \c RRTTL such as "1H" (which means 3600). +/// +/// \throw MasterLexer::LexerError General parsing error such as missing field. +/// \throw Other Exceptions from the Name and RRTTL constructors if +/// construction of textual fields as these objects fail. +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +/// \param origin If non null, specifies the origin of MNAME and RNAME when +/// they are non absolute. +SOA::SOA(MasterLexer& lexer, const Name* origin, + MasterLoader::Options, MasterLoaderCallbacks&) : + mname_(createNameFromLexer(lexer, origin)), + rname_(createNameFromLexer(lexer, origin)) { + fillParameters(lexer, numdata_); +} + +SOA::SOA(const Name& mname, const Name& rname, uint32_t serial, + uint32_t refresh, uint32_t retry, uint32_t expire, uint32_t minimum) : + mname_(mname), rname_(rname) { + OutputBuffer b(20); + b.writeUint32(serial); + b.writeUint32(refresh); + b.writeUint32(retry); + b.writeUint32(expire); + b.writeUint32(minimum); + isc_throw_assert(b.getLength() == sizeof(numdata_)); + memcpy(numdata_, b.getData(), sizeof(numdata_)); +} + +SOA::SOA(const SOA& other) : + Rdata(), mname_(other.mname_), rname_(other.rname_) { + memcpy(numdata_, other.numdata_, sizeof(numdata_)); +} + +void +SOA::toWire(OutputBuffer& buffer) const { + mname_.toWire(buffer); + rname_.toWire(buffer); + buffer.writeData(numdata_, sizeof(numdata_)); +} + +void +SOA::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeName(mname_); + renderer.writeName(rname_); + renderer.writeData(numdata_, sizeof(numdata_)); +} + +Serial +SOA::getSerial() const { + InputBuffer b(numdata_, sizeof(numdata_)); + return (Serial(b.readUint32())); +} + +uint32_t +SOA::getMinimum() const { + // Make sure the buffer access is safe. + BOOST_STATIC_ASSERT(sizeof(numdata_) == + sizeof(uint32_t) * 4 + sizeof(uint32_t)); + + InputBuffer b(&numdata_[sizeof(uint32_t) * 4], sizeof(uint32_t)); + return (b.readUint32()); +} + +string +SOA::toText() const { + InputBuffer b(numdata_, sizeof(numdata_)); + uint32_t serial = b.readUint32(); + uint32_t refresh = b.readUint32(); + uint32_t retry = b.readUint32(); + uint32_t expire = b.readUint32(); + uint32_t minimum = b.readUint32(); + + return (mname_.toText() + " " + rname_.toText() + " " + + lexical_cast<string>(serial) + " " + + lexical_cast<string>(refresh) + " " + + lexical_cast<string>(retry) + " " + + lexical_cast<string>(expire) + " " + + lexical_cast<string>(minimum)); +} + +int +SOA::compare(const Rdata& other) const { + const SOA& other_soa = dynamic_cast<const SOA&>(other); + + int order = compareNames(mname_, other_soa.mname_); + if (order != 0) { + return (order); + } + + order = compareNames(rname_, other_soa.rname_); + if (order != 0) { + return (order); + } + + return (memcmp(numdata_, other_soa.numdata_, sizeof(numdata_))); +} + +const uint16_t TKEY::GSS_API_MODE = 3; + +// straightforward representation of TKEY RDATA fields +struct TKEYImpl { + /// \brief Constructor from RDATA field parameters. + /// + /// \param algorithm The DNS name of the algorithm e.g. gss-tsig. + /// \param inception The inception time (in seconds since 1970). + /// \param expire The expire time (in seconds since 1970). + /// \param mode The mode e.g. Diffie-Hellman (2) or GSS-API (3). + /// \param error The error code (extended error space shared with TSIG). + /// \param key The key (can be empty). + /// \param other_data The other data (can be and usually is empty). + TKEYImpl(const Name& algorithm, uint32_t inception, uint32_t expire, + uint16_t mode, uint16_t error, vector<uint8_t>& key, + vector<uint8_t>& other_data) : + algorithm_(algorithm), inception_(inception), expire_(expire), + mode_(mode), error_(error), key_(key), other_data_(other_data) { + } + + /// \brief Constructor from RDATA field parameters. + /// + /// \param algorithm The DNS name of the algorithm e.g. gss-tsig. + /// \param inception The inception time (in seconds since 1970). + /// \param expire The expire time (in seconds since 1970). + /// \param mode The mode e.g. Diffie-Hellman (2) or GSS-API (3). + /// \param error The error code (extended error space shared with TSIG). + /// \param key_len The key length (0 means no key). + /// \param key The key (can be 0). + /// \param other_len The other data length (0 means no other data). + /// \param other_data The other data (can be and usually is 0). + TKEYImpl(const Name& algorithm, uint32_t inception, uint32_t expire, + uint16_t mode, uint16_t error, size_t key_len, + const void* key, size_t other_len, const void* other_data) : + algorithm_(algorithm), inception_(inception), expire_(expire), + mode_(mode), error_(error), + key_(key_len > 0 ? + vector<uint8_t>(static_cast<const uint8_t*>(key), + static_cast<const uint8_t*>(key) + key_len) : + vector<uint8_t>(key_len)), + other_data_(other_len > 0 ? + vector<uint8_t>(static_cast<const uint8_t*>(other_data), + static_cast<const uint8_t*>(other_data) + + other_len) : + vector<uint8_t>(other_len)) { + } + + /// \brief Common part of toWire methods. + /// \tparam Output \c OutputBuffer or \c AbstractMessageRenderer. + template <typename Output> + void toWireCommon(Output& output) const; + + /// \brief The DNS name of the algorithm e.g. gss-tsig. + const Name algorithm_; + + /// \brief The inception time (in seconds since 1970). + const uint32_t inception_; + + /// \brief The expire time (in seconds since 1970). + const uint32_t expire_; + + /// \brief The mode e.g. Diffie-Hellman (2) or GSS-API (3). + const uint16_t mode_; + + /// \brief The error code (extended error space shared with TSIG). + const uint16_t error_; + + /// \brief The key (can be empty). + const vector<uint8_t> key_; + + /// \brief The other data (can be and usually is empty). + const vector<uint8_t> other_data_; +}; + +// helper function for string and lexer constructors +std::unique_ptr<TKEYImpl> +TKEY::constructFromLexer(MasterLexer& lexer, const Name* origin) { + const Name& algorithm = + createNameFromLexer(lexer, origin ? origin : &Name::ROOT_NAME()); + + const uint32_t inception = + timeFromText32(lexer.getNextToken(MasterToken::STRING).getString()); + + const uint32_t expire = + timeFromText32(lexer.getNextToken(MasterToken::STRING).getString()); + + /// The mode is either a mnemonic (only one is defined: GSS-API) or + /// a number. + const string& mode_txt = + lexer.getNextToken(MasterToken::STRING).getString(); + uint32_t mode = 0; + if (mode_txt == "GSS-API") { + mode = GSS_API_MODE; + } else { + /// we cast to uint32_t and range-check, because casting directly to + /// uint16_t will convert negative numbers to large positive numbers + try { + mode = boost::lexical_cast<uint32_t>(mode_txt); + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidRdataText, "Invalid TKEY Mode"); + } + if (mode > 0xffff) { + isc_throw(InvalidRdataText, "TKEY Mode out of range"); + } + } + + const string& error_txt = + lexer.getNextToken(MasterToken::STRING).getString(); + uint32_t error = 0; + // XXX: In the initial implementation we hardcode the mnemonics. + // We'll soon generalize this. + if (error_txt == "NOERROR") { + error = Rcode::NOERROR_CODE; + } else if (error_txt == "BADSIG") { + error = TSIGError::BAD_SIG_CODE; + } else if (error_txt == "BADKEY") { + error = TSIGError::BAD_KEY_CODE; + } else if (error_txt == "BADTIME") { + error = TSIGError::BAD_TIME_CODE; + } else if (error_txt == "BADMODE") { + error = TSIGError::BAD_MODE_CODE; + } else if (error_txt == "BADNAME") { + error = TSIGError::BAD_NAME_CODE; + } else if (error_txt == "BADALG") { + error = TSIGError::BAD_ALG_CODE; + } else if (error_txt == "BADTRUNC") { + error = TSIGError::BAD_TRUNC_CODE; + } else { + /// we cast to uint32_t and range-check, because casting directly to + /// uint16_t will convert negative numbers to large positive numbers + try { + error = boost::lexical_cast<uint32_t>(error_txt); + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidRdataText, "Invalid TKEY Error"); + } + if (error > 0xffff) { + isc_throw(InvalidRdataText, "TKEY Error out of range"); + } + } + + const uint32_t keylen = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (keylen > 0xffff) { + isc_throw(InvalidRdataText, "TKEY Key Len out of range"); + } + const string keydata_txt = (keylen > 0) ? + lexer.getNextToken(MasterToken::STRING).getString() : ""; + vector<uint8_t> key_data; + decodeBase64(keydata_txt, key_data); + if (key_data.size() != keylen) { + isc_throw(InvalidRdataText, + "TKEY Key Data length does not match Other Len"); + } + + const uint32_t otherlen = + lexer.getNextToken(MasterToken::NUMBER).getNumber(); + if (otherlen > 0xffff) { + isc_throw(InvalidRdataText, "TKEY Other Len out of range"); + } + const string otherdata_txt = (otherlen > 0) ? + lexer.getNextToken(MasterToken::STRING).getString() : ""; + vector<uint8_t> other_data; + decodeBase64(otherdata_txt, other_data); + if (other_data.size() != otherlen) { + isc_throw(InvalidRdataText, + "TKEY Other Data length does not match Other Len"); + } + // RFC2845 says Other Data is "empty unless Error == BADTIME". + // However, we don't enforce that. + + return (std::unique_ptr<TKEYImpl>(new TKEYImpl(algorithm, inception, + expire, mode, error, + key_data, other_data))); +} + +/// \brief Constructor from string. +/// +/// The given string must represent a valid TKEY RDATA. There can be extra +/// space characters at the beginning or end of the text (which are simply +/// ignored), but other extra text, including a new line, will make the +/// construction fail with an exception. +/// +/// \c tkey_str must be formatted as follows: +/// \code <Algorithm Name> <Inception> <Expire> <Mode> <Error> +/// <Key Len> [<Key Data>] <Other Len> [<Other Data>] +/// \endcode +/// +/// Note that, since the Algorithm Name field is defined to be "in domain name +/// syntax", but it is not actually a domain name, it does not have to be +/// fully qualified. +/// +/// The Mode field is an unsigned 16-bit decimal integer as specified +/// in RFC2930 or a common mnemonic. Currently only "GSS-API" (case sensitive) +/// is supported ("Diffie-Hellman" is not). +/// +/// The Error field is an unsigned 16-bit decimal integer or a valid mnemonic +/// as specified in RFC2845. Currently, "NOERROR", "BADSIG", "BADKEY", +/// "BADTIME", "BADMODE", "BADNAME", and "BADALG" are supported +/// (case sensitive). In future versions other representations that +/// are compatible with the DNS RCODE may be supported. +/// +/// The Key Data and Other Data fields are base-64 encoded strings that do not +/// contain space characters. +/// If the Key Len field is 0, the Key Data field must not appear in +/// \c tkey_str. +/// If the Other Len field is 0, the Other Data field must not appear in +/// \c tkey_str. +/// The decoded data of the Key Data field is Key Len bytes of binary stream. +/// The decoded data of the Other Data field is Other Len bytes of binary +/// stream. +/// +/// An example of valid string is: +/// \code "gss-tsig. 20210501120000 20210501130000 0 3 aabbcc 0" \endcode +/// In this example Other Data is missing because Other Len is 0. +/// +/// Note that RFC2930 does not define the standard presentation format +/// of %TKEY RR, so the above syntax is implementation specific. +/// This is, however, compatible with the format acceptable to BIND 9's +/// RDATA parser. +/// +/// \throw Others Exception from the Name constructors. +/// \throw InvalidRdataText if any fields are out of their valid range, +/// or are incorrect. +/// \throw BadValue if Key Data or Other Data is not validly encoded +/// in base-64. +/// +/// \param tkey_str A string containing the RDATA to be created +TKEY::TKEY(const std::string& tkey_str) { + + try { + std::istringstream ss(tkey_str); + MasterLexer lexer; + lexer.pushSource(ss); + + impl_ = constructFromLexer(lexer, 0); + + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, + "Extra input text for TKEY: " << tkey_str); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, + "Failed to construct TKEY from '" << tkey_str << "': " + << ex.what()); + } +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual +/// representation of an TKEY RDATA. +/// +/// See \c TKEY::TKEY(const std::string&) for description of the +/// expected RDATA fields. +/// +/// \throw MasterLexer::LexerError General parsing error such as +/// missing field. +/// \throw InvalidRdataText if any fields are out of their valid range, +/// or are incorrect. +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +TKEY::TKEY(MasterLexer& lexer, const Name* origin, + MasterLoader::Options, MasterLoaderCallbacks&) { + impl_ = constructFromLexer(lexer, origin); +} + +/// \brief Constructor from wire-format data. +/// +/// When a read operation on \c buffer fails (e.g., due to a corrupted +/// message) a corresponding exception from the \c InputBuffer class will +/// be thrown. +/// If the wire-format data does not begin with a valid domain name, +/// a corresponding exception from the \c Name class will be thrown. +/// In addition, this constructor internally involves resource allocation, +/// and if it fails a corresponding standard exception will be thrown. +/// +/// According to RFC3597, the Algorithm field must be a non compressed form +/// of domain name. But this implementation accepts a %TKEY RR even if that +/// field is compressed. +/// +/// \param buffer A buffer storing the wire format data. +/// \param rdata_len The length of the RDATA in bytes, normally expected +/// to be the value of the RDLENGTH field of the corresponding RR. +/// But this constructor does not use this parameter; if necessary, the caller +/// must check consistency between the length parameter and the actual +/// RDATA length. +TKEY::TKEY(InputBuffer& buffer, size_t) { + Name algorithm(buffer); + + const uint32_t inception = buffer.readUint32(); + + const uint32_t expire = buffer.readUint32(); + + const uint16_t mode = buffer.readUint16(); + + const uint16_t error = buffer.readUint16(); + + const uint16_t key_len = buffer.readUint16(); + vector<uint8_t> key(key_len); + if (key_len > 0) { + buffer.readData(&key[0], key_len); + } + + const uint16_t other_len = buffer.readUint16(); + vector<uint8_t> other_data(other_len); + if (other_len > 0) { + buffer.readData(&other_data[0], other_len); + } + + impl_.reset(new TKEYImpl(algorithm, inception, expire, mode, error, + key, other_data)); +} + +TKEY::TKEY(const Name& algorithm, uint32_t inception, uint32_t expire, + uint16_t mode, uint16_t error, uint16_t key_len, + const void* key, uint16_t other_len, const void* other_data) { + if ((key_len == 0 && key != 0) || (key_len > 0 && key == 0)) { + isc_throw(InvalidParameter, "TKEY Key length and data inconsistent"); + } + if ((other_len == 0 && other_data != 0) || + (other_len > 0 && other_data == 0)) { + isc_throw(InvalidParameter, + "TKEY Other data length and data inconsistent"); + } + impl_.reset(new TKEYImpl(algorithm, inception, expire, mode, error, + key_len, key, other_len, other_data)); +} + +/// \brief The copy constructor. +/// +/// It internally allocates a resource, and if it fails a corresponding +/// standard exception will be thrown. +/// This constructor never throws an exception otherwise. +TKEY::TKEY(const TKEY& source) : Rdata(), impl_(new TKEYImpl(*source.impl_)) { +} + +TKEY& +TKEY::operator=(const TKEY& source) { + if (this == &source) { + return (*this); + } + + impl_.reset(new TKEYImpl(*source.impl_)); + + return (*this); +} + +TKEY::~TKEY() { +} + +/// \brief Convert the \c TKEY to a string. +/// +/// The output of this method is formatted as described in the "from string" +/// constructor (\c TKEY(const std::string&))). +/// +/// If internal resource allocation fails, a corresponding +/// standard exception will be thrown. +/// +/// \return A \c string object that represents the \c TKEY object. +std::string +TKEY::toText() const { + string result; + + result += impl_->algorithm_.toText() + " " + + timeToText32(impl_->inception_) + " " + + timeToText32(impl_->expire_) + " "; + if (impl_->mode_ == GSS_API_MODE) { + result += "GSS-API "; + } else { + result += lexical_cast<string>(impl_->mode_) + " "; + } + result += TSIGError(impl_->error_).toText() + " " + + lexical_cast<string>(impl_->key_.size()) + " "; + if (!impl_->key_.empty()) { + result += encodeBase64(impl_->key_) + " "; + } + result += lexical_cast<string>(impl_->other_data_.size()); + if (!impl_->other_data_.empty()) { + result += " " + encodeBase64(impl_->other_data_); + } + + return (result); +} + +// Common sequence of toWire() operations used for the two versions of +// toWire(). +template <typename Output> +void +TKEYImpl::toWireCommon(Output& output) const { + output.writeUint32(inception_); + output.writeUint32(expire_); + output.writeUint16(mode_); + output.writeUint16(error_); + const uint16_t key_len = key_.size(); + output.writeUint16(key_len); + if (key_len > 0) { + output.writeData(&key_[0], key_len); + } + const uint16_t other_len = other_data_.size(); + output.writeUint16(other_len); + if (other_len > 0) { + output.writeData(&other_data_[0], other_len); + } +} + +/// \brief Render the \c TKEY in the wire format without name compression. +/// +/// If internal resource allocation fails, a corresponding +/// standard exception will be thrown. +/// This method never throws an exception otherwise. +/// +/// \param buffer An output buffer to store the wire data. +void +TKEY::toWire(OutputBuffer& buffer) const { + impl_->algorithm_.toWire(buffer); + impl_->toWireCommon<OutputBuffer>(buffer); +} + +/// \brief Render the \c TKEY in the wire format with taking into account +/// compression. +/// +/// As specified in RFC3597, the Algorithm field (a domain name) will not +/// be compressed. However, the domain name could be a target of compression +/// of other compressible names (though pretty unlikely), the offset +/// information of the algorithm name may be recorded in \c renderer. +/// +/// If internal resource allocation fails, a corresponding +/// standard exception will be thrown. +/// This method never throws an exception otherwise. +/// +/// \param renderer DNS message rendering context that encapsulates the +/// output buffer and name compression information. +void +TKEY::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeName(impl_->algorithm_, false); + impl_->toWireCommon<AbstractMessageRenderer>(renderer); +} + +// A helper function commonly used for TKEY::compare(). +int +vectorComp(const vector<uint8_t>& v1, const vector<uint8_t>& v2) { + const size_t this_size = v1.size(); + const size_t other_size = v2.size(); + if (this_size != other_size) { + return (this_size < other_size ? -1 : 1); + } + if (this_size > 0) { + return (memcmp(&v1[0], &v2[0], this_size)); + } + return (0); +} + +/// \brief Compare two instances of \c TKEY RDATA. +/// +/// This method compares \c this and the \c other \c TKEY objects +/// in terms of the DNSSEC sorting order as defined in RFC4034, and returns +/// the result as an integer. +/// +/// This method is expected to be used in a polymorphic way, and the +/// parameter to compare against is therefore of the abstract \c Rdata class. +/// However, comparing two \c Rdata objects of different RR types +/// is meaningless, and \c other must point to a \c TKEY object; +/// otherwise, the standard \c bad_cast exception will be thrown. +/// This method never throws an exception otherwise. +/// +/// \param other the right-hand operand to compare against. +/// \return < 0 if \c this would be sorted before \c other. +/// \return 0 if \c this is identical to \c other in terms of sorting order. +/// \return > 0 if \c this would be sorted after \c other. +int +TKEY::compare(const Rdata& other) const { + const TKEY& other_tkey = dynamic_cast<const TKEY&>(other); + + const int ncmp = compareNames(impl_->algorithm_, + other_tkey.impl_->algorithm_); + if (ncmp != 0) { + return (ncmp); + } + + if (impl_->inception_ != other_tkey.impl_->inception_) { + return (impl_->inception_ < other_tkey.impl_->inception_ ? -1 : 1); + } + if (impl_->expire_ != other_tkey.impl_->expire_) { + return (impl_->expire_ < other_tkey.impl_->expire_ ? -1 : 1); + } + if (impl_->mode_ != other_tkey.impl_->mode_) { + return (impl_->mode_ < other_tkey.impl_->mode_ ? -1 : 1); + } + if (impl_->error_ != other_tkey.impl_->error_) { + return (impl_->error_ < other_tkey.impl_->error_ ? -1 : 1); + } + + const int vcmp = vectorComp(impl_->key_, other_tkey.impl_->key_); + if (vcmp != 0) { + return (vcmp); + } + return (vectorComp(impl_->other_data_, other_tkey.impl_->other_data_)); +} + +const Name& +TKEY::getAlgorithm() const { + return (impl_->algorithm_); +} + +uint32_t +TKEY::getInception() const { + return (impl_->inception_); +} + +string +TKEY::getInceptionDate() const { + return (timeToText32(impl_->inception_)); +} + +uint32_t +TKEY::getExpire() const { + return (impl_->expire_); +} + +string +TKEY::getExpireDate() const { + return (timeToText32(impl_->expire_)); +} + +uint16_t +TKEY::getMode() const { + return (impl_->mode_); +} + +uint16_t +TKEY::getError() const { + return (impl_->error_); +} + +uint16_t +TKEY::getKeyLen() const { + return (impl_->key_.size()); +} + +const void* +TKEY::getKey() const { + if (!impl_->key_.empty()) { + return (&impl_->key_[0]); + } else { + return (0); + } +} + +uint16_t +TKEY::getOtherLen() const { + return (impl_->other_data_.size()); +} + +const void* +TKEY::getOtherData() const { + if (!impl_->other_data_.empty()) { + return (&impl_->other_data_[0]); + } else { + return (0); + } +} + + +TXT& +TXT::operator=(const TXT& source) { + if (this == &source) { + return (*this); + } + + impl_.reset(new TXTImpl(*source.impl_)); + + return (*this); +} + +TXT::~TXT() { +} + +TXT::TXT(InputBuffer& buffer, size_t rdata_len) : + impl_(new TXTImpl(buffer, rdata_len)) { +} + +/// \brief Constructor using the master lexer. +/// +/// This implementation only uses the \c lexer parameters; others are +/// ignored. +/// +/// \throw CharStringTooLong the parameter string length exceeds maximum. +/// \throw InvalidRdataText the method cannot process the parameter data +/// +/// \param lexer A \c MasterLexer object parsing a master file for this +/// RDATA. +TXT::TXT(MasterLexer& lexer, const Name*, MasterLoader::Options, + MasterLoaderCallbacks&) : + impl_(new TXTImpl(lexer)) { +} + +TXT::TXT(const std::string& txtstr) : + impl_(new TXTImpl(txtstr)) { +} + +TXT::TXT(const TXT& other) : + Rdata(), impl_(new TXTImpl(*other.impl_)) { +} + +void +TXT::toWire(OutputBuffer& buffer) const { + impl_->toWire(buffer); +} + +void +TXT::toWire(AbstractMessageRenderer& renderer) const { + impl_->toWire(renderer); +} + +string +TXT::toText() const { + return (impl_->toText()); +} + +int +TXT::compare(const Rdata& other) const { + const TXT& other_txt = dynamic_cast<const TXT&>(other); + + return (impl_->compare(*other_txt.impl_)); +} + +} // end of namespace "generic" + +namespace in { + +namespace { +void +convertToIPv4Addr(const char* src, size_t src_len, uint32_t* dst) { + // This check specifically rejects invalid input that begins with valid + // address text followed by a nul character (and possibly followed by + // further garbage). It cannot be detected by inet_pton(). + // + // Note that this is private subroutine of the in::A constructors, which + // pass std::string.size() or StringRegion::len as src_len, so it should + // be equal to strlen() unless there's an intermediate nul character. + if (src_len != strlen(src)) { + isc_throw(InvalidRdataText, + "Bad IN/A RDATA text: unexpected nul in string: '" + << src << "'"); + } + const int result = inet_pton(AF_INET, src, dst); + if (result == 0) { + isc_throw(InvalidRdataText, "Bad IN/A RDATA text: '" << src << "'"); + } else if (result < 0) { + isc_throw(isc::Unexpected, + "Unexpected failure in parsing IN/A RDATA text: '" + << src << "': " << std::strerror(errno)); + } +} +} + +/// \brief Constructor from string. +/// +/// The given string must be a valid textual representation of an IPv4 +/// address as specified in RFC1035, that is, four decimal numbers separated +/// by dots without any embedded spaces. Note that it excludes abbreviated +/// forms such as "10.1" to mean "10.0.0.1". +/// +/// Internally, this implementation uses the standard inet_pton() library +/// function for the AF_INET family to parse and convert the textual +/// representation. While standard compliant implementations of this function +/// should accept exactly what this constructor expects, specific +/// implementation may behave differently, in which case this constructor +/// will simply accept the result of inet_pton(). In any case, the user of +/// the class shouldn't assume such specific implementation behavior of +/// inet_pton(). +/// +/// No extra character should be contained in \c addrstr other than the +/// textual address. These include spaces and the nul character. +/// +/// \throw InvalidRdata The text extracted by the lexer isn't recognized as +/// a valid IPv4 address. +/// \throw Unexpected Unexpected system error in conversion (this should be +/// very rare). +/// +/// \param addrstr Textual representation of IPv4 address to be used as the +/// RDATA. +A::A(const std::string& addrstr) { + convertToIPv4Addr(addrstr.c_str(), addrstr.size(), &addr_); +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual representation +/// of a class IN A RDATA. +/// +/// The acceptable form of the textual address is generally the same as the +/// string version of the constructor, but this version accepts beginning +/// spaces and trailing spaces or other characters. Trailing non space +/// characters would be considered an invalid form in an RR representation, +/// but handling such errors is not the responsibility of this constructor. +/// It also accepts other unusual syntax that would be considered valid +/// in the context of DNS master file; for example, it accepts an IPv4 +/// address surrounded by parentheses, such as "(192.0.2.1)", although it's +/// very unlikely to be used for this type of RDATA. +/// +/// \throw MasterLexer::LexerError General parsing error such as missing field. +/// \throw InvalidRdata The text extracted by the lexer isn't recognized as +/// a valid IPv4 address. +/// \throw Unexpected Unexpected system error in conversion (this should be +/// very rare). +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +A::A(MasterLexer& lexer, const Name*, + MasterLoader::Options, MasterLoaderCallbacks&) { + const MasterToken& token = lexer.getNextToken(MasterToken::STRING); + convertToIPv4Addr(token.getStringRegion().beg, token.getStringRegion().len, + &addr_); +} + +A::A(InputBuffer& buffer, size_t rdata_len) { + if (rdata_len != sizeof(addr_)) { + isc_throw(DNSMessageFORMERR, + "IN/A RDATA construction from wire failed: Invalid length: " + << rdata_len); + } + if (buffer.getLength() - buffer.getPosition() < sizeof(addr_)) { + isc_throw(DNSMessageFORMERR, + "IN/A RDATA construction from wire failed: " + "insufficient buffer length: " + << buffer.getLength() - buffer.getPosition()); + } + buffer.readData(&addr_, sizeof(addr_)); +} + +/// \brief Copy constructor. +A::A(const A& other) : Rdata(), addr_(other.addr_) { +} + +void +A::toWire(OutputBuffer& buffer) const { + buffer.writeData(&addr_, sizeof(addr_)); +} + +void +A::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeData(&addr_, sizeof(addr_)); +} + +/// \brief Return a textual form of the underlying IPv4 address of the RDATA. +string +A::toText() const { + char addr_string[sizeof("255.255.255.255")]; + + if (inet_ntop(AF_INET, &addr_, addr_string, sizeof(addr_string)) == 0) { + isc_throw(Unexpected, + "Failed to convert IN/A RDATA to textual IPv4 address"); + } + + return (addr_string); +} + +/// \brief Compare two in::A RDATAs. +/// +/// In effect, it compares the two RDATA as an unsigned 32-bit integer. +int +A::compare(const Rdata& other) const { + const A& other_a = dynamic_cast<const A&>(other); + return (memcmp(&addr_, &other_a.addr_, sizeof(addr_))); +} + +namespace { +void +convertToIPv6Addr(const char* src, size_t src_len, void* dst) { + // See a_1.cc for this check. + if (src_len != strlen(src)) { + isc_throw(InvalidRdataText, + "Bad IN/AAAA RDATA text: unexpected nul in string: '" + << src << "'"); + } + const int result = inet_pton(AF_INET6, src, dst); + if (result == 0) { + isc_throw(InvalidRdataText, "Bad IN/AAAA RDATA text: '" << src << "'"); + } else if (result < 0) { + isc_throw(isc::Unexpected, + "Unexpected failure in parsing IN/AAAA RDATA text: '" + << src << "': " << std::strerror(errno)); + } +} +} + +/// \brief Constructor from string. +/// +/// The given string must be a valid textual representation of an IPv6 +/// address as specified in RFC1886. +/// +/// No extra character should be contained in \c addrstr other than the +/// textual address. These include spaces and the nul character. +/// +/// \throw InvalidRdata The text extracted by the lexer isn't recognized as +/// a valid IPv6 address. +/// \throw Unexpected Unexpected system error in conversion (this should be +/// very rare). +/// +/// \param addrstr Textual representation of IPv6 address to be used as the +/// RDATA. +AAAA::AAAA(const std::string& addrstr) { + convertToIPv6Addr(addrstr.c_str(), addrstr.size(), addr_); +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual representation +/// of a class IN AAAA RDATA. +/// +/// The acceptable form of the textual address is generally the same as the +/// string version of the constructor, but this version is slightly more +/// flexible. See the similar constructor of \c in::A class; the same +/// notes apply here. +/// +/// \throw MasterLexer::LexerError General parsing error such as missing field. +/// \throw InvalidRdata The text extracted by the lexer isn't recognized as +/// a valid IPv6 address. +/// \throw Unexpected Unexpected system error in conversion (this should be +/// very rare). +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +AAAA::AAAA(MasterLexer& lexer, const Name*, + MasterLoader::Options, MasterLoaderCallbacks&) { + const MasterToken& token = lexer.getNextToken(MasterToken::STRING); + convertToIPv6Addr(token.getStringRegion().beg, token.getStringRegion().len, + addr_); +} + +/// \brief Copy constructor. +AAAA::AAAA(InputBuffer& buffer, size_t rdata_len) { + if (rdata_len != sizeof(addr_)) { + isc_throw(DNSMessageFORMERR, + "IN/AAAA RDATA construction from wire failed: " + "Invalid length: " << rdata_len); + } + if (buffer.getLength() - buffer.getPosition() < sizeof(addr_)) { + isc_throw(DNSMessageFORMERR, + "IN/AAAA RDATA construction from wire failed: " + "insufficient buffer length: " + << buffer.getLength() - buffer.getPosition()); + } + buffer.readData(&addr_, sizeof(addr_)); +} + +AAAA::AAAA(const AAAA& other) : Rdata() { + memcpy(addr_, other.addr_, sizeof(addr_)); +} + +/// \brief Return a textual form of the underlying IPv6 address of the RDATA. +void +AAAA::toWire(OutputBuffer& buffer) const { + buffer.writeData(&addr_, sizeof(addr_)); +} + +void +AAAA::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeData(&addr_, sizeof(addr_)); +} + +string +AAAA::toText() const { + char addr_string[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; + + if (inet_ntop(AF_INET6, &addr_, addr_string, sizeof(addr_string)) == 0) { + isc_throw(Unexpected, + "Failed to convert IN/AAAA RDATA to textual IPv6 address"); + } + + return (string(addr_string)); +} + +/// \brief Compare two in::AAAA RDATAs. +/// +/// In effect, it compares the two RDATA as an unsigned 128-bit integer. +int +AAAA::compare(const Rdata& other) const { + const AAAA& other_a = dynamic_cast<const AAAA&>(other); + return (memcmp(&addr_, &other_a.addr_, sizeof(addr_))); +} + +void +DHCID::constructFromLexer(MasterLexer& lexer) { + string digest_txt = lexer.getNextToken(MasterToken::STRING).getString(); + + // Whitespace is allowed within base64 text, so read to the end of input. + string digest_part; + while (true) { + const MasterToken& token = + lexer.getNextToken(MasterToken::STRING, true); + if ((token.getType() == MasterToken::END_OF_FILE) || + (token.getType() == MasterToken::END_OF_LINE)) { + break; + } + token.getString(digest_part); + digest_txt.append(digest_part); + } + lexer.ungetToken(); + + decodeBase64(digest_txt, digest_); +} + +/// \brief Constructor from string. +/// +/// \param dhcid_str A base-64 representation of the DHCID binary data. +/// +/// \throw InvalidRdataText if the string could not be parsed correctly. +DHCID::DHCID(const std::string& dhcid_str) { + try { + std::istringstream iss(dhcid_str); + MasterLexer lexer; + lexer.pushSource(iss); + + constructFromLexer(lexer); + + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, "extra input text for DHCID: " + << dhcid_str); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, "Failed to construct DHCID from '" << + dhcid_str << "': " << ex.what()); + } +} + +/// \brief Constructor with a context of MasterLexer. +/// +/// The \c lexer should point to the beginning of valid textual representation +/// of a DHCID RDATA. +/// +/// \throw BadValue if the text is not valid base-64. +/// \throw MasterLexer::LexerError General parsing error such as missing field. +/// +/// \param lexer A \c MasterLexer object parsing a master file for the +/// RDATA to be created +DHCID::DHCID(MasterLexer& lexer, const Name*, + MasterLoader::Options, MasterLoaderCallbacks&) { + constructFromLexer(lexer); +} + +/// \brief Constructor from wire-format data. +/// +/// \param buffer A buffer storing the wire format data. +/// \param rdata_len The length of the RDATA in bytes +DHCID::DHCID(InputBuffer& buffer, size_t rdata_len) { + if (rdata_len == 0) { + isc_throw(InvalidRdataLength, "Missing DHCID rdata"); + } + + digest_.resize(rdata_len); + buffer.readData(&digest_[0], rdata_len); +} + +/// \brief The copy constructor. +/// +/// This trivial copy constructor never throws an exception. +DHCID::DHCID(const DHCID& other) : Rdata(), digest_(other.digest_) { +} + +/// \brief Render the \c DHCID in the wire format. +/// +/// \param buffer An output buffer to store the wire data. +void +DHCID::toWire(OutputBuffer& buffer) const { + buffer.writeData(&digest_[0], digest_.size()); +} + +/// \brief Render the \c DHCID in the wire format into a +/// \c MessageRenderer object. +/// +/// \param renderer DNS message rendering context that encapsulates the +/// output buffer in which the \c DHCID is to be stored. +void +DHCID::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeData(&digest_[0], digest_.size()); +} + +/// \brief Convert the \c DHCID to a string. +/// +/// This method returns a \c std::string object representing the \c DHCID. +/// +/// \return A string representation of \c DHCID. +string +DHCID::toText() const { + return (encodeBase64(digest_)); +} + +/// \brief Compare two instances of \c DHCID RDATA. +/// +/// See documentation in \c Rdata. +int +DHCID::compare(const Rdata& other) const { + const DHCID& other_dhcid = dynamic_cast<const DHCID&>(other); + + size_t this_len = digest_.size(); + size_t other_len = other_dhcid.digest_.size(); + size_t cmplen = min(this_len, other_len); + int cmp = memcmp(&digest_[0], &other_dhcid.digest_[0], cmplen); + if (cmp != 0) { + return (cmp); + } else { + return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1); + } +} + +/// \brief Accessor method to get the DHCID digest +/// +/// \return A reference to the binary DHCID data +const std::vector<uint8_t>& +DHCID::getDigest() const { + return (digest_); +} + +} // end of namespace "in" +} // end of namespace "rdata" +} // end of namespace "dns" +} // end of namespace "isc" diff --git a/src/lib/dns/rdataclass.h b/src/lib/dns/rdataclass.h new file mode 100644 index 0000000..4af7e75 --- /dev/null +++ b/src/lib/dns/rdataclass.h @@ -0,0 +1,618 @@ +// Copyright (C) 2010-2024 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 DNS_RDATACLASS_H +#define DNS_RDATACLASS_H + +#include <dns/master_loader.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rrttl.h> +#include <dns/rrtype.h> +#include <dns/serial.h> +#include <util/buffer.h> + +#include <boost/shared_ptr.hpp> + +#include <memory> +#include <stdint.h> +#include <string> +#include <vector> + +namespace isc { +namespace dns { + +class Name; +class MasterLexer; +class MasterLoaderCallbacks; +class AbstractMessageRenderer; + +namespace rdata { +namespace any { + +struct TSIGImpl; + +/// \brief \c rdata::TSIG class represents the TSIG RDATA as defined %in +/// RFC2845. +/// +/// This class implements the basic interfaces inherited from the abstract +/// \c rdata::Rdata class, and provides trivial accessors specific to the +/// TSIG RDATA. +class TSIG : public Rdata { +public: + explicit TSIG(const std::string& type_str); + TSIG(isc::util::InputBuffer& buffer, size_t rdata_len); + TSIG(const TSIG& other); + TSIG( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + /// \brief Constructor from RDATA field parameters. + /// + /// The parameters are a straightforward mapping of %TSIG RDATA + /// fields as defined %in RFC2845, but there are some implementation + /// specific notes as follows. + /// + /// \c algorithm is a \c Name object that specifies the algorithm. + /// For example, if the algorithm is HMAC-SHA256, \c algorithm would be + /// \c Name("hmac-sha256"). + /// + /// \c time_signed corresponds to the Time Signed field, which is of + /// 48-bit unsigned integer type, and therefore cannot exceed 2^48-1; + /// otherwise, an exception of type \c OutOfRange will be thrown. + /// + /// \c mac_size and \c mac correspond to the MAC Size and MAC fields, + /// respectively. When the MAC field is empty, \c mac must be null. + /// \c mac_size and \c mac must be consistent %in that \c mac_size is 0 if + /// and only if \c mac is null; otherwise an exception of type + /// InvalidParameter will be thrown. + /// + /// The same restriction applies to \c other_len and \c other_data, + /// which correspond to the Other Len and Other Data fields, respectively. + /// + /// This constructor internally involves resource allocation, and if + /// it fails, a corresponding standard exception will be thrown. + TSIG(const Name& algorithm, uint64_t time_signed, uint16_t fudge, + uint16_t mac_size, const void* mac, uint16_t original_id, + uint16_t error, uint16_t other_len, const void* other_data); + + /// \brief Assignment operator. + /// + /// It internally allocates a resource, and if it fails a corresponding + /// standard exception will be thrown. + /// This operator never throws an exception otherwise. + /// + /// This operator provides the strong exception guarantee: When an + /// exception is thrown the content of the assignment target will be + /// intact. + TSIG& operator=(const TSIG& source); + + /// \brief The destructor. + ~TSIG(); + + /// \brief Return the algorithm name. + /// + /// This method never throws an exception. + const Name& getAlgorithm() const; + + /// \brief Return the value of the Time Signed field. + /// + /// The returned value does not exceed 2^48-1. + /// + /// This method never throws an exception. + uint64_t getTimeSigned() const; + + /// \brief Return the value of the Fudge field. + /// + /// This method never throws an exception. + uint16_t getFudge() const; + + /// \brief Return the value of the MAC Size field. + /// + /// This method never throws an exception. + uint16_t getMACSize() const; + + /// \brief Return the value of the MAC field. + /// + /// If the MAC field is empty, it returns null. + /// Otherwise, the memory region beginning at the address returned by + /// this method is valid up to the bytes specified by the return value + /// of \c getMACSize(). + /// The memory region is only valid while the corresponding \c TSIG + /// object is valid. The caller must hold the \c TSIG object while + /// it needs to refer to the region or it must make a local copy of the + /// region. + /// + /// This method never throws an exception. + const void* getMAC() const; + + /// \brief Return the value of the Original ID field. + /// + /// This method never throws an exception. + uint16_t getOriginalID() const; + + /// \brief Return the value of the Error field. + /// + /// This method never throws an exception. + uint16_t getError() const; + + /// \brief Return the value of the Other Len field. + /// + /// This method never throws an exception. + uint16_t getOtherLen() const; + + /// \brief Return the value of the Other Data field. + /// + /// The same note as \c getMAC() applies. + /// + /// This method never throws an exception. + const void* getOtherData() const; +private: + std::unique_ptr<TSIGImpl> constructFromLexer(MasterLexer& lexer, const Name* origin); + + std::unique_ptr<TSIGImpl> impl_; +}; + +} // end of namespace "any" + +namespace ch { + +class A : public Rdata { +public: + explicit A(const std::string& type_str); + A(isc::util::InputBuffer& buffer, size_t rdata_len); + A(const A& other); + A( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; +}; + +} // end of namespace "ch" + +namespace generic { +namespace detail { + +/// \brief Construct a Name object using a master lexer and optional origin. +/// +/// This is a convenient shortcut of commonly used code pattern that would +/// be used to build RDATA that contain a domain name field. +/// +/// Note that this function throws an exception against invalid input. +/// The (direct or indirect) caller's responsibility needs to expect and +/// handle exceptions appropriately. +/// +/// \throw MasterLexer::LexerError The next token from lexer is not string. +/// \throw Other Exceptions from the \c Name class constructor if the next +/// string token from the lexer does not represent a valid name. +/// +/// \param lexer A \c MasterLexer object. Its next token is expected to be +/// a string that represent a domain name. +/// \param origin If non null, specifies the origin of the name to be +/// constructed. +/// +/// \return A new Name object that corresponds to the next string token of +/// the \c lexer. +inline Name +createNameFromLexer(MasterLexer& lexer, const Name* origin) { + const MasterToken::StringRegion& str_region = + lexer.getNextToken(MasterToken::STRING).getStringRegion(); + return (Name(str_region.beg, str_region.len, origin)); +} + +template<class Type, uint16_t typeCode> +class TXTLikeImpl; + +} // namespace detail + +class NS : public Rdata { +public: + explicit NS(const std::string& type_str); + NS(isc::util::InputBuffer& buffer, size_t rdata_len); + NS(const NS& other); + NS( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + /// Specialized constructor + /// + explicit NS(const Name& nsname) : nsname_(nsname) { + } + /// + /// Specialized methods + /// + const Name& getNSName() const; +private: + Name nsname_; +}; + +struct OPTImpl; + +class OPT : public Rdata { +public: + explicit OPT(const std::string& type_str); + OPT(isc::util::InputBuffer& buffer, size_t rdata_len); + OPT(const OPT& other); + OPT( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + // The default constructor makes sense for OPT as it can be empty. + OPT(); + OPT& operator=(const OPT& source); + ~OPT(); + + /// \brief A class representing a pseudo RR (or option) within an + /// OPT RR (see RFC 6891). + class PseudoRR { + public: + /// \brief Constructor. + /// \param code The OPTION-CODE field of the pseudo RR. + /// \param data The OPTION-DATA field of the pseudo + /// RR. OPTION-LENGTH is set to the length of this vector. + PseudoRR(uint16_t code, + boost::shared_ptr<std::vector<uint8_t> >& data); + + /// \brief Return the option code of this pseudo RR. + uint16_t getCode() const; + + /// \brief Return the option data of this pseudo RR. + const uint8_t* getData() const; + + /// \brief Return the length of the option data of this + /// pseudo RR. + uint16_t getLength() const; + + private: + uint16_t code_; + boost::shared_ptr<std::vector<uint8_t>> data_; + }; + + /// \brief Append a pseudo RR (option) in this OPT RR. + /// + /// \param code The OPTION-CODE field of the pseudo RR. + /// \param data The OPTION-DATA field of the pseudo RR. + /// \param length The size of the \c data argument. OPTION-LENGTH is + /// set to this size. + /// \throw isc::InvalidParameter if this pseudo RR would cause + /// the OPT RDATA to overflow its RDLENGTH. + void appendPseudoRR(uint16_t code, const uint8_t* data, uint16_t length); + + /// \brief Return a vector of the pseudo RRs (options) in this + /// OPT RR. + /// + /// Note: The returned reference is only valid during the lifetime + /// of this \c generic::OPT object. It should not be used + /// afterwards. + const std::vector<PseudoRR>& getPseudoRRs() const; + +private: + std::unique_ptr<OPTImpl> impl_; +}; + +class PTR : public Rdata { +public: + explicit PTR(const std::string& type_str); + PTR(isc::util::InputBuffer& buffer, size_t rdata_len); + PTR(const PTR& other); + PTR( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + /// + /// Specialized constructor + /// + explicit PTR(const Name& ptr_name) : ptr_name_(ptr_name) { + } + /// + /// Specialized methods + /// + const Name& getPTRName() const; +private: + Name ptr_name_; +}; + +struct RRSIGImpl; + +/// \brief \c rdata::RRSIG class represents the RRSIG RDATA as defined %in +/// RFC4034. +/// +/// This class implements the basic interfaces inherited from the abstract +/// \c rdata::Rdata class, and provides trivial accessors specific to the +/// RRSIG RDATA. +class RRSIG : public Rdata { +public: + explicit RRSIG(const std::string& type_str); + RRSIG(isc::util::InputBuffer& buffer, size_t rdata_len); + RRSIG(const RRSIG& other); + RRSIG( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + RRSIG& operator=(const RRSIG& source); + ~RRSIG(); + + // specialized methods + const RRType& typeCovered() const; +private: + // helper function for string and lexer constructors + std::unique_ptr<RRSIGImpl> constructFromLexer(MasterLexer& lexer, const Name* origin); + + std::unique_ptr<RRSIGImpl> impl_; +}; + +class SOA : public Rdata { +public: + explicit SOA(const std::string& type_str); + SOA(isc::util::InputBuffer& buffer, size_t rdata_len); + SOA(const SOA& other); + SOA( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + SOA(const Name& mname, const Name& rname, uint32_t serial, + uint32_t refresh, uint32_t retry, uint32_t expire, + uint32_t minimum); + + /// \brief Returns the serial stored in the SOA. + Serial getSerial() const; + + /// brief Returns the minimum TTL field value of the SOA. + uint32_t getMinimum() const; +private: + /// Note: this is a prototype version; we may reconsider + /// this representation later. + Name mname_; + Name rname_; + /// serial, refresh, retry, expire, minimum, stored in network byte order + uint8_t numdata_[20]; +}; + +struct TKEYImpl; + +/// \brief \c rdata::TKEY class represents the TKEY RDATA as defined %in +/// RFC2930. +/// +/// This class implements the basic interfaces inherited from the abstract +/// \c rdata::Rdata class, and provides trivial accessors specific to the +/// TKEY RDATA. +class TKEY : public Rdata { +public: + explicit TKEY(const std::string& type_str); + TKEY(isc::util::InputBuffer& buffer, size_t rdata_len); + TKEY(const TKEY& other); + TKEY( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + /// \brief Constructor from RDATA field parameters. + /// + /// The parameters are a straightforward mapping of %TKEY RDATA + /// fields as defined %in RFC2930. + /// + /// This RR is pretty close to the TSIG RR with 32 bit timestamps, + /// or the RRSIG RR with a second "other" data field. + /// + /// This constructor internally involves resource allocation, and if + /// it fails, a corresponding standard exception will be thrown. + /// + /// \param algorithm The DNS name of the algorithm e.g. gss-tsig. + /// \param inception The inception time (in seconds since 1970). + /// \param expire The expire time (in seconds since 1970). + /// \param mode The mode e.g. Diffie-Hellman (2) or GSS-API (3). + /// \param error The error code (extended error space shared with TSIG). + /// \param key_len The key length (0 means no key). + /// \param key The key (can be 0). + /// \param other_len The other data length (0 means no other data). + /// \param other_data The other data (can be and usually is 0). + TKEY(const Name& algorithm, uint32_t inception, uint32_t expire, + uint16_t mode, uint16_t error, uint16_t key_len, + const void* key, uint16_t other_len, const void* other_data); + + /// \brief Assignment operator. + /// + /// It internally allocates a resource, and if it fails a corresponding + /// standard exception will be thrown. + /// This operator never throws an exception otherwise. + /// + /// This operator provides the strong exception guarantee: When an + /// exception is thrown the content of the assignment target will be + /// intact. + TKEY& operator=(const TKEY& source); + + /// \brief The destructor. + ~TKEY(); + + /// \brief Return the algorithm name. + /// + /// This method never throws an exception. + const Name& getAlgorithm() const; + + /// \brief Return the value of the Inception field as a number. + /// + /// This method never throws an exception. + uint32_t getInception() const; + + /// \brief Return the value of the Inception field as a string. + std::string getInceptionDate() const; + + /// \brief Return the value of the Expire field as a number. + /// + /// This method never throws an exception. + uint32_t getExpire() const; + + /// \brief Return the value of the Expire field as a string. + std::string getExpireDate() const; + + /// \brief Return the value of the Mode field. + /// + /// This method never throws an exception. + uint16_t getMode() const; + + /// \brief Return the value of the Error field. + /// + /// This method never throws an exception. + uint16_t getError() const; + + /// \brief Return the value of the Key Len field. + /// + /// This method never throws an exception. + uint16_t getKeyLen() const; + + /// \brief Return the value of the Key field. + /// + /// This method never throws an exception. + const void* getKey() const; + + /// \brief Return the value of the Other Len field. + /// + /// This method never throws an exception. + uint16_t getOtherLen() const; + + /// \brief Return the value of the Other Data field. + /// + /// The same note as \c getMAC() applies. + /// + /// This method never throws an exception. + const void* getOtherData() const; + + /// \brief The GSS_API constant for the Mode field. + static const uint16_t GSS_API_MODE; + +private: + std::unique_ptr<TKEYImpl> constructFromLexer(MasterLexer& lexer, const Name* origin); + + std::unique_ptr<TKEYImpl> impl_; +}; + +class TXT : public Rdata { +public: + explicit TXT(const std::string& type_str); + TXT(isc::util::InputBuffer& buffer, size_t rdata_len); + TXT(const TXT& other); + TXT( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + TXT& operator=(const TXT& source); + ~TXT(); + +private: + typedef isc::dns::rdata::generic::detail::TXTLikeImpl<TXT, 16> TXTImpl; + std::unique_ptr<TXTImpl> impl_; +}; +} // namespace generic + + +namespace in { + +class A : public Rdata { +public: + explicit A(const std::string& type_str); + A(isc::util::InputBuffer& buffer, size_t rdata_len); + A(const A& other); + A( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; +private: + uint32_t addr_; // raw IPv4 address (network byte order) +}; + +class AAAA : public Rdata { +public: + explicit AAAA(const std::string& type_str); + AAAA(isc::util::InputBuffer& buffer, size_t rdata_len); + AAAA(const AAAA& other); + AAAA( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; +private: + uint8_t addr_[16]; // raw IPv6 address (network byte order) +}; + +/// \brief \c rdata::DHCID class represents the DHCID RDATA as defined %in +/// RFC4701. +/// +/// This class implements the basic interfaces inherited from the abstract +/// \c rdata::Rdata class, and provides trivial accessors specific to the +/// DHCID RDATA. +class DHCID : public Rdata { +public: + explicit DHCID(const std::string& type_str); + DHCID(isc::util::InputBuffer& buffer, size_t rdata_len); + DHCID(const DHCID& other); + DHCID( + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, MasterLoaderCallbacks& callbacks); + virtual std::string toText() const; + virtual void toWire(isc::util::OutputBuffer& buffer) const; + virtual void toWire(AbstractMessageRenderer& renderer) const; + virtual int compare(const Rdata& other) const; + + /// \brief Return the digest. + /// + /// This method never throws an exception. + const std::vector<uint8_t>& getDigest() const; + +private: + // helper for string and lexer constructors + void constructFromLexer(MasterLexer& lexer); + + /// \brief Private data representation + /// + /// Opaque data at least 3 octets long as per RFC4701. + /// + std::vector<uint8_t> digest_; +}; + +} // end of namespace "in" +} // end of namespace "rdata" +} // end of namespace "dns" +} // end of namespace "isc" +#endif // DNS_RDATACLASS_H diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc new file mode 100644 index 0000000..f4f5c49 --- /dev/null +++ b/src/lib/dns/rrclass.cc @@ -0,0 +1,73 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/rrparamregistry.h> +#include <dns/rrclass.h> +#include <util/buffer.h> + +#include <stdint.h> +#include <string> + +using namespace isc::dns; +using namespace isc::util; + +using namespace std; + +namespace isc { +namespace dns { + +RRClass::RRClass(const std::string& class_str) { + uint16_t classcode; + if (!RRParamRegistry::getRegistry().textToClassCode(class_str, classcode)) { + isc_throw(InvalidRRClass, + "Unrecognized RR class string: " + class_str); + } + classcode_ = classcode; +} + +RRClass::RRClass(InputBuffer& buffer) { + if (buffer.getLength() - buffer.getPosition() < sizeof(uint16_t)) { + isc_throw(IncompleteRRClass, "incomplete wire-format RR class"); + } + classcode_ = buffer.readUint16(); +} + +const string +RRClass::toText() const { + return (RRParamRegistry::getRegistry().codeToClassText(classcode_)); +} + +void +RRClass::toWire(OutputBuffer& buffer) const { + buffer.writeUint16(classcode_); +} + +void +RRClass::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeUint16(classcode_); +} + +RRClass* +RRClass::createFromText(const string& class_str) { + uint16_t class_code; + if (RRParamRegistry::getRegistry().textToClassCode(class_str, + class_code)) { + return (new RRClass(class_code)); + } + return (0); +} + +ostream& +operator<<(ostream& os, const RRClass& rrclass) { + os << rrclass.toText(); + return (os); +} +} +} diff --git a/src/lib/dns/rrclass.h b/src/lib/dns/rrclass.h new file mode 100644 index 0000000..e57d458 --- /dev/null +++ b/src/lib/dns/rrclass.h @@ -0,0 +1,339 @@ +// Copyright (C) 2010-2024 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 RRCLASS_H +#define RRCLASS_H + +#include <util/buffer.h> + +#include <stdint.h> + +#include <string> +#include <ostream> + +#include <dns/exceptions.h> + +#include <boost/optional.hpp> + +// Undefine the macro IN which is defined in some operating systems +// but conflicts the IN RR class. + +#ifdef IN +#undef IN +#endif + +namespace isc { + +namespace dns { + +// forward declarations +class AbstractMessageRenderer; + +/// +/// \brief A standard DNS module exception that is thrown if an RRClass object +/// is being constructed from an unrecognized string. +/// +class InvalidRRClass : public DNSTextError { +public: + InvalidRRClass(const char* file, size_t line, const char* what) : + DNSTextError(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if an RRClass object +/// is being constructed from a incomplete (too short) wire-format data. +/// +class IncompleteRRClass : public isc::dns::Exception { +public: + IncompleteRRClass(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// The \c RRClass class encapsulates DNS resource record classes. +/// +/// This class manages the 16-bit integer class codes in quite a straightforward +/// way. The only non trivial task is to handle textual representations of +/// RR classes, such as "IN", "CH", or "CLASS65534". +/// +/// This class consults a helper \c RRParamRegistry class, which is a registry +/// of RR related parameters and has the singleton object. This registry +/// provides a mapping between RR class codes and their "well-known" textual +/// representations. +/// Parameters of RR classes defined by DNS protocol standards are automatically +/// registered at initialization time and are ensured to be always available for +/// applications unless the application explicitly modifies the registry. +/// +/// For convenience, this class defines constant class objects corresponding to +/// standard RR classes. These are generally referred to as the form of +/// <code>RRClass::{class-text}()</code>. +/// For example, \c RRClass::IN() is an \c RRClass object corresponding to the +/// IN class (class code 1). +/// Note that these constants are used through a "proxy" function. +/// This is because they may be used to initialize another non-local (e.g. +/// global or namespace-scope) static object as follows: +/// +/// \code +/// namespace foo { +/// const RRClass default_class = RRClass::IN(); +/// } \endcode +/// +/// In order to ensure that the constant RRClass object has been initialized +/// before the initialization for \c default_class, we need help from +/// the proxy function. +/// +/// Note to developers: same note as \c RRType applies. +class RRClass { +public: + /// + /// \name Constructors and Destructor + /// + //@{ + /// Constructor from an integer class code. + /// + /// This constructor never throws an exception. + /// + /// \param classcode An 16-bit integer code corresponding to the RRClass. + explicit RRClass(uint16_t classcode) : classcode_(classcode) { + } + /// + /// A valid string is one of "well-known" textual class representations + /// such as "IN" or "CH", or in the standard format for "unknown" + /// classes as defined in RFC3597, i.e., "CLASSnnnn". + /// + /// More precisely, the "well-known" representations are the ones stored + /// in the \c RRParamRegistry registry (see the class description). + /// + /// As for the format of "CLASSnnnn", "nnnn" must represent a valid 16-bit + /// unsigned integer, which may contain leading 0's as long as it consists + /// of at most 5 characters (inclusive). + /// For example, "CLASS1" and "CLASS0001" are valid and represent the same + /// class, but "CLASS65536" and "CLASS000001" are invalid. + /// A "CLASSnnnn" representation is valid even if the corresponding class + /// code is registered in the \c RRParamRegistry object. For example, both + /// "IN" and "CLASS1" are valid and represent the same class. + /// + /// All of these representations are case insensitive; "IN" and "in", and + /// "CLASS1" and "class1" are all valid and represent the same classes, + /// respectively. + /// + /// If the given string is not recognized as a valid representation of + /// an RR class, an exception of class \c InvalidRRClass will be thrown. + /// + /// \param class_str A string representation of the \c RRClass + explicit RRClass(const std::string& class_str); + /// Constructor from wire-format data. + /// + /// The \c buffer parameter normally stores a complete DNS message + /// containing the RRClass to be constructed. The current read position of + /// the buffer points to the head of the class. + /// + /// If the given data does not large enough to contain a 16-bit integer, + /// an exception of class \c IncompleteRRClass will be thrown. + /// + /// \param buffer A buffer storing the wire format data. + explicit RRClass(isc::util::InputBuffer& buffer); + + /// A separate factory of RRClass from text. + /// + /// This static method is similar to the constructor that takes a + /// string object, but works as a factory and reports parsing + /// failure in the form of the return value. Normally the + /// constructor version should suffice, but in some cases the caller + /// may have to expect mixture of valid and invalid input, and may + /// want to minimize the overhead of possible exception handling. + /// This version is provided for such purpose. + /// + /// For the format of the \c class_str argument, see the + /// <code>RRClass(const std::string&)</code> constructor. + /// + /// If the given text represents a valid RRClass, it returns a + /// pointer to a new \c RRClass object. If the given text does not + /// represent a valid RRClass, it returns null. + /// + /// One main purpose of this function is to minimize the overhead + /// when the given text does not represent a valid RR class. For + /// this reason this function intentionally omits the capability of + /// delivering a detailed reason for the parse failure, such as in the + /// \c want() string when exception is thrown from the constructor + /// (it will internally require a creation of string object, which + /// is relatively expensive). If such detailed information is + /// necessary, the constructor version should be used to catch the + /// resulting exception. + /// + /// This function never throws the \c InvalidRRClass exception. + /// + /// \param class_str A string representation of the \c RRClass. + /// \return A new RRClass object for the given text or a null value. + static RRClass* createFromText(const std::string& class_str); + + /// + /// We use the default copy constructor intentionally. + //@} + /// We use the default copy assignment operator intentionally. + /// + + /// + /// \name Converter methods + /// + //@{ + /// \brief Convert the \c RRClass to a string. + /// + /// If a "well known" textual representation for the class code is + /// registered in the RR parameter registry (see the class description), + /// that will be used as the return value of this method. Otherwise, this + /// method creates a new string for an "unknown" class in the format defined + /// in RFC3597, i.e., "CLASSnnnn", and returns it. + /// + /// If resource allocation for the string fails, a corresponding standard + /// exception will be thrown. + /// + /// \return A string representation of the \c RRClass. + const std::string toText() const; + /// \brief Render the \c RRClass in the wire format. + /// + /// This method renders the class code in network byte order via + /// \c renderer, which encapsulates output buffer and other rendering + /// contexts. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer in which the RRClass is to be stored. + void toWire(AbstractMessageRenderer& renderer) const; + /// \brief Render the \c RRClass in the wire format. + /// + /// This method renders the class code in network byte order into the + /// \c buffer. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. + /// + /// \param buffer An output buffer to store the wire data. + void toWire(isc::util::OutputBuffer& buffer) const; + //@} + + /// + /// \name Getter Methods + /// + //@{ + /// \brief Returns the RR class code as a 16-bit unsigned integer. + /// + /// This method never throws an exception. + /// + /// \return An 16-bit integer code corresponding to the RRClass. + uint16_t getCode() const { + return (classcode_); + } + //@} + + /// + /// \name Comparison methods + /// + //@{ + /// \brief Return true iff two RRClasses are equal. + /// + /// Two RRClasses are equal iff their class codes are equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRClass object to compare against. + /// \return true if the two RRClasses are equal; otherwise false. + bool equals(const RRClass& other) const { + return (classcode_ == other.classcode_); + } + + /// \brief Same as \c equals(). + bool operator==(const RRClass& other) const { + return (equals(other)); + } + + /// \brief Return true iff two RRClasses are not equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRClass object to compare against. + /// \return true if the two RRClasses are not equal; otherwise false. + bool nequals(const RRClass& other) const { + return (classcode_ != other.classcode_); + } + /// \brief Same as \c nequals(). + bool operator!=(const RRClass& other) const { + return (nequals(other)); + } + + /// \brief Less-than comparison for RRClass against \c other + /// + /// We define the less-than relationship based on their class codes; + /// one RRClass is less than the other iff the code of the former is less + /// than that of the other as unsigned integers. + /// The relationship is meaningless in terms of DNS protocol; the only + /// reason we define this method is that RRClass objects can be stored in + /// STL containers without requiring user-defined less-than relationship. + /// We therefore don't define other comparison operators. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRClass object to compare against. + /// \return true if \c this RRClass is less than the \c other; otherwise + /// false. + bool operator<(const RRClass& other) const { + return (classcode_ < other.classcode_); + } + + static const RRClass& ANY(); + static const RRClass& IN(); + static const RRClass& CH(); + static const RRClass& NONE(); + +private: + uint16_t classcode_; +}; + +inline const RRClass& +RRClass::ANY() { + static RRClass rrclass(255); + return (rrclass); +} + +inline const RRClass& +RRClass::IN() { + static RRClass rrclass(1); + return (rrclass); +} + +inline const RRClass& +RRClass::CH() { + static RRClass rrclass(3); + return (rrclass); +} + +inline const RRClass& +RRClass::NONE() { + static RRClass rrclass(254); + return (rrclass); +} + +/// +/// \brief Insert the \c RRClass as a string into stream. +/// +/// This method convert the \c rrclass into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global operator<< to behave as described in +/// ostream::operator<< but applied to \c RRClass objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param rrclass The \c RRClass object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& +operator<<(std::ostream& os, const RRClass& rrclass); +} +} +#endif // RRCLASS_H diff --git a/src/lib/dns/rrparamregistry.cc b/src/lib/dns/rrparamregistry.cc new file mode 100644 index 0000000..9352d41 --- /dev/null +++ b/src/lib/dns/rrparamregistry.cc @@ -0,0 +1,644 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <exceptions/isc_assert.h> +#include <dns/rrparamregistry.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> + +#include <cassert> +#include <algorithm> +#include <cctype> +#include <functional> +#include <map> +#include <stdint.h> +#include <string> +#include <sstream> +#include <utility> +#include <boost/shared_ptr.hpp> + +using namespace isc::util; +using namespace isc::dns::rdata; + +using namespace std; + +namespace isc { +namespace dns { + +namespace { +/// +/// The following function and class are a helper to define case-insensitive +/// equivalence relationship on strings. They are used in the mapping +/// containers below. +/// +bool +CICharLess(char c1, char c2) { + return (tolower(static_cast<unsigned char>(c1)) < + tolower(static_cast<unsigned char>(c2))); +} + +struct CIStringLess { + bool operator()(const string& s1, const string& s2) const { + return (lexicographical_compare(s1.begin(), s1.end(), + s2.begin(), s2.end(), CICharLess)); + } +}; + +struct RRTypeParam { + RRTypeParam(const string& code_string, uint16_t code) : + code_string_(code_string), code_(code) {} + string code_string_; + uint16_t code_; + + /// magic constants + static const unsigned int MAX_CODE = 0xffff; + static const string& UNKNOWN_PREFIX(); + static size_t UNKNOWN_PREFIXLEN(); + static const string& UNKNOWN_MAX(); + static size_t UNKNOWN_MAXLEN(); +}; + +typedef boost::shared_ptr<RRTypeParam> RRTypeParamPtr; +typedef map<string, RRTypeParamPtr, CIStringLess> StrRRTypeMap; +typedef map<uint16_t, RRTypeParamPtr> CodeRRTypeMap; + +inline const string& +RRTypeParam::UNKNOWN_PREFIX() { + static const string p("TYPE"); + return (p); +} + +inline size_t +RRTypeParam::UNKNOWN_PREFIXLEN() { + static size_t plen = UNKNOWN_PREFIX().size(); + return (plen); +} + +inline const string& +RRTypeParam::UNKNOWN_MAX() { + static const string p("TYPE65535"); + return (p); +} + +inline size_t +RRTypeParam::UNKNOWN_MAXLEN() { + static size_t plen = UNKNOWN_MAX().size(); + return (plen); +} + +struct RRClassParam { + RRClassParam(const string& code_string, uint16_t code) : + code_string_(code_string), code_(code) {} + string code_string_; + uint16_t code_; + + /// magic constants + static const unsigned int MAX_CODE = 0xffff; + static const string& UNKNOWN_PREFIX(); + static size_t UNKNOWN_PREFIXLEN(); + static const string& UNKNOWN_MAX(); + static size_t UNKNOWN_MAXLEN(); +}; + +typedef boost::shared_ptr<RRClassParam> RRClassParamPtr; +typedef map<string, RRClassParamPtr, CIStringLess> StrRRClassMap; +typedef map<uint16_t, RRClassParamPtr> CodeRRClassMap; + +inline const string& +RRClassParam::UNKNOWN_PREFIX() { + static const string p("CLASS"); + return (p); +} + +inline size_t +RRClassParam::UNKNOWN_PREFIXLEN() { + static size_t plen = UNKNOWN_PREFIX().size(); + return (plen); +} + +inline const string& +RRClassParam::UNKNOWN_MAX() { + static const string p("CLASS65535"); + return (p); +} + +inline size_t +RRClassParam::UNKNOWN_MAXLEN() { + static size_t plen = UNKNOWN_MAX().size(); + return (plen); +} +} // end of anonymous namespace + +/// Note: the element ordering in the type/class pair is intentional. +/// The standard library will perform inequality comparison (i.e, '<') +/// in the way that the second elements (RRClass) are compared only when +/// the first elements are equivalent. +/// In practice, when we compare two pairs of RRType and RRClass, RRClass +/// would be the same (and, in particular, be class IN) in the majority of +/// cases. So this comparison ordering should be more efficient in common +/// cases. +typedef pair<RRType, RRClass> RRTypeClass; +typedef map<RRTypeClass, RdataFactoryPtr> RdataFactoryMap; +typedef map<RRType, RdataFactoryPtr> GenericRdataFactoryMap; + +template <typename T> +class RdataFactory : public AbstractRdataFactory { +public: + virtual RdataPtr create(const string& rdata_str) const { + return (RdataPtr(new T(rdata_str))); + } + + virtual RdataPtr create(InputBuffer& buffer, size_t rdata_len) const { + return (RdataPtr(new T(buffer, rdata_len))); + } + + virtual RdataPtr create(const Rdata& source) const { + return (RdataPtr(new T(dynamic_cast<const T&>(source)))); + } + + virtual RdataPtr create(MasterLexer& lexer, const Name* origin, + MasterLoader::Options options, + MasterLoaderCallbacks& callbacks) const { + return (RdataPtr(new T(lexer, origin, options, callbacks))); + } +}; + +/// +/// \brief The \c RRParamRegistryImpl class is the actual implementation of +/// \c RRParamRegistry. +/// +/// The implementation is hidden from applications. We can refer to specific +/// members of this class only within the implementation source file. +/// +struct RRParamRegistryImpl { + /// Mappings from RR type codes to textual representations. + StrRRTypeMap str2typemap; + /// Mappings from textual representations of RR types to integer codes. + CodeRRTypeMap code2typemap; + /// Mappings from RR class codes to textual representations. + StrRRClassMap str2classmap; + /// Mappings from textual representations of RR classes to integer codes. + CodeRRClassMap code2classmap; + RdataFactoryMap rdata_factories; + GenericRdataFactoryMap genericrdata_factories; +}; + +RRParamRegistry::RRParamRegistry() : impl_(new RRParamRegistryImpl()) { + + // set up parameters for well-known RRs + try { + // ANY class well known RR types. + add("TSIG", 250, "ANY", 255, RdataFactoryPtr(new RdataFactory<any::TSIG>())); + // CH class well known RR types. + add("A", 1, "CH", 3, RdataFactoryPtr(new RdataFactory<ch::A>())); + // IN class well known RR types. + add("A", 1, "IN", 1, RdataFactoryPtr(new RdataFactory<in::A>())); +; + add("NS", 2, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::NS>())); + add("SOA", 6, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::SOA>())); + add("PTR", 12, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::PTR>())); + add("TXT", 16, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TXT>())); + add("AAAA", 28, "IN", 1, RdataFactoryPtr(new RdataFactory<in::AAAA>())); + add("OPT", 41, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::OPT>())); + add("RRSIG", 46, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::RRSIG>())); + add("DHCID", 49, "IN", 1, RdataFactoryPtr(new RdataFactory<in::DHCID>())); + add("TKEY", 249, "IN", 1, RdataFactoryPtr(new RdataFactory<generic::TKEY>())); + // Generic well known RR types. + add("NS", 2, RdataFactoryPtr(new RdataFactory<generic::NS>())); + add("SOA", 6, RdataFactoryPtr(new RdataFactory<generic::SOA>())); + add("PTR", 12, RdataFactoryPtr(new RdataFactory<generic::PTR>())); + add("TXT", 16, RdataFactoryPtr(new RdataFactory<generic::TXT>())); + add("OPT", 41, RdataFactoryPtr(new RdataFactory<generic::OPT>())); + add("RRSIG", 46, RdataFactoryPtr(new RdataFactory<generic::RRSIG>())); + add("TKEY", 249, RdataFactoryPtr(new RdataFactory<generic::TKEY>())); + // Meta and Not supported RR classes. + addClass("HS", 4); + addClass("NONE", 254); + // Meta and non-implemented RR types + addType("MD", 3); + addType("MF", 4); + addType("CNAME", 5); + addType("MB", 7); + addType("MG", 8); + addType("MR", 9); + addType("NULL", 10); + addType("WKS", 11); + addType("HINFO", 13); + addType("MINFO", 14); + addType("MX", 15); + addType("RP", 17); + addType("AFSDB", 18); + addType("X25", 19); + addType("ISDN", 20); + addType("RT", 21); + addType("NSAP", 22); + addType("NSAP-PTR", 23); + addType("SIG", 24); + addType("KEY", 25); + addType("PX", 26); + addType("GPOS", 27); + addType("LOC", 29); + addType("NXT", 30); + addType("EID", 31); + addType("NIMLOC", 32); + addType("SRV", 33); + addType("ATMA", 34); + addType("NAPTR", 35); + addType("KX", 36); + addType("CERT", 37); + addType("A6", 38); + addType("DNAME", 39); + addType("SINK", 40); + addType("APL", 42); + addType("DS", 43); + addType("SSHFP", 44); + addType("IPSECKEY", 45); + addType("NSEC", 47); + addType("DNSKEY", 48); + addType("NSEC3", 50); + addType("NSEC3PARAM", 51); + addType("TLSA", 52); + addType("SMIMEA", 53); + // Unassigned 54 + addType("HIP", 55); + addType("NINFO", 56); + addType("RKEY", 57); + addType("TALINK", 58); + addType("CDS", 59); + addType("CDNSKEY", 60); + addType("OPENPGPKEY", 61); + addType("CSYNC", 62); + addType("ZONEMD", 63); + addType("SVCB", 64); + addType("HTTPS", 65); + // Unassigned 66-98 + addType("SPF", 99); + addType("UINFO", 100); + addType("UID", 101); + addType("GID", 102); + addType("UNSPEC", 103); + addType("NID", 104); + addType("L32", 105); + addType("L64", 106); + addType("LP", 107); + addType("EUI48", 108); + addType("EUI64", 109); + // Unassigned 110-248 + addType("IXFR", 251); + addType("AXFR", 252); + addType("MAILB", 253); + addType("MAILA", 254); + addType("ANY", 255); // also known as "*" + addType("URI", 256); + addType("CAA", 257); + addType("AVC", 258); + addType("DOA", 259); + addType("AMTRELAY", 260); + addType("RESINFO", 261); + // Unassigned 262-32767 + addType("TA", 32768); + addType("DLV", 32769); + } catch (...) { + throw; + } +} + +RRParamRegistry::~RRParamRegistry() { +} + +RRParamRegistry& +RRParamRegistry::getRegistry() { + static RRParamRegistry registry; + + return (registry); +} + +void +RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode, + RdataFactoryPtr rdata_factory) { + bool type_added = false; + try { + type_added = addType(typecode_string, typecode); + impl_->genericrdata_factories.insert(pair<RRType, RdataFactoryPtr>( + RRType(typecode), + rdata_factory)); + } catch (...) { + if (type_added) { + removeType(typecode); + } + throw; + } +} + +void +RRParamRegistry::add(const std::string& typecode_string, uint16_t typecode, + const std::string& classcode_string, uint16_t classcode, + RdataFactoryPtr rdata_factory) { + // Rollback logic on failure is complicated. If adding the new type or + // class fails, we should revert to the original state, cleaning up + // intermediate state. But we need to make sure that we don't remove + // existing data. addType()/addClass() will simply ignore an attempt to + // add the same data, so the cleanup should be performed only when we add + // something new but we fail in other part of the process. + bool type_added = false; + bool class_added = false; + + try { + type_added = addType(typecode_string, typecode); + class_added = addClass(classcode_string, classcode); + impl_->rdata_factories.insert(pair<RRTypeClass, RdataFactoryPtr>( + RRTypeClass(RRType(typecode), + RRClass(classcode)), + rdata_factory)); + } catch (...) { + if (type_added) { + removeType(typecode); + } + if (class_added) { + removeClass(classcode); + } + throw; + } +} + +bool +RRParamRegistry::removeRdataFactory(const RRType& rrtype, + const RRClass& rrclass) { + RdataFactoryMap::iterator found = + impl_->rdata_factories.find(RRTypeClass(rrtype, rrclass)); + if (found != impl_->rdata_factories.end()) { + impl_->rdata_factories.erase(found); + return (true); + } + + return (false); +} + +bool +RRParamRegistry::removeRdataFactory(const RRType& rrtype) { + GenericRdataFactoryMap::iterator found = + impl_->genericrdata_factories.find(rrtype); + if (found != impl_->genericrdata_factories.end()) { + impl_->genericrdata_factories.erase(found); + return (true); + } + + return (false); +} + +namespace { +/// +/// These are helper functions to implement case-insensitive string comparison. +/// This could be simplified using strncasecmp(), but unfortunately it's not +/// included in <cstring>. To be as much as portable within the C++ standard +/// we take the "in house" approach here. +/// +bool CICharEqual(char c1, char c2) { + return (tolower(static_cast<unsigned char>(c1)) == + tolower(static_cast<unsigned char>(c2))); +} + +bool +caseStringEqual(const string& s1, const string& s2, size_t n) { + isc_throw_assert(s1.size() >= n && s2.size() >= n); + + return (mismatch(s1.begin(), s1.begin() + n, s2.begin(), CICharEqual).first + == s1.begin() + n); +} + +/// Code logic for RRTypes and RRClasses is mostly common except (C++) type and +/// member names. So we define type-independent templates to describe the +/// common logic and let concrete classes use it to avoid code duplicates. +/// The following summarize template parameters used in the set of template +/// functions: +/// PT: parameter type, either RRTypeParam or RRClassParam +/// MC: type of mapping class from code: either CodeRRTypeMap or CodeRRClassMap +/// MS: type of mapping class from string: either StrRRTypeMap or StrRRClassMap +/// ET: exception type for error handling: either InvalidRRType or +/// InvalidRRClass +template <typename PT, typename MC, typename MS, typename ET> +inline bool +addParam(const string& code_string, uint16_t code, MC& codemap, MS& stringmap) { + // Duplicate type check + typename MC::const_iterator found = codemap.find(code); + if (found != codemap.end()) { + if (found->second->code_string_ != code_string) { + isc_throw(ET, "Duplicate RR parameter registration"); + } + return (false); + } + + typedef boost::shared_ptr<PT> ParamPtr; + typedef pair<string, ParamPtr> StrParamPair; + typedef pair<uint16_t, ParamPtr> CodeParamPair; + ParamPtr param = ParamPtr(new PT(code_string, code)); + try { + stringmap.insert(StrParamPair(code_string, param)); + codemap.insert(CodeParamPair(code, param)); + } catch (...) { + // Rollback to the previous state: not all of the erase operations will + // find the entry, but we don't care. + stringmap.erase(code_string); + codemap.erase(code); + throw; + } + + return (true); +} + +template <typename MC, typename MS> +inline bool +removeParam(uint16_t code, MC& codemap, MS& stringmap) { + typename MC::iterator found = codemap.find(code); + + if (found != codemap.end()) { + size_t erased = stringmap.erase(found->second->code_string_); + // We must have a corresponding entry of the str2 map exists + isc_throw_assert(erased == 1); + + codemap.erase(found); + + return (true); + } + + return (false); +} + +template <typename PT, typename MS> +inline bool +textToCode(const string& code_str, MS& stringmap, uint16_t& ret_code) { + typename MS::const_iterator found; + + found = stringmap.find(code_str); + if (found != stringmap.end()) { + ret_code = found->second->code_; + return (true); + } + + size_t l = code_str.size(); + if (l > PT::UNKNOWN_PREFIXLEN() && + l <= PT::UNKNOWN_MAXLEN() && + caseStringEqual(code_str, PT::UNKNOWN_PREFIX(), + PT::UNKNOWN_PREFIXLEN())) { + unsigned int code; + istringstream iss(code_str.substr(PT::UNKNOWN_PREFIXLEN(), + l - PT::UNKNOWN_PREFIXLEN())); + iss >> dec >> code; + if (iss.rdstate() == ios::eofbit && code <= PT::MAX_CODE) { + ret_code = code; + return (true); + } + } + + return (false); +} + +template <typename PT, typename MC> +inline string +codeToText(uint16_t code, MC& codemap) { + typename MC::const_iterator found; + + found = codemap.find(code); + if (found != codemap.end()) { + return (found->second->code_string_); + } + + ostringstream ss; + ss << code; + return (PT::UNKNOWN_PREFIX() + ss.str()); +} +} + +bool +RRParamRegistry::addType(const string& type_string, uint16_t code) { + return (addParam<RRTypeParam, CodeRRTypeMap, StrRRTypeMap, RRTypeExists> + (type_string, code, impl_->code2typemap, impl_->str2typemap)); +} + +bool +RRParamRegistry::removeType(uint16_t code) { + return (removeParam<CodeRRTypeMap, StrRRTypeMap>(code, impl_->code2typemap, + impl_->str2typemap)); +} + +bool +RRParamRegistry::textToTypeCode(const string& type_string, + uint16_t& type_code) const { + return (textToCode<RRTypeParam, StrRRTypeMap> + (type_string, impl_->str2typemap, type_code)); +} + +string +RRParamRegistry::codeToTypeText(uint16_t code) const { + return (codeToText<RRTypeParam, CodeRRTypeMap>(code, impl_->code2typemap)); +} + +bool +RRParamRegistry::addClass(const string& class_string, uint16_t code) { + return (addParam<RRClassParam, CodeRRClassMap, StrRRClassMap, RRClassExists> + (class_string, code, impl_->code2classmap, impl_->str2classmap)); +} + +bool +RRParamRegistry::removeClass(uint16_t code) { + return (removeParam<CodeRRClassMap, StrRRClassMap>(code, + impl_->code2classmap, + impl_->str2classmap)); +} + +bool +RRParamRegistry::textToClassCode(const string& class_string, + uint16_t& class_code) const { + return (textToCode<RRClassParam, StrRRClassMap> + (class_string, impl_->str2classmap, class_code)); +} + +string +RRParamRegistry::codeToClassText(uint16_t code) const { + return (codeToText<RRClassParam, CodeRRClassMap>(code, + impl_->code2classmap)); +} + +namespace { +inline const AbstractRdataFactory* +findRdataFactory(RRParamRegistryImpl* reg_impl, + const RRType& rrtype, const RRClass& rrclass) { + RdataFactoryMap::const_iterator found; + found = reg_impl->rdata_factories.find(RRTypeClass(rrtype, rrclass)); + if (found != reg_impl->rdata_factories.end()) { + return (found->second.get()); + } + + GenericRdataFactoryMap::const_iterator genfound = + reg_impl->genericrdata_factories.find(rrtype); + if (genfound != reg_impl->genericrdata_factories.end()) { + return (genfound->second.get()); + } + + return (0); +} +} + +RdataPtr +RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass, + const std::string& rdata_string) { + // If the text indicates that it's rdata of an "unknown" type (beginning + // with '\# n'), parse it that way. (TBD) + + const AbstractRdataFactory* factory = + findRdataFactory(impl_.get(), rrtype, rrclass); + if (factory) { + return (factory->create(rdata_string)); + } + + return (RdataPtr(new generic::Generic(rdata_string))); +} + +RdataPtr +RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass, + InputBuffer& buffer, size_t rdata_len) { + const AbstractRdataFactory* factory = + findRdataFactory(impl_.get(), rrtype, rrclass); + if (factory) { + return (factory->create(buffer, rdata_len)); + } + + return (RdataPtr(new generic::Generic(buffer, rdata_len))); +} + +RdataPtr +RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass, + const Rdata& source) { + const AbstractRdataFactory* factory = + findRdataFactory(impl_.get(), rrtype, rrclass); + if (factory) { + return (factory->create(source)); + } + + return (RdataPtr(new rdata::generic::Generic( + dynamic_cast<const generic::Generic&>(source)))); +} + +RdataPtr +RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass, + MasterLexer& lexer, const Name* name, + MasterLoader::Options options, + MasterLoaderCallbacks& callbacks) { + const AbstractRdataFactory* factory = + findRdataFactory(impl_.get(), rrtype, rrclass); + if (factory) { + return (factory->create(lexer, name, options, callbacks)); + } + + return (RdataPtr(new generic::Generic(lexer, name, options, callbacks))); +} +} +} diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h new file mode 100644 index 0000000..01d9059 --- /dev/null +++ b/src/lib/dns/rrparamregistry.h @@ -0,0 +1,542 @@ +// Copyright (C) 2010-2024 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 RRPARAMREGISTRY_H +#define RRPARAMREGISTRY_H + +#include <string> + +#include <stdint.h> + +#include <boost/shared_ptr.hpp> + +#include <dns/exceptions.h> + +#include <dns/rdata.h> + +namespace isc { +namespace dns { + +// forward declarations +struct RRParamRegistryImpl; + +/// +/// \brief A standard DNS module exception that is thrown if a new RR type is +/// being registered with a different type string. +/// +class RRTypeExists : public isc::dns::Exception { +public: + RRTypeExists(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if a new RR class is +/// being registered with a different type string. +/// +class RRClassExists : public isc::dns::Exception { +public: + RRClassExists(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +namespace rdata { +/// \brief The \c AbstractRdataFactory class is an abstract base class to +/// encapsulate a set of Rdata factory methods in a polymorphic way. +/// +/// An external developer who wants to introduce a new or experimental RR type +/// is expected to define a corresponding derived class of \c +/// AbstractRdataFactory and register it via \c RRParamRegistry. +/// +/// Other users of this API normally do not have to care about this class +/// or its derived classes; this class is generally intended to be used +/// as an internal utility of the API implementation. +class AbstractRdataFactory { + /// + /// \name Constructors and Destructor + /// + //@{ +protected: + /// The default constructor + /// + /// This is intentionally defined as \c protected as this base class should + /// never be instantiated (except as part of a derived class). + AbstractRdataFactory() {} +public: + /// The destructor. + virtual ~AbstractRdataFactory() {}; + //@} + + /// + /// \name Factory methods for polymorphic creation. + /// + //@{ + + /// \brief Create RDATA from a string. + /// + /// This method creates from a string an \c Rdata object of specific class + /// corresponding to the specific derived class of \c AbstractRdataFactory. + /// + /// \param rdata_str A string of textual representation of the \c Rdata. + /// \return An \c RdataPtr object pointing to the created \c Rdata object. + virtual RdataPtr create(const std::string& rdata_str) const = 0; + + /// \brief Create RDATA from wire-format data. + /// + /// This method creates from wire-format binary data an \c Rdata object + /// of specific class corresponding to the specific derived class of + /// \c AbstractRdataFactory. + /// + /// \param buffer A reference to an \c InputBuffer object storing the + /// \c Rdata to parse. + /// \param rdata_len The length in buffer of the \c Rdata. In bytes. + /// \return An \c RdataPtr object pointing to the created \c Rdata object. + virtual RdataPtr create(isc::util::InputBuffer& buffer, + size_t rdata_len) const = 0; + + /// \brief Create RDATA from another \c Rdata object of the same type. + /// + /// This method creates an \c Rdata object of specific class corresponding + /// to the specific derived class of \c AbstractRdataFactory, copying the + /// content of the given \c Rdata, \c source. + /// + /// \c source must be an object of the concrete derived class corresponding + /// to the specific derived class of \c AbstractRdataFactory; + /// otherwise, an exception of class \c std::bad_cast will be thrown. + /// + /// \param source A reference to an \c Rdata object whose content is to + /// be copied to the created \c Rdata object. + /// \return An \c RdataPtr object pointing to the created \c Rdata object. + virtual RdataPtr create(const rdata::Rdata& source) const = 0; + + /// \brief Create RDATA using MasterLexer. + /// + /// This version of the method defines the entry point of factory + /// of a specific RR type and class for \c RRParamRegistry::createRdata() + /// that uses \c MasterLexer. See its description for the expected + /// behavior and meaning of the parameters. + virtual RdataPtr create(MasterLexer& lexer, const Name* origin, + MasterLoader::Options options, + MasterLoaderCallbacks& callbacks) const = 0; + //@} +}; + +/// +/// The \c RdataFactoryPtr type is a pointer-like type, pointing to an +/// object of some concrete derived class of \c AbstractRdataFactory. +/// +typedef boost::shared_ptr<AbstractRdataFactory> RdataFactoryPtr; +} // end of namespace rdata + +/// +/// The \c RRParamRegistry class represents a registry of parameters to +/// manipulate DNS resource records (RRs). +/// +/// A \c RRParamRegistry class object stores a mapping between RR types or +/// classes and their textual representations. It will also have knowledge of +/// how to create an RDATA object for a specific pair of RR type and class +/// (not implemented in this version). +/// +/// Normal applications that only handle standard DNS protocols won't have to +/// care about this class. This is mostly an internal class to the DNS library +/// to manage standard parameters. Some advanced applications may still need +/// to use this class explicitly. For example, if an application wants to +/// define and use an experimental non-standard RR type, it may want to register +/// related protocol parameters for its convenience. This class is designed to +/// allow such usage without modifying the library source code or rebuilding +/// the library. +/// +/// It is assumed that at most one instance of this class can exist so that +/// the application uses the consistent set of registered parameters. To ensure +/// this, this class is designed and implemented as a "singleton class": the +/// constructor is intentionally private, and applications must get access to +/// the single instance via the \c getRegistry() static member function. +/// +/// In the current implementation, access to the singleton \c RRParamRegistry +/// object is not thread safe. +/// The application should ensure that multiple threads don't race in the +/// first invocation of \c getRegistry(), and, if the registry needs to +/// be changed dynamically, read and write operations are performed +/// exclusively. +/// Since this class should be static in common usage this restriction would +/// be acceptable in practice. +/// In the future, we may extend the implementation so that multiple threads can +/// get access to the registry fully concurrently without any restriction. +/// +/// Note: the implementation of this class is incomplete: we should at least +/// add RDATA related parameters. This will be done in a near future version, +/// at which point some of method signatures will be changed. +class RRParamRegistry { + /// + /// \name Constructors and Destructor + /// + /// These are intentionally hidden (see the class description). + //@{ +private: + RRParamRegistry(); + RRParamRegistry(const RRParamRegistry& orig); + ~RRParamRegistry(); + //@} +public: + /// + /// \brief Return the singleton instance of \c RRParamRegistry. + /// + /// This method is a unified access point to the singleton instance of + /// the RR parameter registry (\c RRParamRegistry). + /// On first invocation it internally constructs an instance of the + /// \c RRParamRegistry class and returns a reference to it. + /// This is a static object inside this method and will remain valid + /// throughout the rest of the application lifetime. + /// On subsequent calls this method simply returns a reference to the + /// singleton object. + /// + /// If resource allocation fails in the first invocation, + /// a corresponding standard exception will be thrown. + /// This method never fails otherwise. In particular, this method + /// doesn't throw an exception once the singleton instance is constructed. + /// + /// \return A reference to the singleton instance of \c RRParamRegistry. + static RRParamRegistry& getRegistry(); + + /// + /// \name Registry Update Methods + /// + //@{ + /// + /// \brief Add a set of parameters for a pair of RR type and class. + /// + /// This method adds to the registry a specified set of RR parameters, + /// including mappings between type/class codes and their textual + /// representations. + /// + /// Regarding the mappings between textual representations and integer + /// codes, this method behaves in the same way as \c addType() and + /// \c addClass(). That is, it ignores any overriding attempt as + /// long as the mapping is the same; otherwise the corresponding exception + /// will be thrown. + /// + /// This method provides the strong exception guarantee: unless an + /// exception is thrown the entire specified set of parameters must be + /// stored in the registry; if this method throws an exception the + /// registry will remain in the state before this method is called. + /// + /// \param type_string The textual representation of the RR type. + /// \param type_code The integer code of the RR type. + /// \param class_string The textual representation of the RR class. + /// \param class_code The integer code of the RR class. + /// \param rdata_factory An \c RdataFactoryPtr object pointing to a + /// concrete RDATA factory. + void add(const std::string& type_string, uint16_t type_code, + const std::string& class_string, uint16_t class_code, + rdata::RdataFactoryPtr rdata_factory); + + /// \brief Add a set of parameters for a class-independent RR type. + /// + /// This method behaves as exactly same as the other \c add method except + /// that it handles class-independent types (such as NS, CNAME, or SOA). + /// + /// \param type_string The textual representation of the RR type. + /// \param type_code The integer code of the RR type. + /// \param rdata_factory An \c RdataFactoryPtr object pointing to a + /// concrete RDATA factory. + void add(const std::string& type_string, uint16_t type_code, + rdata::RdataFactoryPtr rdata_factory); + + /// \brief Add mappings between RR type code and textual representation. + /// + /// This method adds a mapping from the type code of an RR to its textual + /// representation and the reverse mapping in the registry. + /// + /// If the given RR type is already registered with the same textual + /// representation, this method simply ignores the duplicate mapping; + /// if the given type is registered and a new pair with a different + /// textual representation is being added,an exception of class + /// \c RRTypeExist will be thrown. + /// To replace an existing mapping with a different textual representation, + /// the existing one must be removed by the \c removeType() method + /// beforehand. + /// + /// In addition, if resource allocation for the new mapping entries fails, + /// a corresponding standard exception will be thrown. + /// + /// This method provides the strong exception guarantee: unless an exception + /// is thrown the specified mappings must be stored in the registry + /// (although it may be an already existing one) on completion of the + /// method; if this method throws an exception the registry will remain + /// in the state before this method is called. + /// + /// \param type_string The textual representation of the RR type. + /// \param type_code The integer code of the RR type. + /// \return \c true if a new mapping is added to the repository; \c false + /// if the same mapping is already registered. + bool addType(const std::string& type_string, uint16_t type_code); + + /// \brief Remove mappings between RR type code and textual representation + /// for a given type. + /// + /// This method can safely be called whether or not the specified mappings + /// exist in the registry. If not, this method simply ignores the attempt + /// and returns \c false. + /// + /// This method never throws an exception. + /// + /// \param type_code The integer code of the RR type. + /// \return \c true if mappings for the specified RR type exists and is + /// removed; \c false if no such mapping is in the registry. + bool removeType(uint16_t type_code); + + /// \brief Add mappings between RR class code and textual representation. + /// + /// This method adds a mapping from the class code of an RR to its textual + /// representation and the reverse mapping in the registry. + /// + /// If the given RR class is already registered with the same textual + /// representation, this method simply ignores the duplicate mapping; + /// if the given class is registered and a new pair with a different + /// textual representation is being added,an exception of class + /// \c RRClassExist will be thrown. + /// To replace an existing mapping with a different textual representation, + /// the existing one must be removed by the \c removeClass() method + /// beforehand. + /// + /// In addition, if resource allocation for the new mapping entries fails, + /// a corresponding standard exception will be thrown. + /// + /// This method provides the strong exception guarantee: unless an exception + /// is thrown the specified mappings must be stored in the registry + /// (although it may be an already existing one) on completion of the + /// method; if this method throws an exception the registry will remain + /// in the state before this method is called. + /// + /// \param class_string The textual representation of the RR class. + /// \param class_code The integer code of the RR class. + /// \return \c true if a new mapping is added to the repository; \c false + /// if the same mapping is already registered. + bool addClass(const std::string& class_string, uint16_t class_code); + + /// \brief Remove mappings between RR class code and textual representation + /// for a given class. + /// + /// This method can safely be called whether or not the specified mappings + /// exist in the registry. If not, this method simply ignores the attempt + /// and returns \c false. + /// + /// This method never throws an exception. + /// + /// \param class_code The integer code of the RR class. + /// \return \c true if mappings for the specified RR type exists and is + /// removed; \c false if no such mapping is in the registry. + bool removeClass(uint16_t class_code); + + /// \brief Remove registered RDATA factory for the given pair of \c RRType + /// and \c RRClass. + /// + /// This method can safely be called whether or not the specified factory + /// object exist in the registry. If not, this method simply ignores the + /// attempt and returns \c false. + /// + /// This method never throws an exception. + /// + /// \param rrtype An \c RRType object specifying the type/class pair. + /// \param rrclass An \c RRClass object specifying the type/class pair. + /// \return \c true if a factory object for the specified RR type/class + /// pair exists and is removed; \c false if no such object is in the + /// registry. + bool removeRdataFactory(const RRType& rrtype, const RRClass& rrclass); + + /// \brief Remove registered RDATA factory for the given pair of \c RRType + /// and \c RRClass. + /// + /// This method can safely be called whether or not the specified factory + /// object exist in the registry. If not, this method simply ignores the + /// attempt and returns \c false. + /// + /// This method never throws an exception. + /// + /// \param rrtype An \c RRType object specifying the type/class pair. + /// \return \c true if a factory object for the specified RR type/class + /// pair exists and is removed; \c false if no such object is in the + /// registry. + bool removeRdataFactory(const RRType& rrtype); + //@} + + /// + /// \name Parameter Conversion Methods + /// + //@{ + /// \brief Convert a textual representation of an RR type to the + /// corresponding 16-bit integer code. + /// + /// This method searches the \c RRParamRegistry for the mapping from + /// the given textual representation of RR type to the corresponding + /// integer code. If a mapping is found, it returns true with the + /// associated type code in \c type_code; otherwise, if the given + /// string is in the form of "TYPEnnnn", it returns true with the + /// corresponding number as the type code in \c type_code; + /// otherwise, it returns false and \c type_code is untouched. + /// + /// It returns \c false and avoids throwing an exception in the case + /// of an error to avoid the exception overhead in some situations. + /// + /// \param type_string The textual representation of the RR type. + /// \param type_code Returns the RR type code in this argument. + /// \return true if conversion is successful, false otherwise. + bool textToTypeCode(const std::string& type_string, + uint16_t& type_code) const; + + /// \brief Convert type code into its textual representation. + /// + /// This method searches the \c RRParamRegistry for the mapping from the + /// given RR type code to its textual representation. + /// If a mapping is found, it returns (a copy of) the associated string; + /// otherwise, this method creates a new string in the form of "TYPEnnnn" + /// where "nnnn" is the decimal representation of the type code, and + /// returns the new string. + /// + /// If resource allocation for the returned string fails, + /// a corresponding standard exception will be thrown. + /// This method never fails otherwise. + /// + /// \param type_code The integer code of the RR type. + /// \return A textual representation of the RR type for code \c type_code. + std::string codeToTypeText(uint16_t type_code) const; + + /// \brief Convert a textual representation of an RR class to the + /// corresponding 16-bit integer code. + /// + /// This method searches the \c RRParamRegistry for the mapping from + /// the given textual representation of RR class to the + /// corresponding integer code. If a mapping is found, it returns + /// true with the associated class code in \c class_code; otherwise, + /// if the given string is in the form of "CLASSnnnn", it returns + /// true with the corresponding number as the class code in + /// \c class_code; otherwise, it returns false and \c class_code is + /// untouched. + /// + /// It returns \c false and avoids throwing an exception in the case + /// of an error to avoid the exception overhead in some situations. + /// + /// \param class_string The textual representation of the RR class. + /// \param class_code Returns the RR class code in this argument. + /// \return true if conversion is successful, false otherwise. + bool textToClassCode(const std::string& class_string, + uint16_t& class_code) const; + + /// \brief Convert class code into its textual representation. + /// + /// This method searches the \c RRParamRegistry for the mapping from the + /// given RR class code to its textual representation. + /// If a mapping is found, it returns (a copy of) the associated string; + /// otherwise, this method creates a new string in the form of "CLASSnnnn" + /// where "nnnn" is the decimal representation of the class code, and + /// returns the new string. + /// + /// If resource allocation for the returned string fails, + /// a corresponding standard exception will be thrown. + /// This method never fails otherwise. + /// + /// \param class_code The integer code of the RR class. + /// \return A textual representation of the RR class for code \c class_code. + std::string codeToClassText(uint16_t class_code) const; + //@} + + /// + /// \name RDATA Factories + /// + /// This set of methods provide a unified interface to create an + /// \c rdata::Rdata object in a parameterized polymorphic way, + /// that is, these methods take a pair of \c RRType and \c RRClass + /// objects and data specific to that pair, and create an object of + /// the corresponding concrete derived class of \c rdata::Rdata. + /// + /// These methods first search the \c RRParamRegistry for a factory + /// method (a member of a concrete derived class of + /// \c AbstractRdataFactory) for the given RR type and class pair. + /// If the search fails, they then search for a factory method for + /// the given type ignoring the class, in case a RRClass independent + /// factory method is registered. + /// If it still fails, these methods assume the RDATA is of an "unknown" + /// type, and creates a new object by calling a constructor of the + /// \c rdata::generic::Generic class. + /// + //@{ + /// \brief Create RDATA of a given pair of RR type and class from a string. + /// + /// This method creates from a string an \c Rdata object of the given pair + /// of RR type and class. + /// + /// \param rrtype An \c RRType object specifying the type/class pair. + /// \param rrclass An \c RRClass object specifying the type/class pair. + /// \param rdata_string A string of textual representation of the \c Rdata. + /// \return An \c rdata::RdataPtr object pointing to the created \c Rdata + /// object. + rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass, + const std::string& rdata_string); + /// \brief Create RDATA of a given pair of RR type and class from + /// wire-format data. + /// + /// This method creates from wire-format binary data an \c Rdata object + /// of the given pair of RR type and class. + /// + /// \param rrtype An \c RRType object specifying the type/class pair. + /// \param rrclass An \c RRClass object specifying the type/class pair. + /// \param buffer A reference to an \c InputBuffer object storing the + /// \c Rdata to parse. + /// \param len The length in buffer of the \c Rdata. In bytes. + /// \return An \c rdata::RdataPtr object pointing to the created \c Rdata + /// object. + rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass, + isc::util::InputBuffer& buffer, size_t len); + /// \brief Create RDATA of a given pair of RR type and class, copying + /// of another RDATA of same kind. + /// + /// This method creates an \c Rdata object of the given pair of + /// RR type and class, copying the content of the given \c Rdata, + /// \c source. + /// + /// \c source must be an object of the concrete derived class of + /// \c rdata::Rdata for the given pair of RR type and class; + /// otherwise, an exception of class \c std::bad_cast will be thrown. + /// In case the \c RRParamRegistry doesn't have a factory method for + /// the given pair and it is assumed to be of an "unknown" type, + /// \c source must reference an object of class + /// \c rdata::generic::Generic; otherwise, an exception of class + /// \c std::bad_cast will be thrown. + /// + /// \param rrtype An \c RRType object specifying the type/class pair. + /// \param rrclass An \c RRClass object specifying the type/class pair. + /// \param source A reference to an \c rdata::Rdata object whose content + /// is to be copied to the created \c rdata::Rdata object. + /// \return An \c rdata::RdataPtr object pointing to the created + /// \c rdata::Rdata object. + rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass, + const rdata::Rdata& source); + + /// \brief Create RDATA using MasterLexer + /// + /// This method is expected to be used as the underlying implementation + /// of the same signature of \c rdata::createRdata(). One main + /// difference is that this method is only responsible for constructing + /// the Rdata; it doesn't update the lexer to reach the end of line or + /// file or doesn't care about whether there's an extra (garbage) token + /// after the textual RDATA representation. Another difference is that + /// this method can throw on error and never returns a null pointer. + /// + /// For other details and parameters, see the description of + /// \c rdata::createRdata(). + rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass, + MasterLexer& lexer, const Name* origin, + MasterLoader::Options options, + MasterLoaderCallbacks& callbacks); + //@} + +private: + boost::shared_ptr<RRParamRegistryImpl> impl_; +}; + +} +} +#endif // RRPARAMREGISTRY_H diff --git a/src/lib/dns/rrset.cc b/src/lib/dns/rrset.cc new file mode 100644 index 0000000..1360c12 --- /dev/null +++ b/src/lib/dns/rrset.cc @@ -0,0 +1,465 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/isc_assert.h> +#include <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/rrttl.h> +#include <dns/rrset.h> +#include <util/buffer.h> + +#include <algorithm> +#include <string> +#include <vector> +#include <boost/shared_ptr.hpp> + +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; + +using namespace std; + +namespace isc { +namespace dns { +void +AbstractRRset::addRdata(const Rdata& rdata) { + addRdata(createRdata(getType(), getClass(), rdata)); +} + +string +AbstractRRset::toText() const { + string s; + RdataIteratorPtr it = getRdataIterator(); + + // In the case of an empty rrset, just print name, ttl, class, and + // type + if (it->isLast()) { + // But only for class ANY or NONE + if (getClass() != RRClass::ANY() && + getClass() != RRClass::NONE()) { + isc_throw(EmptyRRset, "toText() is attempted for an empty RRset"); + } + + s += getName().toText() + " " + getTTL().toText() + " " + + getClass().toText() + " " + getType().toText() + "\n"; + return (s); + } + + do { + s += getName().toText() + " " + getTTL().toText() + " " + + getClass().toText() + " " + getType().toText() + " " + + it->getCurrent().toText() + "\n"; + it->next(); + } while (!it->isLast()); + + if (getRRsig()) { + s += getRRsig()->toText(); + } + + return (s); +} + +namespace { // unnamed namespace + +// FIXME: This method's code should somehow be unified with +// BasicRRsetImpl::toWire() below to avoid duplication. +template <typename T> +inline uint32_t +rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) { + uint32_t n = 0; + RdataIteratorPtr it = rrset.getRdataIterator(); + + if (it->isLast()) { + // empty rrsets are only allowed for classes ANY and NONE + if (rrset.getClass() != RRClass::ANY() && + rrset.getClass() != RRClass::NONE()) { + isc_throw(EmptyRRset, "toWire() is attempted for an empty RRset"); + } + + // For an empty RRset, write the name, type, class and TTL once, + // followed by empty rdata. + rrset.getName().toWire(output); + rrset.getType().toWire(output); + rrset.getClass().toWire(output); + rrset.getTTL().toWire(output); + output.writeUint16(0); + // Still counts as 1 'rr'; it does show up in the message + return (1); + } + + // sort the set of Rdata based on rrset-order and sortlist, and possible + // other options. Details to be considered. + do { + const size_t pos0 = output.getLength(); + isc_throw_assert(pos0 < 65536); + + rrset.getName().toWire(output); + rrset.getType().toWire(output); + rrset.getClass().toWire(output); + rrset.getTTL().toWire(output); + + const size_t pos = output.getLength(); + output.skip(sizeof(uint16_t)); // leave the space for RDLENGTH + it->getCurrent().toWire(output); + output.writeUint16At(output.getLength() - pos - sizeof(uint16_t), pos); + + if (limit > 0 && output.getLength() > limit) { + // truncation is needed + output.trim(output.getLength() - pos0); + return (n); + } + + it->next(); + ++n; + } while (!it->isLast()); + + return (n); +} + +} // end of unnamed namespace + +uint32_t +AbstractRRset::toWire(OutputBuffer& buffer) const { + return (rrsetToWire<OutputBuffer>(*this, buffer, 0)); +} + +uint32_t +AbstractRRset::toWire(AbstractMessageRenderer& renderer) const { + const uint32_t rrs_written = rrsetToWire<AbstractMessageRenderer>( + *this, renderer, renderer.getLengthLimit()); + if (getRdataCount() > rrs_written) { + renderer.setTruncated(); + } + return (rrs_written); +} + +bool +AbstractRRset::isSameKind(const AbstractRRset& other) const { + // Compare classes last as they're likely to be identical. Compare + // names late in the list too, as these are expensive. So we compare + // types first, names second and classes last. + return (getType() == other.getType() && + getName() == other.getName() && + getClass() == other.getClass()); +} + +ostream& +operator<<(ostream& os, const AbstractRRset& rrset) { + os << rrset.toText(); + return (os); +} + +/// \brief This encapsulates the actual implementation of the \c BasicRRset +/// class. It's hidden from applications. +class BasicRRsetImpl { +public: + BasicRRsetImpl(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& ttl) : + name_(name), rrclass_(rrclass), rrtype_(rrtype), ttl_(ttl) {} + + uint32_t toWire(AbstractMessageRenderer& renderer, size_t limit) const; + + Name name_; + RRClass rrclass_; + RRType rrtype_; + RRTTL ttl_; + // XXX: "list" is not a good name: It in fact isn't a list; more conceptual + // name than a data structure name is generally better. But since this + // is only used in the internal implementation we'll live with it. + vector<ConstRdataPtr> rdatalist_; +}; + +// FIXME: This method's code should somehow be unified with +// rrsetToWire() above to avoid duplication. +uint32_t +BasicRRsetImpl::toWire(AbstractMessageRenderer& renderer, size_t limit) const { + if (rdatalist_.empty()) { + // empty rrsets are only allowed for classes ANY and NONE + if (rrclass_ != RRClass::ANY() && + rrclass_ != RRClass::NONE()) { + isc_throw(EmptyRRset, "toWire() is attempted for an empty RRset"); + } + + // For an empty RRset, write the name, type, class and TTL once, + // followed by empty rdata. + name_.toWire(renderer); + rrtype_.toWire(renderer); + rrclass_.toWire(renderer); + ttl_.toWire(renderer); + renderer.writeUint16(0); + // Still counts as 1 'rr'; it does show up in the message + return (1); + } + + uint32_t n = 0; + + // sort the set of Rdata based on rrset-order and sortlist, and possible + // other options. Details to be considered. + for (auto const& rdata : rdatalist_) { + const size_t pos0 = renderer.getLength(); + isc_throw_assert(pos0 < 65536); + + name_.toWire(renderer); + rrtype_.toWire(renderer); + rrclass_.toWire(renderer); + ttl_.toWire(renderer); + + const size_t pos = renderer.getLength(); + renderer.skip(sizeof(uint16_t)); // leave the space for RDLENGTH + rdata->toWire(renderer); + renderer.writeUint16At(renderer.getLength() - pos - sizeof(uint16_t), + pos); + + if (limit > 0 && renderer.getLength() > limit) { + // truncation is needed + renderer.trim(renderer.getLength() - pos0); + return (n); + } + ++n; + } + + return (n); +} + +BasicRRset::BasicRRset(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& ttl) { + impl_.reset(new BasicRRsetImpl(name, rrclass, rrtype, ttl)); +} + +BasicRRset::~BasicRRset() { +} + +void +BasicRRset::addRdata(ConstRdataPtr rdata) { + impl_->rdatalist_.push_back(rdata); +} + +void +BasicRRset::addRdata(const Rdata& rdata) { + AbstractRRset::addRdata(rdata); +} + +void +BasicRRset::addRdata(const std::string& rdata_str) { + addRdata(createRdata(getType(), getClass(), rdata_str)); +} + +uint32_t +BasicRRset::getRdataCount() const { + return (impl_->rdatalist_.size()); +} + +const Name& +BasicRRset::getName() const { + return (impl_->name_); +} + +const RRClass& +BasicRRset::getClass() const { + return (impl_->rrclass_); +} + +const RRType& +BasicRRset::getType() const { + return (impl_->rrtype_); +} + +const RRTTL& +BasicRRset::getTTL() const { + return (impl_->ttl_); +} + +void +BasicRRset::setTTL(const RRTTL& ttl) { + impl_->ttl_ = ttl; +} + +string +BasicRRset::toText() const { + return (AbstractRRset::toText()); +} + +uint16_t +BasicRRset::getLength() const { + uint16_t length = 0; + RdataIteratorPtr it = getRdataIterator(); + + if (it->isLast()) { + // empty rrsets are only allowed for classes ANY and NONE + if (getClass() != RRClass::ANY() && + getClass() != RRClass::NONE()) { + isc_throw(EmptyRRset, "getLength() is attempted for an empty RRset"); + } + + // For an empty RRset, write the name, type, class and TTL once, + // followed by empty rdata. + length += getName().getLength(); + length += 2; // TYPE field + length += 2; // CLASS field + length += 4; // TTL field + length += 2; // RDLENGTH field (=0 in wire format) + + return (length); + } + + do { + // This is a size_t as some of the following additions may + // overflow due to a programming mistake somewhere. + size_t rrlen = 0; + + rrlen += getName().getLength(); + rrlen += 2; // TYPE field + rrlen += 2; // CLASS field + rrlen += 4; // TTL field + rrlen += 2; // RDLENGTH field + rrlen += it->getCurrent().getLength(); + + isc_throw_assert(length + rrlen < 65536); + length += rrlen; + + it->next(); + } while (!it->isLast()); + + return (length); +} + +uint32_t +BasicRRset::toWire(OutputBuffer& buffer) const { + return (AbstractRRset::toWire(buffer)); +} + +uint32_t +BasicRRset::toWire(AbstractMessageRenderer& renderer) const { + const uint32_t rrs_written = impl_->toWire(renderer, + renderer.getLengthLimit()); + if (impl_->rdatalist_.size() > rrs_written) { + renderer.setTruncated(); + } + return (rrs_written); +} + +RRset::RRset(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& ttl) : + BasicRRset(name, rrclass, rrtype, ttl) { +} + +RRset::~RRset() { +} + +uint32_t +RRset::getRRsigDataCount() const { + if (rrsig_) { + return (rrsig_->getRdataCount()); + } else { + return (0); + } +} + +uint16_t +RRset::getLength() const { + uint16_t length = BasicRRset::getLength(); + + if (rrsig_) { + const uint16_t rrsigs_length = rrsig_->getLength(); + // the uint16_ts are promoted to ints during addition below, so + // it won't overflow a 16-bit register. + isc_throw_assert(length + rrsigs_length < 65536); + length += rrsigs_length; + } + + return (length); +} + +uint32_t +RRset::toWire(OutputBuffer& buffer) const { + uint32_t rrs_written = BasicRRset::toWire(buffer); + if (getRdataCount() > rrs_written) { + return (rrs_written); + } + + if (rrsig_) { + rrs_written += rrsig_->toWire(buffer); + } + + return (rrs_written); +} + +uint32_t +RRset::toWire(AbstractMessageRenderer& renderer) const { + uint32_t rrs_written = BasicRRset::toWire(renderer); + if (getRdataCount() > rrs_written) { + return (rrs_written); + } + + if (rrsig_) { + rrs_written += rrsig_->toWire(renderer); + + if (getRdataCount() + getRRsigDataCount() > rrs_written) { + renderer.setTruncated(); + } + } + + return (rrs_written); +} + +namespace { + +class BasicRdataIterator : public RdataIterator { +public: + /// @brief Constructor. + BasicRdataIterator(const std::vector<rdata::ConstRdataPtr>& datavector) : + datavector_(&datavector), it_(datavector_->begin()) { + } + + /// @brief Destructor. + ~BasicRdataIterator() { + } + + /// @brief Set iterator at first position. + virtual void first() { + it_ = datavector_->begin(); + } + + /// @brief Advance iterator. + virtual void next() { + ++it_; + } + + /// @brief Get value at current iterator position. + /// + /// @return The value at current iterator position. + virtual const rdata::Rdata& getCurrent() const { + return (**it_); + } + + /// @brief Check if iterator has reached the end. + /// + /// @return true if iterator has reached the end, false otherwise. + virtual bool isLast() const { + return (it_ == datavector_->end()); + } + +private: + /// @brief Vector containing data. + const std::vector<rdata::ConstRdataPtr>* datavector_; + + /// @brief Iterator used to retrieve data. + std::vector<rdata::ConstRdataPtr>::const_iterator it_; +}; + +} + +RdataIteratorPtr +BasicRRset::getRdataIterator() const { + return (RdataIteratorPtr(new BasicRdataIterator(impl_->rdatalist_))); +} + +} +} diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h new file mode 100644 index 0000000..282ff8b --- /dev/null +++ b/src/lib/dns/rrset.h @@ -0,0 +1,952 @@ +// Copyright (C) 2010-2024 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 RRSET_H +#define RRSET_H + +#include <iostream> +#include <string> + +#include <boost/shared_ptr.hpp> + +#include <dns/exceptions.h> + +#include <dns/rdata.h> +#include <dns/rrtype.h> +#include <util/buffer.h> + +namespace isc { +namespace dns { + +/// +/// \brief A standard DNS module exception that is thrown if an RRset object +/// does not contain any RDATA where required. +/// +class EmptyRRset : public isc::dns::Exception { +public: + EmptyRRset(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +// forward declarations +class Name; +class RRType; +class RRClass; +class RRTTL; +class AbstractMessageRenderer; +class AbstractRRset; +class BasicRRset; +class RdataIterator; +class BasicRRsetImpl; +class RRset; + +/// \brief A pointer-like type pointing to an \c RRset object. +/// +/// This type is commonly used as an argument of various functions defined +/// in this library in order to handle RRsets in a polymorphic manner. +typedef boost::shared_ptr<AbstractRRset> RRsetPtr; + +/// \brief A pointer-like type pointing to an (immutable) \c RRset +/// object. +/// +/// This type is commonly used as an argument of various functions defined +/// in this library in order to handle RRsets in a polymorphic manner. +typedef boost::shared_ptr<const AbstractRRset> ConstRRsetPtr; + +/// \brief A pointer-like type point to an \c RdataIterator object. +typedef boost::shared_ptr<RdataIterator> RdataIteratorPtr; + +/// \brief The \c AbstractRRset class is an abstract base class that +/// models a DNS RRset. +/// +/// An object of (a specific derived class of) \c AbstractRRset +/// models an RRset as described in the DNS standard: +/// A set of DNS resource records (RRs) of the same type and class. +/// The standard requires the TTL of all RRs in an RRset be the same; +/// this class follows that requirement. + +/// Note about duplicate RDATA: RFC2181 states that it's meaningless that an +/// RRset contains two identical RRs and that name servers should suppress +/// such duplicates. +/// This class is not responsible for ensuring this requirement: For example, +/// \c addRdata() method doesn't check if there's already RDATA identical +/// to the one being added. +/// This is because such checks can be expensive, and it's often easy to +/// ensure the uniqueness requirement at the %data preparation phase +/// (e.g. when loading a zone). +/// When parsing an incoming DNS message, the uniqueness may not be guaranteed, +/// so the application needs to detect and ignore any duplicate RDATA +/// (the \c Message class of this library should provide this responsibility). +/// +/// Another point to note is that \c AbstractRRset and its derived classes +/// allow an object to have an empty set of RDATA. +/// Even though there's no corresponding notion in the protocol specification, +/// it would be more intuitive for a container-like %data structure +/// to allow an empty set. +/// +/// Since \c AbstractRRset is an abstract class, it is generally used +/// via a pointer (or pointer like object) or a reference. +/// In particular, \c RRsetPtr, a pointer like type for \c AbstractRRset, +/// is used for polymorphic RRset operations throughout this library. +/// +/// The \c AbstractRRset class is also intended to be a major customization +/// point. For example, a high performance server implementation may want +/// to define an optimized "pre-compiled" RRset and provide an optimized +/// implementation of the \c toWire() method. +/// +/// Note about design choice: In BIND9, a set of RDATA with a common tuple +/// of RR class, RR type, and TTL was represented in a structure named +/// \c rdataset. Unlike the RRset classes, an \c rdataset did not contain +/// the information of the owner name. +/// This might be advantageous if we want to handle "RRsets", that is, +/// a set of different types of RRset for the same owner name, because +/// a single "name" structure can be used for multiple RRsets, minimizing +/// %data copy and memory footprint. +/// On the other hand, it's inconvenient for API users since in many cases +/// a pair of name and an \c rdataset must be maintained. It's also counter +/// intuitive in implementing protocol operations as an RRset is often used +/// as an atomic entity in DNS protocols while an \c rdataset is a component +/// of an RRset. +/// +/// We have therefore defined the notion of RRset explicitly in our initial +/// API design. We believe memory footprint is not a big concern because +/// RRsets are generally expected to be used as temporary objects, e.g. +/// while parsing or constructing a DNS message, or searching a DNS %data +/// source; for longer term purposes such as in-memory %data source entries, +/// the corresponding %data would be represented in a different, memory +/// optimized format. As for the concern about %data copy, we believe +/// it can be mitigated by using copy-efficient implementation for the +/// \c Name class implementation, such as reference counted objects. +/// Later, We plan to perform benchmark tests later to see if this assumption +/// is valid and to revisit the design if necessary. +/// +/// Note about terminology: there has been a discussion at the IETF +/// namedroppers ML about RRset vs RRSet (case of "s") +/// [http://ops.ietf.org/lists/namedroppers/namedroppers.2009/msg02737.html]. +/// While RFC2181 uses the latter, many other RFCs use the former, +/// and most of the list members who showed their opinion seem to prefer +/// "RRset". We follow that preference in this implementation. +/// +/// The current design of \c AbstractRRset is still in flux. +/// There are many open questions in design details: +/// - support more set-like operations, e.g, merge two RRsets of the same +/// type? +/// - more convenient methods or non member utility functions, e.g. +/// "sort" and "search(find)" method? +/// - what about comparing two RRsets of the same type? If we need this, +/// should it compare rdata's as a set or as a list (i.e. compare +/// each rdata one by one or as a whole)? c.f. NLnet Labs' ldns +/// (http://www.nlnetlabs.nl/projects/ldns/doc/index.html) +/// has \c ldns_rr_list_compare(), which takes the latter approach +/// (seemingly assuming the caller sorts the lists beforehand). +/// - BIND9 libdns has some special DNSSEC-related methods +/// such as \c addnoqname() or \c addclosest(). Do we need these? +/// (Probably not. We wouldn't want to make the class design too +/// monolithic.) +/// - Do we need to allow the user to remove specific Rdata? +/// Probably not, according to the current usage of the BIND9 code. +class AbstractRRset { + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are intentionally + /// defined as private to make it explicit that this is a pure base class. + //@{ +private: + AbstractRRset(const AbstractRRset& source); + AbstractRRset& operator=(const AbstractRRset& source); +protected: + /// \brief The default constructor. + /// + /// This is intentionally defined as \c protected as this base class should + /// never be instantiated (except as part of a derived class). + AbstractRRset() {} +public: + /// The destructor. + virtual ~AbstractRRset() {} + //@} + + /// + /// \name Getter and Setter Methods + /// + /// These methods are generally expected to be exception free, but it's + /// not guaranteed at the interface level; + /// for example, some performance optimized derived class may manage + /// the information corresponding to the class "attributes" to get or set, + /// and may require dynamic memory allocation to execute the method. + /// Consult the derived class description to see if a specific derived + /// \c RRset class may throw an exception from these methods. + /// + /// Note that setter methods are not provided for \c RRClass and + /// \c RRType. This is intentional. Since the format and semantics of + /// \c Rdata are dependent on the RR type (and RR class for some RR types), + /// allowing dynamically modify these attributes can easily lead to a + /// bug where the RDATA and type and/or class become inconsistent. + /// We want to avoid that situation by restricting the access. + //@{ + /// \brief Returns the number of \c Rdata objects contained in the \c RRset. + /// + /// Note that an \c RRset with an empty set of \c Rdata can exist, so + /// this method may return 0. + /// + /// \return The number of \c Rdata objects contained. + virtual uint32_t getRdataCount() const = 0; + + /// \brief Get the wire format length of the \c AbstractRRset. + /// + /// This method returns the wire format length of the + /// \c AbstractRRset, which is calculated by summing the individual + /// lengths of the various fields that make up each RR. + /// + /// NOTE: When including name lengths, the allocation for + /// uncompressed name wire format representation is used. + /// + /// \return The length of the wire format representation of the + /// \c AbstractRRset. + /// \throw EmptyRRset if the \c AbstractRRset is empty. + virtual uint16_t getLength() const = 0; + + /// \brief Returns the owner name of the \c RRset. + /// + /// \return A reference to a \c Name class object corresponding to the + /// \c RRset owner name. + virtual const Name& getName() const = 0; + + /// \brief Returns the RR Class of the \c RRset. + /// + /// \return A reference to a \c RRClass class object corresponding to the + /// RR class of the \c RRset. + virtual const RRClass& getClass() const = 0; + + /// \brief Returns the RR Type of the \c RRset. + /// + /// \return A reference to a \c RRType class object corresponding to the + /// RR type of the \c RRset. + virtual const RRType& getType() const = 0; + + /// \brief Returns the TTL of the RRset. + /// + /// \return A reference to a \c RRTTL class object corresponding to the + /// TTL of the \c RRset. + virtual const RRTTL& getTTL() const = 0; + + /// \brief Updates the TTL of the \c RRset. + /// + /// \param ttl A reference to a \c RRTTL class object to be copied as the + /// new TTL. + virtual void setTTL(const RRTTL& ttl) = 0; + //@} + + /// + /// \name Converter Methods + /// + /// These methods have the default implementation that can be reused by + /// derived classes. + /// Since they are defined as pure virtual, derived classes + /// that want to reuse the default implementation must explicitly + /// invoke their base class version (see the description for + /// <code>addRdata(const rdata::Rdata&)</code>). + /// + /// Design Note: the default implementations are defined only using + /// other public methods of the \c AbstractRRset class, and could be + /// implemented as non member functions (as some C++ textbooks suggest). + /// However, since derived classes may want to provide customized versions + /// (especially of the \c toWire() method for performance reasons) + /// we chose to define them as virtual functions, and, as a result, + /// member functions. + //@{ + /// \brief Convert the RRset to a string. + /// + /// Unlike other similar methods of this library, this method terminates + /// the resulting string with a trailing newline character. + /// (following the BIND9 convention) + /// + /// If any RRSIGs are associated with the RRset, they are also + /// appended to the returned string. + /// + /// If the class is not ANY or NONE, the RRset must contain some RDATA; + /// otherwise, an exception of class \c EmptyRRset will be thrown. + /// If resource allocation fails, a corresponding standard exception + /// will be thrown. + /// The default implementation may throw other exceptions if the + /// \c toText() method of the RDATA objects throws. + /// If a derived class of \c AbstractRRset overrides the default + /// implementation, the derived version may throw its own exceptions. + /// + /// Open issue: We may want to support multiple output formats as + /// BIND9 does. For example, we might want to allow omitting the owner + /// name when possible in the context of zone dump. This is a future + /// TODO item. + /// + /// \return A string representation of the RRset. + virtual std::string toText() const = 0; + + /// \brief Render the RRset in the wire format with name compression and + /// truncation handling. + /// + /// This method compresses the owner name of the RRset and domain names + /// used in RDATA that should be compressed. + /// In addition, this method detects the case where rendering the entire + /// RRset would cause truncation, and handles the case appropriately + /// (this is a TODO item, and not implemented in this version). + /// + /// If any RRSIGs are associated with the RRset, they are also rendered. + /// + /// Note: perhaps we may want to add more arguments to convey optional + /// information such as an "rrset-order" policy or how to handle truncation + /// case. This is a TODO item. + /// + /// If resource allocation fails, a corresponding standard exception + /// will be thrown. + /// If the class is not ANY or NONE, the RRset must contain some RDATA; + /// otherwise, an exception of class \c EmptyRRset will be thrown. + /// The default implementation may throw other exceptions if the + /// \c toWire() method of the RDATA objects throws. + /// If a derived class of \c AbstractRRset overrides the default + /// implementation, the derived version may throw its own exceptions. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// \return The number of RRs rendered. If the truncation is necessary + /// this value may be different from the number of RDATA objects contained + /// in the RRset. + virtual uint32_t toWire(AbstractMessageRenderer& renderer) const = 0; + + /// \brief Render the RRset in the wire format without any compression. + /// + /// See the other toWire() description about possible exceptions. + /// + /// \param buffer An output buffer to store the wire data. + /// \return The number of RRs rendered. + virtual uint32_t toWire(isc::util::OutputBuffer& buffer) const = 0; + //@} + + /// + /// \name RDATA Manipulation Methods + /// + //@{ + /// \brief Add an RDATA to the RRset (pointer version). + /// + /// This method adds the given RDATA (as a pointer-like type to a + /// derived class object of \c rdata::Rdata) to the \c RRset. + /// + /// \param rdata A pointer (like) type of \c rdata::RdataPtr to be added + /// to the \c RRset. + virtual void addRdata(rdata::ConstRdataPtr rdata) = 0; + + /// \brief Add an RDATA to the RRset (reference version). + /// + /// This method adds the given RDATA (as a reference to a + /// derived class object of \c rdata::Rdata) to the \c RRset. + /// + /// This method has the default implementation that can be reused by + /// derived classes. + /// Since this method is defined as pure virtual, derived classes + /// that want to reuse the default implementation must explicitly + /// invoke this base class version. + /// For example, if the class \c CustomizedRRset, a derived class of + /// \c AbstractRRset, wants to reuse the default implementation of + /// \c %addRdata() (reference version), it would be defined as follows: + /// \code void + /// CustomizedRRset::addRdata(const rdata::Rdata& rdata) + /// { + /// AbstractRRset::addRdata(rdata); + /// } + /// \endcode + /// + /// This method is more strictly typed than the pointer version: + /// If \c rdata does not refer to the appropriate derived + /// \c Rdata class + /// for the \c RRType for this \c RRset, it throws an exception of class + /// \c std::bad_cast. + /// If resource allocation fails, a corresponding standard exception + /// will be thrown. + /// The RRset must contain some RDATA; otherwise, an exception of class + /// \c EmptyRRset will be thrown. + /// The default implementation may throw other exceptions if the + /// \c toWire() method of the RDATA objects throws. + /// If a derived class of \c AbstractRRset overrides the default + /// implementation, the derived version may throw its own exceptions. + /// + /// The default implementation simply constructs an \c rdata::RdataPtr + /// object from a newly allocated RDATA object copying from parameter + /// \c rdata, and calls the other version of + /// \c addRdata(const rdata::RdataPtr). + /// So it is inherently less efficient than the other version. + /// Still, this version would offer a more intuitive interface and is + /// provided as such. + /// + /// NOTE: Because a new Rdata object is constructed, this method can + /// throw a std::bad_cast exception if this RRset's class is NONE, + /// or if some other error occurs. If you want to be able to add + /// RDATA to an RRset whose class is NONE, please use the other + /// variant of \c addRdata() which accepts a \c ConstRdataPtr + /// argument. + /// + /// \param rdata A reference to a \c rdata::RdataPtr (derived) class + /// object, a copy of which is to be added to the \c RRset. + virtual void addRdata(const rdata::Rdata& rdata) = 0; + + /// \brief Add an RDATA to the RRset (string version). + /// + /// This method constructs an Rdata object from the given + /// \c rdata_str in presentation format and adds it to the \c RRset. + /// + /// \param rdata_str RDATA string in presentation format. + /// \throw InvalidRdataText if the \c rdata_str is invalid for this + /// \c RRset. + virtual void addRdata(const std::string& rdata_str) = 0; + + /// \brief Return an iterator to go through all RDATA stored in the + /// \c RRset. + /// + /// The rdata cursor of the returned iterator will point to the first + /// RDATA, that is, it effectively calls \c RdataIterator::first() + /// internally. + /// + /// Using the design pattern terminology, \c getRdataIterator() + /// is an example of a <em>factory method</em>. + /// + /// Whether this method throws an exception depends on the actual + /// implementation of the derived \c AbstractRRset class, but in general + /// it will involve resource allocation and can throw a standard exception + /// if it fails. + /// + /// \return A pointer-like object pointing to the derived \c RdataIterator + /// object. + virtual RdataIteratorPtr getRdataIterator() const = 0; + //@} + + /// + /// \name Associated RRSIG methods + /// + /// These methods access an "associated" RRset, that containing the DNSSEC + /// signatures for this RRset. It can be argued that this is not a + /// fundamental part of the RRset abstraction, since RFC 2181 defined an + /// RRset as a group of records with the same label, class and type but + /// different data. However, BIND 10 had to deal with DNSSEC and in + /// practice, including the information at the AbstractRRset level makes + /// implementation easier. (If a class is ever needed that must be + /// ignorant of the idea of an associated RRSIG RRset - e.g. a specialised + /// RRSIG RRset class - these methods can just throw a "NotImplemented" + /// exception.) DNSSEC is unlikely to be ever needed in Kea, but it does + /// not make sense to redesign the abstract RRSet class now. + //@{ + /// \brief Return pointer to this RRset's RRSIG RRset + /// + /// \return Pointer to the associated RRSIG RRset or null if there is none. + virtual RRsetPtr getRRsig() const = 0; + + /// \brief Returns the number of \c RRSIG records associated with + /// the \c RRset. + /// + /// Note that an \c RRset with no RRSIG records may exist, so this + /// method may return 0. + /// + /// \return The number of \c RRSIG records associated. + virtual uint32_t getRRsigDataCount() const = 0; + + /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset + /// + /// Adds the (assumed) RRSIG rdata the RRSIG RRset associated with this + /// RRset. If one does not exist, it is created using the data given. + /// + /// \param rdata Pointer to RRSIG rdata to be added. + virtual void addRRsig(const rdata::ConstRdataPtr& rdata) = 0; + + /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset + /// + /// Adds the (assumed) RRSIG rdata the RRSIG RRset associated with this + /// RRset. If one does not exist, it is created using the data given. + /// + /// (This overload is for an older version of boost that doesn't support + /// conversion from shared_ptr<X> to shared_ptr<const X>.) + /// + /// \param rdata Pointer to RRSIG rdata to be added. + virtual void addRRsig(const rdata::RdataPtr& rdata) = 0; + + /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset + /// + /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG + /// RRset associated with this RRset. If one does not exist, it is created + /// using the data given. + /// + /// \param sigs RRSIG RRset containing signatures to be added to the + /// RRSIG RRset associated with this class. + virtual void addRRsig(const AbstractRRset& sigs) = 0; + + /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset + /// + /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG + /// RRset associated with this RRset. If one does not exist, it is created + /// using the data given. + /// + /// \param sigs Pointer to a RRSIG RRset containing signatures to be added + /// to the RRSIG RRset associated with this class. + virtual void addRRsig(const ConstRRsetPtr& sigs) = 0; + + /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset + /// + /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG + /// RRset associated with this RRset. If one does not exist, it is created + /// using the data given. + /// + /// (This overload is for an older version of boost that doesn't support + /// conversion from shared_ptr<X> to shared_ptr<const X>.) + /// + /// \param sigs Pointer to a RRSIG RRset containing signatures to be added + /// to the RRSIG RRset associated with this class. + virtual void addRRsig(const RRsetPtr& sigs) = 0; + + /// \brief Clear the RRSIGs for this RRset + virtual void removeRRsig() = 0; + + /// \brief Check whether two RRsets are of the same kind + /// + /// Checks if two RRsets have the same name, RR type, and RR class. + /// + /// \param other Pointer to another AbstractRRset to compare + /// against. + virtual bool isSameKind(const AbstractRRset& other) const; + //@} + +}; + +/// \brief The \c RdataIterator class is an abstract base class that +/// provides an interface for accessing RDATA objects stored in an RRset. +/// +/// While different derived classes of \c AbstractRRset may maintain the RDATA +/// objects in different ways, the \c RdataIterator class provides a +/// unified interface to iterate over the RDATA objects in a polymorphic +/// manner. +/// +/// Each derived class of \c AbstractRRset is expected to provide a concrete +/// derived class of \c RdataIterator, and each derived \c RdataIterator +/// class implements the unified interface in a way specific to the +/// implementation of the corresponding derived \c AbstractRRset class. +/// Using the design pattern terminology, this is a typical example of +/// the \e Iterator pattern. +/// +/// The RDATA objects stored in the \c RRset are considered to form +/// a unidirectional list from the \c RdataIterator point of view (while +/// the actual implementation in the derived \c RRset may not use a list). +/// We call this unidirectional list the <em>rdata list</em>. +/// +/// An \c RdataIterator object internally (and conceptually) holds a +/// <em>rdata cursor</em>, which points to a specific item of the rdata list. +/// +/// Note about design choice: as is clear from the interface, \c RdataIterator +/// is not compatible with the standard iterator classes. +/// Although it would be useful (for example, we could then use STL algorithms) +/// and is not necessarily impossible, it would make the iterator implementation +/// much more complicated. +/// For instance, any standard iterator must be assignable and +/// copy-constructible. +/// So we'd need to implement \c RdataIterator::operator=() in a polymorphic +/// way. This will require non-trivial implementation tricks. +/// We believe the simplified iterator interface as provided by the +/// \c RdataIterator class is sufficient in practice: +/// Most applications will simply go through the RDATA objects contained in +/// an RRset, examining (and possibly using) each object, as one path +/// operation. +class RdataIterator { + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are intentionally + /// defined as private to make it explicit that this is a pure base class. + //@{ +protected: + /// \brief The default constructor. + /// + /// This is intentionally defined as \c protected as this base class should + /// never be instantiated (except as part of a derived class). + RdataIterator() {} +public: + /// \brief Destructor + virtual ~RdataIterator() {} +private: + RdataIterator(const RdataIterator& source); + RdataIterator& operator=(const RdataIterator& source); + //@} + +public: + /// \brief Move the rdata cursor to the first RDATA in the rdata list + /// (if any). + /// + /// This method can safely be called multiple times, even after moving + /// the rdata cursor forward by the \c next() method. + /// + /// This method should never throw an exception. + virtual void first() = 0; + + /// \brief Move the rdata cursor to the next RDATA in the rdata list + /// (if any). + /// + /// This method should never throw an exception. + virtual void next() = 0; + + /// \brief Return the current \c Rdata corresponding to the rdata cursor. + /// + /// \return A reference to an \c rdata::Rdata object corresponding + /// to the rdata cursor. + virtual const rdata::Rdata& getCurrent() const = 0; + + /// \brief Return true iff the rdata cursor has reached the end of the + /// rdata list. + /// + /// Once this method returns \c true, the behavior of any subsequent + /// call to \c next() or \c getCurrent() is undefined. + /// Likewise, the result of \c isLast() call followed by such undefined + /// operations is also undefined. + /// + /// This method should never throw an exception. + /// + /// \return \c true if the rdata cursor has reached the end of the + /// rdata list; otherwise \c false. + virtual bool isLast() const = 0; +}; + +/// \brief The \c BasicRRset class is a concrete derived class of +/// \c AbstractRRset that defines a straightforward RRset implementation. +/// +/// This class is designed to be as portable as possible, and so it adopts +/// the Pimpl idiom to hide as many details as possible. +/// Performance is a secondary concern for this class. +/// +/// This class is intended to be used by applications that only need +/// moderate level of performance with full functionality provided by +/// the \c AbstractRRset interfaces. +/// Highly performance-sensitive applications, such as a large scale +/// authoritative or caching name servers will implement and use a customized +/// version of derived \c AbstractRRset class. +class BasicRRset : public AbstractRRset { + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are intentionally + /// defined as private. The intended use case wouldn't require copies of + /// a \c BasicRRset object; once created, it would normally be used + /// as a \c const object (via references). + //@{ +private: + BasicRRset(const BasicRRset& source); + BasicRRset& operator=(const BasicRRset& source); +public: + /// \brief Constructor from (mostly) fixed parameters of the RRset. + /// + /// This constructor is normally expected to be exception free, but + /// copying the name may involve resource allocation, and if it fails + /// the corresponding standard exception will be thrown. + /// + /// \param name The owner name of the RRset. + /// \param rrclass The RR class of the RRset. + /// \param rrtype The RR type of the RRset. + /// \param ttl The TTL of the RRset. + BasicRRset(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& ttl); + /// \brief The destructor. + virtual ~BasicRRset(); + //@} + + /// + /// \name Getter and Setter Methods + /// + //@{ + /// \brief Returns the number of \c Rdata objects contained in the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return The number of \c Rdata objects contained. + virtual uint32_t getRdataCount() const; + + /// \brief Get the wire format length of the \c BasicRRset. + /// + /// \return The length of the wire format representation of the + /// \c BasicRRset. + /// \throw EmptyRRset if the \c BasicRRset is empty. + virtual uint16_t getLength() const; + + /// \brief Returns the owner name of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c Name class object corresponding to the + /// \c RRset owner name. + virtual const Name& getName() const; + + /// \brief Returns the RR Class of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRClass class object corresponding to the + /// RR class of the \c RRset. + virtual const RRClass& getClass() const; + + /// \brief Returns the RR Type of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRType class object corresponding to the + /// RR type of the \c RRset. + virtual const RRType& getType() const; + + /// \brief Returns the TTL of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \return A reference to a \c RRTTL class object corresponding to the + /// TTL of the \c RRset. + virtual const RRTTL& getTTL() const; + + /// \brief Updates the TTL of the \c RRset. + /// + /// This method never throws an exception. + /// + /// \param ttl A reference to a \c RRTTL class object to be copied as the + /// new TTL. + virtual void setTTL(const RRTTL& ttl); + //@} + + /// + /// \name Converter Methods + /// + //@{ + /// \brief Convert the RRset to a string. + /// + /// This method simply uses the default implementation. + /// See \c AbstractRRset::toText(). + virtual std::string toText() const; + + /// \brief Render the RRset in the wire format with name compression and + /// truncation handling. + /// + /// This method simply uses the default implementation. + /// See \c AbstractRRset::toWire(MessageRenderer&)const. + virtual uint32_t toWire(AbstractMessageRenderer& renderer) const; + + /// \brief Render the RRset in the wire format without any compression. + /// + /// This method simply uses the default implementation. + /// See \c AbstractRRset::toWire(OutputBuffer&)const. + virtual uint32_t toWire(isc::util::OutputBuffer& buffer) const; + //@} + + /// + /// \name RDATA manipulation methods + /// + //@{ + /// \brief Add an RDATA to the RRset (pointer version). + /// + /// This method is normally expected to be exception free, but it may + /// involve resource allocation, and if it fails the corresponding + /// standard exception will be thrown. + /// + /// \param rdata A pointer (like) type of \c rdata::RdataPtr to be added + /// to the \c BasicRRset. + virtual void addRdata(rdata::ConstRdataPtr rdata); + + /// \brief Add an RDATA to the RRset (reference version). + /// + /// This method simply uses the default implementation. + /// See \c AbstractRRset::addRdata(const rdata::Rdata&). + virtual void addRdata(const rdata::Rdata& rdata); + + /// \brief Add an RDATA to the RRset (string version). + /// + /// \param rdata_str RDATA string in presentation format. + /// \throw InvalidRdataText if the \c rdata_str is invalid for this + /// \c RRset. + virtual void addRdata(const std::string& rdata_str); + + /// \brief Return an iterator to go through all RDATA stored in the + /// \c BasicRRset. + /// + /// This is a concrete derived implementation of + /// \c AbstractRRset::getRdataIterator(). + /// + /// This method dynamically allocates resources. If it fails it will + /// throw the corresponding standard exception. + /// The iterator methods for the \c BasicRRset class are exception free. + /// + /// \return A pointer-like object pointing to the derived \c RdataIterator + /// object for the \c BasicRRset class. + virtual RdataIteratorPtr getRdataIterator() const; + //@} + + /// + /// \name Associated RRSIG methods + /// + /// The associated RRSIG RRset is not supported in BasicRRset. For + /// ease of use, getRRsig() returns a null pointer (indicating no RRset). + /// The addRRsig()/removeRRsig() methods throw a "NotImplemented" + /// exception - if you are using a BasicRRset, you should not be trying + /// to modify signatures on it. + //@{ + /// \brief Return pointer to this RRset's RRSIG RRset + /// + /// \return Null pointer, as this class does not support RRSIG records. + virtual RRsetPtr getRRsig() const { + return (RRsetPtr()); + } + + /// \brief Returns the number of \c RRSIG records associated with + /// the \c RRset. + /// + /// \return Always returns 0. Associated RRSIG RRsets are not + /// supported in this class. + virtual uint32_t getRRsigDataCount() const { + return (0); + } + + virtual void addRRsig(const rdata::ConstRdataPtr&) { + isc_throw(NotImplemented, + "BasicRRset does not implement the addRRsig() method"); + } + + virtual void addRRsig(const rdata::RdataPtr&) { + isc_throw(NotImplemented, + "BasicRRset does not implement the addRRsig() method"); + } + + virtual void addRRsig(const AbstractRRset&) { + isc_throw(NotImplemented, + "BasicRRset does not implement the addRRsig() method"); + } + + virtual void addRRsig(const ConstRRsetPtr&) { + isc_throw(NotImplemented, + "BasicRRset does not implement the addRRsig() method"); + } + + virtual void addRRsig(const RRsetPtr&) { + isc_throw(NotImplemented, + "BasicRRset does not implement the addRRsig() method"); + } + + virtual void removeRRsig() { + isc_throw(NotImplemented, + "BasicRRset does not implement the removeRRsig() method"); + } + //@} +private: + boost::shared_ptr<BasicRRsetImpl> impl_; +}; + +/// \brief The \c RRset class is a concrete derived class of +/// \c BasicRRset which contains a pointer to an additional RRset +/// containing associated RRSIG records. This allows DNSSEC aware +/// applications to treat data associated with a particular +/// QNAME/QTYPE/QCLASS as a single object. +class RRset : public BasicRRset { +public: + RRset(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& ttl); + + virtual ~RRset(); + + /// \brief Get the wire format length of the \c RRset. + /// + /// \return The length of the wire format representation of the + /// \c RRset. + /// \throw EmptyRRset if the \c RRset is empty. + virtual uint16_t getLength() const; + + /// \brief Render the RRset in the wire format with name compression and + /// truncation handling. + /// + /// See \c AbstractRRset::toWire(MessageRenderer&)const. + virtual uint32_t toWire(AbstractMessageRenderer& renderer) const; + + /// \brief Render the RRset in the wire format without any compression. + /// + /// See \c AbstractRRset::toWire(OutputBuffer&)const. + virtual uint32_t toWire(isc::util::OutputBuffer& buffer) const; + + /// \brief Updates the owner name of the \c RRset, including RRSIGs if any + virtual void setTTL(const RRTTL& ttl) { + BasicRRset::setTTL(ttl); + if (rrsig_) { + rrsig_->setTTL(ttl); + } + } + + /// \brief Adds an RRSIG RR to this RRset's signatures + virtual void addRRsig(const rdata::ConstRdataPtr& rdata) { + if (!rrsig_) { + rrsig_ = RRsetPtr(new RRset(getName(), getClass(), + RRType::RRSIG(), getTTL())); + } + rrsig_->addRdata(rdata); + } + + // Workaround for older versions of boost: some don't support implicit + // conversion from shared_ptr<X> to shared_ptr<const X>. Note: we should + // revisit the interface of managing RRset signatures, at which point this + // problem may go away. + virtual void addRRsig(const rdata::RdataPtr& rdata) { + // Don't try to convert as a reference here. SunStudio will reject it. + addRRsig(static_cast<const rdata::ConstRdataPtr>(rdata)); + } + + /// \brief Adds an RRSIG RRset to this RRset + virtual void addRRsig(const AbstractRRset& sigs) { + RdataIteratorPtr it = sigs.getRdataIterator(); + + if (!rrsig_) { + rrsig_ = RRsetPtr(new RRset(getName(), getClass(), + RRType::RRSIG(), getTTL())); + } + + for (it->first(); !it->isLast(); it->next()) { + rrsig_->addRdata(it->getCurrent()); + } + } + + virtual void addRRsig(const ConstRRsetPtr& sigs) { addRRsig(*sigs); } + + // Another workaround for older boost (see above) + virtual void addRRsig(const RRsetPtr& sigs) { addRRsig(*sigs); } + + /// \brief Clear the RRSIGs for this RRset + virtual void removeRRsig() { rrsig_ = RRsetPtr(); } + + /// \brief Return a pointer to this RRset's RRSIG RRset + virtual RRsetPtr getRRsig() const { return (rrsig_); } + + /// \brief Returns the number of \c RRSIG records associated with + /// the \c RRset. + /// + /// Note that an \c RRset with no RRSIG records may exist, so this + /// method may return 0. + /// + /// \return The number of \c RRSIG records associated. + virtual uint32_t getRRsigDataCount() const; + +private: + RRsetPtr rrsig_; +}; + + +/// \brief Insert the \c RRset as a string into stream. +/// +/// This method convert the \c rrset into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global \c operator<< to behave as described in +/// \c %ostream::%operator<< but applied to RRset objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param rrset A reference to a (derived class of) \c AbstractRRset object +/// output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const AbstractRRset& rrset); +} // end of namespace dns +} // end of namespace isc +#endif // RRSET_H + diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc new file mode 100644 index 0000000..983e8d0 --- /dev/null +++ b/src/lib/dns/rrttl.cc @@ -0,0 +1,214 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/isc_assert.h> +#include <dns/messagerenderer.h> +#include <dns/rrttl.h> +#include <util/buffer.h> + +#include <algorithm> +#include <cctype> +#include <stdint.h> +#include <sstream> +#include <ostream> + +#include <boost/lexical_cast.hpp> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; + +namespace { + +// We wrap the C isalpha, because it seems to be overloaded with something. +// Then the find_if doesn't work. +bool +myIsalpha(char c) { + return (isalpha(c) != 0); +} + +// The conversion of units to their size +struct Unit { + char unit; + uint32_t multiply; + uint32_t max_allowed; +}; + +Unit units[] = { + { 'S', 1, 0xffffffff / 1 }, + { 'M', 60, 0xffffffff / 60 }, + { 'H', 60 * 60, 0xffffffff / (60 * 60) }, + { 'D', 24 * 60 * 60, 0xffffffff / (24 * 60 * 60) }, + { 'W', 7 * 24 * 60 * 60, 0xffffffff / (7 * 24 * 60 * 60) } +}; + +} + +namespace isc { +namespace dns { + +namespace { +bool +parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) { + if (ttlstr.empty()) { + if (error_txt) { + *error_txt = "Empty TTL string"; + } + return (false); + } + + // We use a larger data type to handle negative number cases. + uint64_t val = 0; + const string::const_iterator end = ttlstr.end(); + string::const_iterator pos = ttlstr.begin(); + + try { + // When we detect we have some units + bool units_mode = false; + + while (pos != end) { + // Find the first unit, if there's any. + const string::const_iterator unit = find_if(pos, end, myIsalpha); + // No unit + if (unit == end) { + if (units_mode) { + // We had some units before. The last one is missing unit. + if (error_txt) { + *error_txt = "Missing the last unit: " + ttlstr; + } + return (false); + } else { + // Case without any units at all. Just convert and store + // it. + val = boost::lexical_cast<uint64_t>(ttlstr); + break; + } + } + // There's a unit now. + units_mode = true; + // Find the unit and get the size. + uint32_t multiply = 1; // initialize to silence compiler warnings + uint32_t max_allowed = 0xffffffff; + bool found = false; + for (size_t i = 0; i < sizeof(units) / sizeof(*units); ++i) { + if (toupper(*unit) == units[i].unit) { + found = true; + multiply = units[i].multiply; + max_allowed = units[i].max_allowed; + break; + } + } + if (!found) { + if (error_txt) { + *error_txt = "Unknown unit used: " + + boost::lexical_cast<string>(*unit) + " in: " + ttlstr; + } + return (false); + } + // Now extract the number. + if (unit == pos) { + if (error_txt) { + *error_txt = "Missing number in TTL: " + ttlstr; + } + return (false); + } + const uint64_t value = + boost::lexical_cast<uint64_t>(string(pos, unit)); + if (value > max_allowed) { + if (error_txt) { + *error_txt = "Part of TTL out of range: " + ttlstr; + } + return (false); + } + + // seconds cannot be out of range at this point. + const uint64_t seconds = value * multiply; + isc_throw_assert(seconds <= 0xffffffff); + + // Add what we found + val += seconds; + // Check the partial value is still in range (the value can only + // grow, so if we get out of range now, it won't get better, so + // there's no need to continue). + if (val < seconds || val > 0xffffffff) { + if (error_txt) { + *error_txt = "Part of TTL out of range: " + ttlstr; + } + return (false); + } + // Move to after the unit. + pos = unit + 1; + } + } catch (const boost::bad_lexical_cast&) { + if (error_txt) { + *error_txt = "invalid TTL: " + ttlstr; + } + return (false); + } + + if (val <= 0xffffffff) { + ttlval = val; + } else { + // This could be due to negative numbers in input, etc. + if (error_txt) { + *error_txt = "TTL out of range: " + ttlstr; + } + return (false); + } + + return (true); +} +} + +RRTTL::RRTTL(const std::string& ttlstr) { + string error_txt; + if (!parseTTLString(ttlstr, ttlval_, &error_txt)) { + isc_throw(InvalidRRTTL, error_txt); + } +} + +RRTTL* +RRTTL::createFromText(const string& ttlstr) { + uint32_t ttlval; + if (parseTTLString(ttlstr, ttlval, 0)) { + return (new RRTTL(ttlval)); + } + return (0); +} + +RRTTL::RRTTL(InputBuffer& buffer) { + if (buffer.getLength() - buffer.getPosition() < sizeof(uint32_t)) { + isc_throw(IncompleteRRTTL, "incomplete wire-format TTL value"); + } + ttlval_ = buffer.readUint32(); +} + +const string +RRTTL::toText() const { + ostringstream oss; + oss << ttlval_; + return (oss.str()); +} + +void +RRTTL::toWire(OutputBuffer& buffer) const { + buffer.writeUint32(ttlval_); +} + +void +RRTTL::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeUint32(ttlval_); +} + +ostream& +operator<<(ostream& os, const RRTTL& rrttl) { + os << rrttl.toText(); + return (os); +} +} +} diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h new file mode 100644 index 0000000..8f6b03e --- /dev/null +++ b/src/lib/dns/rrttl.h @@ -0,0 +1,313 @@ +// Copyright (C) 2010-2024 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 RRTTL_H +#define RRTTL_H + +#include <dns/exceptions.h> +#include <util/buffer.h> + +#include <boost/optional.hpp> + +#include <stdint.h> + +namespace isc { +namespace dns { + +// forward declarations +class AbstractMessageRenderer; + +/// +/// \brief A standard DNS module exception that is thrown if an RRTTL object +/// is being constructed from an unrecognized string. +/// +class InvalidRRTTL : public DNSTextError { +public: + InvalidRRTTL(const char* file, size_t line, const char* what) : + DNSTextError(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if an RRTTL object +/// is being constructed from a incomplete (too short) wire-format data. +/// +class IncompleteRRTTL : public isc::dns::Exception { +public: + IncompleteRRTTL(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// The \c RRTTL class encapsulates TTLs used in DNS resource records. +/// +/// This is a straightforward class; an \c RRTTL object simply maintains a +/// 32-bit unsigned integer corresponding to the TTL value. The main purpose +/// of this class is to provide convenient interfaces to convert a textual +/// representation into the integer TTL value and vice versa, and to handle +/// wire-format representations. +class RRTTL { +public: + /// + /// \name Constructors, Factory and Destructor + /// + /// Note: We use the default copy constructor and the default copy + /// assignment operator intentionally. + //@{ + /// Constructor from an integer TTL value. + /// + /// This constructor never throws an exception. + /// + /// \param ttlval An 32-bit integer of the RRTTL. + explicit RRTTL(uint32_t ttlval) : ttlval_(ttlval) { + } + + /// Constructor from a string. + /// + /// It accepts either a decimal number, specifying number of seconds. Or, + /// it can be given a sequence of numbers and units, like "2H" (meaning + /// two hours), "1W3D" (one week and 3 days). The allowed units are W + /// (week), D (day), H (hour), M (minute) and S (second). They can be also + /// specified in lower-case. No further restrictions are checked (so they + /// can be specified in arbitrary order and even things like "1D1D" can + /// be used to specify two days). + /// + /// \param ttlstr A string representation of the \c RRTTL. + /// + /// \throw InvalidRRTTL in case the string is not recognized as valid + /// TTL representation. + explicit RRTTL(const std::string& ttlstr); + + /// Constructor from wire-format data. + /// + /// The \c buffer parameter normally stores a complete DNS message + /// containing the RRTTL to be constructed. The current read position of + /// the buffer points to the head of the type. + /// + /// If the given data does not large enough to contain a 16-bit integer, + /// an exception of class \c IncompleteRRTTL will be thrown. + /// + /// \param buffer A buffer storing the wire format data. + explicit RRTTL(isc::util::InputBuffer& buffer); + + /// A separate factory of RRTTL from text. + /// + /// This static method is similar to the constructor that takes a string + /// object, but works as a factory and reports parsing failure in the + /// form of the return value. Normally the constructor version should + /// suffice, but in some cases the caller may have to expect mixture of + /// valid and invalid input, and may want to minimize the overhead of + /// possible exception handling. This version is provided for such + /// purpose. + /// + /// If the given text represents a valid RRTTL, it returns a pointer + /// to a new RRTTL object. If the given text does not represent a + /// valid RRTTL, it returns null.. + /// + /// One main purpose of this function is to minimize the overhead + /// when the given text does not represent a valid RR TTL. For this + /// reason this function intentionally omits the capability of delivering + /// a detailed reason for the parse failure, such as in the \c want() + /// string when exception is thrown from the constructor (it will + /// internally require a creation of string object, which is relatively + /// expensive). If such detailed information is necessary, the constructor + /// version should be used to catch the resulting exception. + /// + /// This function never throws the \c InvalidRRTTL exception. + /// + /// \param ttlstr A string representation of the \c RRTTL. + /// \return A new RRTTL object for the given text or a null value. + static RRTTL* createFromText(const std::string& ttlstr); + /// + //@} + + /// + /// \name Converter methods + /// + //@{ + /// \brief Convert the \c RRTTL to a string. + /// + /// This version of implementation simply converts the TTL value into the + /// numeric textual representation. We may introduce more human-readable + /// format depending on the context in future versions. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. + /// + /// \return A string representation of the \c RRTTL. + const std::string toText() const; + /// \brief Render the \c RRTTL in the wire format. + /// + /// This method renders the TTL value in network byte order via \c renderer, + /// which encapsulates output buffer and other rendering contexts. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer in which the RRTTL is to be stored. + void toWire(AbstractMessageRenderer& renderer) const; + /// \brief Render the \c RRTTL in the wire format. + /// + /// This method renders the TTL value in network byte order into the + /// \c buffer. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. + /// + /// \param buffer An output buffer to store the wire data. + void toWire(isc::util::OutputBuffer& buffer) const; + //@} + + /// + /// \name Getter Methods + /// + //@{ + /// \brief Returns the TTL value as a 32-bit unsigned integer. + /// + /// This method never throws an exception. + /// + /// \return An 32-bit integer corresponding to the RRTTL. + uint32_t getValue() const { + return (ttlval_); + } + //@} + + /// + /// \name Comparison methods + /// + /// Comparison between two \c RRTTL objects is performed in a + /// straightforward way, that is, comparing the corresponding TTL values + /// (which is the result of the \c getValue() method) as 32-bit unsigned + /// integers. + //@{ + /// \brief Return true iff two RRTTLs are equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRTTL object to compare against. + bool equals(const RRTTL& other) const { + return (ttlval_ == other.ttlval_); + } + /// \brief Same as \c equals(). + bool operator==(const RRTTL& other) const { + return (ttlval_ == other.ttlval_); + } + /// \brief Return true iff two RRTTLs are not equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRTTL object to compare against. + bool nequals(const RRTTL& other) const { + return (ttlval_ != other.ttlval_); + } + /// \brief Same as \c nequals(). + bool operator!=(const RRTTL& other) const { + return (ttlval_ != other.ttlval_); + } + /// \brief Less-than or equal comparison for RRTTL against \c other. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRTTL object to compare against. + /// \return true if \c this RRTTL is less than or equal to the \c other; + /// otherwise false. + bool leq(const RRTTL& other) const { + return (ttlval_ <= other.ttlval_); + } + + /// Same as \c leq() + bool operator<=(const RRTTL& other) const { + return (ttlval_ <= other.ttlval_); + } + + /// \brief Greater-than or equal comparison for RRTTL against \c other. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRTTL object to compare against. + /// \return true if \c this RRTTL is greater than or equal to the \c other; + /// otherwise false. + bool geq(const RRTTL& other) const { + return (ttlval_ >= other.ttlval_); + } + + /// Same as \c geq() + bool operator>=(const RRTTL& other) const { + return (ttlval_ >= other.ttlval_); + } + + /// \brief Less-than comparison for RRTTL against \c other. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRTTL object to compare against. + /// \return true if \c this RRTTL is less than the \c other; + /// otherwise false. + bool lthan(const RRTTL& other) const { + return (ttlval_ < other.ttlval_); + } + + /// Same as \c lthan() + bool operator<(const RRTTL& other) const { + return (ttlval_ < other.ttlval_); + } + + /// \brief Greater-than comparison for RRTTL against \c other. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRTTL object to compare against. + /// \return true if \c this RRTTL is greater than the \c other; + /// otherwise false. + bool gthan(const RRTTL& other) const { + return (ttlval_ > other.ttlval_); + } + + /// Same as \c gthan() + bool operator>(const RRTTL& other) const { + return (ttlval_ > other.ttlval_); + } + //@} + + /// + /// \name Protocol constants + /// + //@{ + /// \brief The TTL of the max allowable value, per RFC2181 Section 8. + /// + /// The max value is the largest unsigned 31 bit integer, 2^31-1. + /// + /// \note At the moment an RRTTL object can have a value larger than + /// this limit. We may revisit it in a future version. + static const RRTTL& MAX_TTL() { + static const RRTTL max_ttl(0x7fffffff); + return (max_ttl); + } + //@} + +private: + uint32_t ttlval_; +}; + +/// +/// \brief Insert the \c RRTTL as a string into stream. +/// +/// This method convert the \c rrttl into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global operator<< to behave as described in +/// ostream::operator<< but applied to \c RRTTL objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param rrttl The \c RRTTL object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& +operator<<(std::ostream& os, const RRTTL& rrttl); +} +} +#endif // RRTTL_H diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc new file mode 100644 index 0000000..d060c41 --- /dev/null +++ b/src/lib/dns/rrtype.cc @@ -0,0 +1,64 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/rrparamregistry.h> +#include <dns/rrtype.h> +#include <util/buffer.h> + +#include <stdint.h> +#include <string> +#include <ostream> + +using namespace isc::util; +using isc::dns::RRType; + +using namespace std; + +namespace isc { +namespace dns { + +RRType::RRType(const std::string& type_str) { + uint16_t typecode; + if (!RRParamRegistry::getRegistry().textToTypeCode(type_str, typecode)) { + isc_throw(InvalidRRType, + "Unrecognized RR type string: " + type_str); + } + typecode_ = typecode; +} + +RRType::RRType(InputBuffer& buffer) { + if (buffer.getLength() - buffer.getPosition() < sizeof(uint16_t)) { + isc_throw(IncompleteRRType, "incomplete wire-format RR type"); + } + typecode_ = buffer.readUint16(); +} + +const string +RRType::toText() const { + return (RRParamRegistry::getRegistry().codeToTypeText(typecode_)); +} + +void +RRType::toWire(OutputBuffer& buffer) const { + buffer.writeUint16(typecode_); +} + +void +RRType::toWire(AbstractMessageRenderer& renderer) const { + renderer.writeUint16(typecode_); +} + +ostream& +operator<<(ostream& os, const RRType& rrtype) { + os << rrtype.toText(); + return (os); +} +} +} diff --git a/src/lib/dns/rrtype.h b/src/lib/dns/rrtype.h new file mode 100644 index 0000000..ae575d0 --- /dev/null +++ b/src/lib/dns/rrtype.h @@ -0,0 +1,368 @@ +// Copyright (C) 2010-2024 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 RRTYPE_H +#define RRTYPE_H + +#include <stdint.h> + +#include <string> +#include <ostream> + +#include <dns/exceptions.h> +#include <util/buffer.h> + +// Solaris x86 defines DS in <sys/regset.h>, which gets pulled in by Boost +#if defined(__sun) && defined(DS) +# undef DS +#endif + +namespace isc { + +namespace dns { + +// forward declarations +class AbstractMessageRenderer; + +/// +/// \brief A standard DNS module exception that is thrown if an RRType object +/// is being constructed from an unrecognized string. +/// +class InvalidRRType : public DNSTextError { +public: + InvalidRRType(const char* file, size_t line, const char* what) : + DNSTextError(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if an RRType object +/// is being constructed from a incomplete (too short) wire-format data. +/// +class IncompleteRRType : public isc::dns::Exception { +public: + IncompleteRRType(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// The \c RRType class encapsulates DNS resource record types. +/// +/// This class manages the 16-bit integer type codes in quite a straightforward +/// way. The only non trivial task is to handle textual representations of +/// RR types, such as "A", "AAAA", or "TYPE65534". +/// +/// This class consults a helper \c RRParamRegistry class, which is a registry +/// of RR related parameters and has the singleton object. This registry +/// provides a mapping between RR type codes and their "well-known" textual +/// representations. +/// Parameters of RR types defined by DNS protocol standards are automatically +/// registered at initialization time and are ensured to be always available for +/// applications unless the application explicitly modifies the registry. +/// +/// For convenience, this class defines constant class objects corresponding to +/// standard RR types. These are generally referred to as the form of +/// <code>RRType::{type-text}()</code>. +/// For example, \c RRType::NS() is an \c RRType object corresponding to the NS +/// resource record (type code 2). +/// Note that these constants are used through a "proxy" function. +/// This is because they may be used to initialize another non-local (e.g. +/// global or namespace-scope) static object as follows: +/// +/// \code +/// namespace foo { +/// const RRType default_type = RRType::A(); +/// } \endcode +/// +/// In order to ensure that the constant RRType object has been initialized +/// before the initialization for \c default_type, we need help from +/// the proxy function. +/// +/// In the current implementation, the initialization of the well-known +/// static objects is not thread safe. The same consideration as the +/// \c RRParamRegistry class applies. We may extend the implementation so +/// that the initialization is ensured to be thread safe in a future version. +/// +/// Note to developers: since it's expected that some of these constant +/// \c RRType objects are frequently used in a performance sensitive path, +/// we define these proxy functions as inline. This makes sense only when +/// the corresponding static objects are defined only once even if they used +/// in different source files. Sufficiently modern compilers should meet +/// this assumption, but if we encounter memory bloat due to this problem with +/// particular compilers we need to revisit the design or think about +/// workaround. +class RRType { +public: + /// + /// \name Constructors and Destructor + /// + //@{ + /// Constructor from an integer type code. + /// + /// This constructor never throws an exception. + /// + /// \param typecode An 16-bit integer code corresponding to the RRType. + explicit RRType(uint16_t typecode) : typecode_(typecode) { + } + /// Constructor from a string. + /// + /// A valid string is one of "well-known" textual type representations + /// such as "A", "AAAA", or "NS", or in the standard format for "unknown" + /// RR types as defined in RFC3597, i.e., "TYPEnnnn". + /// + /// More precisely, the "well-known" representations are the ones stored + /// in the \c RRParamRegistry registry (see the class description). + /// + /// As for the format of "TYPEnnnn", "nnnn" must represent a valid 16-bit + /// unsigned integer, which may contain leading 0's as long as it consists + /// of at most 5 characters (inclusive). + /// For example, "TYPE1" and "TYPE001" are valid and represent the same + /// RR type, but "TYPE65536" and "TYPE000001" are invalid. + /// A "TYPEnnnn" representation is valid even if the corresponding type code + /// is registered in the \c RRParamRegistry object. For example, both + /// "A" and "TYPE1" are valid and represent the same RR type. + /// + /// All of these representations are case insensitive; "NS" and "ns", and + /// "TYPE1" and "type1" are all valid and represent the same RR types, + /// respectively. + /// + /// If the given string is not recognized as a valid representation of + /// an RR type, an exception of class \c InvalidRRType will be thrown. + /// + /// \param typestr A string representation of the \c RRType + explicit RRType(const std::string& typestr); + /// Constructor from wire-format data. + /// + /// The \c buffer parameter normally stores a complete DNS message + /// containing the RRType to be constructed. The current read position of + /// the buffer points to the head of the type. + /// + /// If the given data does not large enough to contain a 16-bit integer, + /// an exception of class \c IncompleteRRType will be thrown. + /// + /// \param buffer A buffer storing the wire format data. + explicit RRType(isc::util::InputBuffer& buffer); + /// + /// We use the default copy constructor intentionally. + //@} + /// We use the default copy assignment operator intentionally. + /// + + /// + /// \name Converter methods + /// + //@{ + /// \brief Convert the \c RRType to a string. + /// + /// If a "well known" textual representation for the type code is registered + /// in the RR parameter registry (see the class description), that will be + /// used as the return value of this method. Otherwise, this method creates + /// a new string for an "unknown" RR type in the format defined in RFC3597, + /// i.e., "TYPEnnnn", and returns it. + /// + /// If resource allocation for the string fails, a corresponding standard + /// exception will be thrown. + /// + /// \return A string representation of the \c RRType. + const std::string toText() const; + /// \brief Render the \c RRType in the wire format. + /// + /// This method renders the type code in network byte order via \c renderer, + /// which encapsulates output buffer and other rendering contexts. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer in which the RRType is to be stored. + void toWire(AbstractMessageRenderer& renderer) const; + /// \brief Render the \c RRType in the wire format. + /// + /// This method renders the type code in network byte order into the + /// \c buffer. + /// + /// If resource allocation in rendering process fails, a corresponding + /// standard exception will be thrown. + /// + /// \param buffer An output buffer to store the wire data. + void toWire(isc::util::OutputBuffer& buffer) const; + //@} + + /// + /// \name Getter Methods + /// + //@{ + /// \brief Returns the RR type code as a 16-bit unsigned integer. + /// + /// This method never throws an exception. + /// + /// \return An 16-bit integer code corresponding to the RRType. + uint16_t getCode() const { + return (typecode_); + } + //@} + + /// + /// \name Comparison methods + /// + //@{ + /// \brief Return true iff two RRTypes are equal. + /// + /// Two RRTypes are equal iff their type codes are equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRType object to compare against. + /// \return true if the two RRTypes are equal; otherwise false. + bool equals(const RRType& other) const { + return (typecode_ == other.typecode_); + } + /// \brief Same as \c equals(). + bool operator==(const RRType& other) const { + return (equals(other)); + } + + /// \brief Return true iff two RRTypes are not equal. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRType object to compare against. + /// \return true if the two RRTypes are not equal; otherwise false. + bool nequals(const RRType& other) const { + return (typecode_ != other.typecode_); + } + /// \brief Same as \c nequals(). + bool operator!=(const RRType& other) const { + return (nequals(other)); + } + + /// \brief Less-than comparison for RRType against \c other + /// + /// We define the less-than relationship based on their type codes; + /// one RRType is less than the other iff the code of the former is less + /// than that of the other as unsigned integers. + /// The relationship is meaningless in terms of DNS protocol; the only + /// reason we define this method is that RRType objects can be stored in + /// STL containers without requiring user-defined less-than relationship. + /// We therefore don't define other comparison operators. + /// + /// This method never throws an exception. + /// + /// \param other the \c RRType object to compare against. + /// \return true if \c this RRType is less than the \c other; otherwise + /// false. + bool operator<(const RRType& other) const { + return (typecode_ < other.typecode_); + } + //@} + + static const RRType& A(); + static const RRType& NS(); + static const RRType& SOA(); + static const RRType& OPT(); + static const RRType& PTR(); + static const RRType& TXT(); + static const RRType& AAAA(); + static const RRType& RRSIG(); + static const RRType& DHCID(); + static const RRType& TKEY(); + static const RRType& TSIG(); + static const RRType& ANY(); + +private: + uint16_t typecode_; +}; + +inline const RRType& +RRType::A() { + static RRType rrtype(1); + return (rrtype); +} + +inline const RRType& +RRType::NS() { + static RRType rrtype(2); + return (rrtype); +} + +inline const RRType& +RRType::SOA() { + static RRType rrtype(6); + return (rrtype); +} + +inline const RRType& +RRType::OPT() { + static RRType rrtype(41); + return (rrtype); +} + +inline const RRType& +RRType::PTR() { + static RRType rrtype(12); + return (rrtype); +} + +inline const RRType& +RRType::TXT() { + static RRType rrtype(16); + return (rrtype); +} + +inline const RRType& +RRType::AAAA() { + static RRType rrtype(28); + return (rrtype); +} + +inline const RRType& +RRType::RRSIG() { + static RRType rrtype(46); + return (rrtype); +} + +inline const RRType& +RRType::DHCID() { + static RRType rrtype(49); + return (rrtype); +} + +inline const RRType& +RRType::TKEY() { + static RRType rrtype(249); + return (rrtype); +} + +inline const RRType& +RRType::TSIG() { + static RRType rrtype(250); + return (rrtype); +} + +inline const RRType& +RRType::ANY() { + static RRType rrtype(255); + return (rrtype); +} + +/// +/// \brief Insert the \c RRType as a string into stream. +/// +/// This method convert the \c rrtype into a string and inserts it into the +/// output stream \c os. +/// +/// This function overloads the global operator<< to behave as described in +/// ostream::operator<< but applied to \c RRType objects. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param rrtype The \c RRType object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& +operator<<(std::ostream& os, const RRType& rrtype); +} +} +#endif // RRTYPE_H diff --git a/src/lib/dns/serial.cc b/src/lib/dns/serial.cc new file mode 100644 index 0000000..842bff8 --- /dev/null +++ b/src/lib/dns/serial.cc @@ -0,0 +1,70 @@ +// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <dns/serial.h> + +namespace isc { +namespace dns { + +bool +Serial::operator==(const Serial& other) const { + return (value_ == other.getValue()); +} + +bool +Serial::operator!=(const Serial& other) const { + return (value_ != other.getValue()); +} + +bool +Serial::operator<(const Serial& other) const { + uint32_t other_val = other.getValue(); + bool result = false; + if (value_ < other_val) { + result = ((other_val - value_) <= MAX_SERIAL_INCREMENT); + } else if (other_val < value_) { + result = ((value_ - other_val) > MAX_SERIAL_INCREMENT); + } + return (result); +} + +bool +Serial::operator<=(const Serial& other) const { + return (operator==(other) || operator<(other)); +} + +bool +Serial::operator>(const Serial& other) const { + return (!operator==(other) && !operator<(other)); +} + +bool +Serial::operator>=(const Serial& other) const { + return (!operator<(other)); +} + +Serial +Serial::operator+(uint32_t other_val) const { + uint64_t new_val = static_cast<uint64_t>(value_) + + static_cast<uint64_t>(other_val); + return Serial(static_cast<uint32_t>(new_val % MAX_SERIAL_VALUE)); +} + +Serial +Serial::operator+(const Serial& other) const { + return (operator+(other.getValue())); +} + +std::ostream& +operator<<(std::ostream& os, const Serial& serial) { + return (os << serial.getValue()); +} + +} // end namespace dns +} // end namespace isc + diff --git a/src/lib/dns/serial.h b/src/lib/dns/serial.h new file mode 100644 index 0000000..324c046 --- /dev/null +++ b/src/lib/dns/serial.h @@ -0,0 +1,150 @@ +// Copyright (C) 2011-2024 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 SERIAL_H +#define SERIAL_H + +#include <stdint.h> +#include <iostream> + +namespace isc { +namespace dns { + +/// The maximum difference between two serial numbers. If the (plain uint32_t) +/// difference between two serials is greater than this number, the smaller one +/// is considered greater. +const uint32_t MAX_SERIAL_INCREMENT = 2147483647; + +/// Maximum value a serial can have, used in + operator. +const uint64_t MAX_SERIAL_VALUE = 4294967296ull; + +/// \brief This class defines DNS serial numbers and serial arithmetic. +/// +/// DNS Serial number are in essence unsigned 32-bits numbers, with one +/// catch; they should be compared using sequence space arithmetic. +/// So given that they are 32-bits; as soon as the difference between two +/// serial numbers is greater than 2147483647 (2^31 - 1), the lower number +/// (in plain comparison) is considered the higher one. +/// +/// In order to do this as transparently as possible, these numbers are +/// stored in the Serial class, which overrides the basic comparison operators. +/// +/// In this specific context, these operations are called 'serial number +/// arithmetic', and they are defined in RFC 1982. +/// +/// \note RFC 1982 defines everything based on the value SERIAL_BITS. Since +/// the serial number has a fixed length of 32 bits, the values we use are +/// hard-coded, and not computed based on variable bit lengths. +class Serial { +public: + /// \brief Constructor with value + /// + /// \param value The uint32_t value of the serial + explicit Serial(uint32_t value) : value_(value) {} + + /// \brief Copy constructor + Serial(const Serial& other) : value_(other.getValue()) {} + + /// \brief Direct assignment from other Serial + /// + /// \param other The Serial to assign the value from + Serial& operator=(const Serial& other) { + value_ = other.getValue(); + return (*this); + } + + /// \brief Direct assignment from value + /// + /// \param value the uint32_t value to assign + void operator=(uint32_t value) { value_ = value; } + + /// \brief Returns the uint32_t representation of this serial value + /// + /// \return The uint32_t value of this Serial + uint32_t getValue() const { return (value_); } + + /// \brief Returns true if the serial values are equal + /// + /// \return True if the values are equal + bool operator==(const Serial& other) const; + + /// \brief Returns true if the serial values are not equal + /// + /// \return True if the values are not equal + bool operator!=(const Serial& other) const; + + /// \brief Returns true if the serial value of this serial is smaller than + /// the other, according to serial arithmetic as described in RFC 1982 + /// + /// \param other The Serial to compare to + /// + /// \return True if this is smaller than the given value + bool operator<(const Serial& other) const; + + /// \brief Returns true if the serial value of this serial is equal to or + /// smaller than the other, according to serial arithmetic as described + /// in RFC 1982 + /// + /// \param other The Serial to compare to + /// + /// \return True if this is smaller than or equal to the given value + bool operator<=(const Serial& other) const; + + /// \brief Returns true if the serial value of this serial is greater than + /// the other, according to serial arithmetic as described in RFC 1982 + /// + /// \param other The Serial to compare to + /// + /// \return True if this is greater than the given value + bool operator>(const Serial& other) const; + + /// \brief Returns true if the serial value of this serial is equal to or + /// greater than the other, according to serial arithmetic as described in + /// RFC 1982 + /// + /// \param other The Serial to compare to + /// + /// \return True if this is greater than or equal to the given value + bool operator>=(const Serial& other) const; + + /// \brief Adds the given value to the serial number. If this would make + /// the number greater than 2^32-1, it is 'wrapped'. + /// \note According to the specification, an addition greater than + /// MAX_SERIAL_INCREMENT is undefined. We do NOT catch this error (so as not + /// to raise exceptions), but this behaviour remains undefined. + /// + /// \param other The Serial to add + /// + /// \return The result of the addition + Serial operator+(const Serial& other) const; + + /// \brief Adds the given value to the serial number. If this would make + /// the number greater than 2^32-1, it is 'wrapped'. + /// + /// \note According to the specification, an addition greater than + /// MAX_SERIAL_INCREMENT is undefined. We do NOT catch this error (so as not + /// to raise exceptions), but this behaviour remains undefined. + /// + /// \param other_val The uint32_t value to add + /// + /// \return The result of the addition + Serial operator+(uint32_t other_val) const; + +private: + uint32_t value_; +}; + +/// \brief Helper operator for output streams, writes the value to the stream +/// +/// \param os The ostream to write to +/// \param serial The Serial to write +/// \return the output stream +std::ostream& operator<<(std::ostream& os, const Serial& serial); + +} // end namespace dns +} // end namespace isc + +#endif // SERIAL_H diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am new file mode 100644 index 0000000..9594b93 --- /dev/null +++ b/src/lib/dns/tests/Makefile.am @@ -0,0 +1,72 @@ +SUBDIRS = testdata . + +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib +AM_CPPFLAGS += $(BOOST_INCLUDES) +AM_CPPFLAGS += -DTEST_DATA_SRCDIR=\"$(abs_top_srcdir)/src/lib/dns/tests/testdata\" +AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dns/tests/testdata\" +AM_CXXFLAGS = $(KEA_CXXFLAGS) + +if USE_STATIC_LINK +AM_LDFLAGS = -static +endif + +CLEANFILES = *.gcno *.gcda + +TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) + +TESTS = +if HAVE_GTEST +TESTS += run_unittests +run_unittests_SOURCES = unittest_util.h unittest_util.cc +run_unittests_SOURCES += dns_exceptions_unittest.cc +run_unittests_SOURCES += edns_unittest.cc +run_unittests_SOURCES += master_lexer_inputsource_unittest.cc +run_unittests_SOURCES += labelsequence_unittest.cc +run_unittests_SOURCES += messagerenderer_unittest.cc +run_unittests_SOURCES += master_lexer_token_unittest.cc +run_unittests_SOURCES += master_lexer_unittest.cc +run_unittests_SOURCES += master_loader_unittest.cc +run_unittests_SOURCES += master_lexer_state_unittest.cc +run_unittests_SOURCES += name_unittest.cc +run_unittests_SOURCES += rrttl_unittest.cc +run_unittests_SOURCES += rrclass_unittest.cc +run_unittests_SOURCES += rrtype_unittest.cc +run_unittests_SOURCES += opcode_unittest.cc +run_unittests_SOURCES += rcode_unittest.cc +run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc +run_unittests_SOURCES += rdata_char_string_unittest.cc +run_unittests_SOURCES += rdata_char_string_data_unittest.cc +run_unittests_SOURCES += rdata_in_a_unittest.cc +run_unittests_SOURCES += rdata_in_aaaa_unittest.cc +run_unittests_SOURCES += rdata_ns_unittest.cc +run_unittests_SOURCES += rdata_soa_unittest.cc +run_unittests_SOURCES += rdata_txt_like_unittest.cc +run_unittests_SOURCES += rdata_ptr_unittest.cc +run_unittests_SOURCES += rdata_opt_unittest.cc +run_unittests_SOURCES += rdata_dhcid_unittest.cc +run_unittests_SOURCES += rdata_rrsig_unittest.cc +run_unittests_SOURCES += rdata_tsig_unittest.cc +run_unittests_SOURCES += rdata_tkey_unittest.cc +run_unittests_SOURCES += rrset_unittest.cc +run_unittests_SOURCES += question_unittest.cc +run_unittests_SOURCES += rrparamregistry_unittest.cc +run_unittests_SOURCES += message_unittest.cc +run_unittests_SOURCES += serial_unittest.cc +run_unittests_SOURCES += time_utils_unittest.cc +run_unittests_SOURCES += tsig_unittest.cc +run_unittests_SOURCES += tsigerror_unittest.cc +run_unittests_SOURCES += tsigkey_unittest.cc +run_unittests_SOURCES += tsigrecord_unittest.cc +run_unittests_SOURCES += master_loader_callbacks_test.cc +run_unittests_SOURCES += run_unittests.cc +run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) +run_unittests_LDADD = $(top_builddir)/src/lib/dns/libkea-dns++.la +run_unittests_LDADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la +run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la +run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la +run_unittests_LDADD += $(CRYPTO_LIBS) $(GTEST_LDADD) +endif + +noinst_PROGRAMS = $(TESTS) diff --git a/src/lib/dns/tests/Makefile.in b/src/lib/dns/tests/Makefile.in new file mode 100644 index 0000000..e7818a9 --- /dev/null +++ b/src/lib/dns/tests/Makefile.in @@ -0,0 +1,1835 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +TESTS = $(am__EXEEXT_1) +@HAVE_GTEST_TRUE@am__append_1 = run_unittests +noinst_PROGRAMS = $(am__EXEEXT_2) +subdir = src/lib/dns/tests +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp14.m4 \ + $(top_srcdir)/m4macros/ax_cpp20.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_netconf.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +@HAVE_GTEST_TRUE@am__EXEEXT_1 = run_unittests$(EXEEXT) +am__EXEEXT_2 = $(am__EXEEXT_1) +PROGRAMS = $(noinst_PROGRAMS) +am__run_unittests_SOURCES_DIST = unittest_util.h unittest_util.cc \ + dns_exceptions_unittest.cc edns_unittest.cc \ + master_lexer_inputsource_unittest.cc labelsequence_unittest.cc \ + messagerenderer_unittest.cc master_lexer_token_unittest.cc \ + master_lexer_unittest.cc master_loader_unittest.cc \ + master_lexer_state_unittest.cc name_unittest.cc \ + rrttl_unittest.cc rrclass_unittest.cc rrtype_unittest.cc \ + opcode_unittest.cc rcode_unittest.cc rdata_unittest.h \ + rdata_unittest.cc rdata_char_string_unittest.cc \ + rdata_char_string_data_unittest.cc rdata_in_a_unittest.cc \ + rdata_in_aaaa_unittest.cc rdata_ns_unittest.cc \ + rdata_soa_unittest.cc rdata_txt_like_unittest.cc \ + rdata_ptr_unittest.cc rdata_opt_unittest.cc \ + rdata_dhcid_unittest.cc rdata_rrsig_unittest.cc \ + rdata_tsig_unittest.cc rdata_tkey_unittest.cc \ + rrset_unittest.cc question_unittest.cc \ + rrparamregistry_unittest.cc message_unittest.cc \ + serial_unittest.cc time_utils_unittest.cc tsig_unittest.cc \ + tsigerror_unittest.cc tsigkey_unittest.cc \ + tsigrecord_unittest.cc master_loader_callbacks_test.cc \ + run_unittests.cc +@HAVE_GTEST_TRUE@am_run_unittests_OBJECTS = \ +@HAVE_GTEST_TRUE@ run_unittests-unittest_util.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-dns_exceptions_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-edns_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-master_lexer_inputsource_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-labelsequence_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-messagerenderer_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-master_lexer_token_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-master_lexer_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-master_loader_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-master_lexer_state_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-name_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rrttl_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rrclass_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rrtype_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-opcode_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rcode_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_char_string_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_char_string_data_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_in_a_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_in_aaaa_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_ns_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_soa_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_txt_like_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_ptr_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_opt_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_dhcid_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_rrsig_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_tsig_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rdata_tkey_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rrset_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-question_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-rrparamregistry_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-message_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-serial_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-time_utils_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-tsig_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-tsigerror_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-tsigkey_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-tsigrecord_unittest.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-master_loader_callbacks_test.$(OBJEXT) \ +@HAVE_GTEST_TRUE@ run_unittests-run_unittests.$(OBJEXT) +run_unittests_OBJECTS = $(am_run_unittests_OBJECTS) +am__DEPENDENCIES_1 = +@HAVE_GTEST_TRUE@run_unittests_DEPENDENCIES = \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/dns/libkea-dns++.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +run_unittests_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CXXLD) \ + $(AM_CXXFLAGS) $(CXXFLAGS) $(run_unittests_LDFLAGS) $(LDFLAGS) \ + -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = \ + ./$(DEPDIR)/run_unittests-dns_exceptions_unittest.Po \ + ./$(DEPDIR)/run_unittests-edns_unittest.Po \ + ./$(DEPDIR)/run_unittests-labelsequence_unittest.Po \ + ./$(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po \ + ./$(DEPDIR)/run_unittests-master_lexer_state_unittest.Po \ + ./$(DEPDIR)/run_unittests-master_lexer_token_unittest.Po \ + ./$(DEPDIR)/run_unittests-master_lexer_unittest.Po \ + ./$(DEPDIR)/run_unittests-master_loader_callbacks_test.Po \ + ./$(DEPDIR)/run_unittests-master_loader_unittest.Po \ + ./$(DEPDIR)/run_unittests-message_unittest.Po \ + ./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po \ + ./$(DEPDIR)/run_unittests-name_unittest.Po \ + ./$(DEPDIR)/run_unittests-opcode_unittest.Po \ + ./$(DEPDIR)/run_unittests-question_unittest.Po \ + ./$(DEPDIR)/run_unittests-rcode_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_tsig_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po \ + ./$(DEPDIR)/run_unittests-rdata_unittest.Po \ + ./$(DEPDIR)/run_unittests-rrclass_unittest.Po \ + ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po \ + ./$(DEPDIR)/run_unittests-rrset_unittest.Po \ + ./$(DEPDIR)/run_unittests-rrttl_unittest.Po \ + ./$(DEPDIR)/run_unittests-rrtype_unittest.Po \ + ./$(DEPDIR)/run_unittests-run_unittests.Po \ + ./$(DEPDIR)/run_unittests-serial_unittest.Po \ + ./$(DEPDIR)/run_unittests-time_utils_unittest.Po \ + ./$(DEPDIR)/run_unittests-tsig_unittest.Po \ + ./$(DEPDIR)/run_unittests-tsigerror_unittest.Po \ + ./$(DEPDIR)/run_unittests-tsigkey_unittest.Po \ + ./$(DEPDIR)/run_unittests-tsigrecord_unittest.Po \ + ./$(DEPDIR)/run_unittests-unittest_util.Po +am__mv = mv -f +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +AM_V_CXX = $(am__v_CXX_@AM_V@) +am__v_CXX_ = $(am__v_CXX_@AM_DEFAULT_V@) +am__v_CXX_0 = @echo " CXX " $@; +am__v_CXX_1 = +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CXXLD = $(am__v_CXXLD_@AM_V@) +am__v_CXXLD_ = $(am__v_CXXLD_@AM_DEFAULT_V@) +am__v_CXXLD_0 = @echo " CXXLD " $@; +am__v_CXXLD_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(run_unittests_SOURCES) +DIST_SOURCES = $(am__run_unittests_SOURCES_DIST) +RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \ + ctags-recursive dvi-recursive html-recursive info-recursive \ + install-data-recursive install-dvi-recursive \ + install-exec-recursive install-html-recursive \ + install-info-recursive install-pdf-recursive \ + install-ps-recursive install-recursive installcheck-recursive \ + installdirs-recursive pdf-recursive ps-recursive \ + tags-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ + distclean-recursive maintainer-clean-recursive +am__recursive_targets = \ + $(RECURSIVE_TARGETS) \ + $(RECURSIVE_CLEAN_TARGETS) \ + $(am__extra_recursive_targets) +AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ + distdir distdir-am +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +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@ +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@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +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@ +DPKG = @DPKG@ +DPKGQUERY = @DPKGQUERY@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +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@ +XMLLINT = @XMLLINT@ +YACC = @YACC@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +pyexecdir = @pyexecdir@ +pythondir = @pythondir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +SUBDIRS = testdata . +AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib \ + $(BOOST_INCLUDES) \ + -DTEST_DATA_SRCDIR=\"$(abs_top_srcdir)/src/lib/dns/tests/testdata\" \ + -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dns/tests/testdata\" +AM_CXXFLAGS = $(KEA_CXXFLAGS) +@USE_STATIC_LINK_TRUE@AM_LDFLAGS = -static +CLEANFILES = *.gcno *.gcda +TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND) +@HAVE_GTEST_TRUE@run_unittests_SOURCES = unittest_util.h \ +@HAVE_GTEST_TRUE@ unittest_util.cc dns_exceptions_unittest.cc \ +@HAVE_GTEST_TRUE@ edns_unittest.cc \ +@HAVE_GTEST_TRUE@ master_lexer_inputsource_unittest.cc \ +@HAVE_GTEST_TRUE@ labelsequence_unittest.cc \ +@HAVE_GTEST_TRUE@ messagerenderer_unittest.cc \ +@HAVE_GTEST_TRUE@ master_lexer_token_unittest.cc \ +@HAVE_GTEST_TRUE@ master_lexer_unittest.cc \ +@HAVE_GTEST_TRUE@ master_loader_unittest.cc \ +@HAVE_GTEST_TRUE@ master_lexer_state_unittest.cc \ +@HAVE_GTEST_TRUE@ name_unittest.cc rrttl_unittest.cc \ +@HAVE_GTEST_TRUE@ rrclass_unittest.cc rrtype_unittest.cc \ +@HAVE_GTEST_TRUE@ opcode_unittest.cc rcode_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_unittest.h rdata_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_char_string_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_char_string_data_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_in_a_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_in_aaaa_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_ns_unittest.cc rdata_soa_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_txt_like_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_ptr_unittest.cc rdata_opt_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_dhcid_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_rrsig_unittest.cc \ +@HAVE_GTEST_TRUE@ rdata_tsig_unittest.cc rdata_tkey_unittest.cc \ +@HAVE_GTEST_TRUE@ rrset_unittest.cc question_unittest.cc \ +@HAVE_GTEST_TRUE@ rrparamregistry_unittest.cc \ +@HAVE_GTEST_TRUE@ message_unittest.cc serial_unittest.cc \ +@HAVE_GTEST_TRUE@ time_utils_unittest.cc tsig_unittest.cc \ +@HAVE_GTEST_TRUE@ tsigerror_unittest.cc tsigkey_unittest.cc \ +@HAVE_GTEST_TRUE@ tsigrecord_unittest.cc \ +@HAVE_GTEST_TRUE@ master_loader_callbacks_test.cc \ +@HAVE_GTEST_TRUE@ run_unittests.cc +@HAVE_GTEST_TRUE@run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) +@HAVE_GTEST_TRUE@run_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS) +@HAVE_GTEST_TRUE@run_unittests_LDADD = \ +@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/util/unittests/libutil_unittests.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/util/libkea-util.la \ +@HAVE_GTEST_TRUE@ $(top_builddir)/src/lib/exceptions/libkea-exceptions.la \ +@HAVE_GTEST_TRUE@ $(CRYPTO_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/dns/tests/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/dns/tests/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +clean-noinstPROGRAMS: + @list='$(noinst_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +run_unittests$(EXEEXT): $(run_unittests_OBJECTS) $(run_unittests_DEPENDENCIES) $(EXTRA_run_unittests_DEPENDENCIES) + @rm -f run_unittests$(EXEEXT) + $(AM_V_CXXLD)$(run_unittests_LINK) $(run_unittests_OBJECTS) $(run_unittests_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-dns_exceptions_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-edns_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-labelsequence_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_lexer_state_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_lexer_token_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_lexer_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_loader_callbacks_test.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-master_loader_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-message_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-name_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-opcode_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-question_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rcode_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_tsig_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rdata_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrclass_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrset_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrttl_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-rrtype_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-run_unittests.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-serial_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-time_utils_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tsig_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tsigerror_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tsigkey_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-tsigrecord_unittest.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_unittests-unittest_util.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.cc.o: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(LTCXXCOMPILE) -c -o $@ $< + +run_unittests-unittest_util.o: unittest_util.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unittest_util.o -MD -MP -MF $(DEPDIR)/run_unittests-unittest_util.Tpo -c -o run_unittests-unittest_util.o `test -f 'unittest_util.cc' || echo '$(srcdir)/'`unittest_util.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unittest_util.Tpo $(DEPDIR)/run_unittests-unittest_util.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unittest_util.cc' object='run_unittests-unittest_util.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-unittest_util.o `test -f 'unittest_util.cc' || echo '$(srcdir)/'`unittest_util.cc + +run_unittests-unittest_util.obj: unittest_util.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-unittest_util.obj -MD -MP -MF $(DEPDIR)/run_unittests-unittest_util.Tpo -c -o run_unittests-unittest_util.obj `if test -f 'unittest_util.cc'; then $(CYGPATH_W) 'unittest_util.cc'; else $(CYGPATH_W) '$(srcdir)/unittest_util.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-unittest_util.Tpo $(DEPDIR)/run_unittests-unittest_util.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='unittest_util.cc' object='run_unittests-unittest_util.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-unittest_util.obj `if test -f 'unittest_util.cc'; then $(CYGPATH_W) 'unittest_util.cc'; else $(CYGPATH_W) '$(srcdir)/unittest_util.cc'; fi` + +run_unittests-dns_exceptions_unittest.o: dns_exceptions_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dns_exceptions_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-dns_exceptions_unittest.Tpo -c -o run_unittests-dns_exceptions_unittest.o `test -f 'dns_exceptions_unittest.cc' || echo '$(srcdir)/'`dns_exceptions_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dns_exceptions_unittest.Tpo $(DEPDIR)/run_unittests-dns_exceptions_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_exceptions_unittest.cc' object='run_unittests-dns_exceptions_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-dns_exceptions_unittest.o `test -f 'dns_exceptions_unittest.cc' || echo '$(srcdir)/'`dns_exceptions_unittest.cc + +run_unittests-dns_exceptions_unittest.obj: dns_exceptions_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-dns_exceptions_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-dns_exceptions_unittest.Tpo -c -o run_unittests-dns_exceptions_unittest.obj `if test -f 'dns_exceptions_unittest.cc'; then $(CYGPATH_W) 'dns_exceptions_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dns_exceptions_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-dns_exceptions_unittest.Tpo $(DEPDIR)/run_unittests-dns_exceptions_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='dns_exceptions_unittest.cc' object='run_unittests-dns_exceptions_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-dns_exceptions_unittest.obj `if test -f 'dns_exceptions_unittest.cc'; then $(CYGPATH_W) 'dns_exceptions_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/dns_exceptions_unittest.cc'; fi` + +run_unittests-edns_unittest.o: edns_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-edns_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-edns_unittest.Tpo -c -o run_unittests-edns_unittest.o `test -f 'edns_unittest.cc' || echo '$(srcdir)/'`edns_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-edns_unittest.Tpo $(DEPDIR)/run_unittests-edns_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='edns_unittest.cc' object='run_unittests-edns_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-edns_unittest.o `test -f 'edns_unittest.cc' || echo '$(srcdir)/'`edns_unittest.cc + +run_unittests-edns_unittest.obj: edns_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-edns_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-edns_unittest.Tpo -c -o run_unittests-edns_unittest.obj `if test -f 'edns_unittest.cc'; then $(CYGPATH_W) 'edns_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/edns_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-edns_unittest.Tpo $(DEPDIR)/run_unittests-edns_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='edns_unittest.cc' object='run_unittests-edns_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-edns_unittest.obj `if test -f 'edns_unittest.cc'; then $(CYGPATH_W) 'edns_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/edns_unittest.cc'; fi` + +run_unittests-master_lexer_inputsource_unittest.o: master_lexer_inputsource_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_inputsource_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Tpo -c -o run_unittests-master_lexer_inputsource_unittest.o `test -f 'master_lexer_inputsource_unittest.cc' || echo '$(srcdir)/'`master_lexer_inputsource_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_inputsource_unittest.cc' object='run_unittests-master_lexer_inputsource_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_inputsource_unittest.o `test -f 'master_lexer_inputsource_unittest.cc' || echo '$(srcdir)/'`master_lexer_inputsource_unittest.cc + +run_unittests-master_lexer_inputsource_unittest.obj: master_lexer_inputsource_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_inputsource_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Tpo -c -o run_unittests-master_lexer_inputsource_unittest.obj `if test -f 'master_lexer_inputsource_unittest.cc'; then $(CYGPATH_W) 'master_lexer_inputsource_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_inputsource_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_inputsource_unittest.cc' object='run_unittests-master_lexer_inputsource_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_inputsource_unittest.obj `if test -f 'master_lexer_inputsource_unittest.cc'; then $(CYGPATH_W) 'master_lexer_inputsource_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_inputsource_unittest.cc'; fi` + +run_unittests-labelsequence_unittest.o: labelsequence_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-labelsequence_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-labelsequence_unittest.Tpo -c -o run_unittests-labelsequence_unittest.o `test -f 'labelsequence_unittest.cc' || echo '$(srcdir)/'`labelsequence_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-labelsequence_unittest.Tpo $(DEPDIR)/run_unittests-labelsequence_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labelsequence_unittest.cc' object='run_unittests-labelsequence_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-labelsequence_unittest.o `test -f 'labelsequence_unittest.cc' || echo '$(srcdir)/'`labelsequence_unittest.cc + +run_unittests-labelsequence_unittest.obj: labelsequence_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-labelsequence_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-labelsequence_unittest.Tpo -c -o run_unittests-labelsequence_unittest.obj `if test -f 'labelsequence_unittest.cc'; then $(CYGPATH_W) 'labelsequence_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/labelsequence_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-labelsequence_unittest.Tpo $(DEPDIR)/run_unittests-labelsequence_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='labelsequence_unittest.cc' object='run_unittests-labelsequence_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-labelsequence_unittest.obj `if test -f 'labelsequence_unittest.cc'; then $(CYGPATH_W) 'labelsequence_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/labelsequence_unittest.cc'; fi` + +run_unittests-messagerenderer_unittest.o: messagerenderer_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-messagerenderer_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-messagerenderer_unittest.Tpo -c -o run_unittests-messagerenderer_unittest.o `test -f 'messagerenderer_unittest.cc' || echo '$(srcdir)/'`messagerenderer_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-messagerenderer_unittest.Tpo $(DEPDIR)/run_unittests-messagerenderer_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='messagerenderer_unittest.cc' object='run_unittests-messagerenderer_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-messagerenderer_unittest.o `test -f 'messagerenderer_unittest.cc' || echo '$(srcdir)/'`messagerenderer_unittest.cc + +run_unittests-messagerenderer_unittest.obj: messagerenderer_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-messagerenderer_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-messagerenderer_unittest.Tpo -c -o run_unittests-messagerenderer_unittest.obj `if test -f 'messagerenderer_unittest.cc'; then $(CYGPATH_W) 'messagerenderer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/messagerenderer_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-messagerenderer_unittest.Tpo $(DEPDIR)/run_unittests-messagerenderer_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='messagerenderer_unittest.cc' object='run_unittests-messagerenderer_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-messagerenderer_unittest.obj `if test -f 'messagerenderer_unittest.cc'; then $(CYGPATH_W) 'messagerenderer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/messagerenderer_unittest.cc'; fi` + +run_unittests-master_lexer_token_unittest.o: master_lexer_token_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_token_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_token_unittest.Tpo -c -o run_unittests-master_lexer_token_unittest.o `test -f 'master_lexer_token_unittest.cc' || echo '$(srcdir)/'`master_lexer_token_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_token_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_token_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_token_unittest.cc' object='run_unittests-master_lexer_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_token_unittest.o `test -f 'master_lexer_token_unittest.cc' || echo '$(srcdir)/'`master_lexer_token_unittest.cc + +run_unittests-master_lexer_token_unittest.obj: master_lexer_token_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_token_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_token_unittest.Tpo -c -o run_unittests-master_lexer_token_unittest.obj `if test -f 'master_lexer_token_unittest.cc'; then $(CYGPATH_W) 'master_lexer_token_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_token_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_token_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_token_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_token_unittest.cc' object='run_unittests-master_lexer_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) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_token_unittest.obj `if test -f 'master_lexer_token_unittest.cc'; then $(CYGPATH_W) 'master_lexer_token_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_token_unittest.cc'; fi` + +run_unittests-master_lexer_unittest.o: master_lexer_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_unittest.Tpo -c -o run_unittests-master_lexer_unittest.o `test -f 'master_lexer_unittest.cc' || echo '$(srcdir)/'`master_lexer_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_unittest.cc' object='run_unittests-master_lexer_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_unittest.o `test -f 'master_lexer_unittest.cc' || echo '$(srcdir)/'`master_lexer_unittest.cc + +run_unittests-master_lexer_unittest.obj: master_lexer_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_unittest.Tpo -c -o run_unittests-master_lexer_unittest.obj `if test -f 'master_lexer_unittest.cc'; then $(CYGPATH_W) 'master_lexer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_unittest.cc' object='run_unittests-master_lexer_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_unittest.obj `if test -f 'master_lexer_unittest.cc'; then $(CYGPATH_W) 'master_lexer_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_unittest.cc'; fi` + +run_unittests-master_loader_unittest.o: master_loader_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_loader_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_loader_unittest.Tpo -c -o run_unittests-master_loader_unittest.o `test -f 'master_loader_unittest.cc' || echo '$(srcdir)/'`master_loader_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_loader_unittest.Tpo $(DEPDIR)/run_unittests-master_loader_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_unittest.cc' object='run_unittests-master_loader_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_loader_unittest.o `test -f 'master_loader_unittest.cc' || echo '$(srcdir)/'`master_loader_unittest.cc + +run_unittests-master_loader_unittest.obj: master_loader_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_loader_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_loader_unittest.Tpo -c -o run_unittests-master_loader_unittest.obj `if test -f 'master_loader_unittest.cc'; then $(CYGPATH_W) 'master_loader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_loader_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_loader_unittest.Tpo $(DEPDIR)/run_unittests-master_loader_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_unittest.cc' object='run_unittests-master_loader_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_loader_unittest.obj `if test -f 'master_loader_unittest.cc'; then $(CYGPATH_W) 'master_loader_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_loader_unittest.cc'; fi` + +run_unittests-master_lexer_state_unittest.o: master_lexer_state_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_state_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_state_unittest.Tpo -c -o run_unittests-master_lexer_state_unittest.o `test -f 'master_lexer_state_unittest.cc' || echo '$(srcdir)/'`master_lexer_state_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_state_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_state_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_state_unittest.cc' object='run_unittests-master_lexer_state_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_state_unittest.o `test -f 'master_lexer_state_unittest.cc' || echo '$(srcdir)/'`master_lexer_state_unittest.cc + +run_unittests-master_lexer_state_unittest.obj: master_lexer_state_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_lexer_state_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_lexer_state_unittest.Tpo -c -o run_unittests-master_lexer_state_unittest.obj `if test -f 'master_lexer_state_unittest.cc'; then $(CYGPATH_W) 'master_lexer_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_state_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_lexer_state_unittest.Tpo $(DEPDIR)/run_unittests-master_lexer_state_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_lexer_state_unittest.cc' object='run_unittests-master_lexer_state_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_lexer_state_unittest.obj `if test -f 'master_lexer_state_unittest.cc'; then $(CYGPATH_W) 'master_lexer_state_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/master_lexer_state_unittest.cc'; fi` + +run_unittests-name_unittest.o: name_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-name_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-name_unittest.Tpo -c -o run_unittests-name_unittest.o `test -f 'name_unittest.cc' || echo '$(srcdir)/'`name_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-name_unittest.Tpo $(DEPDIR)/run_unittests-name_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='name_unittest.cc' object='run_unittests-name_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-name_unittest.o `test -f 'name_unittest.cc' || echo '$(srcdir)/'`name_unittest.cc + +run_unittests-name_unittest.obj: name_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-name_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-name_unittest.Tpo -c -o run_unittests-name_unittest.obj `if test -f 'name_unittest.cc'; then $(CYGPATH_W) 'name_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/name_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-name_unittest.Tpo $(DEPDIR)/run_unittests-name_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='name_unittest.cc' object='run_unittests-name_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-name_unittest.obj `if test -f 'name_unittest.cc'; then $(CYGPATH_W) 'name_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/name_unittest.cc'; fi` + +run_unittests-rrttl_unittest.o: rrttl_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrttl_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrttl_unittest.Tpo -c -o run_unittests-rrttl_unittest.o `test -f 'rrttl_unittest.cc' || echo '$(srcdir)/'`rrttl_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrttl_unittest.Tpo $(DEPDIR)/run_unittests-rrttl_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrttl_unittest.cc' object='run_unittests-rrttl_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrttl_unittest.o `test -f 'rrttl_unittest.cc' || echo '$(srcdir)/'`rrttl_unittest.cc + +run_unittests-rrttl_unittest.obj: rrttl_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrttl_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrttl_unittest.Tpo -c -o run_unittests-rrttl_unittest.obj `if test -f 'rrttl_unittest.cc'; then $(CYGPATH_W) 'rrttl_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrttl_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrttl_unittest.Tpo $(DEPDIR)/run_unittests-rrttl_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrttl_unittest.cc' object='run_unittests-rrttl_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrttl_unittest.obj `if test -f 'rrttl_unittest.cc'; then $(CYGPATH_W) 'rrttl_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrttl_unittest.cc'; fi` + +run_unittests-rrclass_unittest.o: rrclass_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrclass_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrclass_unittest.Tpo -c -o run_unittests-rrclass_unittest.o `test -f 'rrclass_unittest.cc' || echo '$(srcdir)/'`rrclass_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrclass_unittest.Tpo $(DEPDIR)/run_unittests-rrclass_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrclass_unittest.cc' object='run_unittests-rrclass_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrclass_unittest.o `test -f 'rrclass_unittest.cc' || echo '$(srcdir)/'`rrclass_unittest.cc + +run_unittests-rrclass_unittest.obj: rrclass_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrclass_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrclass_unittest.Tpo -c -o run_unittests-rrclass_unittest.obj `if test -f 'rrclass_unittest.cc'; then $(CYGPATH_W) 'rrclass_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrclass_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrclass_unittest.Tpo $(DEPDIR)/run_unittests-rrclass_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrclass_unittest.cc' object='run_unittests-rrclass_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrclass_unittest.obj `if test -f 'rrclass_unittest.cc'; then $(CYGPATH_W) 'rrclass_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrclass_unittest.cc'; fi` + +run_unittests-rrtype_unittest.o: rrtype_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrtype_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrtype_unittest.Tpo -c -o run_unittests-rrtype_unittest.o `test -f 'rrtype_unittest.cc' || echo '$(srcdir)/'`rrtype_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrtype_unittest.Tpo $(DEPDIR)/run_unittests-rrtype_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrtype_unittest.cc' object='run_unittests-rrtype_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrtype_unittest.o `test -f 'rrtype_unittest.cc' || echo '$(srcdir)/'`rrtype_unittest.cc + +run_unittests-rrtype_unittest.obj: rrtype_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrtype_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrtype_unittest.Tpo -c -o run_unittests-rrtype_unittest.obj `if test -f 'rrtype_unittest.cc'; then $(CYGPATH_W) 'rrtype_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrtype_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrtype_unittest.Tpo $(DEPDIR)/run_unittests-rrtype_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrtype_unittest.cc' object='run_unittests-rrtype_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrtype_unittest.obj `if test -f 'rrtype_unittest.cc'; then $(CYGPATH_W) 'rrtype_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrtype_unittest.cc'; fi` + +run_unittests-opcode_unittest.o: opcode_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-opcode_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-opcode_unittest.Tpo -c -o run_unittests-opcode_unittest.o `test -f 'opcode_unittest.cc' || echo '$(srcdir)/'`opcode_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-opcode_unittest.Tpo $(DEPDIR)/run_unittests-opcode_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opcode_unittest.cc' object='run_unittests-opcode_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-opcode_unittest.o `test -f 'opcode_unittest.cc' || echo '$(srcdir)/'`opcode_unittest.cc + +run_unittests-opcode_unittest.obj: opcode_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-opcode_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-opcode_unittest.Tpo -c -o run_unittests-opcode_unittest.obj `if test -f 'opcode_unittest.cc'; then $(CYGPATH_W) 'opcode_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opcode_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-opcode_unittest.Tpo $(DEPDIR)/run_unittests-opcode_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='opcode_unittest.cc' object='run_unittests-opcode_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-opcode_unittest.obj `if test -f 'opcode_unittest.cc'; then $(CYGPATH_W) 'opcode_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/opcode_unittest.cc'; fi` + +run_unittests-rcode_unittest.o: rcode_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rcode_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rcode_unittest.Tpo -c -o run_unittests-rcode_unittest.o `test -f 'rcode_unittest.cc' || echo '$(srcdir)/'`rcode_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rcode_unittest.Tpo $(DEPDIR)/run_unittests-rcode_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rcode_unittest.cc' object='run_unittests-rcode_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rcode_unittest.o `test -f 'rcode_unittest.cc' || echo '$(srcdir)/'`rcode_unittest.cc + +run_unittests-rcode_unittest.obj: rcode_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rcode_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rcode_unittest.Tpo -c -o run_unittests-rcode_unittest.obj `if test -f 'rcode_unittest.cc'; then $(CYGPATH_W) 'rcode_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rcode_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rcode_unittest.Tpo $(DEPDIR)/run_unittests-rcode_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rcode_unittest.cc' object='run_unittests-rcode_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rcode_unittest.obj `if test -f 'rcode_unittest.cc'; then $(CYGPATH_W) 'rcode_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rcode_unittest.cc'; fi` + +run_unittests-rdata_unittest.o: rdata_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_unittest.Tpo -c -o run_unittests-rdata_unittest.o `test -f 'rdata_unittest.cc' || echo '$(srcdir)/'`rdata_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_unittest.Tpo $(DEPDIR)/run_unittests-rdata_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_unittest.cc' object='run_unittests-rdata_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_unittest.o `test -f 'rdata_unittest.cc' || echo '$(srcdir)/'`rdata_unittest.cc + +run_unittests-rdata_unittest.obj: rdata_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_unittest.Tpo -c -o run_unittests-rdata_unittest.obj `if test -f 'rdata_unittest.cc'; then $(CYGPATH_W) 'rdata_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_unittest.Tpo $(DEPDIR)/run_unittests-rdata_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_unittest.cc' object='run_unittests-rdata_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_unittest.obj `if test -f 'rdata_unittest.cc'; then $(CYGPATH_W) 'rdata_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_unittest.cc'; fi` + +run_unittests-rdata_char_string_unittest.o: rdata_char_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_char_string_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_char_string_unittest.Tpo -c -o run_unittests-rdata_char_string_unittest.o `test -f 'rdata_char_string_unittest.cc' || echo '$(srcdir)/'`rdata_char_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_char_string_unittest.Tpo $(DEPDIR)/run_unittests-rdata_char_string_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_char_string_unittest.cc' object='run_unittests-rdata_char_string_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_char_string_unittest.o `test -f 'rdata_char_string_unittest.cc' || echo '$(srcdir)/'`rdata_char_string_unittest.cc + +run_unittests-rdata_char_string_unittest.obj: rdata_char_string_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_char_string_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_char_string_unittest.Tpo -c -o run_unittests-rdata_char_string_unittest.obj `if test -f 'rdata_char_string_unittest.cc'; then $(CYGPATH_W) 'rdata_char_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_char_string_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_char_string_unittest.Tpo $(DEPDIR)/run_unittests-rdata_char_string_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_char_string_unittest.cc' object='run_unittests-rdata_char_string_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_char_string_unittest.obj `if test -f 'rdata_char_string_unittest.cc'; then $(CYGPATH_W) 'rdata_char_string_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_char_string_unittest.cc'; fi` + +run_unittests-rdata_char_string_data_unittest.o: rdata_char_string_data_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_char_string_data_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Tpo -c -o run_unittests-rdata_char_string_data_unittest.o `test -f 'rdata_char_string_data_unittest.cc' || echo '$(srcdir)/'`rdata_char_string_data_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Tpo $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_char_string_data_unittest.cc' object='run_unittests-rdata_char_string_data_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_char_string_data_unittest.o `test -f 'rdata_char_string_data_unittest.cc' || echo '$(srcdir)/'`rdata_char_string_data_unittest.cc + +run_unittests-rdata_char_string_data_unittest.obj: rdata_char_string_data_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_char_string_data_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Tpo -c -o run_unittests-rdata_char_string_data_unittest.obj `if test -f 'rdata_char_string_data_unittest.cc'; then $(CYGPATH_W) 'rdata_char_string_data_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_char_string_data_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Tpo $(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_char_string_data_unittest.cc' object='run_unittests-rdata_char_string_data_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_char_string_data_unittest.obj `if test -f 'rdata_char_string_data_unittest.cc'; then $(CYGPATH_W) 'rdata_char_string_data_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_char_string_data_unittest.cc'; fi` + +run_unittests-rdata_in_a_unittest.o: rdata_in_a_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_in_a_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_in_a_unittest.Tpo -c -o run_unittests-rdata_in_a_unittest.o `test -f 'rdata_in_a_unittest.cc' || echo '$(srcdir)/'`rdata_in_a_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_in_a_unittest.Tpo $(DEPDIR)/run_unittests-rdata_in_a_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_in_a_unittest.cc' object='run_unittests-rdata_in_a_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_in_a_unittest.o `test -f 'rdata_in_a_unittest.cc' || echo '$(srcdir)/'`rdata_in_a_unittest.cc + +run_unittests-rdata_in_a_unittest.obj: rdata_in_a_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_in_a_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_in_a_unittest.Tpo -c -o run_unittests-rdata_in_a_unittest.obj `if test -f 'rdata_in_a_unittest.cc'; then $(CYGPATH_W) 'rdata_in_a_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_in_a_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_in_a_unittest.Tpo $(DEPDIR)/run_unittests-rdata_in_a_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_in_a_unittest.cc' object='run_unittests-rdata_in_a_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_in_a_unittest.obj `if test -f 'rdata_in_a_unittest.cc'; then $(CYGPATH_W) 'rdata_in_a_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_in_a_unittest.cc'; fi` + +run_unittests-rdata_in_aaaa_unittest.o: rdata_in_aaaa_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_in_aaaa_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Tpo -c -o run_unittests-rdata_in_aaaa_unittest.o `test -f 'rdata_in_aaaa_unittest.cc' || echo '$(srcdir)/'`rdata_in_aaaa_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_in_aaaa_unittest.cc' object='run_unittests-rdata_in_aaaa_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_in_aaaa_unittest.o `test -f 'rdata_in_aaaa_unittest.cc' || echo '$(srcdir)/'`rdata_in_aaaa_unittest.cc + +run_unittests-rdata_in_aaaa_unittest.obj: rdata_in_aaaa_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_in_aaaa_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Tpo -c -o run_unittests-rdata_in_aaaa_unittest.obj `if test -f 'rdata_in_aaaa_unittest.cc'; then $(CYGPATH_W) 'rdata_in_aaaa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_in_aaaa_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_in_aaaa_unittest.cc' object='run_unittests-rdata_in_aaaa_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_in_aaaa_unittest.obj `if test -f 'rdata_in_aaaa_unittest.cc'; then $(CYGPATH_W) 'rdata_in_aaaa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_in_aaaa_unittest.cc'; fi` + +run_unittests-rdata_ns_unittest.o: rdata_ns_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_ns_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ns_unittest.Tpo -c -o run_unittests-rdata_ns_unittest.o `test -f 'rdata_ns_unittest.cc' || echo '$(srcdir)/'`rdata_ns_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ns_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ns_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ns_unittest.cc' object='run_unittests-rdata_ns_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_ns_unittest.o `test -f 'rdata_ns_unittest.cc' || echo '$(srcdir)/'`rdata_ns_unittest.cc + +run_unittests-rdata_ns_unittest.obj: rdata_ns_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_ns_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ns_unittest.Tpo -c -o run_unittests-rdata_ns_unittest.obj `if test -f 'rdata_ns_unittest.cc'; then $(CYGPATH_W) 'rdata_ns_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ns_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ns_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ns_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ns_unittest.cc' object='run_unittests-rdata_ns_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_ns_unittest.obj `if test -f 'rdata_ns_unittest.cc'; then $(CYGPATH_W) 'rdata_ns_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ns_unittest.cc'; fi` + +run_unittests-rdata_soa_unittest.o: rdata_soa_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_soa_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_soa_unittest.Tpo -c -o run_unittests-rdata_soa_unittest.o `test -f 'rdata_soa_unittest.cc' || echo '$(srcdir)/'`rdata_soa_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_soa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_soa_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_soa_unittest.cc' object='run_unittests-rdata_soa_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_soa_unittest.o `test -f 'rdata_soa_unittest.cc' || echo '$(srcdir)/'`rdata_soa_unittest.cc + +run_unittests-rdata_soa_unittest.obj: rdata_soa_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_soa_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_soa_unittest.Tpo -c -o run_unittests-rdata_soa_unittest.obj `if test -f 'rdata_soa_unittest.cc'; then $(CYGPATH_W) 'rdata_soa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_soa_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_soa_unittest.Tpo $(DEPDIR)/run_unittests-rdata_soa_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_soa_unittest.cc' object='run_unittests-rdata_soa_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_soa_unittest.obj `if test -f 'rdata_soa_unittest.cc'; then $(CYGPATH_W) 'rdata_soa_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_soa_unittest.cc'; fi` + +run_unittests-rdata_txt_like_unittest.o: rdata_txt_like_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_txt_like_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Tpo -c -o run_unittests-rdata_txt_like_unittest.o `test -f 'rdata_txt_like_unittest.cc' || echo '$(srcdir)/'`rdata_txt_like_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Tpo $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_txt_like_unittest.cc' object='run_unittests-rdata_txt_like_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_txt_like_unittest.o `test -f 'rdata_txt_like_unittest.cc' || echo '$(srcdir)/'`rdata_txt_like_unittest.cc + +run_unittests-rdata_txt_like_unittest.obj: rdata_txt_like_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_txt_like_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Tpo -c -o run_unittests-rdata_txt_like_unittest.obj `if test -f 'rdata_txt_like_unittest.cc'; then $(CYGPATH_W) 'rdata_txt_like_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_txt_like_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Tpo $(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_txt_like_unittest.cc' object='run_unittests-rdata_txt_like_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_txt_like_unittest.obj `if test -f 'rdata_txt_like_unittest.cc'; then $(CYGPATH_W) 'rdata_txt_like_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_txt_like_unittest.cc'; fi` + +run_unittests-rdata_ptr_unittest.o: rdata_ptr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_ptr_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ptr_unittest.Tpo -c -o run_unittests-rdata_ptr_unittest.o `test -f 'rdata_ptr_unittest.cc' || echo '$(srcdir)/'`rdata_ptr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ptr_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ptr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ptr_unittest.cc' object='run_unittests-rdata_ptr_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_ptr_unittest.o `test -f 'rdata_ptr_unittest.cc' || echo '$(srcdir)/'`rdata_ptr_unittest.cc + +run_unittests-rdata_ptr_unittest.obj: rdata_ptr_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_ptr_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_ptr_unittest.Tpo -c -o run_unittests-rdata_ptr_unittest.obj `if test -f 'rdata_ptr_unittest.cc'; then $(CYGPATH_W) 'rdata_ptr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ptr_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_ptr_unittest.Tpo $(DEPDIR)/run_unittests-rdata_ptr_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_ptr_unittest.cc' object='run_unittests-rdata_ptr_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_ptr_unittest.obj `if test -f 'rdata_ptr_unittest.cc'; then $(CYGPATH_W) 'rdata_ptr_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_ptr_unittest.cc'; fi` + +run_unittests-rdata_opt_unittest.o: rdata_opt_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_opt_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_opt_unittest.Tpo -c -o run_unittests-rdata_opt_unittest.o `test -f 'rdata_opt_unittest.cc' || echo '$(srcdir)/'`rdata_opt_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_opt_unittest.Tpo $(DEPDIR)/run_unittests-rdata_opt_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_opt_unittest.cc' object='run_unittests-rdata_opt_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_opt_unittest.o `test -f 'rdata_opt_unittest.cc' || echo '$(srcdir)/'`rdata_opt_unittest.cc + +run_unittests-rdata_opt_unittest.obj: rdata_opt_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_opt_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_opt_unittest.Tpo -c -o run_unittests-rdata_opt_unittest.obj `if test -f 'rdata_opt_unittest.cc'; then $(CYGPATH_W) 'rdata_opt_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_opt_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_opt_unittest.Tpo $(DEPDIR)/run_unittests-rdata_opt_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_opt_unittest.cc' object='run_unittests-rdata_opt_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_opt_unittest.obj `if test -f 'rdata_opt_unittest.cc'; then $(CYGPATH_W) 'rdata_opt_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_opt_unittest.cc'; fi` + +run_unittests-rdata_dhcid_unittest.o: rdata_dhcid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_dhcid_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Tpo -c -o run_unittests-rdata_dhcid_unittest.o `test -f 'rdata_dhcid_unittest.cc' || echo '$(srcdir)/'`rdata_dhcid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Tpo $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_dhcid_unittest.cc' object='run_unittests-rdata_dhcid_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_dhcid_unittest.o `test -f 'rdata_dhcid_unittest.cc' || echo '$(srcdir)/'`rdata_dhcid_unittest.cc + +run_unittests-rdata_dhcid_unittest.obj: rdata_dhcid_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_dhcid_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Tpo -c -o run_unittests-rdata_dhcid_unittest.obj `if test -f 'rdata_dhcid_unittest.cc'; then $(CYGPATH_W) 'rdata_dhcid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_dhcid_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Tpo $(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_dhcid_unittest.cc' object='run_unittests-rdata_dhcid_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_dhcid_unittest.obj `if test -f 'rdata_dhcid_unittest.cc'; then $(CYGPATH_W) 'rdata_dhcid_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_dhcid_unittest.cc'; fi` + +run_unittests-rdata_rrsig_unittest.o: rdata_rrsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_rrsig_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Tpo -c -o run_unittests-rdata_rrsig_unittest.o `test -f 'rdata_rrsig_unittest.cc' || echo '$(srcdir)/'`rdata_rrsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Tpo $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_rrsig_unittest.cc' object='run_unittests-rdata_rrsig_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_rrsig_unittest.o `test -f 'rdata_rrsig_unittest.cc' || echo '$(srcdir)/'`rdata_rrsig_unittest.cc + +run_unittests-rdata_rrsig_unittest.obj: rdata_rrsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_rrsig_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Tpo -c -o run_unittests-rdata_rrsig_unittest.obj `if test -f 'rdata_rrsig_unittest.cc'; then $(CYGPATH_W) 'rdata_rrsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_rrsig_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Tpo $(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_rrsig_unittest.cc' object='run_unittests-rdata_rrsig_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_rrsig_unittest.obj `if test -f 'rdata_rrsig_unittest.cc'; then $(CYGPATH_W) 'rdata_rrsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_rrsig_unittest.cc'; fi` + +run_unittests-rdata_tsig_unittest.o: rdata_tsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tsig_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tsig_unittest.Tpo -c -o run_unittests-rdata_tsig_unittest.o `test -f 'rdata_tsig_unittest.cc' || echo '$(srcdir)/'`rdata_tsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tsig_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tsig_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tsig_unittest.cc' object='run_unittests-rdata_tsig_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_tsig_unittest.o `test -f 'rdata_tsig_unittest.cc' || echo '$(srcdir)/'`rdata_tsig_unittest.cc + +run_unittests-rdata_tsig_unittest.obj: rdata_tsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tsig_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tsig_unittest.Tpo -c -o run_unittests-rdata_tsig_unittest.obj `if test -f 'rdata_tsig_unittest.cc'; then $(CYGPATH_W) 'rdata_tsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tsig_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tsig_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tsig_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tsig_unittest.cc' object='run_unittests-rdata_tsig_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_tsig_unittest.obj `if test -f 'rdata_tsig_unittest.cc'; then $(CYGPATH_W) 'rdata_tsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tsig_unittest.cc'; fi` + +run_unittests-rdata_tkey_unittest.o: rdata_tkey_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tkey_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tkey_unittest.Tpo -c -o run_unittests-rdata_tkey_unittest.o `test -f 'rdata_tkey_unittest.cc' || echo '$(srcdir)/'`rdata_tkey_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tkey_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tkey_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tkey_unittest.cc' object='run_unittests-rdata_tkey_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_tkey_unittest.o `test -f 'rdata_tkey_unittest.cc' || echo '$(srcdir)/'`rdata_tkey_unittest.cc + +run_unittests-rdata_tkey_unittest.obj: rdata_tkey_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rdata_tkey_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rdata_tkey_unittest.Tpo -c -o run_unittests-rdata_tkey_unittest.obj `if test -f 'rdata_tkey_unittest.cc'; then $(CYGPATH_W) 'rdata_tkey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tkey_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rdata_tkey_unittest.Tpo $(DEPDIR)/run_unittests-rdata_tkey_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rdata_tkey_unittest.cc' object='run_unittests-rdata_tkey_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rdata_tkey_unittest.obj `if test -f 'rdata_tkey_unittest.cc'; then $(CYGPATH_W) 'rdata_tkey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rdata_tkey_unittest.cc'; fi` + +run_unittests-rrset_unittest.o: rrset_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrset_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrset_unittest.Tpo -c -o run_unittests-rrset_unittest.o `test -f 'rrset_unittest.cc' || echo '$(srcdir)/'`rrset_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrset_unittest.Tpo $(DEPDIR)/run_unittests-rrset_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset_unittest.cc' object='run_unittests-rrset_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrset_unittest.o `test -f 'rrset_unittest.cc' || echo '$(srcdir)/'`rrset_unittest.cc + +run_unittests-rrset_unittest.obj: rrset_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrset_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrset_unittest.Tpo -c -o run_unittests-rrset_unittest.obj `if test -f 'rrset_unittest.cc'; then $(CYGPATH_W) 'rrset_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrset_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrset_unittest.Tpo $(DEPDIR)/run_unittests-rrset_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrset_unittest.cc' object='run_unittests-rrset_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrset_unittest.obj `if test -f 'rrset_unittest.cc'; then $(CYGPATH_W) 'rrset_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrset_unittest.cc'; fi` + +run_unittests-question_unittest.o: question_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-question_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-question_unittest.Tpo -c -o run_unittests-question_unittest.o `test -f 'question_unittest.cc' || echo '$(srcdir)/'`question_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-question_unittest.Tpo $(DEPDIR)/run_unittests-question_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='question_unittest.cc' object='run_unittests-question_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-question_unittest.o `test -f 'question_unittest.cc' || echo '$(srcdir)/'`question_unittest.cc + +run_unittests-question_unittest.obj: question_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-question_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-question_unittest.Tpo -c -o run_unittests-question_unittest.obj `if test -f 'question_unittest.cc'; then $(CYGPATH_W) 'question_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/question_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-question_unittest.Tpo $(DEPDIR)/run_unittests-question_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='question_unittest.cc' object='run_unittests-question_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-question_unittest.obj `if test -f 'question_unittest.cc'; then $(CYGPATH_W) 'question_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/question_unittest.cc'; fi` + +run_unittests-rrparamregistry_unittest.o: rrparamregistry_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrparamregistry_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-rrparamregistry_unittest.Tpo -c -o run_unittests-rrparamregistry_unittest.o `test -f 'rrparamregistry_unittest.cc' || echo '$(srcdir)/'`rrparamregistry_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrparamregistry_unittest.Tpo $(DEPDIR)/run_unittests-rrparamregistry_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrparamregistry_unittest.cc' object='run_unittests-rrparamregistry_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrparamregistry_unittest.o `test -f 'rrparamregistry_unittest.cc' || echo '$(srcdir)/'`rrparamregistry_unittest.cc + +run_unittests-rrparamregistry_unittest.obj: rrparamregistry_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-rrparamregistry_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-rrparamregistry_unittest.Tpo -c -o run_unittests-rrparamregistry_unittest.obj `if test -f 'rrparamregistry_unittest.cc'; then $(CYGPATH_W) 'rrparamregistry_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrparamregistry_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-rrparamregistry_unittest.Tpo $(DEPDIR)/run_unittests-rrparamregistry_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='rrparamregistry_unittest.cc' object='run_unittests-rrparamregistry_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-rrparamregistry_unittest.obj `if test -f 'rrparamregistry_unittest.cc'; then $(CYGPATH_W) 'rrparamregistry_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/rrparamregistry_unittest.cc'; fi` + +run_unittests-message_unittest.o: message_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-message_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-message_unittest.Tpo -c -o run_unittests-message_unittest.o `test -f 'message_unittest.cc' || echo '$(srcdir)/'`message_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-message_unittest.Tpo $(DEPDIR)/run_unittests-message_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_unittest.cc' object='run_unittests-message_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-message_unittest.o `test -f 'message_unittest.cc' || echo '$(srcdir)/'`message_unittest.cc + +run_unittests-message_unittest.obj: message_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-message_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-message_unittest.Tpo -c -o run_unittests-message_unittest.obj `if test -f 'message_unittest.cc'; then $(CYGPATH_W) 'message_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-message_unittest.Tpo $(DEPDIR)/run_unittests-message_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='message_unittest.cc' object='run_unittests-message_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-message_unittest.obj `if test -f 'message_unittest.cc'; then $(CYGPATH_W) 'message_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/message_unittest.cc'; fi` + +run_unittests-serial_unittest.o: serial_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-serial_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-serial_unittest.Tpo -c -o run_unittests-serial_unittest.o `test -f 'serial_unittest.cc' || echo '$(srcdir)/'`serial_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-serial_unittest.Tpo $(DEPDIR)/run_unittests-serial_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='serial_unittest.cc' object='run_unittests-serial_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-serial_unittest.o `test -f 'serial_unittest.cc' || echo '$(srcdir)/'`serial_unittest.cc + +run_unittests-serial_unittest.obj: serial_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-serial_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-serial_unittest.Tpo -c -o run_unittests-serial_unittest.obj `if test -f 'serial_unittest.cc'; then $(CYGPATH_W) 'serial_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/serial_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-serial_unittest.Tpo $(DEPDIR)/run_unittests-serial_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='serial_unittest.cc' object='run_unittests-serial_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-serial_unittest.obj `if test -f 'serial_unittest.cc'; then $(CYGPATH_W) 'serial_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/serial_unittest.cc'; fi` + +run_unittests-time_utils_unittest.o: time_utils_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-time_utils_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-time_utils_unittest.Tpo -c -o run_unittests-time_utils_unittest.o `test -f 'time_utils_unittest.cc' || echo '$(srcdir)/'`time_utils_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-time_utils_unittest.Tpo $(DEPDIR)/run_unittests-time_utils_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utils_unittest.cc' object='run_unittests-time_utils_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-time_utils_unittest.o `test -f 'time_utils_unittest.cc' || echo '$(srcdir)/'`time_utils_unittest.cc + +run_unittests-time_utils_unittest.obj: time_utils_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-time_utils_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-time_utils_unittest.Tpo -c -o run_unittests-time_utils_unittest.obj `if test -f 'time_utils_unittest.cc'; then $(CYGPATH_W) 'time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/time_utils_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-time_utils_unittest.Tpo $(DEPDIR)/run_unittests-time_utils_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='time_utils_unittest.cc' object='run_unittests-time_utils_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-time_utils_unittest.obj `if test -f 'time_utils_unittest.cc'; then $(CYGPATH_W) 'time_utils_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/time_utils_unittest.cc'; fi` + +run_unittests-tsig_unittest.o: tsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsig_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tsig_unittest.Tpo -c -o run_unittests-tsig_unittest.o `test -f 'tsig_unittest.cc' || echo '$(srcdir)/'`tsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsig_unittest.Tpo $(DEPDIR)/run_unittests-tsig_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsig_unittest.cc' object='run_unittests-tsig_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsig_unittest.o `test -f 'tsig_unittest.cc' || echo '$(srcdir)/'`tsig_unittest.cc + +run_unittests-tsig_unittest.obj: tsig_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsig_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tsig_unittest.Tpo -c -o run_unittests-tsig_unittest.obj `if test -f 'tsig_unittest.cc'; then $(CYGPATH_W) 'tsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsig_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsig_unittest.Tpo $(DEPDIR)/run_unittests-tsig_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsig_unittest.cc' object='run_unittests-tsig_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsig_unittest.obj `if test -f 'tsig_unittest.cc'; then $(CYGPATH_W) 'tsig_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsig_unittest.cc'; fi` + +run_unittests-tsigerror_unittest.o: tsigerror_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigerror_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tsigerror_unittest.Tpo -c -o run_unittests-tsigerror_unittest.o `test -f 'tsigerror_unittest.cc' || echo '$(srcdir)/'`tsigerror_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigerror_unittest.Tpo $(DEPDIR)/run_unittests-tsigerror_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigerror_unittest.cc' object='run_unittests-tsigerror_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigerror_unittest.o `test -f 'tsigerror_unittest.cc' || echo '$(srcdir)/'`tsigerror_unittest.cc + +run_unittests-tsigerror_unittest.obj: tsigerror_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigerror_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tsigerror_unittest.Tpo -c -o run_unittests-tsigerror_unittest.obj `if test -f 'tsigerror_unittest.cc'; then $(CYGPATH_W) 'tsigerror_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigerror_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigerror_unittest.Tpo $(DEPDIR)/run_unittests-tsigerror_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigerror_unittest.cc' object='run_unittests-tsigerror_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigerror_unittest.obj `if test -f 'tsigerror_unittest.cc'; then $(CYGPATH_W) 'tsigerror_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigerror_unittest.cc'; fi` + +run_unittests-tsigkey_unittest.o: tsigkey_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigkey_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tsigkey_unittest.Tpo -c -o run_unittests-tsigkey_unittest.o `test -f 'tsigkey_unittest.cc' || echo '$(srcdir)/'`tsigkey_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigkey_unittest.Tpo $(DEPDIR)/run_unittests-tsigkey_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigkey_unittest.cc' object='run_unittests-tsigkey_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigkey_unittest.o `test -f 'tsigkey_unittest.cc' || echo '$(srcdir)/'`tsigkey_unittest.cc + +run_unittests-tsigkey_unittest.obj: tsigkey_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigkey_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tsigkey_unittest.Tpo -c -o run_unittests-tsigkey_unittest.obj `if test -f 'tsigkey_unittest.cc'; then $(CYGPATH_W) 'tsigkey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigkey_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigkey_unittest.Tpo $(DEPDIR)/run_unittests-tsigkey_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigkey_unittest.cc' object='run_unittests-tsigkey_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigkey_unittest.obj `if test -f 'tsigkey_unittest.cc'; then $(CYGPATH_W) 'tsigkey_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigkey_unittest.cc'; fi` + +run_unittests-tsigrecord_unittest.o: tsigrecord_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigrecord_unittest.o -MD -MP -MF $(DEPDIR)/run_unittests-tsigrecord_unittest.Tpo -c -o run_unittests-tsigrecord_unittest.o `test -f 'tsigrecord_unittest.cc' || echo '$(srcdir)/'`tsigrecord_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigrecord_unittest.Tpo $(DEPDIR)/run_unittests-tsigrecord_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigrecord_unittest.cc' object='run_unittests-tsigrecord_unittest.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigrecord_unittest.o `test -f 'tsigrecord_unittest.cc' || echo '$(srcdir)/'`tsigrecord_unittest.cc + +run_unittests-tsigrecord_unittest.obj: tsigrecord_unittest.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-tsigrecord_unittest.obj -MD -MP -MF $(DEPDIR)/run_unittests-tsigrecord_unittest.Tpo -c -o run_unittests-tsigrecord_unittest.obj `if test -f 'tsigrecord_unittest.cc'; then $(CYGPATH_W) 'tsigrecord_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigrecord_unittest.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-tsigrecord_unittest.Tpo $(DEPDIR)/run_unittests-tsigrecord_unittest.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='tsigrecord_unittest.cc' object='run_unittests-tsigrecord_unittest.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-tsigrecord_unittest.obj `if test -f 'tsigrecord_unittest.cc'; then $(CYGPATH_W) 'tsigrecord_unittest.cc'; else $(CYGPATH_W) '$(srcdir)/tsigrecord_unittest.cc'; fi` + +run_unittests-master_loader_callbacks_test.o: master_loader_callbacks_test.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_loader_callbacks_test.o -MD -MP -MF $(DEPDIR)/run_unittests-master_loader_callbacks_test.Tpo -c -o run_unittests-master_loader_callbacks_test.o `test -f 'master_loader_callbacks_test.cc' || echo '$(srcdir)/'`master_loader_callbacks_test.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_loader_callbacks_test.Tpo $(DEPDIR)/run_unittests-master_loader_callbacks_test.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_callbacks_test.cc' object='run_unittests-master_loader_callbacks_test.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_loader_callbacks_test.o `test -f 'master_loader_callbacks_test.cc' || echo '$(srcdir)/'`master_loader_callbacks_test.cc + +run_unittests-master_loader_callbacks_test.obj: master_loader_callbacks_test.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-master_loader_callbacks_test.obj -MD -MP -MF $(DEPDIR)/run_unittests-master_loader_callbacks_test.Tpo -c -o run_unittests-master_loader_callbacks_test.obj `if test -f 'master_loader_callbacks_test.cc'; then $(CYGPATH_W) 'master_loader_callbacks_test.cc'; else $(CYGPATH_W) '$(srcdir)/master_loader_callbacks_test.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-master_loader_callbacks_test.Tpo $(DEPDIR)/run_unittests-master_loader_callbacks_test.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='master_loader_callbacks_test.cc' object='run_unittests-master_loader_callbacks_test.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-master_loader_callbacks_test.obj `if test -f 'master_loader_callbacks_test.cc'; then $(CYGPATH_W) 'master_loader_callbacks_test.cc'; else $(CYGPATH_W) '$(srcdir)/master_loader_callbacks_test.cc'; fi` + +run_unittests-run_unittests.o: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.o -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.o `test -f 'run_unittests.cc' || echo '$(srcdir)/'`run_unittests.cc + +run_unittests-run_unittests.obj: run_unittests.cc +@am__fastdepCXX_TRUE@ $(AM_V_CXX)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -MT run_unittests-run_unittests.obj -MD -MP -MF $(DEPDIR)/run_unittests-run_unittests.Tpo -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` +@am__fastdepCXX_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/run_unittests-run_unittests.Tpo $(DEPDIR)/run_unittests-run_unittests.Po +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(AM_V_CXX)source='run_unittests.cc' object='run_unittests-run_unittests.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(AM_V_CXX@am__nodep@)$(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(run_unittests_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) -c -o run_unittests-run_unittests.obj `if test -f 'run_unittests.cc'; then $(CYGPATH_W) 'run_unittests.cc'; else $(CYGPATH_W) '$(srcdir)/run_unittests.cc'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +# This directory's subdirectories are mostly independent; you can cd +# into them and run 'make' without going through this Makefile. +# To change the values of 'make' variables: instead of editing Makefiles, +# (1) if the variable is set in 'config.status', edit 'config.status' +# (which will cause the Makefiles to be regenerated when you run 'make'); +# (2) otherwise, pass the desired values on the 'make' command line. +$(am__recursive_targets): + @fail=; \ + if $(am__make_keepgoing); then \ + failcom='fail=yes'; \ + else \ + failcom='exit 1'; \ + fi; \ + dot_seen=no; \ + target=`echo $@ | sed s/-recursive//`; \ + case "$@" in \ + distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \ + *) list='$(SUBDIRS)' ;; \ + esac; \ + for subdir in $$list; do \ + echo "Making $$target in $$subdir"; \ + if test "$$subdir" = "."; then \ + dot_seen=yes; \ + local_target="$$target-am"; \ + else \ + local_target="$$target"; \ + fi; \ + ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \ + || eval $$failcom; \ + done; \ + if test "$$dot_seen" = "no"; then \ + $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \ + fi; test -z "$$fail" + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-recursive +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ + include_option=--etags-include; \ + empty_fix=.; \ + else \ + include_option=--include; \ + empty_fix=; \ + fi; \ + list='$(SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + test ! -f $$subdir/TAGS || \ + set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \ + fi; \ + done; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-recursive + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-recursive + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + fi; \ + echo "$${col}$$dashes$${std}"; \ + echo "$${col}$$banner$${std}"; \ + test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \ + test -z "$$report" || echo "$${col}$$report$${std}"; \ + echo "$${col}$$dashes$${std}"; \ + test "$$failed" -eq 0; \ + else :; fi +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done + @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ + if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ + dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ + $(am__relativize); \ + new_distdir=$$reldir; \ + dir1=$$subdir; dir2="$(top_distdir)"; \ + $(am__relativize); \ + new_top_distdir=$$reldir; \ + echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \ + echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \ + ($(am__cd) $$subdir && \ + $(MAKE) $(AM_MAKEFLAGS) \ + top_distdir="$$new_top_distdir" \ + distdir="$$new_distdir" \ + am__remove_distdir=: \ + am__skip_length_check=: \ + am__skip_mode_fix=: \ + distdir) \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-recursive +all-am: Makefile $(PROGRAMS) +installdirs: installdirs-recursive +installdirs-am: +install: install-recursive +install-exec: install-exec-recursive +install-data: install-data-recursive +uninstall: uninstall-recursive + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-recursive +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-recursive + +clean-am: clean-generic clean-libtool clean-noinstPROGRAMS \ + mostlyclean-am + +distclean: distclean-recursive + -rm -f ./$(DEPDIR)/run_unittests-dns_exceptions_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-edns_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-labelsequence_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_lexer_state_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_lexer_token_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_lexer_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_loader_callbacks_test.Po + -rm -f ./$(DEPDIR)/run_unittests-master_loader_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-message_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-name_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-opcode_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-question_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rcode_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_tsig_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrclass_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrset_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrttl_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrtype_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-serial_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-time_utils_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-tsig_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-tsigerror_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-tsigkey_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-tsigrecord_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-unittest_util.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-recursive + +dvi-am: + +html: html-recursive + +html-am: + +info: info-recursive + +info-am: + +install-data-am: + +install-dvi: install-dvi-recursive + +install-dvi-am: + +install-exec-am: + +install-html: install-html-recursive + +install-html-am: + +install-info: install-info-recursive + +install-info-am: + +install-man: + +install-pdf: install-pdf-recursive + +install-pdf-am: + +install-ps: install-ps-recursive + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-recursive + -rm -f ./$(DEPDIR)/run_unittests-dns_exceptions_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-edns_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-labelsequence_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_lexer_inputsource_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_lexer_state_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_lexer_token_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_lexer_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-master_loader_callbacks_test.Po + -rm -f ./$(DEPDIR)/run_unittests-master_loader_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-message_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-messagerenderer_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-name_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-opcode_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-question_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rcode_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_char_string_data_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_char_string_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_dhcid_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_in_a_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_in_aaaa_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_ns_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_opt_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_ptr_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_rrsig_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_soa_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_tkey_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_tsig_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_txt_like_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rdata_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrclass_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrparamregistry_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrset_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrttl_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-rrtype_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-run_unittests.Po + -rm -f ./$(DEPDIR)/run_unittests-serial_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-time_utils_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-tsig_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-tsigerror_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-tsigkey_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-tsigrecord_unittest.Po + -rm -f ./$(DEPDIR)/run_unittests-unittest_util.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/dns/tests/dns_exceptions_unittest.cc b/src/lib/dns/tests/dns_exceptions_unittest.cc new file mode 100644 index 0000000..0e9fc62 --- /dev/null +++ b/src/lib/dns/tests/dns_exceptions_unittest.cc @@ -0,0 +1,65 @@ +// Copyright (C) 2014-2024 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 <dns/exceptions.h> + +#include <gtest/gtest.h> + +namespace { // begin unnamed namespace + +TEST(DNSExceptionsTest, checkExceptionsHierarchy) { + EXPECT_NO_THROW({ + const isc::dns::Exception exception("", 0, ""); + const isc::Exception& exception_cast = + dynamic_cast<const isc::Exception&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::DNSTextError exception("", 0, ""); + const isc::dns::Exception& exception_cast = + dynamic_cast<const isc::dns::Exception&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::NameParserException exception("", 0, ""); + const isc::dns::DNSTextError& exception_cast = + dynamic_cast<const isc::dns::DNSTextError&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::DNSMessageFORMERR exception("", 0, ""); + const isc::dns::DNSProtocolError& exception_cast = + dynamic_cast<const isc::dns::DNSProtocolError&>(exception); + const isc::dns::Exception& exception_cast2 = + dynamic_cast<const isc::dns::Exception&>(exception); + // to avoid compiler warning + exception_cast.getRcode(); + exception_cast.what(); + exception_cast2.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::DNSMessageBADVERS exception("", 0, ""); + const isc::dns::DNSProtocolError& exception_cast = + dynamic_cast<const isc::dns::DNSProtocolError&>(exception); + const isc::dns::Exception& exception_cast2 = + dynamic_cast<const isc::dns::Exception&>(exception); + // to avoid compiler warning + exception_cast.getRcode(); + exception_cast.what(); + exception_cast2.what(); + }); +} + +} // end unnamed namespace diff --git a/src/lib/dns/tests/edns_unittest.cc b/src/lib/dns/tests/edns_unittest.cc new file mode 100644 index 0000000..239c2eb --- /dev/null +++ b/src/lib/dns/tests/edns_unittest.cc @@ -0,0 +1,258 @@ +// Copyright (C) 2010-2024 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 <sstream> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> +#include <dns/edns.h> +#include <dns/exceptions.h> +#include <dns/message.h> +#include <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/rcode.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrttl.h> +#include <dns/rrtype.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +const uint8_t EDNS::SUPPORTED_VERSION; + +namespace { +class EDNSTest : public ::testing::Test { +protected: + EDNSTest() : rrtype(RRType::OPT()), buffer(0, 0), obuffer(0), rcode(0) { + opt_rdata = ConstRdataPtr(new generic::OPT()); + edns_base.setUDPSize(4096); + } + RRType rrtype; + EDNS edns_base; + ConstEDNSPtr edns; + InputBuffer buffer; + OutputBuffer obuffer; + MessageRenderer renderer; + ConstRdataPtr opt_rdata; + Rcode rcode; + vector<unsigned char> wiredata; +}; + +// RRClass of EDNS OPT means UDP buffer size +const RRClass rrclass(4096); +// RRTTL of EDNS OPT encodes extended-rcode, version, and DO bit +const RRTTL rrttl_do_on(0x00008000); // DO=on +const RRTTL rrttl_do_off(0); // DO=off +const RRTTL rrttl_badver(0x00018000); // version=1, DO=on + +TEST_F(EDNSTest, badVerConstruct) { + EXPECT_THROW(EDNS(1), isc::InvalidParameter); +} + +TEST_F(EDNSTest, DNSSECDOBit) { + // tests for EDNS from RR + + // DO bit is on, so DNSSEC should be considered to be supported. + EXPECT_TRUE(EDNS(Name::ROOT_NAME(), rrclass, rrtype, + rrttl_do_on, *opt_rdata).getDNSSECAwareness()); + + // DO bit is off. DNSSEC should be considered to be unsupported. + EXPECT_FALSE(EDNS(Name::ROOT_NAME(), rrclass, rrtype, + rrttl_do_off, *opt_rdata).getDNSSECAwareness()); + + // tests for EDNS constructed by hand + EXPECT_FALSE(edns_base.getDNSSECAwareness()); // false by default + edns_base.setDNSSECAwareness(true); // enable by hand + EXPECT_TRUE(edns_base.getDNSSECAwareness()); + edns_base.setDNSSECAwareness(false); // disable by hand + EXPECT_FALSE(edns_base.getDNSSECAwareness()); +} + +TEST_F(EDNSTest, UDPSize) { + EXPECT_EQ(4096, EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on, + *opt_rdata).getUDPSize()); + + // EDNS UDP size smaller than the traditional max, 512. Unusual, but + // not prohibited. + edns_base.setUDPSize(511); + EXPECT_EQ(511, edns_base.getUDPSize()); + + // Even 0 is okay. + edns_base.setUDPSize(0); + EXPECT_EQ(0, edns_base.getUDPSize()); + + // Possible max value is also okay, although the higher layer app may + // adjust it to a reasonably lower value + edns_base.setUDPSize(65535); + EXPECT_EQ(65535, edns_base.getUDPSize()); +} + +TEST_F(EDNSTest, getVersion) { + // Constructed by hand + EXPECT_EQ(EDNS::SUPPORTED_VERSION, EDNS().getVersion()); + + // From RR + EXPECT_EQ(EDNS::SUPPORTED_VERSION, + EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on, + *opt_rdata).getVersion()); +} + +TEST_F(EDNSTest, BadWireData) { + // Incompatible RR type + EXPECT_THROW(EDNS(Name::ROOT_NAME(), rrclass, RRType::A(), + rrttl_do_on, *opt_rdata), isc::InvalidParameter); + + // OPT RR of a non root name + EXPECT_THROW(EDNS(Name("example.com"), rrclass, rrtype, + rrttl_do_on, *opt_rdata), DNSMessageFORMERR); + + // Unsupported Version + EXPECT_THROW(EDNS(Name::ROOT_NAME(), rrclass, rrtype, + rrttl_badver, *opt_rdata), DNSMessageBADVERS); +} + +TEST_F(EDNSTest, toText) { + // Typical case, disabling DNSSEC + EXPECT_EQ("; EDNS: version: 0, flags:; udp: 4096\n", + EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_off, + *opt_rdata).toText()); + + // Typical case, enabling DNSSEC + EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n", + EDNS(Name::ROOT_NAME(), rrclass, rrtype, rrttl_do_on, + *opt_rdata).toText()); + + // Non-0 extended Rcode: ignored in the toText() output. + EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n", + EDNS(Name::ROOT_NAME(), rrclass, rrtype, + RRTTL(0x01008000), *opt_rdata).toText()); + + // Unknown flag: ignored in the toText() output. + EXPECT_EQ("; EDNS: version: 0, flags: do; udp: 4096\n", + EDNS(Name::ROOT_NAME(), rrclass, rrtype, + RRTTL(0x00008001), *opt_rdata).toText()); +} + +TEST_F(EDNSTest, toWireRenderer) { + // Typical case, (explicitly) disabling DNSSEC + edns_base.setDNSSECAwareness(false); + EXPECT_EQ(1, edns_base.toWire(renderer, + Rcode::NOERROR().getExtendedCode())); + UnitTestUtil::readWireData("edns_toWire1.wire", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); + + // Typical case, enabling DNSSEC + renderer.clear(); + wiredata.clear(); + edns_base.setDNSSECAwareness(true); + EXPECT_EQ(1, edns_base.toWire(renderer, + Rcode::NOERROR().getExtendedCode())); + UnitTestUtil::readWireData("edns_toWire2.wire", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); + + // Non-0 extended Rcode + renderer.clear(); + wiredata.clear(); + edns_base.setDNSSECAwareness(true); + EXPECT_EQ(1, edns_base.toWire(renderer, + Rcode::BADVERS().getExtendedCode())); + UnitTestUtil::readWireData("edns_toWire3.wire", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); + + // Uncommon UDP buffer size + renderer.clear(); + wiredata.clear(); + edns_base.setDNSSECAwareness(true); + edns_base.setUDPSize(511); + EXPECT_EQ(1, edns_base.toWire(renderer, + Rcode::NOERROR().getExtendedCode())); + UnitTestUtil::readWireData("edns_toWire4.wire", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); + + // From RR with unknown flag. If used for toWire(), the unknown flag + // should disappear. + renderer.clear(); + wiredata.clear(); + EXPECT_EQ(1, EDNS(Name::ROOT_NAME(), rrclass, rrtype, RRTTL(0x00008001), + *opt_rdata).toWire(renderer, + Rcode::NOERROR().getExtendedCode())); + UnitTestUtil::readWireData("edns_toWire2.wire", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); + + // If the available length in the renderer is not sufficient for the OPT + // RR, it shouldn't be inserted. + renderer.clear(); + renderer.setLengthLimit(10); // 10 = minimum length of OPT RR - 1 + edns_base.setDNSSECAwareness(true); + edns_base.setUDPSize(4096); + EXPECT_EQ(0, edns_base.toWire(renderer, + Rcode::NOERROR().getExtendedCode())); + // renderer should be intact + EXPECT_EQ(0, renderer.getLength()); +} + +TEST_F(EDNSTest, toWireBuffer) { + // "to renderer" and "to buffer" should generally produce the same result. + // for simplicity we only check one typical case to confirm that. + EXPECT_EQ(1, edns_base.toWire(obuffer, + Rcode::NOERROR().getExtendedCode())); + UnitTestUtil::readWireData("edns_toWire1.wire", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(EDNSTest, createFromRR) { + uint8_t extended_rcode; + + // normal case + edns = ConstEDNSPtr(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype, + rrttl_do_on, *opt_rdata, + extended_rcode)); + EXPECT_EQ(EDNS::SUPPORTED_VERSION, edns->getVersion()); + EXPECT_TRUE(edns->getDNSSECAwareness()); + EXPECT_EQ(4096, edns->getUDPSize()); + EXPECT_EQ(0, static_cast<int>(extended_rcode)); + + // non-0 extended rcode + extended_rcode = 0; + ConstEDNSPtr(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype, + RRTTL(0x01008000), *opt_rdata, + extended_rcode)); + EXPECT_EQ(1, static_cast<int>(extended_rcode)); + + // creation triggers an exception. extended_rcode must be intact. + extended_rcode = 0; + EXPECT_THROW(createEDNSFromRR(Name::ROOT_NAME(), rrclass, rrtype, + rrttl_badver, *opt_rdata, extended_rcode), + DNSMessageBADVERS); + EXPECT_EQ(0, static_cast<int>(extended_rcode)); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(EDNSTest, LeftShiftOperator) { + ostringstream oss; + oss << edns_base; + EXPECT_EQ(edns_base.toText(), oss.str()); +} +} diff --git a/src/lib/dns/tests/labelsequence_unittest.cc b/src/lib/dns/tests/labelsequence_unittest.cc new file mode 100644 index 0000000..1e8f232 --- /dev/null +++ b/src/lib/dns/tests/labelsequence_unittest.cc @@ -0,0 +1,1242 @@ +// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <util/buffer.h> + +#include <dns/labelsequence.h> +#include <dns/name.h> +#include <exceptions/exceptions.h> + +#include <gtest/gtest.h> + +#include <boost/functional/hash.hpp> + +#include <string> +#include <vector> +#include <utility> +#include <set> + +using namespace isc::dns; +using namespace std; + +// XXX: this is defined as class static constants, but some compilers +// seemingly cannot find the symbols when used in the EXPECT_xxx macros. +const size_t LabelSequence::MAX_SERIALIZED_LENGTH; + +namespace { + +// Common check that two labelsequences are equal +void check_equal(const LabelSequence& ls1, const LabelSequence& ls2) { + NameComparisonResult result = ls1.compare(ls2); + EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL, + result.getRelation()) << ls1.toText() << " != " << ls2.toText(); + EXPECT_EQ(0, result.getOrder()) << ls1.toText() << " != " << ls2.toText(); + EXPECT_EQ(ls1.getLabelCount(), result.getCommonLabels()); +} + +// Common check for general comparison of two labelsequences +void check_compare(const LabelSequence& ls1, const LabelSequence& ls2, + isc::dns::NameComparisonResult::NameRelation relation, + size_t common_labels, bool check_order, int order=0) { + NameComparisonResult result = ls1.compare(ls2); + EXPECT_EQ(relation, result.getRelation()); + EXPECT_EQ(common_labels, result.getCommonLabels()); + if (check_order) { + EXPECT_EQ(order, result.getOrder()); + } +} + +class LabelSequenceTest : public ::testing::Test { +public: + LabelSequenceTest() : n1("example.org"), n2("example.com"), + n3("example.org"), n4("foo.bar.test.example"), + n5("example.ORG"), n6("ExAmPlE.org"), + n7("."), n8("foo.example.org.bar"), + n9("\\000xample.org"), + n10("\\000xample.org"), + n11("\\000xample.com"), + n12("\\000xamplE.com"), + n_maxlabel("0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6"), + ls1(n1), ls2(n2), ls3(n3), ls4(n4), ls5(n5), + ls6(n6), ls7(n7), ls8(n8), + ls9(n9), ls10(n10), ls11(n11), ls12(n12) + {}; + // Need to keep names in scope for at least the lifetime of + // the labelsequences + Name n1, n2, n3, n4, n5, n6, n7, n8; + Name n9, n10, n11, n12; + const Name n_maxlabel; + + LabelSequence ls1, ls2, ls3, ls4, ls5, ls6, ls7, ls8; + LabelSequence ls9, ls10, ls11, ls12; +}; + +// Check the assignment operator. +TEST_F(LabelSequenceTest, assign) { + // Create the label sequence with example.org (n1 name). + LabelSequence ls(n1); + EXPECT_TRUE(ls == ls1); + + // Assign the label sequence to example.com (n2 name). + ls = ls2; + EXPECT_FALSE(ls == ls1); + EXPECT_TRUE(ls == ls2); +} + +// Basic equality tests +TEST_F(LabelSequenceTest, equals_sensitive) { + EXPECT_TRUE(ls1.equals(ls1, true)); + EXPECT_FALSE(ls1.equals(ls2, true)); + EXPECT_TRUE(ls1.equals(ls3, true)); + EXPECT_FALSE(ls1.equals(ls4, true)); + EXPECT_FALSE(ls1.equals(ls5, true)); + EXPECT_FALSE(ls1.equals(ls6, true)); + EXPECT_FALSE(ls1.equals(ls7, true)); + EXPECT_FALSE(ls1.equals(ls8, true)); + + EXPECT_FALSE(ls2.equals(ls1, true)); + EXPECT_TRUE(ls2.equals(ls2, true)); + EXPECT_FALSE(ls2.equals(ls3, true)); + EXPECT_FALSE(ls2.equals(ls4, true)); + EXPECT_FALSE(ls2.equals(ls5, true)); + EXPECT_FALSE(ls2.equals(ls6, true)); + EXPECT_FALSE(ls2.equals(ls7, true)); + EXPECT_FALSE(ls2.equals(ls8, true)); + + EXPECT_FALSE(ls4.equals(ls1, true)); + EXPECT_FALSE(ls4.equals(ls2, true)); + EXPECT_FALSE(ls4.equals(ls3, true)); + EXPECT_TRUE(ls4.equals(ls4, true)); + EXPECT_FALSE(ls4.equals(ls5, true)); + EXPECT_FALSE(ls4.equals(ls6, true)); + EXPECT_FALSE(ls4.equals(ls7, true)); + EXPECT_FALSE(ls4.equals(ls8, true)); + + EXPECT_FALSE(ls5.equals(ls1, true)); + EXPECT_FALSE(ls5.equals(ls2, true)); + EXPECT_FALSE(ls5.equals(ls3, true)); + EXPECT_FALSE(ls5.equals(ls4, true)); + EXPECT_TRUE(ls5.equals(ls5, true)); + EXPECT_FALSE(ls5.equals(ls6, true)); + EXPECT_FALSE(ls5.equals(ls7, true)); + EXPECT_FALSE(ls5.equals(ls8, true)); + + EXPECT_TRUE(ls9.equals(ls10, true)); + EXPECT_FALSE(ls9.equals(ls11, true)); + EXPECT_FALSE(ls9.equals(ls12, true)); + EXPECT_FALSE(ls11.equals(ls12, true)); +} + +TEST_F(LabelSequenceTest, equals_insensitive) { + EXPECT_TRUE(ls1.equals(ls1)); + EXPECT_FALSE(ls1.equals(ls2)); + EXPECT_TRUE(ls1.equals(ls3)); + EXPECT_FALSE(ls1.equals(ls4)); + EXPECT_TRUE(ls1.equals(ls5)); + EXPECT_TRUE(ls1.equals(ls6)); + EXPECT_FALSE(ls1.equals(ls7)); + + EXPECT_FALSE(ls2.equals(ls1)); + EXPECT_TRUE(ls2.equals(ls2)); + EXPECT_FALSE(ls2.equals(ls3)); + EXPECT_FALSE(ls2.equals(ls4)); + EXPECT_FALSE(ls2.equals(ls5)); + EXPECT_FALSE(ls2.equals(ls6)); + EXPECT_FALSE(ls2.equals(ls7)); + + EXPECT_TRUE(ls3.equals(ls1)); + EXPECT_FALSE(ls3.equals(ls2)); + EXPECT_TRUE(ls3.equals(ls3)); + EXPECT_FALSE(ls3.equals(ls4)); + EXPECT_TRUE(ls3.equals(ls5)); + EXPECT_TRUE(ls3.equals(ls6)); + EXPECT_FALSE(ls3.equals(ls7)); + + EXPECT_FALSE(ls4.equals(ls1)); + EXPECT_FALSE(ls4.equals(ls2)); + EXPECT_FALSE(ls4.equals(ls3)); + EXPECT_TRUE(ls4.equals(ls4)); + EXPECT_FALSE(ls4.equals(ls5)); + EXPECT_FALSE(ls4.equals(ls6)); + EXPECT_FALSE(ls4.equals(ls7)); + + EXPECT_TRUE(ls5.equals(ls1)); + EXPECT_FALSE(ls5.equals(ls2)); + EXPECT_TRUE(ls5.equals(ls3)); + EXPECT_FALSE(ls5.equals(ls4)); + EXPECT_TRUE(ls5.equals(ls5)); + EXPECT_TRUE(ls5.equals(ls6)); + EXPECT_FALSE(ls5.equals(ls7)); + + EXPECT_TRUE(ls9.equals(ls10)); + EXPECT_FALSE(ls9.equals(ls11)); + EXPECT_FALSE(ls9.equals(ls12)); + EXPECT_TRUE(ls11.equals(ls12)); +} + +// operator==(). This is mostly trivial wrapper, so it should suffice to +// check some basic cases. +TEST_F(LabelSequenceTest, operatorEqual) { + // cppcheck-suppress duplicateExpression + EXPECT_TRUE(ls1 == ls1); // self equivalence + EXPECT_TRUE(ls1 == LabelSequence(n1)); // equivalent two different objects + EXPECT_FALSE(ls1 == ls2); // non equivalent objects + EXPECT_TRUE(ls1 == ls5); // it's always case insensitive +} + +// Compare tests +TEST_F(LabelSequenceTest, compare) { + // "example.org." and "example.org.", case sensitive + NameComparisonResult result = ls1.compare(ls3, true); + EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL, + result.getRelation()); + EXPECT_EQ(0, result.getOrder()); + EXPECT_EQ(3, result.getCommonLabels()); + + // "example.org." and "example.ORG.", case sensitive + result = ls3.compare(ls5, true); + EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR, + result.getRelation()); + EXPECT_LT(0, result.getOrder()); + EXPECT_EQ(1, result.getCommonLabels()); + + // "example.org." and "example.ORG.", case in-sensitive + result = ls3.compare(ls5); + EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL, + result.getRelation()); + EXPECT_EQ(0, result.getOrder()); + EXPECT_EQ(3, result.getCommonLabels()); + + Name na("a.example.org"); + Name nb("b.example.org"); + LabelSequence lsa(na); + LabelSequence lsb(nb); + + // "a.example.org." and "b.example.org.", case in-sensitive + result = lsa.compare(lsb); + EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR, + result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(3, result.getCommonLabels()); + + // "example.org." and "b.example.org.", case in-sensitive + lsa.stripLeft(1); + result = lsa.compare(lsb); + EXPECT_EQ(isc::dns::NameComparisonResult::SUPERDOMAIN, + result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(3, result.getCommonLabels()); + + Name nc("g.f.e.d.c.example.org"); + LabelSequence lsc(nc); + + // "g.f.e.d.c.example.org." and "b.example.org" (not absolute), case + // in-sensitive; the absolute one is always smaller. + lsb.stripRight(1); + result = lsc.compare(lsb); + EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(0, result.getCommonLabels()); + + // "g.f.e.d.c.example.org." and "example.org.", case in-sensitive + result = lsc.compare(ls1); + EXPECT_EQ(isc::dns::NameComparisonResult::SUBDOMAIN, + result.getRelation()); + EXPECT_LT(0, result.getOrder()); + EXPECT_EQ(3, result.getCommonLabels()); + + // "e.d.c.example.org." and "example.org.", case in-sensitive + lsc.stripLeft(2); + result = lsc.compare(ls1); + EXPECT_EQ(isc::dns::NameComparisonResult::SUBDOMAIN, + result.getRelation()); + EXPECT_LT(0, result.getOrder()); + EXPECT_EQ(3, result.getCommonLabels()); + + // "example.org." and "example.org.", case in-sensitive + lsc.stripLeft(3); + result = lsc.compare(ls1); + EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL, + result.getRelation()); + EXPECT_EQ(0, result.getOrder()); + EXPECT_EQ(3, result.getCommonLabels()); + + // "." and "example.org.", case in-sensitive + lsc.stripLeft(2); + result = lsc.compare(ls1); + EXPECT_EQ(isc::dns::NameComparisonResult::SUPERDOMAIN, + result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(1, result.getCommonLabels()); + + Name nd("a.b.c.isc.example.org"); + LabelSequence lsd(nd); + Name ne("w.x.y.isc.EXAMPLE.org"); + LabelSequence lse(ne); + + // "a.b.c.isc.example.org." and "w.x.y.isc.EXAMPLE.org.", + // case sensitive + result = lsd.compare(lse, true); + EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR, + result.getRelation()); + EXPECT_LT(0, result.getOrder()); + EXPECT_EQ(2, result.getCommonLabels()); + + // "a.b.c.isc.example.org." and "w.x.y.isc.EXAMPLE.org.", + // case in-sensitive + result = lsd.compare(lse); + EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR, + result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(4, result.getCommonLabels()); + + // "isc.example.org." and "isc.EXAMPLE.org.", case sensitive + lsd.stripLeft(3); + lse.stripLeft(3); + result = lsd.compare(lse, true); + EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR, + result.getRelation()); + EXPECT_LT(0, result.getOrder()); + EXPECT_EQ(2, result.getCommonLabels()); + + // "isc.example.org." and "isc.EXAMPLE.org.", case in-sensitive + result = lsd.compare(lse); + EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL, + result.getRelation()); + EXPECT_EQ(0, result.getOrder()); + EXPECT_EQ(4, result.getCommonLabels()); + + Name nf("a.b.c.isc.example.org"); + LabelSequence lsf(nf); + Name ng("w.x.y.isc.EXAMPLE.org"); + LabelSequence lsg(ng); + + // lsf: "a.b.c.isc.example.org." + // lsg: "w.x.y.isc.EXAMPLE.org" (not absolute), case in-sensitive. + // the absolute one is always smaller. + lsg.stripRight(1); + result = lsg.compare(lsf); // lsg > lsf + EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation()); + EXPECT_LT(0, result.getOrder()); + EXPECT_EQ(0, result.getCommonLabels()); + + // "a.b.c.isc.example.org" (not absolute) and + // "w.x.y.isc.EXAMPLE.org" (not absolute), case in-sensitive + lsf.stripRight(1); + result = lsg.compare(lsf); + EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR, + result.getRelation()); + EXPECT_LT(0, result.getOrder()); + EXPECT_EQ(3, result.getCommonLabels()); + + // "a.b.c.isc.example" (not absolute) and + // "w.x.y.isc.EXAMPLE" (not absolute), case in-sensitive + lsf.stripRight(1); + lsg.stripRight(1); + result = lsg.compare(lsf); + EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR, + result.getRelation()); + EXPECT_LT(0, result.getOrder()); + EXPECT_EQ(2, result.getCommonLabels()); + + // lsf: "a.b.c" (not absolute) and + // lsg: "w.x.y" (not absolute), case in-sensitive; a.b.c < w.x.y; + // no common labels. + lsf.stripRight(2); + lsg.stripRight(2); + result = lsf.compare(lsg); + EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(0, result.getCommonLabels()); + + // lsf2: a.b.cc (not absolute); a.b.c < a.b.cc, no common labels. + const Name nf2("a.b.cc"); + LabelSequence lsf2(nf2); + lsf2.stripRight(1); + result = lsf.compare(lsf2); + EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(0, result.getCommonLabels()); + + Name nh("aexample.org"); + LabelSequence lsh(nh); + Name ni("bexample.org"); + LabelSequence lsi(ni); + + // "aexample.org" (not absolute) and + // "bexample.org" (not absolute), case in-sensitive + lsh.stripRight(1); + lsi.stripRight(1); + result = lsh.compare(lsi); + EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR, + result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(1, result.getCommonLabels()); + + // "aexample" (not absolute) and + // "bexample" (not absolute), case in-sensitive; + // aexample < bexample; no common labels. + lsh.stripRight(1); + lsi.stripRight(1); + result = lsh.compare(lsi); + EXPECT_EQ(isc::dns::NameComparisonResult::NONE, result.getRelation()); + EXPECT_GT(0, result.getOrder()); + EXPECT_EQ(0, result.getCommonLabels()); + + Name nj("example.org"); + LabelSequence lsj(nj); + Name nk("example.org"); + LabelSequence lsk(nk); + + // "example.org" (not absolute) and + // "example.org" (not absolute), case in-sensitive + lsj.stripRight(1); + lsk.stripRight(1); + result = lsj.compare(lsk); + EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL, + result.getRelation()); + EXPECT_EQ(0, result.getOrder()); + EXPECT_EQ(2, result.getCommonLabels()); + + // "example" (not absolute) and + // "example" (not absolute), case in-sensitive + lsj.stripRight(1); + lsk.stripRight(1); + result = lsj.compare(lsk); + EXPECT_EQ(isc::dns::NameComparisonResult::EQUAL, + result.getRelation()); + EXPECT_EQ(0, result.getOrder()); + EXPECT_EQ(1, result.getCommonLabels()); +} + +void +getDataCheck(const uint8_t* expected_data, size_t expected_len, + const LabelSequence& ls) +{ + size_t len; + const uint8_t* data = ls.getData(&len); + ASSERT_EQ(expected_len, len) << "Expected data: " << expected_data << + ", label sequence: " << ls; + EXPECT_EQ(expected_len, ls.getDataLength()) << + "Expected data: " << expected_data << + ", label sequence: " << ls; + for (size_t i = 0; i < len; ++i) { + EXPECT_EQ(expected_data[i], data[i]) << + "Difference at pos " << i << ": Expected data: " << expected_data << + ", label sequence: " << ls; + } +} + +// Convenient data converter for expected data. Label data must be of +// uint8_t*, while it's convenient if we can specify some test data in +// plain string (which is of char*). This wrapper converts the latter to +// the former in a safer way. +void +getDataCheck(const char* expected_char_data, size_t expected_len, + const LabelSequence& ls) +{ + const vector<uint8_t> expected_data(expected_char_data, + expected_char_data + expected_len); + getDataCheck(&expected_data[0], expected_len, ls); +} + +TEST_F(LabelSequenceTest, getData) { + getDataCheck("\007example\003org\000", 13, ls1); + getDataCheck("\007example\003com\000", 13, ls2); + getDataCheck("\007example\003org\000", 13, ls3); + getDataCheck("\003foo\003bar\004test\007example\000", 22, ls4); + getDataCheck("\007example\003ORG\000", 13, ls5); + getDataCheck("\007ExAmPlE\003org\000", 13, ls6); + getDataCheck("\000", 1, ls7); +}; + +TEST_F(LabelSequenceTest, stripLeft) { + EXPECT_TRUE(ls1.equals(ls3)); + ls1.stripLeft(0); + getDataCheck("\007example\003org\000", 13, ls1); + EXPECT_TRUE(ls1.equals(ls3)); + ls1.stripLeft(1); + getDataCheck("\003org\000", 5, ls1); + EXPECT_FALSE(ls1.equals(ls3)); + ls1.stripLeft(1); + getDataCheck("\000", 1, ls1); + EXPECT_TRUE(ls1.equals(ls7)); + + ls2.stripLeft(2); + getDataCheck("\000", 1, ls2); + EXPECT_TRUE(ls2.equals(ls7)); +} + +TEST_F(LabelSequenceTest, stripRight) { + EXPECT_TRUE(ls1.equals(ls3)); + ls1.stripRight(1); + getDataCheck("\007example\003org", 12, ls1); + EXPECT_FALSE(ls1.equals(ls3)); + ls1.stripRight(1); + getDataCheck("\007example", 8, ls1); + EXPECT_FALSE(ls1.equals(ls3)); + + ASSERT_FALSE(ls1.equals(ls2)); + ls2.stripRight(2); + getDataCheck("\007example", 8, ls2); + EXPECT_TRUE(ls1.equals(ls2)); +} + +TEST_F(LabelSequenceTest, stripOutOfRange) { + EXPECT_THROW(ls1.stripLeft(100), isc::OutOfRange); + EXPECT_THROW(ls1.stripLeft(5), isc::OutOfRange); + EXPECT_THROW(ls1.stripLeft(4), isc::OutOfRange); + EXPECT_THROW(ls1.stripLeft(3), isc::OutOfRange); + getDataCheck("\007example\003org\000", 13, ls1); + + EXPECT_THROW(ls1.stripRight(100), isc::OutOfRange); + EXPECT_THROW(ls1.stripRight(5), isc::OutOfRange); + EXPECT_THROW(ls1.stripRight(4), isc::OutOfRange); + EXPECT_THROW(ls1.stripRight(3), isc::OutOfRange); + getDataCheck("\007example\003org\000", 13, ls1); +} + +TEST_F(LabelSequenceTest, getLabelCount) { + EXPECT_EQ(3, ls1.getLabelCount()); + ls1.stripLeft(0); + EXPECT_EQ(3, ls1.getLabelCount()); + ls1.stripLeft(1); + EXPECT_EQ(2, ls1.getLabelCount()); + ls1.stripLeft(1); + EXPECT_EQ(1, ls1.getLabelCount()); + + EXPECT_EQ(3, ls2.getLabelCount()); + ls2.stripRight(1); + EXPECT_EQ(2, ls2.getLabelCount()); + ls2.stripRight(1); + EXPECT_EQ(1, ls2.getLabelCount()); + + EXPECT_EQ(3, ls3.getLabelCount()); + ls3.stripRight(2); + EXPECT_EQ(1, ls3.getLabelCount()); + + EXPECT_EQ(5, ls4.getLabelCount()); + ls4.stripRight(3); + EXPECT_EQ(2, ls4.getLabelCount()); + + EXPECT_EQ(3, ls5.getLabelCount()); + ls5.stripLeft(2); + EXPECT_EQ(1, ls5.getLabelCount()); +} + +TEST_F(LabelSequenceTest, comparePart) { + EXPECT_FALSE(ls1.equals(ls8)); + + // strip root label from example.org. + ls1.stripRight(1); + // strip foo from foo.example.org.bar. + ls8.stripLeft(1); + // strip bar. (i.e. bar and root) too + ls8.stripRight(2); + + EXPECT_TRUE(ls1.equals(ls8)); + + // Data comparison + size_t len; + const uint8_t* data = ls1.getData(&len); + getDataCheck(data, len, ls8); +} + +TEST_F(LabelSequenceTest, isAbsolute) { + ASSERT_TRUE(ls1.isAbsolute()); + + ls1.stripLeft(1); + ASSERT_TRUE(ls1.isAbsolute()); + ls1.stripRight(1); + ASSERT_FALSE(ls1.isAbsolute()); + + ASSERT_TRUE(ls2.isAbsolute()); + ls2.stripRight(1); + ASSERT_FALSE(ls2.isAbsolute()); + + ASSERT_TRUE(ls3.isAbsolute()); + ls3.stripLeft(2); + ASSERT_TRUE(ls3.isAbsolute()); +} + +TEST_F(LabelSequenceTest, toText) { + EXPECT_EQ(".", ls7.toText()); + + EXPECT_EQ("example.org.", ls1.toText()); + ls1.stripLeft(1); + EXPECT_EQ("org.", ls1.toText()); + ls1.stripLeft(1); + EXPECT_EQ(".", ls1.toText()); + + EXPECT_EQ("example.com.", ls2.toText()); + ls2.stripRight(1); + EXPECT_EQ("example.com", ls2.toText()); + ls2.stripRight(1); + EXPECT_EQ("example", ls2.toText()); + + EXPECT_EQ("foo.example.org.bar.", ls8.toText()); + ls8.stripRight(2); + EXPECT_EQ("foo.example.org", ls8.toText()); + + EXPECT_EQ(".", ls7.toText()); + EXPECT_THROW(ls7.stripLeft(1), isc::OutOfRange); + + Name n_long1("012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "0123456789012345678901234567890"); + LabelSequence ls_long1(n_long1); + + EXPECT_EQ("012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "0123456789012345678901234567890.", ls_long1.toText()); + ls_long1.stripRight(1); + EXPECT_EQ("012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "012345678901234567890123456789012." + "012345678901234567890123456789" + "0123456789012345678901234567890", ls_long1.toText()); + + LabelSequence ls_long2(n_maxlabel); + + EXPECT_EQ("0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.", ls_long2.toText()); + ls_long2.stripRight(1); + EXPECT_EQ("0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." + "0.1.2.3.4.5.6", ls_long2.toText()); + ls_long2.stripRight(125); + EXPECT_EQ("0.1", ls_long2.toText()); +} + +// The following verifies that toRawText() returns a string +// actual characters in place of escape sequences. We do not +// bother with an exhaustive set of tests here as this is +// not a primary use case. +TEST_F(LabelSequenceTest, toRawText) { + Name n("a bc.$exa(m)ple.@org"); + LabelSequence l(n); + EXPECT_EQ("a bc.$exa(m)ple.@org", l.toRawText(true)); + EXPECT_EQ("a bc.$exa(m)ple.@org.", l.toRawText(false)); + + // toRawText is not supposed to do any sanity checks. + // Let's try with a very weird name. + Name n2("xtra\tchars\n.in.name"); + LabelSequence l2(n2); + EXPECT_EQ("xtra\tchars\n.in.name.", l2.toRawText(false)); +} + +// The following are test data used in the getHash test below. Normally +// we use example/documentation domain names for testing, but in this case +// we'd specifically like to use more realistic data, and are intentionally +// using real-world samples: They are the NS names of root and some top level +// domains as of this test. +const char* const root_servers[] = { + "a.root-servers.net", "b.root-servers.net", "c.root-servers.net", + "d.root-servers.net", "e.root-servers.net", "f.root-servers.net", + "g.root-servers.net", "h.root-servers.net", "i.root-servers.net", + "j.root-servers.net", "k.root-servers.net", "l.root-servers.net", + "m.root-servers.net", 0 +}; + +const char* const jp_servers[] = { + "a.dns.jp", "b.dns.jp", "c.dns.jp", "d.dns.jp", "e.dns.jp", + "f.dns.jp", "g.dns.jp", 0 +}; +const char* const cn_servers[] = { + "a.dns.cn", "b.dns.cn", "c.dns.cn", "d.dns.cn", "e.dns.cn", + "ns.cernet.net", 0 +}; +const char* const ca_servers[] = { + "k.ca-servers.ca", "e.ca-servers.ca", "a.ca-servers.ca", "z.ca-servers.ca", + "tld.isc-sns.net", "c.ca-servers.ca", "j.ca-servers.ca", "l.ca-servers.ca", + "sns-pb.isc.org", "f.ca-servers.ca", 0 +}; + +// A helper function used in the getHash test below. +void +hashDistributionCheck(const char* const* servers) { + const size_t BUCKETS = 64; // constant used in the MessageRenderer + set<Name> names; + vector<size_t> hash_counts(BUCKETS); + + // Store all test names and their super domain names (excluding the + // "root" label) in the set, calculates their hash values, and increments + // the counter for the corresponding hash "bucket". + for (size_t i = 0; servers[i]; ++i) { + const Name name(servers[i]); + for (size_t l = 0; l < name.getLabelCount() - 1; ++l) { + pair<set<Name>::const_iterator, bool> ret = + names.insert(name.split(l)); + if (ret.second) { + hash_counts[LabelSequence((*ret.first)).getHash(false) % + BUCKETS]++; + } + } + } + + // See how many conflicts we have in the buckets. For the testing purpose + // we expect there's at most 2 conflicts in each set, which is an + // arbitrary choice (it should happen to succeed with the hash function + // and data we are using; if it's not the case, maybe with an update to + // the hash implementation, we should revise the test). + for (size_t i = 0; i < BUCKETS; ++i) { + EXPECT_GE(3, hash_counts[i]); + } +} + +TEST_F(LabelSequenceTest, getHash) { + // Trivial case. The same sequence should have the same hash. + EXPECT_EQ(ls1.getHash(true), ls1.getHash(true)); + + // Check the case-insensitive mode behavior. + EXPECT_EQ(ls1.getHash(false), ls5.getHash(false)); + + // Check that the distribution of hash values is "not too bad" (such as + // everything has the same hash value due to a stupid bug). It's + // difficult to check such things reliably. We do some ad hoc tests here. + hashDistributionCheck(root_servers); + hashDistributionCheck(jp_servers); + hashDistributionCheck(cn_servers); + hashDistributionCheck(ca_servers); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(LabelSequenceTest, LeftShiftOperator) { + ostringstream oss; + oss << ls1; + EXPECT_EQ(ls1.toText(), oss.str()); +} + +TEST_F(LabelSequenceTest, serialize) { + // placeholder for serialized data. We use a sufficiently large space + // for testing the overwrapping cases below. + uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH * 3]; + + // vector to store expected and actual data + vector<LabelSequence> actual_labelseqs; + typedef pair<size_t, const uint8_t*> DataPair; + vector<DataPair> expected; + + // An absolute sequence directly constructed from a valid name. + // labels = 3, offset sequence = 0, 8, 12, data = "example.com." + actual_labelseqs.push_back(ls1); + const uint8_t expected_data1[] = { + 3, 0, 8, 12, 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'o', 'r', 'g', 0 }; + expected.push_back(DataPair(sizeof(expected_data1), expected_data1)); + + // Strip the original one from right. + // labels = 2, offset sequence = 0, 8, data = "example.com" (non absolute) + LabelSequence ls_rstripped = ls1; + ls_rstripped.stripRight(1); + actual_labelseqs.push_back(ls_rstripped); + const uint8_t expected_data2[] = { + 2, 0, 8, 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 3, 'o', 'r', 'g'}; + expected.push_back(DataPair(sizeof(expected_data2), expected_data2)); + + // Strip the original one from left. + // labels = 2, offset sequence = 0, 4, data = "com." + // Note that offsets are adjusted so that they begin with 0. + LabelSequence ls_lstripped = ls1; + ls_lstripped.stripLeft(1); + actual_labelseqs.push_back(ls_lstripped); + const uint8_t expected_data3[] = { 2, 0, 4, 3, 'o', 'r', 'g', 0 }; + expected.push_back(DataPair(sizeof(expected_data3), expected_data3)); + + // Root label. + LabelSequence ls_root(Name::ROOT_NAME()); + actual_labelseqs.push_back(ls_root); + const uint8_t expected_data4[] = { 1, 0, 0 }; + expected.push_back(DataPair(sizeof(expected_data4), expected_data4)); + + // Non absolute single-label. + LabelSequence ls_single = ls_rstripped; + ls_single.stripRight(1); + actual_labelseqs.push_back(ls_single); + const uint8_t expected_data5[] = { + 1, 0, 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e' }; + expected.push_back(DataPair(sizeof(expected_data5), expected_data5)); + + // Labels containing a longest possible label + const Name name_longlabel(std::string(63, 'x')); // 63 'x's + LabelSequence ls_longlabel(name_longlabel); + actual_labelseqs.push_back(ls_longlabel); + vector<uint8_t> expected_data6; + expected_data6.push_back(2); // 2 labels + expected_data6.push_back(0); // 1st offset + expected_data6.push_back(64); // 2nd offset + expected_data6.push_back(63); // 1st label length + expected_data6.insert(expected_data6.end(), 63, 'x'); // 1st label: 63 'x's + expected_data6.push_back(0); // 2nd label: trailing 0 + expected.push_back(DataPair(expected_data6.size(), &expected_data6[0])); + + // Max number of labels and longest possible name + EXPECT_EQ(Name::MAX_WIRE, n_maxlabel.getLength()); + LabelSequence ls_maxlabel(n_maxlabel); + actual_labelseqs.push_back(ls_maxlabel); + vector<uint8_t> expected_data7; + expected_data7.push_back(Name::MAX_LABELS); // number of labels + for (size_t i = 0; i < Name::MAX_LABELS; ++i) { + expected_data7.push_back(i * 2); // each label has length and 1 byte + } + // Copy wire data of the name + isc::util::OutputBuffer ob(0); + n_maxlabel.toWire(ob); + expected_data7.insert(expected_data7.end(), + ob.getVector().cbegin(), + ob.getVector().cend()); + expected.push_back(DataPair(expected_data7.size(), &expected_data7[0])); + + // For each data set, serialize the labels and compare the data to the + // expected one. + vector<DataPair>::const_iterator it = expected.begin(); + vector<LabelSequence>::const_iterator itl = actual_labelseqs.begin(); + for (; it != expected.end(); ++it, ++itl) { + SCOPED_TRACE(itl->toText()); + + const size_t serialized_len = itl->getSerializedLength(); + + ASSERT_GE(LabelSequence::MAX_SERIALIZED_LENGTH, serialized_len); + itl->serialize(labels_buf, serialized_len); + EXPECT_EQ(it->first, serialized_len); + EXPECT_EQ(0, memcmp(it->second, labels_buf, serialized_len)); + + EXPECT_EQ(NameComparisonResult::EQUAL, + LabelSequence(labels_buf).compare(*itl).getRelation()); + + // Shift the data to the middle of the buffer for overwrap check + uint8_t* const bp = labels_buf; + std::memcpy(bp + serialized_len, bp, serialized_len); + // Memory layout is now as follows: + // <- ser_len -> <- ser_len ------> + // bp bp+ser_len bp+(ser_len*2) + // olen,odata,ndata + + // end of buffer would be the first byte of offsets: invalid. + EXPECT_THROW(LabelSequence(bp + serialized_len). + serialize(bp + 2, serialized_len), + isc::BadValue); + // begin of buffer would be the last byte of ndata: invalid. + EXPECT_THROW(LabelSequence(bp + serialized_len). + serialize(bp + (2 * serialized_len) - 1, serialized_len), + isc::BadValue); + // A boundary safe case: buffer is placed after the sequence data. + // should cause no disruption. + LabelSequence(bp + serialized_len). + serialize(bp + 2 * serialized_len, serialized_len); + // A boundary safe case: buffer is placed before the sequence data + // should cause no disruption. (but the original serialized data will + // be overridden, so it can't be used any more) + LabelSequence(bp + serialized_len). + serialize(bp + 1, serialized_len); + } + + EXPECT_THROW(ls1.serialize(labels_buf, ls1.getSerializedLength() - 1), + isc::BadValue); +} + +#ifdef ENABLE_DEBUG + +// These checks are enabled only in debug mode in the LabelSequence +// class. +TEST_F(LabelSequenceTest, badDeserialize) { + EXPECT_THROW(LabelSequence(0), isc::BadValue); + const uint8_t zero_offsets[] = { 0 }; + EXPECT_THROW(LabelSequence ls(zero_offsets), isc::BadValue); + const uint8_t toomany_offsets[] = { Name::MAX_LABELS + 1 }; + EXPECT_THROW(LabelSequence ls(toomany_offsets), isc::BadValue); + + // (second) offset does not match actual label length + const uint8_t offsets_wrongoffset[] = { 2, 0, 64, 1 }; + EXPECT_THROW(LabelSequence ls(offsets_wrongoffset), isc::BadValue); + + // offset matches, but exceeds MAX_LABEL_LEN + const uint8_t offsets_toolonglabel[] = { 2, 0, 64, 64 }; + EXPECT_THROW(LabelSequence ls(offsets_toolonglabel), isc::BadValue); + + // Inconsistent data: an offset is lower than the previous offset + const uint8_t offsets_lower[] = { 3, // # of offsets + 0, 2, 1, // offsets + 1, 'a', 1, 'b', 0}; + EXPECT_THROW(LabelSequence ls(offsets_lower), isc::BadValue); + + // Inconsistent data: an offset is equal to the previous offset + const uint8_t offsets_noincrease[] = { 2, 0, 0, 0, 0 }; + EXPECT_THROW(LabelSequence ls(offsets_noincrease), isc::BadValue); +} + +#endif + +namespace { + +// Helper function; repeatedly calls +// - Initially, all three labelsequences should be the same +// - repeatedly performs: +// - checks all three are equal +// - stripLeft on ls1 +// - checks ls1 and ls2 are different, and ls2 and ls3 are equal +// - stripLeft on ls2 +// - checks ls1 and ls2 are equal, and ls2 and ls3 are different +// - stripLeft on ls3 +// +// (this test makes sure the stripLeft of one has no effect on the other +// two, and that the strip properties hold regardless of how they were +// constructed) +// +void stripLeftCheck(LabelSequence ls1, LabelSequence ls2, LabelSequence ls3) { + ASSERT_LT(1, ls1.getLabelCount()); + while (ls1.getLabelCount() > 1) { + check_equal(ls1, ls2); + check_equal(ls2, ls3); + + ls1.stripLeft(1); + check_compare(ls1, ls2, isc::dns::NameComparisonResult::SUPERDOMAIN, + ls1.getLabelCount(), true, -1); + check_equal(ls2, ls3); + + ls2.stripLeft(1); + check_equal(ls1, ls2); + check_compare(ls2, ls3, isc::dns::NameComparisonResult::SUPERDOMAIN, + ls1.getLabelCount(), true, -1); + + ls3.stripLeft(1); + } +} + +// Similar to stripLeftCheck, but using stripRight() +void stripRightCheck(LabelSequence ls1, LabelSequence ls2, LabelSequence ls3) { + ASSERT_LT(1, ls1.getLabelCount()); + while (ls1.getLabelCount() > 1) { + check_equal(ls1, ls2); + check_equal(ls2, ls3); + + ls1.stripRight(1); + check_compare(ls1, ls2, isc::dns::NameComparisonResult::NONE, 0, + false); + check_equal(ls2, ls3); + + ls2.stripRight(1); + check_equal(ls1, ls2); + check_compare(ls2, ls3, isc::dns::NameComparisonResult::NONE, 0, + false); + + ls3.stripRight(1); + } +} + +} // end anonymous namespace + +class ExtendableLabelSequenceTest : public ::testing::Test { +public: + ExtendableLabelSequenceTest() : bar("bar."), + example_org("example.org"), + foo("foo."), + foo_bar("foo.bar."), + foo_bar_example_org("foo.bar.example.org."), + foo_bar_foo_bar("foo.bar.foo.bar."), + foo_example("foo.example."), + org("org") + { + // explicitly set to non-zero data, to make sure + // we don't try to use data we don't set + memset(buf, 0xff, LabelSequence::MAX_SERIALIZED_LENGTH); + } + + Name bar; + Name example_org; + Name foo; + Name foo_bar; + Name foo_bar_example_org; + Name foo_bar_foo_bar; + Name foo_example; + Name org; + + uint8_t buf[LabelSequence::MAX_SERIALIZED_LENGTH]; +}; + +// Test that 'extendable' labelsequences behave correctly when using +// stripLeft() and stripRight() +TEST_F(ExtendableLabelSequenceTest, extendableLabelSequence) { + LabelSequence ls1(example_org); + LabelSequence ls2(example_org); + + LabelSequence els(ls1, buf); + // ls1 is absolute, so els should be too + EXPECT_TRUE(els.isAbsolute()); + check_equal(ls1, els); + + ASSERT_EQ(ls1.getDataLength(), els.getDataLength()); + stripLeftCheck(ls1, els, ls2); + stripRightCheck(ls1, els, ls2); + + // Creating an extendable labelsequence from a non-absolute + // label sequence should result in a non-absolute label sequence + ls1.stripRight(1); + els = LabelSequence(ls1, buf); + EXPECT_FALSE(els.isAbsolute()); + check_equal(ls1, els); + + // and extending with the root label should make it absolute again + els.extend(LabelSequence(Name(".")), buf); + EXPECT_TRUE(els.isAbsolute()); + check_equal(ls2, els); +} + +// Test that 'extendable' LabelSequences behave correctly when initialized +// with a stripped source LabelSequence +TEST_F(ExtendableLabelSequenceTest, extendableLabelSequenceLeftStrippedSource) { + LabelSequence ls1(foo_bar_example_org); + LabelSequence ls2(foo_bar_example_org); + + while (ls1.getLabelCount() > 2) { + ls1.stripLeft(1); + ls2.stripLeft(1); + + LabelSequence els(ls1, buf); + + ASSERT_EQ(ls1.getDataLength(), els.getDataLength()); + stripLeftCheck(ls1, els, ls2); + stripRightCheck(ls1, els, ls2); + } +} + +TEST_F(ExtendableLabelSequenceTest, extendableLabelSequenceRightStrippedSource) { + LabelSequence ls1(foo_bar_example_org); + LabelSequence ls2(foo_bar_example_org); + + while (ls1.getLabelCount() > 2) { + ls1.stripRight(1); + ls2.stripRight(1); + + LabelSequence els(ls1, buf); + + ASSERT_EQ(ls1.getDataLength(), els.getDataLength()); + stripLeftCheck(ls1, els, ls2); + stripRightCheck(ls1, els, ls2); + } +} + +// Check some basic 'extend' functionality +TEST_F(ExtendableLabelSequenceTest, extend) { + LabelSequence ls1(foo_bar); + LabelSequence ls2(foo); + LabelSequence ls3(bar); + LabelSequence ls4(foo_bar); + + LabelSequence els(ls2, buf); + + check_compare(ls1, els, isc::dns::NameComparisonResult::COMMONANCESTOR, 1, + true, -4); + els.extend(ls3, buf); + EXPECT_TRUE(els.isAbsolute()); + + check_equal(ls1, els); + stripLeftCheck(ls1, els, ls4); + stripRightCheck(ls1, els, ls4); + + // strip, then extend again + els.stripRight(2); // (2, 1 for root label, 1 for last label) + els.extend(ls3, buf); + EXPECT_TRUE(els.isAbsolute()); + check_equal(ls1, els); + + // Extending again should make it different + els.extend(ls3, buf); + EXPECT_TRUE(els.isAbsolute()); + check_compare(ls1, els, isc::dns::NameComparisonResult::COMMONANCESTOR, 2, + true, 4); + + // Extending with a non-absolute name should make it non-absolute as well + ls3.stripRight(1); + els.extend(ls3, buf); + EXPECT_FALSE(els.isAbsolute()); + + Name check_name("foo.bar.bar.bar"); + LabelSequence check_ls(check_name); + check_ls.stripRight(1); + check_equal(check_ls, els); + + // And try extending when both are not absolute + els.stripRight(3); + ls1.stripRight(1); + EXPECT_FALSE(els.isAbsolute()); + els.extend(ls3, buf); + EXPECT_FALSE(els.isAbsolute()); + check_equal(ls1, els); + + // Extending non-absolute with absolute should make it absolute again + EXPECT_FALSE(els.isAbsolute()); + els.extend(LabelSequence(Name("absolute.")), buf); + EXPECT_TRUE(els.isAbsolute()); + check_equal(LabelSequence(Name("foo.bar.absolute")), els); +} + +TEST_F(ExtendableLabelSequenceTest, extendLeftStripped) { + LabelSequence ls1(foo_example); + LabelSequence ls2(example_org); + LabelSequence ls3(org); + + LabelSequence els(ls1, buf); + + els.stripLeft(1); + els.extend(ls3, buf); + EXPECT_TRUE(els.isAbsolute()); + check_equal(ls2, els); +} + +// Check that when extending with itself, it does not cause horrible failures +TEST_F(ExtendableLabelSequenceTest, extendWithItself) { + LabelSequence ls1(foo_bar); + LabelSequence ls2(foo_bar_foo_bar); + + LabelSequence els(ls1, buf); + + els.extend(els, buf); + EXPECT_TRUE(els.isAbsolute()); + check_equal(ls2, els); + + // Also try for non-absolute names + ls2.stripRight(1); + els = LabelSequence(ls1, buf); + els.stripRight(1); + els.extend(els, buf); + EXPECT_FALSE(els.isAbsolute()); + check_equal(ls2, els); + + // Once more, now start out with non-absolute labelsequence + ls1.stripRight(1); + els = LabelSequence(ls1, buf); + els.extend(els, buf); + EXPECT_FALSE(els.isAbsolute()); + check_equal(ls2, els); +} + +// Test that 'extending' with just a root label is a no-op, iff the original +// was already absolute +TEST_F(ExtendableLabelSequenceTest, extendWithRoot) { + LabelSequence ls1(example_org); + + LabelSequence els(LabelSequence(ls1, buf)); + check_equal(ls1, els); + els.extend(LabelSequence(Name(".")), buf); + EXPECT_TRUE(els.isAbsolute()); + check_equal(ls1, els); + + // but not if the original was not absolute (it will be equal to + // the original labelsequence used above, but not the one it was based + // on). + LabelSequence ls2(example_org); + ls2.stripRight(1); + els = LabelSequence(ls2, buf); + EXPECT_FALSE(els.isAbsolute()); + els.extend(LabelSequence(Name(".")), buf); + EXPECT_TRUE(els.isAbsolute()); + check_equal(ls1, els); + check_compare(ls2, els, isc::dns::NameComparisonResult::NONE, 0, true, 3); +} + +// Check possible failure modes of extend() +TEST_F(ExtendableLabelSequenceTest, extendBadData) { + LabelSequence ls1(example_org); + + LabelSequence els(ls1, buf); + + // try use with unrelated labelsequence + EXPECT_THROW(ls1.extend(ls1, buf), isc::BadValue); + + // Create a long name, but so that we can still extend once + Name longlabel("1234567890123456789012345678901234567890" + "12345678901234567890"); + LabelSequence long_ls(longlabel); + els = LabelSequence(long_ls, buf); + els.extend(els, buf); + els.extend(long_ls, buf); + els.extend(long_ls, buf); + ASSERT_EQ(245, els.getDataLength()); + // Extending once more with 10 bytes should still work + els.extend(LabelSequence(Name("123456789")), buf); + EXPECT_TRUE(els.isAbsolute()); + + // Extended label sequence should now look like + const Name full_name( + "123456789012345678901234567890123456789012345678901234567890." + "123456789012345678901234567890123456789012345678901234567890." + "123456789012345678901234567890123456789012345678901234567890." + "123456789012345678901234567890123456789012345678901234567890." + "123456789."); + const LabelSequence full_ls(full_name); + check_equal(full_ls, els); + + // But now, even the shortest extension should fail + EXPECT_THROW(els.extend(LabelSequence(Name("1")), buf), isc::BadValue); + + // Check it hasn't been changed + EXPECT_TRUE(els.isAbsolute()); + check_equal(full_ls, els); + + // Also check that extending past MAX_LABELS is not possible + Name shortname("1."); + LabelSequence short_ls(shortname); + els = LabelSequence(short_ls, buf); + for (size_t i=0; i < 126; ++i) { + els.extend(short_ls, buf); + } + + // Should now look like this + const Name full_name2( + "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."); + const LabelSequence full_ls2(full_name2); + EXPECT_TRUE(els.isAbsolute()); + check_equal(full_ls2, els); + + EXPECT_THROW(els.extend(short_ls, buf), isc::BadValue); + + EXPECT_TRUE(els.isAbsolute()); + check_equal(full_ls2, els); +} + +// Check the static fixed 'wildcard' LabelSequence +TEST(WildCardLabelSequence, wildcard) { + ASSERT_FALSE(LabelSequence::WILDCARD().isAbsolute()); + ASSERT_EQ("*", LabelSequence::WILDCARD().toText()); +} + +} diff --git a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc new file mode 100644 index 0000000..5167f54 --- /dev/null +++ b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc @@ -0,0 +1,368 @@ +// Copyright (C) 2012-2024 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 <dns/master_lexer_inputsource.h> +#include <dns/master_lexer.h> +#include <exceptions/exceptions.h> + +#include <gtest/gtest.h> + +#include <iostream> +#include <sstream> +#include <string> + +#include <string.h> + +using namespace std; +using namespace isc::dns; +using namespace isc::dns::master_lexer_internal; + +namespace { + +const char* const test_input = + "Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n"; + +class InputSourceTest : public ::testing::Test { +protected: + InputSourceTest() : + str_(test_input), + str_length_(strlen(str_)), + iss_(str_), + source_(iss_) + {} + + const char* str_; + const size_t str_length_; + stringstream iss_; + InputSource source_; +}; + +// Test the default return values set during InputSource construction. +TEST_F(InputSourceTest, defaults) { + EXPECT_EQ(1, source_.getCurrentLine()); + EXPECT_FALSE(source_.atEOF()); +} + +// getName() on file and stream sources +TEST_F(InputSourceTest, getName) { + EXPECT_EQ(0, source_.getName().find("stream-")); + + // Use some file; doesn't really matter what. + InputSource source2(TEST_DATA_SRCDIR "/masterload.txt"); + EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", source2.getName()); +} + +TEST_F(InputSourceTest, nonExistentFile) { + EXPECT_THROW({ + InputSource source(TEST_DATA_SRCDIR "/does-not-exist"); + }, InputSource::OpenError); +} + +// getChar() should return characters from the input stream in +// sequence. ungetChar() should skip backwards. +void +checkGetAndUngetChar(InputSource& source, + const char* str, const size_t str_length) +{ + for (size_t i = 0; i < str_length; ++i) { + EXPECT_EQ(str[i], source.getChar()); + EXPECT_EQ(i + 1, source.getPosition()); + EXPECT_FALSE(source.atEOF()); + } + + // At this point, we still have not reached EOF. + EXPECT_FALSE(source.atEOF()); + + // This should cause EOF to be set. + EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar()); + + // Now, EOF should be set. + EXPECT_TRUE(source.atEOF()); + + // It doesn't increase the position count. + EXPECT_EQ(str_length, source.getPosition()); + EXPECT_EQ(str_length, source.getSize()); // this should be == getSize(). + + // Now, let's go backwards. This should cause the EOF to be set to + // false. + source.ungetChar(); + + // Now, EOF should be false. + EXPECT_FALSE(source.atEOF()); + + // But the position shouldn't change. + EXPECT_EQ(str_length, source.getPosition()); + + // This should cause EOF to be set again. + EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar()); + + // Now, EOF should be set. + EXPECT_TRUE(source.atEOF()); + + // Now, let's go backwards in a loop. Start by skipping the EOF. + source.ungetChar(); + + for (size_t i = 0; i < str_length; ++i) { + const size_t index = str_length - 1 - i; + // Skip one character. + source.ungetChar(); + EXPECT_EQ(str[index], source.getChar()); + EXPECT_EQ(index + 1, source.getPosition()); + // Skip the character we received again. + source.ungetChar(); + } + + // Skipping past the start of buffer should throw. + EXPECT_THROW(source.ungetChar(), InputSource::UngetBeforeBeginning); +} + +TEST_F(InputSourceTest, stream) { + checkGetAndUngetChar(source_, str_, str_length_); +} + +TEST_F(InputSourceTest, file) { + std::ifstream fs(TEST_DATA_SRCDIR "/masterload.txt"); + const std::string str((std::istreambuf_iterator<char>(fs)), + std::istreambuf_iterator<char>()); + fs.close(); + + InputSource source(TEST_DATA_SRCDIR "/masterload.txt"); + checkGetAndUngetChar(source, str.c_str(), str.size()); +} + +// ungetAll() should skip back to the place where the InputSource +// started at construction, or the last saved start of line. +TEST_F(InputSourceTest, ungetAll) { + while (!source_.atEOF()) { + source_.getChar(); + } + + // Now, we are at EOF. + EXPECT_TRUE(source_.atEOF()); + EXPECT_EQ(4, source_.getCurrentLine()); + + source_.ungetAll(); + + // Now we are back to where we started. + EXPECT_EQ(1, source_.getCurrentLine()); + EXPECT_FALSE(source_.atEOF()); + EXPECT_EQ(0, source_.getPosition()); +} + +TEST_F(InputSourceTest, compact) { + // Compact at the start + source_.compact(); + + // Ungetting here must throw. + EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning); + + for (size_t i = 0; i < str_length_; ++i) { + EXPECT_EQ(str_[i], source_.getChar()); + EXPECT_FALSE(source_.atEOF()); + } + + // At this point, we still have not reached EOF. + EXPECT_FALSE(source_.atEOF()); + + // This should cause EOF to be set. + EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar()); + + // Now, EOF should be set. + EXPECT_TRUE(source_.atEOF()); + EXPECT_EQ(4, source_.getCurrentLine()); + + // Compact again + source_.compact(); + + // We are still at EOF. + EXPECT_TRUE(source_.atEOF()); + EXPECT_EQ(4, source_.getCurrentLine()); + + // compact shouldn't change the position count. + EXPECT_EQ(source_.getSize(), source_.getPosition()); + + // Skip the EOF. + source_.ungetChar(); + + // Ungetting here must throw. + EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning); + + EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar()); + EXPECT_TRUE(source_.atEOF()); +} + +TEST_F(InputSourceTest, markDuring) { + // First, skip to line 2. + while (!source_.atEOF() && + (source_.getCurrentLine() != 2)) { + source_.getChar(); + } + EXPECT_FALSE(source_.atEOF()); + EXPECT_EQ(2, source_.getCurrentLine()); + + // Now, unget a couple of characters. This should cause the + // buffer_pos_ to be not equal to the size of the buffer. + source_.ungetChar(); + source_.ungetChar(); + + // Now "mark" the source, meaning that we save line number and also + // compact the internal buffer at this stage. + source_.mark(); + + // Ungetting here must throw. + EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning); + + for (size_t i = 13; i < str_length_; ++i) { + EXPECT_EQ(str_[i], source_.getChar()); + EXPECT_FALSE(source_.atEOF()); + } + + // At this point, we still have not reached EOF. + EXPECT_FALSE(source_.atEOF()); + + // This should cause EOF to be set. + EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar()); + + // Now, EOF should be set. + EXPECT_TRUE(source_.atEOF()); + + // Now, ungetAll() and check where it goes back. + source_.ungetAll(); + + // Ungetting here must throw. + EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning); + + for (size_t i = 13; i < str_length_; ++i) { + EXPECT_EQ(str_[i], source_.getChar()); + EXPECT_FALSE(source_.atEOF()); + } + + // At this point, we still have not reached EOF. + EXPECT_FALSE(source_.atEOF()); + + // This should cause EOF to be set. + EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar()); + + // Now, EOF should be set. + EXPECT_TRUE(source_.atEOF()); +} + +// Test line counters. +TEST_F(InputSourceTest, lines) { + size_t line = 1; + while (!source_.atEOF()) { + if (source_.getChar() == '\n') { + ++line; + } + EXPECT_EQ(line, source_.getCurrentLine()); + } + + // Now, we are at EOF. + EXPECT_TRUE(source_.atEOF()); + EXPECT_EQ(4, source_.getCurrentLine()); + + // Go backwards 2 characters, skipping the last EOF and '\n'. + source_.ungetChar(); + source_.ungetChar(); + + EXPECT_FALSE(source_.atEOF()); + EXPECT_EQ(3, source_.getCurrentLine()); + + source_.ungetAll(); + + // Now we are back to where we started. + EXPECT_EQ(1, source_.getCurrentLine()); + EXPECT_FALSE(source_.atEOF()); + + // Now check that line numbers are decremented properly (as much as + // possible using the available API). + while (!source_.atEOF()) { + source_.getChar(); + } + line = source_.getCurrentLine(); + + // Now, we are at EOF. + EXPECT_TRUE(source_.atEOF()); + EXPECT_EQ(4, line); + + EXPECT_THROW({ + while (true) { + source_.ungetChar(); + EXPECT_TRUE(((line == source_.getCurrentLine()) || + ((line - 1) == source_.getCurrentLine()))); + line = source_.getCurrentLine(); + } + }, InputSource::UngetBeforeBeginning); + + // Now we are back to where we started. + EXPECT_EQ(1, source_.getCurrentLine()); +} + +// ungetAll() after saveLine() should skip back to the last-saved place. +TEST_F(InputSourceTest, saveLine) { + // First, skip to line 2. + while (!source_.atEOF() && + (source_.getCurrentLine() != 2)) { + source_.getChar(); + } + EXPECT_FALSE(source_.atEOF()); + EXPECT_EQ(2, source_.getCurrentLine()); + + // Now, save the line. + source_.saveLine(); + + // Now, go to EOF + while (!source_.atEOF()) { + source_.getChar(); + } + + // Now, we are at EOF. + EXPECT_TRUE(source_.atEOF()); + EXPECT_EQ(4, source_.getCurrentLine()); + + // Now, ungetAll() and check where it goes back. + source_.ungetAll(); + + // Now we are back to where we last-saved. + EXPECT_EQ(2, source_.getCurrentLine()); + EXPECT_FALSE(source_.atEOF()); +} + +TEST_F(InputSourceTest, getSize) { + // A simple case using string stream + EXPECT_EQ(strlen(test_input), source_.getSize()); + + // Check it works with an empty input + istringstream iss(""); + EXPECT_EQ(0, InputSource(iss).getSize()); + + // Pretend there's an error in seeking in the stream. It will be + // considered a seek specific error, and getSize() returns "unknown". + iss.setstate(std::ios_base::failbit); + EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, InputSource(iss).getSize()); + // The fail bit should have been cleared. + EXPECT_FALSE(iss.fail()); + + // Pretend there's a *critical* error in the stream. The constructor will + // throw in the attempt of getting the input size. + iss.setstate(std::ios_base::badbit); + EXPECT_THROW(InputSource isrc(iss), InputSource::OpenError); + + // Check with input source from file name. We hardcode the file size + // for simplicity. It won't change too often. + EXPECT_EQ(143, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getSize()); +} + +TEST_F(InputSourceTest, getPosition) { + // Initially the position is set to 0. Other cases are tested in tests + // for get and unget. + EXPECT_EQ(0, source_.getPosition()); + EXPECT_EQ(0, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getPosition()); +} + +} // end namespace diff --git a/src/lib/dns/tests/master_lexer_state_unittest.cc b/src/lib/dns/tests/master_lexer_state_unittest.cc new file mode 100644 index 0000000..4413ba5 --- /dev/null +++ b/src/lib/dns/tests/master_lexer_state_unittest.cc @@ -0,0 +1,607 @@ +// Copyright (C) 2012-2024 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 <dns/master_lexer.h> +#include <dns/master_lexer_inputsource.h> +#include <dns/master_lexer_state.h> + +#include <gtest/gtest.h> + +#include <sstream> + +using namespace isc::dns; +using namespace master_lexer_internal; + +namespace { +typedef MasterToken Token; // shortcut + +class MasterLexerStateTest : public ::testing::Test { +protected: + MasterLexerStateTest() : common_options(MasterLexer::INITIAL_WS), + s_null(0), + s_crlf(State::getInstance(State::CRLF)), + s_string(State::getInstance(State::String)), + s_qstring(State::getInstance(State::QString)), + s_number(State::getInstance(State::Number)), + options(MasterLexer::NONE), + orig_options(options) + {} + + // Specify INITIAL_WS as common initial options. + const MasterLexer::Options common_options; + MasterLexer lexer; + const State* const s_null; + const State& s_crlf; + const State& s_string; + const State& s_qstring; + const State& s_number; + std::stringstream ss; + MasterLexer::Options options, orig_options; +}; + +// Common check for the end-of-file condition. +// Token is set to END_OF_FILE, and the lexer was NOT last eol state. +// Passed state can be any valid one; they are stateless, just providing the +// interface for inspection. +void +eofCheck(const State& state, MasterLexer& lexer) { + EXPECT_EQ(Token::END_OF_FILE, state.getToken(lexer).getType()); + EXPECT_FALSE(state.wasLastEOL(lexer)); +} + +TEST_F(MasterLexerStateTest, startAndEnd) { + // A simple case: the input is empty, so we begin with start and + // are immediately done. + lexer.pushSource(ss); + EXPECT_EQ(s_null, State::start(lexer, common_options)); + eofCheck(s_crlf, lexer); +} + +TEST_F(MasterLexerStateTest, startToEOL) { + ss << "\n"; + lexer.pushSource(ss); + + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_TRUE(s_crlf.wasLastEOL(lexer)); + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + + // The next lexer session will reach EOF. Same eof check should pass. + EXPECT_EQ(s_null, State::start(lexer, common_options)); + eofCheck(s_crlf, lexer); +} + +TEST_F(MasterLexerStateTest, space) { + // repeat '\t\n' twice (see below), then space after EOL + ss << " \t\n\t\n "; + lexer.pushSource(ss); + + // by default space characters and tabs will be ignored. We check this + // twice; at the second iteration, it's a white space at the beginning + // of line, but since we don't specify INITIAL_WS option, it's treated as + // normal space and ignored. + for (size_t i = 0; i < 2; ++i) { + EXPECT_EQ(s_null, State::start(lexer, MasterLexer::NONE)); + EXPECT_TRUE(s_crlf.wasLastEOL(lexer)); + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + } + + // Now we specify the INITIAL_WS option. It will be recognized and the + // corresponding token will be returned. + EXPECT_EQ(s_null, State::start(lexer, MasterLexer::INITIAL_WS)); + EXPECT_FALSE(s_crlf.wasLastEOL(lexer)); + EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType()); +} + +TEST_F(MasterLexerStateTest, parentheses) { + ss << "\n(\na\n )\n "; // 1st \n is to check if 'was EOL' is set to false + lexer.pushSource(ss); + + EXPECT_EQ(s_null, State::start(lexer, common_options)); // handle \n + + // Now handle '('. It skips \n and recognize 'a' as string + EXPECT_EQ(0, s_crlf.getParenCount(lexer)); // check pre condition + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + EXPECT_EQ(1, s_crlf.getParenCount(lexer)); // check post condition + EXPECT_FALSE(s_crlf.wasLastEOL(lexer)); + + // skip 'a' + s_string.handle(lexer); + + // Then handle ')'. '\n' before ')' isn't recognized because + // it's canceled due to the '('. Likewise, the space after the '\n' + // shouldn't be recognized but should be just ignored. + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(0, s_crlf.getParenCount(lexer)); + + // Now, temporarily disabled options are restored: Both EOL and the + // initial WS are recognized + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType()); +} + +TEST_F(MasterLexerStateTest, nestedParentheses) { + // This is an unusual, but allowed (in this implementation) case. + ss << "(a(b)\n c)\n "; + lexer.pushSource(ss); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '(' + s_string.handle(lexer); // consume 'a' + EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '(' + s_string.handle(lexer); // consume 'b' + EXPECT_EQ(2, s_crlf.getParenCount(lexer)); // now the count is 2 + + // Close the inner most parentheses. count will be decreased, but option + // shouldn't be restored yet, so the intermediate EOL or initial WS won't + // be recognized. + EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume ')' + s_string.handle(lexer); // consume 'c' + EXPECT_EQ(1, s_crlf.getParenCount(lexer)); + + // Close the outermost parentheses. count will be reset to 0, and original + // options are restored. + EXPECT_EQ(s_null, State::start(lexer, common_options)); + + // Now, temporarily disabled options are restored: Both EOL and the + // initial WS are recognized + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType()); +} + +TEST_F(MasterLexerStateTest, unbalancedParentheses) { + // Only closing paren is provided. We prepend a \n to check if it's + // correctly canceled after detecting the error. + ss << "\n)"; + ss << "(a"; + lexer.pushSource(ss); + + EXPECT_EQ(s_null, State::start(lexer, common_options)); // consume '\n' + EXPECT_TRUE(s_crlf.wasLastEOL(lexer)); // this \n was remembered + + // Now checking ')'. The result should be error, count shouldn't be + // changed. "last EOL" should be canceled. + EXPECT_EQ(0, s_crlf.getParenCount(lexer)); + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(0, s_crlf.getParenCount(lexer)); + ASSERT_EQ(Token::ERROR, s_crlf.getToken(lexer).getType()); + EXPECT_EQ(Token::UNBALANCED_PAREN, s_crlf.getToken(lexer).getErrorCode()); + EXPECT_FALSE(s_crlf.wasLastEOL(lexer)); + + // Reach EOF with a dangling open parenthesis. + EXPECT_EQ(&s_string, State::start(lexer, common_options)); // consume '(' + s_string.handle(lexer); // consume 'a' + EXPECT_EQ(1, s_crlf.getParenCount(lexer)); + EXPECT_EQ(s_null, State::start(lexer, common_options)); // reach EOF + ASSERT_EQ(Token::ERROR, s_crlf.getToken(lexer).getType()); + EXPECT_EQ(Token::UNBALANCED_PAREN, s_crlf.getToken(lexer).getErrorCode()); + EXPECT_EQ(0, s_crlf.getParenCount(lexer)); // should be reset to 0 +} + +TEST_F(MasterLexerStateTest, startToComment) { + // Begin with 'start', detect space, then encounter a comment. Skip + // the rest of the line, and recognize the new line. Note that the + // second ';' is simply ignored. + ss << " ;a;\n"; + ss << ";a;"; // Likewise, but the comment ends with EOF. + lexer.pushSource(ss); + + // Initial whitespace (asked for in common_options) + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType()); + // Comment ending with EOL + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + + // Comment ending with EOF + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType()); +} + +TEST_F(MasterLexerStateTest, commentAfterParen) { + // comment after an opening parenthesis. The code that is tested by + // other tests should also ensure that it works correctly, but we + // check it explicitly. + ss << "( ;this is a comment\na)\n"; + lexer.pushSource(ss); + + // consume '(', skip comments, consume 'a', then consume ')' + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); +} + +TEST_F(MasterLexerStateTest, crlf) { + ss << "\r\n"; // case 1 + ss << "\r "; // case 2 + ss << "\r;comment\na"; // case 3 + ss << "\r"; // case 4 + lexer.pushSource(ss); + + // 1. A sequence of \r, \n is recognized as a single 'end-of-line' + EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r' + s_crlf.handle(lexer); // recognize '\n' + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + EXPECT_TRUE(s_crlf.wasLastEOL(lexer)); + + // 2. Single '\r' (not followed by \n) is recognized as a single + // 'end-of-line'. then there will be "initial WS" + EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r' + // see ' ', "unget" it + s_crlf.handle(lexer); + EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize ' ' + EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType()); + + // 3. comment between \r and \n + EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r' + // skip comments, recognize '\n' + s_crlf.handle(lexer); + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // skip 'a' + + // 4. \r then EOF + EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r' + // see EOF, then "unget" it + s_crlf.handle(lexer); + EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize EOF + EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType()); +} + +// Commonly used check for string related test cases, checking if the given +// token has expected values. +void +stringTokenCheck(const std::string& expected, const MasterToken& token, + bool quoted = false) +{ + EXPECT_EQ(quoted ? Token::QSTRING : Token::STRING, token.getType()); + EXPECT_EQ(expected, token.getString()); + const std::string actual(token.getStringRegion().beg, + token.getStringRegion().beg + + token.getStringRegion().len); + EXPECT_EQ(expected, actual); + + // There should be "hidden" nul-terminator after the string data. + ASSERT_NE(static_cast<const char*>(0), token.getStringRegion().beg); + EXPECT_EQ(0, *(token.getStringRegion().beg + token.getStringRegion().len)); +} + +TEST_F(MasterLexerStateTest, string) { + // Check with simple strings followed by separate characters + ss << "followed-by-EOL\n"; + ss << "followed-by-CR\r"; + ss << "followed-by-space "; + ss << "followed-by-tab\t"; + ss << "followed-by-comment;this is comment and ignored\n"; + ss << "followed-by-paren(closing)"; + ss << "followed-by-EOF"; + lexer.pushSource(ss); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see \n + EXPECT_FALSE(s_string.wasLastEOL(lexer)); + stringTokenCheck("followed-by-EOL", s_string.getToken(lexer)); + EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see \r + stringTokenCheck("followed-by-CR", s_string.getToken(lexer)); + EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // handle \r... + s_crlf.handle(lexer); // ...and skip it + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see ' ' + stringTokenCheck("followed-by-space", s_string.getToken(lexer)); + + // skip ' ', then recognize the next string + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see \t + stringTokenCheck("followed-by-tab", s_string.getToken(lexer)); + + // skip \t, then recognize the next string + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see comment + stringTokenCheck("followed-by-comment", s_string.getToken(lexer)); + EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see '(' + stringTokenCheck("followed-by-paren", s_string.getToken(lexer)); + EXPECT_EQ(&s_string, State::start(lexer, common_options)); // str in () + s_string.handle(lexer); // recognize the str, see ')' + stringTokenCheck("closing", s_string.getToken(lexer)); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see EOF + stringTokenCheck("followed-by-EOF", s_string.getToken(lexer)); +} + +TEST_F(MasterLexerStateTest, stringEscape) { + // some of the separate characters should be considered part of the + // string if escaped. + ss << "escaped\\ space "; + ss << "escaped\\\ttab "; + ss << "escaped\\(paren "; + ss << "escaped\\)close "; + ss << "escaped\\;comment "; + ss << "escaped\\\\ backslash "; // second '\' shouldn't escape ' ' + lexer.pushSource(ss); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("escaped\\ space", s_string.getToken(lexer)); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("escaped\\\ttab", s_string.getToken(lexer)); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("escaped\\(paren", s_string.getToken(lexer)); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("escaped\\)close", s_string.getToken(lexer)); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("escaped\\;comment", s_string.getToken(lexer)); + + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see ' ' in mid + stringTokenCheck("escaped\\\\", s_string.getToken(lexer)); + + // Confirm the word that follows the escaped '\' is correctly recognized. + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("backslash", s_string.getToken(lexer)); +} + +TEST_F(MasterLexerStateTest, quotedString) { + ss << "\"ignore-quotes\"\n"; + ss << "\"quoted string\" "; // space is part of the qstring + ss << "\"\" "; // empty quoted string + // also check other separator characters. note that \r doesn't cause + // UNBALANCED_QUOTES. Not sure if it's intentional, but that's how the + // BIND 9 version works, so we follow it (it should be too minor to matter + // in practice anyway) + ss << "\"quoted()\t\rstring\" "; + ss << "\"escape\\ in quote\" "; + ss << "\"escaped\\\"\" "; + ss << "\"escaped backslash\\\\\" "; + ss << "\"no;comment\""; + lexer.pushSource(ss); + + // by default, '"' is unexpected (when QSTRING is not specified), + // and it returns MasterToken::UNEXPECTED_QUOTES. + EXPECT_EQ(s_null, State::start(lexer, common_options)); + EXPECT_EQ(Token::UNEXPECTED_QUOTES, s_string.getToken(lexer).getErrorCode()); + // Read it as a QSTRING. + s_qstring.handle(lexer); // recognize quoted str, see \n + stringTokenCheck("ignore-quotes", s_qstring.getToken(lexer), true); + EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it + EXPECT_TRUE(s_string.wasLastEOL(lexer)); + + // If QSTRING is specified in option, '"' is regarded as a beginning of + // a quoted string. + const MasterLexer::Options options = common_options | MasterLexer::QSTRING; + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + EXPECT_FALSE(s_string.wasLastEOL(lexer)); // EOL is canceled due to '"' + s_qstring.handle(lexer); + stringTokenCheck("quoted string", s_string.getToken(lexer), true); + + // Empty string is okay as qstring + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + stringTokenCheck("", s_string.getToken(lexer), true); + + // Also checks other separator characters within a qstring + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + stringTokenCheck("quoted()\t\rstring", s_string.getToken(lexer), true); + + // escape character mostly doesn't have any effect in the qstring + // processing + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + stringTokenCheck("escape\\ in quote", s_string.getToken(lexer), true); + + // The only exception is the quotation mark itself. Note that the escape + // only works on the quotation mark immediately after it. + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + stringTokenCheck("escaped\"", s_string.getToken(lexer), true); + + // quoted '\' then '"'. Unlike the previous case '"' shouldn't be + // escaped. + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + stringTokenCheck("escaped backslash\\\\", s_string.getToken(lexer), true); + + // ';' has no meaning in a quoted string (not indicating a comment) + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + stringTokenCheck("no;comment", s_string.getToken(lexer), true); +} + +TEST_F(MasterLexerStateTest, brokenQuotedString) { + ss << "\"unbalanced-quote\n"; + ss << "\"quoted\\\n\" "; + ss << "\"unclosed quote and EOF"; + lexer.pushSource(ss); + + // EOL is encountered without closing the quote + const MasterLexer::Options options = common_options | MasterLexer::QSTRING; + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType()); + EXPECT_EQ(Token::UNBALANCED_QUOTES, + s_qstring.getToken(lexer).getErrorCode()); + // We can resume after the error from the '\n' + EXPECT_EQ(s_null, State::start(lexer, options)); + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + + // \n is okay in a quoted string if escaped + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + stringTokenCheck("quoted\\\n", s_string.getToken(lexer), true); + + // EOF is encountered without closing the quote + EXPECT_EQ(&s_qstring, State::start(lexer, options)); + s_qstring.handle(lexer); + ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType()); + EXPECT_EQ(Token::UNEXPECTED_END, s_qstring.getToken(lexer).getErrorCode()); + // If we continue we'll simply see the EOF + EXPECT_EQ(s_null, State::start(lexer, options)); + EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType()); +} + +TEST_F(MasterLexerStateTest, basicNumbers) { + ss << "0 "; + ss << "1 "; + ss << "12345 "; + ss << "4294967295 "; // 2^32-1 + ss << "4294967296 "; // Out of range + ss << "340282366920938463463374607431768211456 "; + // Very much out of range (2^128) + ss << "005 "; // Leading zeroes are ignored + ss << "42;asdf\n"; // Number with comment + ss << "37"; // Simple number again, here to make + // sure none of the above messed up + // the tokenizer + lexer.pushSource(ss); + + // Ask the lexer to recognize numbers as well + const MasterLexer::Options options = common_options | MasterLexer::NUMBER; + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(0, s_number.getToken(lexer).getNumber()); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(1, s_number.getToken(lexer).getNumber()); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(12345, s_number.getToken(lexer).getNumber()); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(4294967295u, s_number.getToken(lexer).getNumber()); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE, + s_number.getToken(lexer).getErrorCode()); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE, + s_number.getToken(lexer).getErrorCode()); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(5, s_number.getToken(lexer).getNumber()); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(42, s_number.getToken(lexer).getNumber()); + + EXPECT_EQ(s_null, State::start(lexer, options)); + EXPECT_TRUE(s_crlf.wasLastEOL(lexer)); + EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType()); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + EXPECT_EQ(37, s_number.getToken(lexer).getNumber()); + + // If we continue we'll simply see the EOF + EXPECT_EQ(s_null, State::start(lexer, options)); + EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType()); +} + +// Test tokens that look like (or start out as) numbers, +// but turn out to be strings. Tests include escaped characters. +TEST_F(MasterLexerStateTest, stringNumbers) { + ss << "123 "; // Should be read as a string if the + // NUMBER option is not given + ss << "-1 "; // Negative numbers are interpreted + // as strings (unsigned integers only) + ss << "123abc456 "; // 'Numbers' containing non-digits should + // be interpreted as strings + ss << "123\\456 "; // Numbers containing escaped digits are + // interpreted as strings + ss << "3scaped\\ space "; + ss << "3scaped\\\ttab "; + ss << "3scaped\\(paren "; + ss << "3scaped\\)close "; + ss << "3scaped\\;comment "; + ss << "3scaped\\\\ 8ackslash "; // second '\' shouldn't escape ' ' + + lexer.pushSource(ss); + + // Note that common_options does not include MasterLexer::NUMBER, + // so the token should be recognized as a string + EXPECT_EQ(&s_string, State::start(lexer, common_options)); + s_string.handle(lexer); + stringTokenCheck("123", s_string.getToken(lexer), false); + + // Ask the lexer to recognize numbers as well + const MasterLexer::Options options = common_options | MasterLexer::NUMBER; + + EXPECT_EQ(&s_string, State::start(lexer, options)); + s_string.handle(lexer); + stringTokenCheck("-1", s_string.getToken(lexer), false); + + // Starts out as a number, but ends up being a string + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + stringTokenCheck("123abc456", s_number.getToken(lexer), false); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); + stringTokenCheck("123\\456", s_number.getToken(lexer), false); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("3scaped\\ space", s_number.getToken(lexer)); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("3scaped\\\ttab", s_number.getToken(lexer)); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("3scaped\\(paren", s_number.getToken(lexer)); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("3scaped\\)close", s_number.getToken(lexer)); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("3scaped\\;comment", s_number.getToken(lexer)); + + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); // recognize str, see ' ' in mid + stringTokenCheck("3scaped\\\\", s_number.getToken(lexer)); + + // Confirm the word that follows the escaped '\' is correctly recognized. + EXPECT_EQ(&s_number, State::start(lexer, options)); + s_number.handle(lexer); // recognize str, see ' ' at end + stringTokenCheck("8ackslash", s_number.getToken(lexer)); + + // If we continue we'll simply see the EOF + EXPECT_EQ(s_null, State::start(lexer, options)); + EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType()); +} + +} // end anonymous namespace + diff --git a/src/lib/dns/tests/master_lexer_token_unittest.cc b/src/lib/dns/tests/master_lexer_token_unittest.cc new file mode 100644 index 0000000..4a3787c --- /dev/null +++ b/src/lib/dns/tests/master_lexer_token_unittest.cc @@ -0,0 +1,162 @@ +// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> + +#include <dns/master_lexer.h> + +#include <gtest/gtest.h> + +#include <string> + +using namespace isc::dns; + +namespace { + +const char TEST_STRING[] = "string token"; +// This excludes the ending \0 character +const size_t TEST_STRING_LEN = sizeof(TEST_STRING) - 1; + +class MasterLexerTokenTest : public ::testing::Test { +protected: + MasterLexerTokenTest() : + token_eof(MasterToken::END_OF_FILE), + token_str(TEST_STRING, TEST_STRING_LEN), + token_num(42), + token_err(MasterToken::UNEXPECTED_END) + {} + + const MasterToken token_eof; // an example of non-value type token + const MasterToken token_str; + const MasterToken token_num; + const MasterToken token_err; +}; + + +TEST_F(MasterLexerTokenTest, strings) { + // basic construction and getter checks + EXPECT_EQ(MasterToken::STRING, token_str.getType()); + EXPECT_EQ(std::string("string token"), token_str.getString()); + std::string strval = "dummy"; // this should be replaced + token_str.getString(strval); + EXPECT_EQ(std::string("string token"), strval); + const MasterToken::StringRegion str_region = + token_str.getStringRegion(); + EXPECT_EQ(TEST_STRING, str_region.beg); + EXPECT_EQ(TEST_STRING_LEN, str_region.len); + + // Even if the stored string contains a nul character (in this case, + // it happens to be at the end of the string, but could be in the middle), + // getString() should return a string object containing the nul. + std::string expected_str("string token"); + expected_str.push_back('\0'); + EXPECT_EQ(expected_str, + MasterToken(TEST_STRING, TEST_STRING_LEN + 1).getString()); + MasterToken(TEST_STRING, TEST_STRING_LEN + 1).getString(strval); + EXPECT_EQ(expected_str, strval); + + // Construct type of qstring + EXPECT_EQ(MasterToken::QSTRING, + MasterToken(TEST_STRING, sizeof(TEST_STRING), true). + getType()); + // if we explicitly set 'quoted' to false, it should be normal string + EXPECT_EQ(MasterToken::STRING, + MasterToken(TEST_STRING, sizeof(TEST_STRING), false). + getType()); + + // getString/StringRegion() aren't allowed for non string(-variant) types + EXPECT_THROW(token_eof.getString(), isc::InvalidOperation); + EXPECT_THROW(token_eof.getString(strval), isc::InvalidOperation); + EXPECT_THROW(token_num.getString(), isc::InvalidOperation); + EXPECT_THROW(token_num.getString(strval), isc::InvalidOperation); + EXPECT_THROW(token_eof.getStringRegion(), isc::InvalidOperation); + EXPECT_THROW(token_num.getStringRegion(), isc::InvalidOperation); +} + +TEST_F(MasterLexerTokenTest, numbers) { + EXPECT_EQ(42, token_num.getNumber()); + EXPECT_EQ(MasterToken::NUMBER, token_num.getType()); + + // It's copyable and assignable. + MasterToken token(token_num); + EXPECT_EQ(42, token.getNumber()); + EXPECT_EQ(MasterToken::NUMBER, token.getType()); + + token = token_num; + EXPECT_EQ(42, token.getNumber()); + EXPECT_EQ(MasterToken::NUMBER, token.getType()); + + // it's okay to replace it with a different type of token + token = token_eof; + EXPECT_EQ(MasterToken::END_OF_FILE, token.getType()); + + // Possible max value + token = MasterToken(0xffffffff); + EXPECT_EQ(4294967295u, token.getNumber()); + + // getNumber() isn't allowed for non number types + EXPECT_THROW(token_eof.getNumber(), isc::InvalidOperation); + EXPECT_THROW(token_str.getNumber(), isc::InvalidOperation); +} + +TEST_F(MasterLexerTokenTest, novalues) { + // Just checking we can construct them and getType() returns correct value. + EXPECT_EQ(MasterToken::END_OF_FILE, token_eof.getType()); + EXPECT_EQ(MasterToken::END_OF_LINE, + MasterToken(MasterToken::END_OF_LINE).getType()); + EXPECT_EQ(MasterToken::INITIAL_WS, + MasterToken(MasterToken::INITIAL_WS).getType()); + + // Special types of tokens cannot have value-based types + EXPECT_THROW(MasterToken t(MasterToken::STRING), isc::InvalidParameter); + EXPECT_THROW(MasterToken t(MasterToken::QSTRING), isc::InvalidParameter); + EXPECT_THROW(MasterToken t(MasterToken::NUMBER), isc::InvalidParameter); + EXPECT_THROW(MasterToken t(MasterToken::ERROR), isc::InvalidParameter); +} + +TEST_F(MasterLexerTokenTest, errors) { + EXPECT_EQ(MasterToken::ERROR, token_err.getType()); + EXPECT_EQ(MasterToken::UNEXPECTED_END, token_err.getErrorCode()); + EXPECT_EQ("unexpected end of input", token_err.getErrorText()); + EXPECT_EQ("lexer not started", MasterToken(MasterToken::NOT_STARTED). + getErrorText()); + EXPECT_EQ("unbalanced parentheses", + MasterToken(MasterToken::UNBALANCED_PAREN). + getErrorText()); + EXPECT_EQ("unbalanced quotes", MasterToken(MasterToken::UNBALANCED_QUOTES). + getErrorText()); + EXPECT_EQ("no token produced", MasterToken(MasterToken::NO_TOKEN_PRODUCED). + getErrorText()); + EXPECT_EQ("number out of range", + MasterToken(MasterToken::NUMBER_OUT_OF_RANGE). + getErrorText()); + EXPECT_EQ("not a valid number", + MasterToken(MasterToken::BAD_NUMBER).getErrorText()); + EXPECT_EQ("unexpected quotes", + MasterToken(MasterToken::UNEXPECTED_QUOTES).getErrorText()); + + // getErrorCode/Text() isn't allowed for non number types + EXPECT_THROW(token_num.getErrorCode(), isc::InvalidOperation); + EXPECT_THROW(token_num.getErrorText(), isc::InvalidOperation); + + // Only the pre-defined error code is accepted. Hardcoding '8' (max code + // + 1) is intentional; it'd be actually better if we notice it when we + // update the enum list (which shouldn't happen too often). + // + // Note: if you fix this testcase, you probably want to update the + // getErrorText() tests above too. + EXPECT_THROW(MasterToken(MasterToken::ErrorCode(8)), + isc::InvalidParameter); + + // Check the coexistence of "from number" and "from error-code" + // constructors won't cause confusion. + EXPECT_EQ(MasterToken::NUMBER, + MasterToken(static_cast<uint32_t>(MasterToken::NOT_STARTED)). + getType()); +} +} diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc new file mode 100644 index 0000000..a97e18d --- /dev/null +++ b/src/lib/dns/tests/master_lexer_unittest.cc @@ -0,0 +1,521 @@ +// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> + +#include <dns/master_lexer.h> +#include <dns/master_lexer_state.h> + +#include <gtest/gtest.h> + +#include <boost/lexical_cast.hpp> +#include <boost/scoped_ptr.hpp> + +#include <string> +#include <sstream> + +using namespace isc::dns; +using std::string; +using std::stringstream; +using boost::lexical_cast; +using boost::scoped_ptr; +using master_lexer_internal::State; + +namespace { + +class MasterLexerTest : public ::testing::Test { +protected: + MasterLexerTest() : + expected_stream_name("stream-" + lexical_cast<string>(&ss)) + {} + + MasterLexer lexer; + stringstream ss; + const string expected_stream_name; +}; + +// Commonly used check case where the input sources stack is empty. +void +checkEmptySource(const MasterLexer& lexer) { + EXPECT_TRUE(lexer.getSourceName().empty()); + EXPECT_EQ(0, lexer.getSourceLine()); + EXPECT_EQ(0, lexer.getPosition()); +} + +TEST_F(MasterLexerTest, preOpen) { + // Initially sources stack is empty. + checkEmptySource(lexer); +} + +TEST_F(MasterLexerTest, pushStream) { + EXPECT_EQ(0, lexer.getSourceCount()); + ss << "test"; + lexer.pushSource(ss); + EXPECT_EQ(expected_stream_name, lexer.getSourceName()); + EXPECT_EQ(1, lexer.getSourceCount()); + EXPECT_EQ(4, lexer.getTotalSourceSize()); // 4 = len("test") + + // From the point of view of this test, we only have to check (though + // indirectly) getSourceLine calls InputSource::getCurrentLine. It should + // return 1 initially. + EXPECT_EQ(1, lexer.getSourceLine()); + + // By popping it the stack will be empty again. + lexer.popSource(); + EXPECT_EQ(0, lexer.getSourceCount()); + checkEmptySource(lexer); + EXPECT_EQ(4, lexer.getTotalSourceSize()); // this shouldn't change +} + +TEST_F(MasterLexerTest, pushStreamFail) { + // Pretend a "bad" thing happened in the stream. This will make the + // initialization throw an exception. + ss << "test"; + ss.setstate(std::ios_base::badbit); + + EXPECT_THROW(lexer.pushSource(ss), isc::Unexpected); +} + +TEST_F(MasterLexerTest, pushFile) { + // We use zone file (-like) data, but in this test that actually doesn't + // matter. + EXPECT_EQ(0, lexer.getSourceCount()); + EXPECT_TRUE(lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt")); + EXPECT_EQ(1, lexer.getSourceCount()); + EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName()); + EXPECT_EQ(1, lexer.getSourceLine()); + + // 143 = size of the test zone file. hardcode it assuming it won't change + // too often. + EXPECT_EQ(143, lexer.getTotalSourceSize()); + + lexer.popSource(); + checkEmptySource(lexer); + EXPECT_EQ(0, lexer.getSourceCount()); + EXPECT_EQ(143, lexer.getTotalSourceSize()); // this shouldn't change + + // If we give a non null string pointer, its content will be intact + // if pushSource succeeds. + std::string error_txt = "dummy"; + EXPECT_TRUE(lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt", + &error_txt)); + EXPECT_EQ("dummy", error_txt); +} + +TEST_F(MasterLexerTest, pushBadFileName) { + EXPECT_THROW(lexer.pushSource(0), isc::InvalidParameter); +} + +TEST_F(MasterLexerTest, pushFileFail) { + // The file to be pushed doesn't exist. pushSource() fails and + // some non empty error string should be set. + std::string error_txt; + EXPECT_TRUE(error_txt.empty()); + EXPECT_FALSE(lexer.pushSource("no-such-file", &error_txt)); + EXPECT_FALSE(error_txt.empty()); + + // It's safe to pass null error_txt (either explicitly or implicitly as + // the default) + EXPECT_FALSE(lexer.pushSource("no-such-file", 0)); + EXPECT_FALSE(lexer.pushSource("no-such-file")); +} + +TEST_F(MasterLexerTest, nestedPush) { + const string test_txt = "test"; + ss << test_txt; + lexer.pushSource(ss); + + EXPECT_EQ(test_txt.size(), lexer.getTotalSourceSize()); + EXPECT_EQ(0, lexer.getPosition()); + + EXPECT_EQ(expected_stream_name, lexer.getSourceName()); + + // Read the string; getPosition() should reflect that. + EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType()); + EXPECT_EQ(test_txt.size(), lexer.getPosition()); + + // We can push another source without popping the previous one. + lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt"); + EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName()); + EXPECT_EQ(143 + test_txt.size(), + lexer.getTotalSourceSize()); // see above for magic nums + + // the next token should be the EOL (skipping a comment line), its + // position in the file is 35 (hardcoded). + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + EXPECT_EQ(test_txt.size() + 35, lexer.getPosition()); + + // popSource() works on the "topmost" (last-pushed) source + lexer.popSource(); + EXPECT_EQ(expected_stream_name, lexer.getSourceName()); + + // pop shouldn't change the total size and the current position + EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize()); + EXPECT_EQ(test_txt.size() + 35, lexer.getPosition()); + + lexer.popSource(); + EXPECT_TRUE(lexer.getSourceName().empty()); + + // size and position still shouldn't change + EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize()); + EXPECT_EQ(test_txt.size() + 35, lexer.getPosition()); +} + +TEST_F(MasterLexerTest, unknownSourceSize) { + // Similar to the previous case, but the size of the second source + // will be considered "unknown" (by emulating an error). + ss << "test"; + lexer.pushSource(ss); + EXPECT_EQ(4, lexer.getTotalSourceSize()); + + stringstream ss2; + ss2.setstate(std::ios_base::failbit); // this will make the size unknown + lexer.pushSource(ss2); + // Then the total size is also unknown. + EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize()); + + // Even if we pop that source, the size is still unknown. + lexer.popSource(); + EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize()); +} + +TEST_F(MasterLexerTest, invalidPop) { + // popSource() cannot be called if the sources stack is empty. + EXPECT_THROW(lexer.popSource(), isc::InvalidOperation); +} + +// Test it is not possible to get token when no source is available. +TEST_F(MasterLexerTest, noSource) { + EXPECT_THROW(lexer.getNextToken(), isc::InvalidOperation); +} + +// Test getting some tokens. It also check basic behavior of getPosition(). +TEST_F(MasterLexerTest, getNextToken) { + ss << "\n \n\"STRING\"\n"; + lexer.pushSource(ss); + + // First, the newline should get out. + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + EXPECT_EQ(1, lexer.getPosition()); + // Then the whitespace, if we specify the option. + EXPECT_EQ(MasterToken::INITIAL_WS, + lexer.getNextToken(MasterLexer::INITIAL_WS).getType()); + EXPECT_EQ(2, lexer.getPosition()); + // The newline + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + EXPECT_EQ(5, lexer.getPosition()); // 1st \n + 3 spaces, then 2nd \n + // The (quoted) string + EXPECT_EQ(MasterToken::QSTRING, + lexer.getNextToken(MasterLexer::QSTRING).getType()); + EXPECT_EQ(5 + 8, lexer.getPosition()); // 8 = len("STRING') + quotes + + // And the end of line and file + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // previous + 3rd \n + EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType()); + EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // position doesn't change +} + +// Test we correctly find end of file. +TEST_F(MasterLexerTest, eof) { + // Let the ss empty. + lexer.pushSource(ss); + + // The first one is found to be EOF + EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType()); + // And it stays on EOF for any following attempts + EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType()); + // And we can step back one token, but that is the EOF too. + lexer.ungetToken(); + EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType()); +} + +// Check we properly return error when there's an opened parentheses and no +// closing one +TEST_F(MasterLexerTest, getUnbalancedParen) { + ss << "(string"; + lexer.pushSource(ss); + + // The string gets out first + EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType()); + // Then an unbalanced parenthesis + EXPECT_EQ(MasterToken::UNBALANCED_PAREN, + lexer.getNextToken().getErrorCode()); + // And then EOF + EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType()); +} + +// Check we properly return error when there's an opened quoted string and no +// closing one +TEST_F(MasterLexerTest, getUnbalancedString) { + ss << "\"string"; + lexer.pushSource(ss); + + // Then an unbalanced qstring (reported as an unexpected end) + EXPECT_EQ(MasterToken::UNEXPECTED_END, + lexer.getNextToken(MasterLexer::QSTRING).getErrorCode()); + // And then EOF + EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType()); +} + +// Test ungetting tokens works. Also check getPosition() is adjusted +TEST_F(MasterLexerTest, ungetToken) { + ss << "\n (\"string\"\n) more"; + lexer.pushSource(ss); + + // Try getting the newline + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + EXPECT_EQ(1, lexer.getPosition()); + // Return it and get again + lexer.ungetToken(); + EXPECT_EQ(0, lexer.getPosition()); + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + EXPECT_EQ(1, lexer.getPosition()); + // Get the string and return it back + EXPECT_EQ(MasterToken::QSTRING, + lexer.getNextToken(MasterLexer::QSTRING).getType()); + EXPECT_EQ(string("\n (\"string\"").size(), lexer.getPosition()); + lexer.ungetToken(); + EXPECT_EQ(1, lexer.getPosition()); // back to just after 1st \n + // But if we change the options, it honors them + EXPECT_EQ(MasterToken::INITIAL_WS, + lexer.getNextToken(MasterLexer::QSTRING | + MasterLexer::INITIAL_WS).getType()); + // Get to the "more" string + EXPECT_EQ(MasterToken::QSTRING, + lexer.getNextToken(MasterLexer::QSTRING).getType()); + EXPECT_EQ(MasterToken::STRING, + lexer.getNextToken(MasterLexer::QSTRING).getType()); + // Return it back. It should get inside the parentheses. + // Upon next attempt to get it again, the newline inside the parentheses + // should be still ignored. + lexer.ungetToken(); + EXPECT_EQ(MasterToken::STRING, + lexer.getNextToken(MasterLexer::QSTRING).getType()); +} + +// Check ungetting token without overriding the start method. We also +// check it works well with changing options between the calls. +TEST_F(MasterLexerTest, ungetRealOptions) { + ss << " \n"; + lexer.pushSource(ss); + + // If we call it the usual way, it skips up to the newline and returns + // it + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + + // Now we return it. If we call it again, but with different options, + // we get the initial whitespace. + lexer.ungetToken(); + EXPECT_EQ(MasterToken::INITIAL_WS, + lexer.getNextToken(MasterLexer::INITIAL_WS).getType()); +} + +// Check the initial whitespace is found even in the first line of included +// file. It also confirms getPosition() works for multiple sources, each +// of which is partially parsed. +TEST_F(MasterLexerTest, includeAndInitialWS) { + ss << " \n"; + lexer.pushSource(ss); + + stringstream ss2; + ss2 << " \n"; + + EXPECT_EQ(MasterToken::INITIAL_WS, + lexer.getNextToken(MasterLexer::INITIAL_WS).getType()); + EXPECT_EQ(1, lexer.getPosition()); + lexer.pushSource(ss2); + EXPECT_EQ(MasterToken::INITIAL_WS, + lexer.getNextToken(MasterLexer::INITIAL_WS).getType()); + EXPECT_EQ(2, lexer.getPosition()); // should be sum of pushed positions. +} + +// Test only one token can be ungotten +TEST_F(MasterLexerTest, ungetTwice) { + ss << "\n"; + lexer.pushSource(ss); + + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + // Unget the token. It can be done once + lexer.ungetToken(); + // But not twice + EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation); +} + +// Test we can't unget a token before we get one +TEST_F(MasterLexerTest, ungetBeforeGet) { + lexer.pushSource(ss); // Just to eliminate the missing source problem + EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation); +} + +// Test we can't unget a token after a source switch, even when we got +// something before. +TEST_F(MasterLexerTest, ungetAfterSwitch) { + ss << "\n\n"; + lexer.pushSource(ss); + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + // Switch the source + std::stringstream ss2; + ss2 << "\n\n"; + lexer.pushSource(ss2); + EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation); + // We can get from the new source + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); + // And when we drop the current source, we can't unget again + lexer.popSource(); + EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation); +} + +// Common checks for the case when getNextToken() should result in LexerError +void +lexerErrorCheck(MasterLexer& lexer, MasterToken::Type expect, + MasterToken::ErrorCode expected_error) +{ + bool thrown = false; + try { + lexer.getNextToken(expect); + } catch (const MasterLexer::LexerError& error) { + EXPECT_EQ(expected_error, error.token_.getErrorCode()); + thrown = true; + } + EXPECT_TRUE(thrown); +} + +// Common checks regarding expected/unexpected end-of-line +// +// The 'lexer' should be at a position before two consecutive '\n's. +// The first one will be recognized, and the second one will be considered an +// unexpected token. Then this helper consumes the second '\n', so the caller +// can continue the test after these '\n's. +void +eolCheck(MasterLexer& lexer, MasterToken::Type expect) { + // If EOL is found and eol_ok is true, we get it. + EXPECT_EQ(MasterToken::END_OF_LINE, + lexer.getNextToken(expect, true).getType()); + // We'll see the second '\n'; by default it will fail. + EXPECT_THROW(lexer.getNextToken(expect), MasterLexer::LexerError); + // Same if eol_ok is explicitly set to false. This also checks the + // offending '\n' was "ungotten". + EXPECT_THROW(lexer.getNextToken(expect, false), MasterLexer::LexerError); + + // And also check the error token set in the exception object. + lexerErrorCheck(lexer, expect, MasterToken::UNEXPECTED_END); + + // Then skip the 2nd '\n' + EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType()); +} + +// Common checks regarding expected/unexpected end-of-file +// +// The 'lexer' should be at a position just before an end-of-file. +void +eofCheck(MasterLexer& lexer, MasterToken::Type expect) { + EXPECT_EQ(MasterToken::END_OF_FILE, + lexer.getNextToken(expect, true).getType()); + EXPECT_THROW(lexer.getNextToken(expect), MasterLexer::LexerError); + EXPECT_THROW(lexer.getNextToken(expect, false), MasterLexer::LexerError); +} + +TEST_F(MasterLexerTest, getNextTokenString) { + ss << "normal-string\n"; + ss << "\n"; + ss << "another-string"; + lexer.pushSource(ss); + + // Normal successful case: Expecting a string and get one. + EXPECT_EQ("normal-string", + lexer.getNextToken(MasterToken::STRING).getString()); + eolCheck(lexer, MasterToken::STRING); + + // Same set of tests but for end-of-file + EXPECT_EQ("another-string", + lexer.getNextToken(MasterToken::STRING, true).getString()); + eofCheck(lexer, MasterToken::STRING); +} + +TEST_F(MasterLexerTest, getNextTokenQString) { + ss << "\"quoted-string\"\n"; + ss << "\n"; + ss << "normal-string"; + lexer.pushSource(ss); + + // Expecting a quoted string and get one. + EXPECT_EQ("quoted-string", + lexer.getNextToken(MasterToken::QSTRING).getString()); + eolCheck(lexer, MasterToken::QSTRING); + + // Expecting a quoted string but see a normal string. It's okay. + EXPECT_EQ("normal-string", + lexer.getNextToken(MasterToken::QSTRING).getString()); + eofCheck(lexer, MasterToken::QSTRING); +} + +TEST_F(MasterLexerTest, getNextTokenNumber) { + ss << "3600\n"; + ss << "\n"; + ss << "4294967296 "; // =2^32, out of range + ss << "not-a-number "; + ss << "123abc "; // starting with digits, but resulting in a string + ss << "86400"; + lexer.pushSource(ss); + + // Expecting a number string and get one. + EXPECT_EQ(3600, + lexer.getNextToken(MasterToken::NUMBER).getNumber()); + eolCheck(lexer, MasterToken::NUMBER); + + // Expecting a number, but it's too big for uint32. + lexerErrorCheck(lexer, MasterToken::NUMBER, + MasterToken::NUMBER_OUT_OF_RANGE); + // The token should have been "ungotten". Re-read and skip it. + EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType()); + + // Expecting a number, but see a string. + lexerErrorCheck(lexer, MasterToken::NUMBER, MasterToken::BAD_NUMBER); + // The unexpected string should have been "ungotten". Re-read and skip it. + EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType()); + + // Expecting a number, but see a string. + lexerErrorCheck(lexer, MasterToken::NUMBER, MasterToken::BAD_NUMBER); + // The unexpected string should have been "ungotten". Re-read and skip it. + EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType()); + + // Unless we specify NUMBER, decimal number string should be recognized + // as a string. + EXPECT_EQ("86400", + lexer.getNextToken(MasterToken::STRING).getString()); + eofCheck(lexer, MasterToken::NUMBER); +} + +TEST_F(MasterLexerTest, getNextTokenErrors) { + // Check miscellaneous error cases + + ss << ") "; // unbalanced parenthesis + ss << "string-after-error "; + lexer.pushSource(ss); + + // Only string/qstring/number can be "expected". + EXPECT_THROW(lexer.getNextToken(MasterToken::END_OF_LINE), + isc::InvalidParameter); + EXPECT_THROW(lexer.getNextToken(MasterToken::END_OF_FILE), + isc::InvalidParameter); + EXPECT_THROW(lexer.getNextToken(MasterToken::INITIAL_WS), + isc::InvalidParameter); + EXPECT_THROW(lexer.getNextToken(MasterToken::ERROR), + isc::InvalidParameter); + + // If it encounters a syntax error, it results in LexerError exception. + lexerErrorCheck(lexer, MasterToken::STRING, MasterToken::UNBALANCED_PAREN); + + // Unlike the NUMBER_OUT_OF_RANGE case, the error part has been skipped + // within getNextToken(). We should be able to get the next token. + EXPECT_EQ("string-after-error", + lexer.getNextToken(MasterToken::STRING).getString()); +} + +} diff --git a/src/lib/dns/tests/master_loader_callbacks_test.cc b/src/lib/dns/tests/master_loader_callbacks_test.cc new file mode 100644 index 0000000..e89fa92 --- /dev/null +++ b/src/lib/dns/tests/master_loader_callbacks_test.cc @@ -0,0 +1,79 @@ +// Copyright (C) 2012-2024 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 <dns/master_loader_callbacks.h> +#include <dns/rrset.h> +#include <dns/name.h> +#include <dns/rrttl.h> +#include <dns/rrclass.h> + +#include <exceptions/exceptions.h> + +#include <gtest/gtest.h> +#include <functional> + +namespace { + +using std::string; +using namespace isc::dns; +namespace ph = std::placeholders; + +class MasterLoaderCallbacksTest : public ::testing::Test { +protected: + MasterLoaderCallbacksTest() : + last_was_error_(false), // Not needed, but then cppcheck complains + issue_called_(false), + rrset_(new RRset(Name("example.org"), RRClass::IN(), RRType::A(), + RRTTL(3600))), + error_(std::bind(&MasterLoaderCallbacksTest::checkCallback, this, + true, ph::_1, ph::_2, ph::_3)), + warning_(std::bind(&MasterLoaderCallbacksTest::checkCallback, this, + false, ph::_1, ph::_2, ph::_3)), + callbacks_(error_, warning_) + {} + + void checkCallback(bool error, const string& source, size_t line, + const string& reason) + { + issue_called_ = true; + last_was_error_ = error; + EXPECT_EQ("source", source); + EXPECT_EQ(1, line); + EXPECT_EQ("reason", reason); + } + bool last_was_error_; + bool issue_called_; + const RRsetPtr rrset_; + const MasterLoaderCallbacks::IssueCallback error_, warning_; + MasterLoaderCallbacks callbacks_; +}; + +// Check the constructor rejects empty callbacks, but accepts non-empty ones +TEST_F(MasterLoaderCallbacksTest, constructor) { + EXPECT_THROW(MasterLoaderCallbacks(MasterLoaderCallbacks::IssueCallback(), + warning_), isc::InvalidParameter); + EXPECT_THROW(MasterLoaderCallbacks(error_, + MasterLoaderCallbacks::IssueCallback()), + isc::InvalidParameter); + EXPECT_NO_THROW(MasterLoaderCallbacks(error_, warning_)); +} + +// Call the issue callbacks +TEST_F(MasterLoaderCallbacksTest, issueCall) { + callbacks_.error("source", 1, "reason"); + EXPECT_TRUE(last_was_error_); + EXPECT_TRUE(issue_called_); + + issue_called_ = false; + + callbacks_.warning("source", 1, "reason"); + EXPECT_FALSE(last_was_error_); + EXPECT_TRUE(issue_called_); +} + +} diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc new file mode 100644 index 0000000..ce7b850 --- /dev/null +++ b/src/lib/dns/tests/master_loader_unittest.cc @@ -0,0 +1,967 @@ +// Copyright (C) 2012-2024 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 <dns/master_loader_callbacks.h> +#include <dns/master_loader.h> +#include <dns/rrtype.h> +#include <dns/rrset.h> +#include <dns/rrclass.h> +#include <dns/rrttl.h> +#include <dns/name.h> +#include <dns/rdata.h> + +#include <gtest/gtest.h> + +#include <boost/lexical_cast.hpp> +#include <boost/scoped_ptr.hpp> + +#include <functional> +#include <string> +#include <vector> +#include <list> +#include <sstream> + +using namespace isc::dns; +using std::vector; +using std::string; +using std::list; +using std::stringstream; +using std::endl; +using boost::lexical_cast; +namespace ph = std::placeholders; + +namespace { +class MasterLoaderTest : public ::testing::Test { +public: + MasterLoaderTest() : + callbacks_(std::bind(&MasterLoaderTest::callback, this, + &errors_, ph::_1, ph::_2, ph::_3), + std::bind(&MasterLoaderTest::callback, this, + &warnings_, ph::_1, ph::_2, ph::_3)) { + } + + void TearDown() { + // Check there are no more RRs we didn't expect + EXPECT_TRUE(rrsets_.empty()); + } + + /// Concatenate file, line, and reason, and add it to either errors + /// or warnings + void callback(vector<string>* target, const std::string& file, size_t line, + const std::string& reason) { + std::stringstream ss; + ss << reason << " [" << file << ":" << line << "]"; + target->push_back(ss.str()); + } + + void addRRset(const Name& name, const RRClass& rrclass, + const RRType& rrtype, const RRTTL& rrttl, + const rdata::RdataPtr& data) { + const RRsetPtr rrset(new BasicRRset(name, rrclass, rrtype, rrttl)); + rrset->addRdata(data); + rrsets_.push_back(rrset); + } + + void setLoader(const char* file, const Name& origin, + const RRClass& rrclass, const MasterLoader::Options options) { + loader_.reset(new MasterLoader(file, origin, rrclass, callbacks_, + std::bind(&MasterLoaderTest::addRRset, + this, ph::_1, ph::_2, ph::_3, + ph::_4, ph::_5), + options)); + } + + void setLoader(std::istream& stream, const Name& origin, + const RRClass& rrclass, const MasterLoader::Options options) { + loader_.reset(new MasterLoader(stream, origin, rrclass, callbacks_, + std::bind(&MasterLoaderTest::addRRset, + this, ph::_1, ph::_2, ph::_3, + ph::_4, ph::_5), + options)); + } + + static string prepareZone(const string& line, bool include_last) { + string result; + result += "example.org. 3600 IN SOA ns1.example.org. " + "admin.example.org. 1234 3600 1800 2419200 7200\n"; + result += line; + if (include_last) { + result += "\n"; + result += "correct 3600 IN A 192.0.2.2\n"; + } + return (result); + } + + void clear() { + warnings_.clear(); + errors_.clear(); + rrsets_.clear(); + } + + // Check the next RR in the ones produced by the loader + // Other than passed arguments are checked to be the default for the tests + void checkRR(const string& name, const RRType& type, const string& data, + const RRTTL& rrttl = RRTTL(3600)) { + ASSERT_FALSE(rrsets_.empty()); + RRsetPtr current = rrsets_.front(); + rrsets_.pop_front(); + + EXPECT_EQ(Name(name), current->getName()); + EXPECT_EQ(type, current->getType()); + EXPECT_EQ(RRClass::IN(), current->getClass()); + EXPECT_EQ(rrttl, current->getTTL()); + ASSERT_EQ(1, current->getRdataCount()); + EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)-> + compare(current->getRdataIterator()->getCurrent())) + << data << " vs. " + << current->getRdataIterator()->getCurrent().toText(); + } + + void checkBasicRRs() { + checkRR("example.org", RRType::SOA(), + "ns1.example.org. admin.example.org. " + "1234 3600 1800 2419200 7200"); + checkRR("example.org", RRType::NS(), "ns1.example.org."); + checkRR("www.example.org", RRType::A(), "192.0.2.1"); + checkRR("www.example.org", RRType::AAAA(), "2001:db8::1"); + } + + void checkARR(const string& name) { + checkRR(name, RRType::A(), "192.0.2.1"); + } + + MasterLoaderCallbacks callbacks_; + boost::scoped_ptr<MasterLoader> loader_; + vector<string> errors_; + vector<string> warnings_; + list<RRsetPtr> rrsets_; +}; + +// Test simple loading. The zone file contains no tricky things, and nothing is +// omitted. No RRset contains more than one RR Also no errors or warnings. +TEST_F(MasterLoaderTest, basicLoad) { + setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."), + RRClass::IN(), MasterLoader::MANY_ERRORS); + + EXPECT_FALSE(loader_->loadedSuccessfully()); + + // The following three should be set to 0 initially in case the loader + // is constructed from a file name. + EXPECT_EQ(0, loader_->getSize()); + EXPECT_EQ(0, loader_->getPosition()); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + + EXPECT_TRUE(errors_.empty()); + EXPECT_TRUE(warnings_.empty()); + + // Hardcode expected values taken from the test data file, assuming it + // won't change too often. + EXPECT_EQ(550, loader_->getSize()); + EXPECT_EQ(550, loader_->getPosition()); + + checkBasicRRs(); +} + +// Test the $INCLUDE directive +TEST_F(MasterLoaderTest, include) { + // Test various cases of include + const char* includes[] = { + "$include", + "$INCLUDE", + "$Include", + "$InCluDe", + "\"$INCLUDE\"", + 0 + }; + for (const char** include = includes; *include != 0; ++include) { + SCOPED_TRACE(*include); + + clear(); + // Prepare input source that has the include and some more data + // below (to see it returns back to the original source). + const string include_str = string(*include) + " " + + TEST_DATA_SRCDIR + "/example.org\nwww 3600 IN AAAA 2001:db8::1\n"; + stringstream ss(include_str); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_TRUE(errors_.empty()); + EXPECT_TRUE(warnings_.empty()); + + checkBasicRRs(); + checkRR("www.example.org", RRType::AAAA(), "2001:db8::1"); + } +} + +TEST_F(MasterLoaderTest, includeAndIncremental) { + // Check getSize() and getPosition() are adjusted before and after + // $INCLUDE. + const string first_rr = "before.example.org. 0 A 192.0.2.1\n"; + const string include_str = "$INCLUDE " TEST_DATA_SRCDIR "/example.org"; + const string zone_data = first_rr + include_str + "\n" + + "www 3600 IN AAAA 2001:db8::1\n"; + stringstream ss(zone_data); + setLoader(ss, Name("example.org."), RRClass::IN(), MasterLoader::DEFAULT); + + // On construction, getSize() returns the size of the data (exclude the + // the file to be included); position is set to 0. + EXPECT_EQ(zone_data.size(), loader_->getSize()); + EXPECT_EQ(0, loader_->getPosition()); + + // Read the first RR. getSize() doesn't change; position should be + // at the end of the first line. + loader_->loadIncremental(1); + EXPECT_EQ(zone_data.size(), loader_->getSize()); + EXPECT_EQ(first_rr.size(), loader_->getPosition()); + + // Read next 4. It includes $INCLUDE processing. Magic number of 550 + // is the size of the test zone file (see above); 507 is the position in + // the file at the end of 4th RR (due to extra comments it's smaller than + // the file size). + loader_->loadIncremental(4); + EXPECT_EQ(zone_data.size() + 550, loader_->getSize()); + EXPECT_EQ(first_rr.size() + include_str.size() + 507, + loader_->getPosition()); + + // Read the last one. At this point getSize and getPosition return + // the same value, indicating progress of 100%. + loader_->loadIncremental(1); + EXPECT_EQ(zone_data.size() + 550, loader_->getSize()); + EXPECT_EQ(zone_data.size() + 550, loader_->getPosition()); + + // we were not interested in checking RRs in this test. clear them to + // not confuse TearDown(). + rrsets_.clear(); +} + +// A commonly used helper to check callback message. +void +checkCallbackMessage(const string& actual_msg, const string& expected_msg, + size_t expected_line) { + // The actual message should begin with the expected message. + EXPECT_EQ(0, actual_msg.find(expected_msg)) << "actual message: " << + actual_msg << " expected: " << + expected_msg; + + // and it should end with "...:<line_num>]" + const string line_desc = ":" + lexical_cast<string>(expected_line) + "]"; + EXPECT_EQ(actual_msg.size() - line_desc.size(), + actual_msg.find(line_desc)) << "Expected on line " << + expected_line; +} + +TEST_F(MasterLoaderTest, origin) { + // Various forms of the directive + const char* origins[] = { + "$origin", + "$ORIGIN", + "$Origin", + "$OrigiN", + "\"$ORIGIN\"", + 0 + }; + for (const char** origin = origins; *origin != 0; ++origin) { + SCOPED_TRACE(*origin); + + clear(); + const string directive = *origin; + const string input = + "@ 1H IN A 192.0.2.1\n" + + directive + " sub.example.org.\n" + "\"www\" 1H IN A 192.0.2.1\n" + + // Relative name in the origin + directive + " relative\n" + "@ 1H IN A 192.0.2.1\n" + // Origin is _not_ used here (absolute name) + "noorigin.example.org. 60M IN A 192.0.2.1\n"; + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_TRUE(errors_.empty()); + // There's a relative origin in it, we warn about that. + EXPECT_EQ(1, warnings_.size()); + checkCallbackMessage(warnings_.at(0), + "The new origin is relative, did you really mean " + "relative.sub.example.org.?", 4); + + checkARR("example.org"); + checkARR("www.sub.example.org"); + checkARR("relative.sub.example.org"); + checkARR("noorigin.example.org"); + } +} + +// Test the source is correctly popped even after error +TEST_F(MasterLoaderTest, popAfterError) { + const string include_str = "$include " TEST_DATA_SRCDIR + "/broken.zone\nwww 3600 IN AAAA 2001:db8::1\n"; + stringstream ss(include_str); + // We perform the test with MANY_ERRORS, we want to see what happens + // after the error. + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_EQ(1, errors_.size()); // For the broken RR + EXPECT_EQ(1, warnings_.size()); // For missing EOLN + + // The included file doesn't contain anything usable, but the + // line after the include should be there. + checkRR("www.example.org", RRType::AAAA(), "2001:db8::1"); +} + +// Check it works the same when created based on a stream, not filename +TEST_F(MasterLoaderTest, streamConstructor) { + const string zone_data(prepareZone("", true)); + stringstream zone_stream(zone_data); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + EXPECT_FALSE(loader_->loadedSuccessfully()); + + // Unlike the basicLoad test, if we construct the loader from a stream + // getSize() returns the data size in the stream immediately after the + // construction. + EXPECT_EQ(zone_data.size(), loader_->getSize()); + EXPECT_EQ(0, loader_->getPosition()); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + + EXPECT_TRUE(errors_.empty()); + EXPECT_TRUE(warnings_.empty()); + checkRR("example.org", RRType::SOA(), "ns1.example.org. " + "admin.example.org. 1234 3600 1800 2419200 7200"); + checkRR("correct.example.org", RRType::A(), "192.0.2.2"); + + // On completion of the load, both getSize() and getPosition() return the + // size of the data. + EXPECT_EQ(zone_data.size(), loader_->getSize()); + EXPECT_EQ(zone_data.size(), loader_->getPosition()); +} + +// Try loading data incrementally. +TEST_F(MasterLoaderTest, incrementalLoad) { + setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."), + RRClass::IN(), MasterLoader::MANY_ERRORS); + + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_FALSE(loader_->loadIncremental(2)); + EXPECT_FALSE(loader_->loadedSuccessfully()); + + EXPECT_TRUE(errors_.empty()); + EXPECT_TRUE(warnings_.empty()); + + checkRR("example.org", RRType::SOA(), + "ns1.example.org. admin.example.org. " + "1234 3600 1800 2419200 7200"); + checkRR("example.org", RRType::NS(), "ns1.example.org."); + + // The third one is not loaded yet + EXPECT_TRUE(rrsets_.empty()); + + // Load the rest. + EXPECT_TRUE(loader_->loadIncremental(20)); + EXPECT_TRUE(loader_->loadedSuccessfully()); + + EXPECT_TRUE(errors_.empty()); + EXPECT_TRUE(warnings_.empty()); + + checkRR("www.example.org", RRType::A(), "192.0.2.1"); + checkRR("www.example.org", RRType::AAAA(), "2001:db8::1"); +} + +// Try loading from file that doesn't exist. There should be single error +// saying so. +TEST_F(MasterLoaderTest, invalidFile) { + setLoader("This file doesn't exist at all", + Name("example.org."), RRClass::IN(), MasterLoader::MANY_ERRORS); + + // Nothing yet. The loader is dormant until invoked. + // Is it really what we want? + EXPECT_TRUE(errors_.empty()); + + loader_->load(); + + EXPECT_TRUE(warnings_.empty()); + EXPECT_TRUE(rrsets_.empty()); + ASSERT_EQ(1, errors_.size()); + EXPECT_EQ(0, errors_[0].find("Error opening the input source file: ")) << + "Different error: " << errors_[0]; +} + +struct ErrorCase { + const char* const line; // The broken line in master file + const char* const reason; // If non null, the reason string + const char* const problem; // Description of the problem for SCOPED_TRACE +} const error_cases[] = { + { "www... 3600 IN A 192.0.2.1", 0, "Invalid name" }, + { "www FORTNIGHT IN A 192.0.2.1", 0, "Invalid TTL" }, + { "www 3600 XX A 192.0.2.1", 0, "Invalid class" }, + { "www 3600 IN A bad_ip", 0, "Invalid Rdata" }, + + // Parameter ordering errors + { "www IN A 3600 192.168.2.7", + "createRdata from text failed: Bad IN/A RDATA text: '3600'", + "Incorrect order of class, TTL and type" }, + { "www A IN 3600 192.168.2.8", + "createRdata from text failed: Bad IN/A RDATA text: 'IN'", + "Incorrect order of class, TTL and type" }, + { "www 3600 A IN 192.168.2.7", + "createRdata from text failed: Bad IN/A RDATA text: 'IN'", + "Incorrect order of class, TTL and type" }, + { "www A 3600 IN 192.168.2.8", + "createRdata from text failed: Bad IN/A RDATA text: '3600'", + "Incorrect order of class, TTL and type" }, + + // Missing type and Rdata + { "www", "unexpected end of input", "Missing type and Rdata" }, + { "www 3600", "unexpected end of input", "Missing type and Rdata" }, + { "www IN", "unexpected end of input", "Missing type and Rdata" }, + { "www 3600 IN", "unexpected end of input", "Missing type and Rdata" }, + { "www IN 3600", "unexpected end of input", "Missing type and Rdata" }, + + // Missing Rdata + { "www A", + "createRdata from text failed: unexpected end of input", + "Missing Rdata" }, + { "www 3600 A", + "createRdata from text failed: unexpected end of input", + "Missing Rdata" }, + { "www IN A", + "createRdata from text failed: unexpected end of input", + "Missing Rdata" }, + { "www 3600 IN A", + "createRdata from text failed: unexpected end of input", + "Missing Rdata" }, + { "www IN 3600 A", + "createRdata from text failed: unexpected end of input", + "Missing Rdata" }, + + { "www 3600 IN", 0, "Unexpected EOLN" }, + { "www 3600 CH TXT nothing", "Class mismatch: CH vs. IN", + "Class mismatch" }, + { "www \"3600\" IN A 192.0.2.1", 0, "Quoted TTL" }, + { "www 3600 \"IN\" A 192.0.2.1", 0, "Quoted class" }, + { "www 3600 IN \"A\" 192.0.2.1", 0, "Quoted type" }, + { "unbalanced)paren 3600 IN A 192.0.2.1", 0, "Token error 1" }, + { "www 3600 unbalanced)paren A 192.0.2.1", 0, + "Token error 2" }, + // Check the unknown directive. The rest looks like ordinary RR, + // so we see the $ is actually special. + { "$UNKNOWN 3600 IN A 192.0.2.1", 0, "Unknown $ directive" }, + { "$INCLUD " TEST_DATA_SRCDIR "/example.org", "Unknown directive 'INCLUD'", + "Include too short" }, + { "$INCLUDES " TEST_DATA_SRCDIR "/example.org", + "Unknown directive 'INCLUDES'", "Include too long" }, + { "$INCLUDE", "unexpected end of input", "Missing include path" }, + // The following two error messages are system dependent, omitting + { "$INCLUDE /file/not/found", 0, "Include file not found" }, + { "$INCLUDE /file/not/found example.org. and here goes bunch of garbage", + 0, "Include file not found and garbage at the end of line" }, + { "$ORIGIN", "unexpected end of input", "Missing origin name" }, + { "$ORIGIN invalid...name", "duplicate period in invalid...name", + "Invalid name for origin" }, + { "$ORIGIN )brokentoken", "unbalanced parentheses", + "Broken token in origin" }, + { "$ORIGIN example.org. garbage", "Extra tokens at the end of line", + "Garbage after origin" }, + { "$ORIGI name.", "Unknown directive 'ORIGI'", "$ORIGIN too short" }, + { "$ORIGINAL name.", "Unknown directive 'ORIGINAL'", "$ORIGIN too long" }, + { "$TTL 100 extra-garbage", "Extra tokens at the end of line", + "$TTL with extra token" }, + { "$TTL", "unexpected end of input", "missing TTL" }, + { "$TTL No-ttl", "Unknown unit used: N in: No-ttl", "bad TTL" }, + { "$TTL \"100\"", "unexpected quotes", "bad TTL, quoted" }, + { "$TT 100", "Unknown directive 'TT'", "bad directive, too short" }, + { "$TTLLIKE 100", "Unknown directive 'TTLLIKE'", "bad directive, extra" }, + { 0, 0, 0 } +}; + +// Test a broken zone is handled properly. We test several problems, +// both in strict and lenient mode. +TEST_F(MasterLoaderTest, brokenZone) { + for (const ErrorCase* ec = error_cases; ec->line; ++ec) { + SCOPED_TRACE(ec->problem); + const string zone(prepareZone(ec->line, true)); + + { + SCOPED_TRACE("Strict mode"); + clear(); + stringstream zone_stream(zone); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::DEFAULT); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_THROW(loader_->load(), MasterLoaderError); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_EQ(1, errors_.size()); + if (ec->reason) { + checkCallbackMessage(errors_.at(0), ec->reason, 2); + } + EXPECT_TRUE(warnings_.empty()); + + checkRR("example.org", RRType::SOA(), "ns1.example.org. " + "admin.example.org. 1234 3600 1800 2419200 7200"); + // In the strict mode, it is aborted. The last RR is not + // even attempted. + EXPECT_TRUE(rrsets_.empty()); + } + + { + SCOPED_TRACE("Lenient mode"); + clear(); + stringstream zone_stream(zone); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_NO_THROW(loader_->load()); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_EQ(1, errors_.size()); + EXPECT_TRUE(warnings_.empty()); + checkRR("example.org", RRType::SOA(), "ns1.example.org. " + "admin.example.org. 1234 3600 1800 2419200 7200"); + // This one is below the error one. + checkRR("correct.example.org", RRType::A(), "192.0.2.2"); + EXPECT_TRUE(rrsets_.empty()); + } + + { + SCOPED_TRACE("Error at EOF"); + // This case is interesting only in the lenient mode. + clear(); + const string zoneEOF(prepareZone(ec->line, false)); + stringstream zone_stream(zoneEOF); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_NO_THROW(loader_->load()); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_EQ(1, errors_.size()) << errors_[0] << "\n" << errors_[1]; + // The unexpected EOF warning + EXPECT_EQ(1, warnings_.size()); + checkRR("example.org", RRType::SOA(), "ns1.example.org. " + "admin.example.org. 1234 3600 1800 2419200 7200"); + EXPECT_TRUE(rrsets_.empty()); + } + } +} + +// Check that a garbage after the include generates an error, but not fatal +// one (in lenient mode) and we can recover. +TEST_F(MasterLoaderTest, includeWithGarbage) { + // Include an origin (example.org) because we expect it to be handled + // soon and we don't want it to break here. + const string include_str("$INCLUDE " TEST_DATA_SRCDIR + "/example.org example.org. bunch of other stuff\n" + "www 3600 IN AAAA 2001:db8::1\n"); + stringstream zone_stream(include_str); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + EXPECT_NO_THROW(loader_->load()); + EXPECT_FALSE(loader_->loadedSuccessfully()); + ASSERT_EQ(1, errors_.size()); + checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1); + // It says something about extra tokens at the end + EXPECT_NE(string::npos, errors_[0].find("Extra")); + EXPECT_TRUE(warnings_.empty()); + checkBasicRRs(); + checkRR("www.example.org", RRType::AAAA(), "2001:db8::1"); +} + +// Check we error about garbage at the end of $ORIGIN line (but the line +// works). +TEST_F(MasterLoaderTest, originWithGarbage) { + const string origin_str = "$ORIGIN www.example.org. More garbage here\n" + "@ 1H IN A 192.0.2.1\n"; + stringstream ss(origin_str); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + EXPECT_NO_THROW(loader_->load()); + EXPECT_FALSE(loader_->loadedSuccessfully()); + ASSERT_EQ(1, errors_.size()); + checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1); + EXPECT_TRUE(warnings_.empty()); + checkARR("www.example.org"); +} + +// Test we can pass both file to include and the origin to switch +TEST_F(MasterLoaderTest, includeAndOrigin) { + // First, switch origin to something else, so we can check it is + // switched back. + const string include_string = "$ORIGIN www.example.org.\n" + "@ 1H IN A 192.0.2.1\n" + // Then include the file with data and switch origin back + "$INCLUDE " TEST_DATA_SRCDIR "/example.org example.org.\n" + // Another RR to see we fall back to the previous origin. + "www 1H IN A 192.0.2.1\n"; + stringstream ss(include_string); + setLoader(ss, Name("example.org"), RRClass::IN(), + MasterLoader::MANY_ERRORS); + // Successfully load the data + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_TRUE(errors_.empty()); + EXPECT_TRUE(warnings_.empty()); + // And check it's the correct data + checkARR("www.example.org"); + checkBasicRRs(); + checkARR("www.www.example.org"); +} + +// Like above, but the origin after include is bogus. The whole line should +// be rejected. +TEST_F(MasterLoaderTest, includeAndBadOrigin) { + const string include_string = + "$INCLUDE " TEST_DATA_SRCDIR "/example.org example..org.\n" + // Another RR to see the switch survives after we exit include + "www 1H IN A 192.0.2.1\n"; + stringstream ss(include_string); + setLoader(ss, Name("example.org"), RRClass::IN(), + MasterLoader::MANY_ERRORS); + loader_->load(); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_EQ(1, errors_.size()); + checkCallbackMessage(errors_.at(0), "duplicate period in example..org.", + 1); + EXPECT_TRUE(warnings_.empty()); + // And check it's the correct data + checkARR("www.example.org"); +} + +// Check the origin doesn't get outside of the included file. +TEST_F(MasterLoaderTest, includeOriginRestore) { + const string include_string = + "$INCLUDE " TEST_DATA_SRCDIR "/origincheck.txt\n" + "@ 1H IN A 192.0.2.1\n"; + stringstream ss(include_string); + setLoader(ss, Name("example.org"), RRClass::IN(), + MasterLoader::MANY_ERRORS); + // Successfully load the data + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_TRUE(errors_.empty()); + EXPECT_TRUE(warnings_.empty()); + // And check it's the correct data + checkARR("www.example.org"); + checkARR("example.org"); +} + +// Check we restore the last name for initial whitespace when returning from +// include. But we do produce a warning if there's one just ofter the include. +TEST_F(MasterLoaderTest, includeAndInitialWS) { + const string include_string = "xyz 1H IN A 192.0.2.1\n" + "$INCLUDE " TEST_DATA_SRCDIR "/example.org\n" + " 1H IN A 192.0.2.1\n"; + stringstream ss(include_string); + setLoader(ss, Name("example.org"), RRClass::IN(), + MasterLoader::MANY_ERRORS); + // Successfully load the data + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_TRUE(errors_.empty()); + EXPECT_EQ(1, warnings_.size()); + checkCallbackMessage(warnings_.at(0), + "Owner name omitted around $INCLUDE, the result might " + "not be as expected", 3); + checkARR("xyz.example.org"); + checkBasicRRs(); + checkARR("xyz.example.org"); +} + +// Test for "$TTL" +TEST_F(MasterLoaderTest, ttlDirective) { + stringstream zone_stream; + + // Set the default TTL with $TTL followed by an RR omitting the TTL + zone_stream << "$TTL 1800\nexample.org. IN A 192.0.2.1\n"; + // $TTL can be quoted. Also testing the case of $TTL being changed. + zone_stream << "\"$TTL\" 100\na.example.org. IN A 192.0.2.2\n"; + // Extended TTL form is accepted. + zone_stream << "$TTL 1H\nb.example.org. IN A 192.0.2.3\n"; + // Matching is case insensitive. + zone_stream << "$tTl 360\nc.example.org. IN A 192.0.2.4\n"; + // Maximum allowable TTL + zone_stream << "$TTL 2147483647\nd.example.org. IN A 192.0.2.5\n"; + + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::DEFAULT); + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + checkRR("example.org", RRType::A(), "192.0.2.1", RRTTL(1800)); + checkRR("a.example.org", RRType::A(), "192.0.2.2", RRTTL(100)); + checkRR("b.example.org", RRType::A(), "192.0.2.3", RRTTL(3600)); + checkRR("c.example.org", RRType::A(), "192.0.2.4", RRTTL(360)); + checkRR("d.example.org", RRType::A(), "192.0.2.5", RRTTL(2147483647)); +} + +TEST_F(MasterLoaderTest, ttlFromSOA) { + // No $TTL, and the SOA doesn't have an explicit TTL field. Its minimum + // TTL field will be used as the RR's TTL, and it'll be used as the + // default TTL for others. + stringstream zone_stream("example.org. IN SOA . . 0 0 0 0 1800\n" + "a.example.org. IN A 192.0.2.1\n"); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::DEFAULT); + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(1800)); + checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800)); + + // The use of SOA minimum TTL should have caused a warning. + EXPECT_EQ(1, warnings_.size()); + checkCallbackMessage(warnings_.at(0), + "no TTL specified; using SOA MINTTL instead", 1); +} + +TEST_F(MasterLoaderTest, ttlFromPrevious) { + // No available default TTL. 2nd and 3rd RR will use the TTL of the + // 1st RR. This will result in a warning, but only for the first time. + stringstream zone_stream("a.example.org. 1800 IN A 192.0.2.1\n" + "b.example.org. IN A 192.0.2.2\n" + "c.example.org. IN A 192.0.2.3\n"); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::DEFAULT); + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800)); + checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800)); + checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(1800)); + + EXPECT_EQ(1, warnings_.size()); + checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2); +} + +TEST_F(MasterLoaderTest, RRParamsOrdering) { + // We test the order and existence of TTL, class and type. See + // MasterLoader::MasterLoaderImpl::parseRRParams() for ordering. + + stringstream zone_stream; + // <TTL> <class> <type> <RDATA> + zone_stream << "a.example.org. 1800 IN A 192.0.2.1\n"; + // <type> <RDATA> + zone_stream << "b.example.org. A 192.0.2.2\n"; + // <class> <TTL> <type> <RDATA> + zone_stream << "c.example.org. IN 3600 A 192.0.2.3\n"; + // <TTL> <type> <RDATA> + zone_stream << "d.example.org. 7200 A 192.0.2.4\n"; + // <class> <type> <RDATA> + zone_stream << "e.example.org. IN A 192.0.2.5\n"; + + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::DEFAULT); + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800)); + checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800)); + checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(3600)); + checkRR("d.example.org", RRType::A(), "192.0.2.4", RRTTL(7200)); + checkRR("e.example.org", RRType::A(), "192.0.2.5", RRTTL(7200)); + + EXPECT_EQ(1, warnings_.size()); + checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2); +} + +TEST_F(MasterLoaderTest, ttlFromPreviousSOA) { + // Mixture of the previous two cases: SOA has explicit TTL, followed by + // an RR without an explicit TTL. In this case the minimum TTL won't be + // recognized as the "default TTL". + stringstream zone_stream("example.org. 100 IN SOA . . 0 0 0 0 1800\n" + "a.example.org. IN A 192.0.2.1\n"); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::DEFAULT); + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + + checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(100)); + checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(100)); + + EXPECT_EQ(1, warnings_.size()); + checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2); +} + +TEST_F(MasterLoaderTest, ttlUnknown) { + // No available TTL is known for the first RR. + stringstream zone_stream("a.example.org. IN A 192.0.2.1\n"); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::DEFAULT); + EXPECT_THROW(loader_->load(), MasterLoaderError); +} + +TEST_F(MasterLoaderTest, ttlUnknownAndContinue) { + stringstream zone_stream("a.example.org. IN A 192.0.2.1\n" + "b.example.org. 1800 IN A 192.0.2.2\n"); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + loader_->load(); + EXPECT_FALSE(loader_->loadedSuccessfully()); + checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800)); + + EXPECT_TRUE(warnings_.empty()); + EXPECT_EQ(1, errors_.size()); + checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1); +} + +TEST_F(MasterLoaderTest, ttlUnknownAndEOF) { + // Similar to the previous case, but the input will be abruptly terminated + // after the offending RR. This will cause an additional warning. + stringstream zone_stream("a.example.org. IN A 192.0.2.1"); + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + loader_->load(); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_TRUE(rrsets_.empty()); + + EXPECT_EQ(1, errors_.size()); + checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1); + + // RDATA implementation can complain about it, too. To be independent of + // its details, we focus on the very last warning. + EXPECT_FALSE(warnings_.empty()); + checkCallbackMessage(*warnings_.rbegin(), "File does not end with newline", + 1); +} + +TEST_F(MasterLoaderTest, ttlOverflow) { + stringstream zone_stream; + zone_stream << "example.org. IN SOA . . 0 0 0 0 2147483648\n"; + zone_stream << "$TTL 3600\n"; // reset to an in-range value + zone_stream << "$TTL 2147483649\n"; + zone_stream << "a.example.org. IN A 192.0.2.1\n"; + zone_stream << "$TTL 3600\n"; // reset to an in-range value + zone_stream << "b.example.org. 2147483650 IN A 192.0.2.2\n"; + setLoader(zone_stream, Name("example.org."), RRClass::IN(), + MasterLoader::DEFAULT); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_EQ(3, rrsets_.size()); + + checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 2147483648", RRTTL(0)); + checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(0)); + checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(0)); + + EXPECT_EQ(4, warnings_.size()); + checkCallbackMessage(warnings_.at(1), + "TTL 2147483648 > MAXTTL, setting to 0 per RFC2181", + 1); + checkCallbackMessage(warnings_.at(2), + "TTL 2147483649 > MAXTTL, setting to 0 per RFC2181", + 3); + checkCallbackMessage(warnings_.at(3), + "TTL 2147483650 > MAXTTL, setting to 0 per RFC2181", + 6); +} + +// Test the constructor rejects empty add callback. +TEST_F(MasterLoaderTest, emptyCallback) { + EXPECT_THROW(MasterLoader(TEST_DATA_SRCDIR "/example.org", + Name("example.org"), RRClass::IN(), callbacks_, + AddRRCallback()), isc::InvalidParameter); + // And the same with the second constructor + stringstream ss(""); + EXPECT_THROW(MasterLoader(ss, Name("example.org"), RRClass::IN(), + callbacks_, AddRRCallback()), + isc::InvalidParameter); +} + +// Check it throws when we try to load after loading was complete. +TEST_F(MasterLoaderTest, loadTwice) { + setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."), + RRClass::IN(), MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_THROW(loader_->load(), isc::InvalidOperation); + // Don't check them, they are not interesting, so suppress the error + // at TearDown + rrsets_.clear(); +} + +// Load 0 items should be rejected +TEST_F(MasterLoaderTest, loadZero) { + setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."), + RRClass::IN(), MasterLoader::MANY_ERRORS); + EXPECT_THROW(loader_->loadIncremental(0), isc::InvalidParameter); +} + +// Test there's a warning when the file terminates without end of +// line. +TEST_F(MasterLoaderTest, noEOLN) { + // No \n at the end + const string input("example.org. 3600 IN SOA ns1.example.org. " + "admin.example.org. 1234 3600 1800 2419200 7200"); + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_TRUE(errors_.empty()); + // There should be one warning about the EOLN + EXPECT_EQ(1, warnings_.size()); + checkRR("example.org", RRType::SOA(), "ns1.example.org. " + "admin.example.org. 1234 3600 1800 2419200 7200"); +} + +// Test it rejects when we don't have the previous name to use in place of +// initial whitespace +TEST_F(MasterLoaderTest, noPreviousName) { + const string input(" 1H IN A 192.0.2.1\n"); + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + loader_->load(); + EXPECT_FALSE(loader_->loadedSuccessfully()); + EXPECT_EQ(1, errors_.size()); + checkCallbackMessage(errors_.at(0), "No previous name to use in place of " + "initial whitespace", 1); + EXPECT_TRUE(warnings_.empty()); +} + +// Check we warn if the first RR in an included file has omitted name +TEST_F(MasterLoaderTest, previousInInclude) { + const string input("www 1H IN A 192.0.2.1\n" + "$INCLUDE " TEST_DATA_SRCDIR "/omitcheck.txt\n"); + stringstream ss(input); + setLoader(ss, Name("example.org"), RRClass::IN(), + MasterLoader::MANY_ERRORS); + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_TRUE(errors_.empty()); + // There should be one warning about the EOLN + EXPECT_EQ(1, warnings_.size()); + checkCallbackMessage(warnings_.at(0), "Owner name omitted around " + "$INCLUDE, the result might not be as expected", 1); + checkARR("www.example.org"); + checkARR("www.example.org"); +} + +TEST_F(MasterLoaderTest, numericOwnerName) { + const string input("$ORIGIN example.org.\n" + "1 3600 IN A 192.0.2.1\n"); + stringstream ss(input); + setLoader(ss, Name("example.org."), RRClass::IN(), + MasterLoader::MANY_ERRORS); + + loader_->load(); + EXPECT_TRUE(loader_->loadedSuccessfully()); + EXPECT_TRUE(errors_.empty()); + EXPECT_TRUE(warnings_.empty()); + + checkRR("1.example.org", RRType::A(), "192.0.2.1"); +} + +} diff --git a/src/lib/dns/tests/message_unittest.cc b/src/lib/dns/tests/message_unittest.cc new file mode 100644 index 0000000..5754c50 --- /dev/null +++ b/src/lib/dns/tests/message_unittest.cc @@ -0,0 +1,1156 @@ +// Copyright (C) 2010-2024 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 <boost/scoped_ptr.hpp> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> + +#include <util/unittests/testdata.h> +#include <util/unittests/textdata.h> + +#include <dns/edns.h> +#include <dns/exceptions.h> +#include <dns/message.h> +#include <dns/messagerenderer.h> +#include <dns/question.h> +#include <dns/opcode.h> +#include <dns/rcode.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrttl.h> +#include <dns/rrtype.h> +#include <dns/tsig.h> +#include <dns/tsigkey.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +// +// Note: we need more tests, including: +// parsing malformed headers +// more complete tests about parsing/rendering header flags, opcode, rcode, etc. +// tests for adding RRsets +// tests for RRset/Question iterators +// But, we'll ship with the current set of tests for now, partly because many +// of the above are covered as part of other tests, and partly due to time +// limitation. We also expect to revisit the fundamental design of the Message +// class, at which point we'll also revise the tests including more cases. +// + +const uint16_t Message::DEFAULT_MAX_UDPSIZE; + +namespace isc { +namespace util { +namespace detail { +extern int64_t (*getTimeFunction)(); +} +} +} + +// XXX: this is defined as class static constants, but some compilers +// seemingly cannot find the symbol when used in the EXPECT_xxx macros. +const uint16_t TSIGContext::DEFAULT_FUDGE; + +namespace { +class MessageTest : public ::testing::Test { +protected: + MessageTest() : test_name("test.example.com"), obuffer(0), + message_parse(Message::PARSE), + message_render(Message::RENDER), + bogus_section(static_cast<Message::Section>( + Message::SECTION_ADDITIONAL + 1)), + tsig_ctx(TSIGKey("www.example.com:" + "SFuWd/q99SzF8Yzd1QbB9g==")) { + rrset_a = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::A(), RRTTL(3600))); + rrset_a->addRdata(in::A("192.0.2.1")); + rrset_a->addRdata(in::A("192.0.2.2")); + + rrset_aaaa = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::AAAA(), RRTTL(3600))); + rrset_aaaa->addRdata(in::AAAA("2001:db8::1234")); + + rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::RRSIG(), RRTTL(3600))); + rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 " + "20100220084538 1 example.com. " + "FAKEFAKEFAKEFAKE")); + rrset_aaaa->addRRsig(rrset_rrsig); + } + + static Question factoryFromFile(const char* datafile); + const Name test_name; + OutputBuffer obuffer; + MessageRenderer renderer; + Message message_parse; + Message message_render; + const Message::Section bogus_section; + RRsetPtr rrset_a; // A RRset with two RDATAs + RRsetPtr rrset_aaaa; // AAAA RRset with one RDATA with RRSIG + RRsetPtr rrset_rrsig; // RRSIG for the AAAA RRset + TSIGContext tsig_ctx; + vector<unsigned char> received_data; + vector<unsigned char> expected_data; + + void factoryFromFile(Message& message, const char* datafile, + Message::ParseOptions options = + Message::PARSE_DEFAULT); +}; + +void +MessageTest::factoryFromFile(Message& message, const char* datafile, + Message::ParseOptions options) +{ + received_data.clear(); + UnitTestUtil::readWireData(datafile, received_data); + + InputBuffer buffer(&received_data[0], received_data.size()); + message.fromWire(buffer, options); +} + +TEST_F(MessageTest, headerFlag) { + // by default no flag is set + EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_QR)); + EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AA)); + EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_TC)); + EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_RD)); + EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_RA)); + EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AD)); + EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_CD)); + + // set operation: by default it will be on + message_render.setHeaderFlag(Message::HEADERFLAG_QR); + EXPECT_TRUE(message_render.getHeaderFlag(Message::HEADERFLAG_QR)); + + // it can be set to on explicitly, too + message_render.setHeaderFlag(Message::HEADERFLAG_AA, true); + EXPECT_TRUE(message_render.getHeaderFlag(Message::HEADERFLAG_AA)); + + // the bit can also be cleared + message_render.setHeaderFlag(Message::HEADERFLAG_AA, false); + EXPECT_FALSE(message_render.getHeaderFlag(Message::HEADERFLAG_AA)); + + // Invalid flag values + EXPECT_THROW(message_render.setHeaderFlag( + static_cast<Message::HeaderFlag>(0)), InvalidParameter); + EXPECT_THROW(message_render.setHeaderFlag( + static_cast<Message::HeaderFlag>(0x7000)), + InvalidParameter); + EXPECT_THROW(message_render.setHeaderFlag( + static_cast<Message::HeaderFlag>(0x0800)), + InvalidParameter); + EXPECT_THROW(message_render.setHeaderFlag( + static_cast<Message::HeaderFlag>(0x0040)), + InvalidParameter); + EXPECT_THROW(message_render.setHeaderFlag( + static_cast<Message::HeaderFlag>(0x10000)), + InvalidParameter); + EXPECT_THROW(message_render.setHeaderFlag( + static_cast<Message::HeaderFlag>(0x80000000)), + InvalidParameter); + + // set operation isn't allowed in the parse mode. + EXPECT_THROW(message_parse.setHeaderFlag(Message::HEADERFLAG_QR), + InvalidMessageOperation); +} +TEST_F(MessageTest, getEDNS) { + EXPECT_FALSE(message_parse.getEDNS()); // by default EDNS isn't set + + factoryFromFile(message_parse, "message_fromWire10.wire"); + EXPECT_TRUE(message_parse.getEDNS()); + EXPECT_EQ(0, message_parse.getEDNS()->getVersion()); + EXPECT_EQ(4096, message_parse.getEDNS()->getUDPSize()); + EXPECT_TRUE(message_parse.getEDNS()->getDNSSECAwareness()); +} + +TEST_F(MessageTest, setEDNS) { + // setEDNS() isn't allowed in the parse mode + EXPECT_THROW(message_parse.setEDNS(EDNSPtr(new EDNS())), + InvalidMessageOperation); + + EDNSPtr edns = EDNSPtr(new EDNS()); + message_render.setEDNS(edns); + EXPECT_EQ(edns, message_render.getEDNS()); +} + +TEST_F(MessageTest, fromWireWithTSIG) { + // Initially there should be no TSIG + EXPECT_FALSE(message_parse.getTSIGRecord()); + + // getTSIGRecord() is only valid in the parse mode. + EXPECT_THROW(message_render.getTSIGRecord(), InvalidMessageOperation); + + factoryFromFile(message_parse, "message_toWire2.wire"); + const uint8_t expected_mac[] = { + 0x22, 0x70, 0x26, 0xad, 0x29, 0x7b, 0xee, 0xe7, + 0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3 + }; + const TSIGRecord* tsig_rr = message_parse.getTSIGRecord(); + ASSERT_TRUE(tsig_rr); + EXPECT_EQ(Name("www.example.com"), tsig_rr->getName()); + EXPECT_EQ(85, tsig_rr->getLength()); // see TSIGRecordTest.getLength + EXPECT_EQ(TSIGKey::HMACMD5_NAME(), tsig_rr->getRdata().getAlgorithm()); + EXPECT_EQ(0x4da8877a, tsig_rr->getRdata().getTimeSigned()); + EXPECT_EQ(TSIGContext::DEFAULT_FUDGE, tsig_rr->getRdata().getFudge()); + matchWireData(expected_mac, sizeof(expected_mac), + tsig_rr->getRdata().getMAC(), + tsig_rr->getRdata().getMACSize()); + EXPECT_EQ(0, tsig_rr->getRdata().getError()); + EXPECT_EQ(0, tsig_rr->getRdata().getOtherLen()); + EXPECT_FALSE(tsig_rr->getRdata().getOtherData()); + + // If we clear the message for reuse, the recorded TSIG will be cleared. + message_parse.clear(Message::PARSE); + EXPECT_FALSE(message_parse.getTSIGRecord()); +} + +TEST_F(MessageTest, fromWireWithTSIGCompressed) { + // Mostly same as fromWireWithTSIG, but the TSIG owner name is compressed. + factoryFromFile(message_parse, "message_fromWire12.wire"); + const TSIGRecord* tsig_rr = message_parse.getTSIGRecord(); + ASSERT_TRUE(tsig_rr); + EXPECT_EQ(Name("www.example.com"), tsig_rr->getName()); + // len(www.example.com) = 17, but when fully compressed, the length is + // 2 bytes. So the length of the record should be 15 bytes shorter. + EXPECT_EQ(70, tsig_rr->getLength()); +} + +TEST_F(MessageTest, fromWireWithBadTSIG) { + // Multiple TSIG RRs + EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire13.wire"), + DNSMessageFORMERR); + message_parse.clear(Message::PARSE); + + // TSIG in the answer section (must be in additional) + EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire14.wire"), + DNSMessageFORMERR); + message_parse.clear(Message::PARSE); + + // TSIG is not the last record. + EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire15.wire"), + DNSMessageFORMERR); + message_parse.clear(Message::PARSE); + + // Unexpected RR Class (this will fail in constructing TSIGRecord) + EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire16.wire"), + DNSMessageFORMERR); +} + +TEST_F(MessageTest, getRRCount) { + // by default all counters should be 0 + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL)); + + message_render.addQuestion(Question(Name("test.example.com"), + RRClass::IN(), RRType::A())); + EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION)); + + // rrset_a contains two RRs + message_render.addRRset(Message::SECTION_ANSWER, rrset_a); + EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER)); + + // parse a message containing a Question and EDNS OPT RR. + // OPT shouldn't be counted as normal RR, so result of getRRCount + // shouldn't change. + factoryFromFile(message_parse, "message_fromWire11.wire"); + EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL)); + + // out-of-band section ID + EXPECT_THROW(message_parse.getRRCount(bogus_section), isc::OutOfRange); +} + +TEST_F(MessageTest, addRRset) { + // initially, we have 0 + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER)); + + // add two A RRs (unsigned) + message_render.addRRset(Message::SECTION_ANSWER, rrset_a); + EXPECT_EQ(rrset_a, + *message_render.beginSection(Message::SECTION_ANSWER)); + EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER)); + + message_render.clear(Message::RENDER); + + // add one AAAA RR (signed) + message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa); + EXPECT_EQ(rrset_aaaa, + *message_render.beginSection(Message::SECTION_ANSWER)); + EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER)); +} + +TEST_F(MessageTest, badAddRRset) { + // addRRset() isn't allowed in the parse mode. + EXPECT_THROW(message_parse.addRRset(Message::SECTION_ANSWER, + rrset_a), InvalidMessageOperation); + // out-of-band section ID + EXPECT_THROW(message_render.addRRset(bogus_section, rrset_a), isc::OutOfRange); + + // NULL RRset + EXPECT_THROW(message_render.addRRset(Message::SECTION_ANSWER, RRsetPtr()), + InvalidParameter); +} + +TEST_F(MessageTest, hasRRset) { + message_render.addRRset(Message::SECTION_ANSWER, rrset_a); + EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::A())); + // section doesn't match + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name, + RRClass::IN(), RRType::A())); + // name doesn't match + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, + Name("nomatch.example"), + RRClass::IN(), RRType::A())); + // RR class doesn't match + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::CH(), RRType::A())); + // RR type doesn't match + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::AAAA())); + + // out-of-band section ID + EXPECT_THROW(message_render.hasRRset(bogus_section, test_name, + RRClass::IN(), RRType::A()), + isc::OutOfRange); + + // Repeat the checks having created an RRset of the appropriate type. + + RRsetPtr rrs1(new RRset(test_name, RRClass::IN(), RRType::A(), RRTTL(60))); + EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, rrs1)); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, rrs1)); + + RRsetPtr rrs2(new RRset(Name("nomatch.example"), RRClass::IN(), RRType::A(), + RRTTL(5))); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs2)); + + RRsetPtr rrs3(new RRset(test_name, RRClass::CH(), RRType::A(), RRTTL(60))); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs3)); + + RRsetPtr rrs4(new RRset(test_name, RRClass::IN(), RRType::AAAA(), RRTTL(5))); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs4)); + + RRsetPtr rrs5(new RRset(test_name, RRClass::IN(), RRType::AAAA(), RRTTL(5))); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, rrs4)); + + EXPECT_THROW(message_render.hasRRset(bogus_section, rrs1), isc::OutOfRange); +} + +TEST_F(MessageTest, removeRRset) { + message_render.addRRset(Message::SECTION_ANSWER, rrset_a); + message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa); + EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::A())); + EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::AAAA())); + EXPECT_EQ(4, message_render.getRRCount(Message::SECTION_ANSWER)); + + // Locate the AAAA RRset and remove it and any associated RRSIGs + RRsetIterator i = message_render.beginSection(Message::SECTION_ANSWER); + if ((*i)->getType() == RRType::A()) { + ++i; + } + EXPECT_EQ(RRType::AAAA(), (*i)->getType()); + message_render.removeRRset(Message::SECTION_ANSWER, i); + + EXPECT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::A())); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::AAAA())); + EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER)); +} + +TEST_F(MessageTest, clearQuestionSection) { + QuestionPtr q(new Question(Name("www.example.com"), RRClass::IN(), + RRType::A())); + message_render.addQuestion(q); + ASSERT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION)); + + message_render.clearSection(Message::SECTION_QUESTION); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_QUESTION)); + EXPECT_TRUE(message_render.beginQuestion() == + message_render.endQuestion()); +} + + +TEST_F(MessageTest, clearAnswerSection) { + // Add two RRsets, check they are present, clear the section, + // check if they are gone. + message_render.addRRset(Message::SECTION_ANSWER, rrset_a); + message_render.addRRset(Message::SECTION_ANSWER, rrset_aaaa); + ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::A())); + ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::AAAA())); + ASSERT_EQ(4, message_render.getRRCount(Message::SECTION_ANSWER)); + + message_render.clearSection(Message::SECTION_ANSWER); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::A())); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::AAAA())); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ANSWER)); +} + +TEST_F(MessageTest, clearAuthoritySection) { + // Add two RRsets, check they are present, clear the section, + // check if they are gone. + message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a); + message_render.addRRset(Message::SECTION_AUTHORITY, rrset_aaaa); + ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name, + RRClass::IN(), RRType::A())); + ASSERT_TRUE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name, + RRClass::IN(), RRType::AAAA())); + ASSERT_EQ(4, message_render.getRRCount(Message::SECTION_AUTHORITY)); + + message_render.clearSection(Message::SECTION_AUTHORITY); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name, + RRClass::IN(), RRType::A())); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_AUTHORITY, test_name, + RRClass::IN(), RRType::AAAA())); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY)); +} + +TEST_F(MessageTest, clearAdditionalSection) { + // Add two RRsets, check they are present, clear the section, + // check if they are gone. + message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a); + message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa); + ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name, + RRClass::IN(), RRType::A())); + ASSERT_TRUE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name, + RRClass::IN(), RRType::AAAA())); + ASSERT_EQ(4, message_render.getRRCount(Message::SECTION_ADDITIONAL)); + + message_render.clearSection(Message::SECTION_ADDITIONAL); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name, + RRClass::IN(), RRType::A())); + EXPECT_FALSE(message_render.hasRRset(Message::SECTION_ADDITIONAL, test_name, + RRClass::IN(), RRType::AAAA())); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL)); +} + +TEST_F(MessageTest, badClearSection) { + // attempt of clearing a message in the parse mode. + EXPECT_THROW(message_parse.clearSection(Message::SECTION_QUESTION), + InvalidMessageOperation); + // attempt of clearing out-of-range section + EXPECT_THROW(message_render.clearSection(bogus_section), isc::OutOfRange); +} + +TEST_F(MessageTest, badBeginSection) { + // valid cases are tested via other tests + EXPECT_THROW(message_render.beginSection(Message::SECTION_QUESTION), + InvalidMessageSection); + EXPECT_THROW(message_render.beginSection(bogus_section), isc::OutOfRange); +} + +TEST_F(MessageTest, badEndSection) { + // valid cases are tested via other tests + EXPECT_THROW(message_render.endSection(Message::SECTION_QUESTION), + InvalidMessageSection); + EXPECT_THROW(message_render.endSection(bogus_section), isc::OutOfRange); +} + +TEST_F(MessageTest, appendSection) { + Message target(Message::RENDER); + + // Section check + EXPECT_THROW(target.appendSection(bogus_section, message_render), + isc::OutOfRange); + + // Make sure nothing is copied if there is nothing to copy + target.appendSection(Message::SECTION_QUESTION, message_render); + EXPECT_EQ(0, target.getRRCount(Message::SECTION_QUESTION)); + target.appendSection(Message::SECTION_ANSWER, message_render); + EXPECT_EQ(0, target.getRRCount(Message::SECTION_ANSWER)); + target.appendSection(Message::SECTION_AUTHORITY, message_render); + EXPECT_EQ(0, target.getRRCount(Message::SECTION_AUTHORITY)); + target.appendSection(Message::SECTION_ADDITIONAL, message_render); + EXPECT_EQ(0, target.getRRCount(Message::SECTION_ADDITIONAL)); + + // Now add some data, copy again, and see if it got added + message_render.addQuestion(Question(Name("test.example.com"), + RRClass::IN(), RRType::A())); + message_render.addRRset(Message::SECTION_ANSWER, rrset_a); + message_render.addRRset(Message::SECTION_AUTHORITY, rrset_a); + message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_a); + message_render.addRRset(Message::SECTION_ADDITIONAL, rrset_aaaa); + + target.appendSection(Message::SECTION_QUESTION, message_render); + EXPECT_EQ(1, target.getRRCount(Message::SECTION_QUESTION)); + + target.appendSection(Message::SECTION_ANSWER, message_render); + EXPECT_EQ(2, target.getRRCount(Message::SECTION_ANSWER)); + EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::A())); + + target.appendSection(Message::SECTION_AUTHORITY, message_render); + EXPECT_EQ(2, target.getRRCount(Message::SECTION_AUTHORITY)); + EXPECT_TRUE(target.hasRRset(Message::SECTION_AUTHORITY, test_name, + RRClass::IN(), RRType::A())); + + target.appendSection(Message::SECTION_ADDITIONAL, message_render); + EXPECT_EQ(4, target.getRRCount(Message::SECTION_ADDITIONAL)); + EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name, + RRClass::IN(), RRType::A())); + EXPECT_TRUE(target.hasRRset(Message::SECTION_ADDITIONAL, test_name, + RRClass::IN(), RRType::AAAA())); + + // One more test, test to see if the section gets added, not replaced + Message source2(Message::RENDER); + source2.addRRset(Message::SECTION_ANSWER, rrset_aaaa); + target.appendSection(Message::SECTION_ANSWER, source2); + EXPECT_EQ(4, target.getRRCount(Message::SECTION_ANSWER)); + EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::A())); + EXPECT_TRUE(target.hasRRset(Message::SECTION_ANSWER, test_name, + RRClass::IN(), RRType::AAAA())); +} + +TEST_F(MessageTest, parseHeader) { + received_data.clear(); + UnitTestUtil::readWireData("message_fromWire1", received_data); + + // parseHeader() isn't allowed in the render mode. + InputBuffer buffer(&received_data[0], received_data.size()); + EXPECT_THROW(message_render.parseHeader(buffer), InvalidMessageOperation); + + message_parse.parseHeader(buffer); + EXPECT_EQ(0x1035, message_parse.getQid()); + EXPECT_EQ(Opcode::QUERY(), message_parse.getOpcode()); + EXPECT_EQ(Rcode::NOERROR(), message_parse.getRcode()); + EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_QR)); + EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_AA)); + EXPECT_FALSE(message_parse.getHeaderFlag(Message::HEADERFLAG_TC)); + EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_RD)); + EXPECT_FALSE(message_parse.getHeaderFlag(Message::HEADERFLAG_RA)); + EXPECT_FALSE(message_parse.getHeaderFlag(Message::HEADERFLAG_AD)); + EXPECT_FALSE(message_parse.getHeaderFlag(Message::HEADERFLAG_CD)); + EXPECT_EQ(1, message_parse.getRRCount(Message::SECTION_QUESTION)); + EXPECT_EQ(2, message_parse.getRRCount(Message::SECTION_ANSWER)); + EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_AUTHORITY)); + EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_ADDITIONAL)); + + // Only the header part should have been examined. + EXPECT_EQ(12, buffer.getPosition()); // 12 = size of the header section + EXPECT_TRUE(message_parse.beginQuestion() == message_parse.endQuestion()); + EXPECT_TRUE(message_parse.beginSection(Message::SECTION_ANSWER) == + message_parse.endSection(Message::SECTION_ANSWER)); + EXPECT_TRUE(message_parse.beginSection(Message::SECTION_AUTHORITY) == + message_parse.endSection(Message::SECTION_AUTHORITY)); + EXPECT_TRUE(message_parse.beginSection(Message::SECTION_ADDITIONAL) == + message_parse.endSection(Message::SECTION_ADDITIONAL)); +} + +void +checkMessageFromWire(const Message& message_parse, + const Name& test_name) +{ + EXPECT_EQ(0x1035, message_parse.getQid()); + EXPECT_EQ(Opcode::QUERY(), message_parse.getOpcode()); + EXPECT_EQ(Rcode::NOERROR(), message_parse.getRcode()); + EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_QR)); + EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_RD)); + EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_AA)); + + QuestionPtr q = *message_parse.beginQuestion(); + EXPECT_EQ(test_name, q->getName()); + EXPECT_EQ(RRType::A(), q->getType()); + EXPECT_EQ(RRClass::IN(), q->getClass()); + EXPECT_EQ(1, message_parse.getRRCount(Message::SECTION_QUESTION)); + EXPECT_EQ(2, message_parse.getRRCount(Message::SECTION_ANSWER)); + EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_AUTHORITY)); + EXPECT_EQ(0, message_parse.getRRCount(Message::SECTION_ADDITIONAL)); + + RRsetPtr rrset = *message_parse.beginSection(Message::SECTION_ANSWER); + EXPECT_EQ(test_name, rrset->getName()); + EXPECT_EQ(RRType::A(), rrset->getType()); + EXPECT_EQ(RRClass::IN(), rrset->getClass()); + // TTL should be 3600, even though that of the 2nd RR is 7200 + EXPECT_EQ(RRTTL(3600), rrset->getTTL()); + RdataIteratorPtr it = rrset->getRdataIterator(); + EXPECT_EQ("192.0.2.1", it->getCurrent().toText()); + it->next(); + EXPECT_EQ("192.0.2.2", it->getCurrent().toText()); + it->next(); + EXPECT_TRUE(it->isLast()); +} + + +TEST_F(MessageTest, fromWire) { + // fromWire() isn't allowed in the render mode. + EXPECT_THROW(factoryFromFile(message_render, "message_fromWire1"), + InvalidMessageOperation); + + factoryFromFile(message_parse, "message_fromWire1"); + checkMessageFromWire(message_parse, test_name); +} + +TEST_F(MessageTest, fromWireMultiple) { + // Parse from wire multiple times. + factoryFromFile(message_parse, "message_fromWire1"); + factoryFromFile(message_parse, "message_fromWire1"); + factoryFromFile(message_parse, "message_fromWire1"); + factoryFromFile(message_parse, "message_fromWire1"); + checkMessageFromWire(message_parse, test_name); + + // Calling parseHeader() directly before fromWire() should not cause + // any problems. + received_data.clear(); + UnitTestUtil::readWireData("message_fromWire1", received_data); + + InputBuffer buffer(&received_data[0], received_data.size()); + message_parse.parseHeader(buffer); + message_parse.fromWire(buffer); + message_parse.parseHeader(buffer); + message_parse.fromWire(buffer); + checkMessageFromWire(message_parse, test_name); +} + +TEST_F(MessageTest, fromWireShortBuffer) { + // We trim a valid message (ending with an SOA RR) for one byte. + // fromWire() should throw an exception while parsing the trimmed RR. + UnitTestUtil::readWireData("message_fromWire22.wire", received_data); + InputBuffer buffer(&received_data[0], received_data.size() - 1); + EXPECT_THROW(message_parse.fromWire(buffer), isc::OutOfRange); +} + +TEST_F(MessageTest, fromWireCombineRRs) { + // This message contains 3 RRs in the answer section in the order of + // A, AAAA, A types. fromWire() should combine the two A RRs into a + // single RRset by default. + factoryFromFile(message_parse, "message_fromWire19.wire"); + + RRsetIterator it = message_parse.beginSection(Message::SECTION_ANSWER); + RRsetIterator it_end = message_parse.endSection(Message::SECTION_ANSWER); + ASSERT_TRUE(it != it_end); + EXPECT_EQ(RRType::A(), (*it)->getType()); + EXPECT_EQ(2, (*it)->getRdataCount()); + + ++it; + ASSERT_TRUE(it != it_end); + EXPECT_EQ(RRType::AAAA(), (*it)->getType()); + EXPECT_EQ(1, (*it)->getRdataCount()); +} + +// A helper function for a test pattern commonly used in several tests below. +void +preserveRRCheck(const Message& message, Message::Section section) { + RRsetIterator it = message.beginSection(section); + RRsetIterator it_end = message.endSection(section); + ASSERT_TRUE(it != it_end); + EXPECT_EQ(RRType::A(), (*it)->getType()); + EXPECT_EQ(1, (*it)->getRdataCount()); + EXPECT_EQ("192.0.2.1", (*it)->getRdataIterator()->getCurrent().toText()); + + ++it; + ASSERT_TRUE(it != it_end); + EXPECT_EQ(RRType::AAAA(), (*it)->getType()); + EXPECT_EQ(1, (*it)->getRdataCount()); + EXPECT_EQ("2001:db8::1", (*it)->getRdataIterator()->getCurrent().toText()); + + ++it; + ASSERT_TRUE(it != it_end); + EXPECT_EQ(RRType::A(), (*it)->getType()); + EXPECT_EQ(1, (*it)->getRdataCount()); + EXPECT_EQ("192.0.2.2", (*it)->getRdataIterator()->getCurrent().toText()); +} + +TEST_F(MessageTest, fromWirePreserveAnswer) { + // Using the same data as the previous test, but specify the PRESERVE_ORDER + // option. The received order of RRs should be preserved, and each RR + // should be stored in a single RRset. + factoryFromFile(message_parse, "message_fromWire19.wire", + Message::PRESERVE_ORDER); + { + SCOPED_TRACE("preserve answer RRs"); + preserveRRCheck(message_parse, Message::SECTION_ANSWER); + } +} + +TEST_F(MessageTest, fromWirePreserveAuthority) { + // Same for the previous test, but for the authority section. + factoryFromFile(message_parse, "message_fromWire20.wire", + Message::PRESERVE_ORDER); + { + SCOPED_TRACE("preserve authority RRs"); + preserveRRCheck(message_parse, Message::SECTION_AUTHORITY); + } +} + +TEST_F(MessageTest, fromWirePreserveAdditional) { + // Same for the previous test, but for the additional section. + factoryFromFile(message_parse, "message_fromWire21.wire", + Message::PRESERVE_ORDER); + { + SCOPED_TRACE("preserve additional RRs"); + preserveRRCheck(message_parse, Message::SECTION_ADDITIONAL); + } +} + +TEST_F(MessageTest, EDNS0ExtRcode) { + // Extended Rcode = BADVERS + factoryFromFile(message_parse, "message_fromWire10.wire"); + EXPECT_EQ(Rcode::BADVERS(), message_parse.getRcode()); + + // Maximum extended Rcode + message_parse.clear(Message::PARSE); + factoryFromFile(message_parse, "message_fromWire11.wire"); + EXPECT_EQ(0xfff, message_parse.getRcode().getCode()); +} + +TEST_F(MessageTest, BadEDNS0) { + // OPT RR in the answer section + EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire4"), + DNSMessageFORMERR); + // multiple OPT RRs (in the additional section) + message_parse.clear(Message::PARSE); + EXPECT_THROW(factoryFromFile(message_parse, "message_fromWire5"), + DNSMessageFORMERR); +} + +TEST_F(MessageTest, toWire) { + message_render.setQid(0x1035); + message_render.setOpcode(Opcode::QUERY()); + message_render.setRcode(Rcode::NOERROR()); + message_render.setHeaderFlag(Message::HEADERFLAG_QR, true); + message_render.setHeaderFlag(Message::HEADERFLAG_RD, true); + message_render.setHeaderFlag(Message::HEADERFLAG_AA, true); + message_render.addQuestion(Question(Name("test.example.com"), RRClass::IN(), + RRType::A())); + message_render.addRRset(Message::SECTION_ANSWER, rrset_a); + + EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION)); + EXPECT_EQ(2, message_render.getRRCount(Message::SECTION_ANSWER)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL)); + + message_render.toWire(renderer); + vector<unsigned char> data; + UnitTestUtil::readWireData("message_toWire1", data); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageTest, toWireSigned) { + message_render.setQid(0x75c1); + message_render.setOpcode(Opcode::QUERY()); + message_render.setRcode(Rcode::NOERROR()); + message_render.setHeaderFlag(Message::HEADERFLAG_QR, true); + message_render.setHeaderFlag(Message::HEADERFLAG_RD, true); + message_render.setHeaderFlag(Message::HEADERFLAG_AA, true); + message_render.addQuestion(Question(Name("test.example.com"), RRClass::IN(), + RRType::A())); + + rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::RRSIG(), RRTTL(3600))); + // one signature algorithm (5 = RSA/SHA-1) + rrset_rrsig->addRdata(generic::RRSIG("A 5 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + // another signature algorithm (3 = DSA/SHA-1) + rrset_rrsig->addRdata(generic::RRSIG("A 3 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + rrset_a->addRRsig(rrset_rrsig); + EXPECT_EQ(2, rrset_a->getRRsigDataCount()); + + message_render.addRRset(Message::SECTION_ANSWER, rrset_a); + + EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION)); + EXPECT_EQ(4, message_render.getRRCount(Message::SECTION_ANSWER)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL)); + + message_render.toWire(renderer); + vector<unsigned char> data; + UnitTestUtil::readWireData("message_toWire6", data); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageTest, toWireSignedAndTruncated) { + message_render.setQid(0x75c1); + message_render.setOpcode(Opcode::QUERY()); + message_render.setRcode(Rcode::NOERROR()); + message_render.setHeaderFlag(Message::HEADERFLAG_QR, true); + message_render.setHeaderFlag(Message::HEADERFLAG_RD, true); + message_render.setHeaderFlag(Message::HEADERFLAG_AA, true); + message_render.addQuestion(Question(Name("test.example.com"), RRClass::IN(), + RRType::TXT())); + + RRsetPtr rrset_txt = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::TXT(), RRTTL(3600))); + rrset_txt->addRdata(generic::TXT(string(255, 'a'))); + rrset_txt->addRdata(generic::TXT(string(255, 'b'))); + rrset_txt->addRdata(generic::TXT(string(255, 'c'))); + rrset_txt->addRdata(generic::TXT(string(255, 'd'))); + rrset_txt->addRdata(generic::TXT(string(255, 'e'))); + rrset_txt->addRdata(generic::TXT(string(255, 'f'))); + rrset_txt->addRdata(generic::TXT(string(255, 'g'))); + rrset_txt->addRdata(generic::TXT(string(255, 'h'))); + + rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::RRSIG(), RRTTL(3600))); + // one signature algorithm (5 = RSA/SHA-1) + rrset_rrsig->addRdata(generic::RRSIG("TXT 5 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + rrset_txt->addRRsig(rrset_rrsig); + EXPECT_EQ(1, rrset_txt->getRRsigDataCount()); + + message_render.addRRset(Message::SECTION_ANSWER, rrset_txt); + + EXPECT_EQ(1, message_render.getRRCount(Message::SECTION_QUESTION)); + EXPECT_EQ(9, message_render.getRRCount(Message::SECTION_ANSWER)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_AUTHORITY)); + EXPECT_EQ(0, message_render.getRRCount(Message::SECTION_ADDITIONAL)); + + message_render.toWire(renderer); + vector<unsigned char> data; + UnitTestUtil::readWireData("message_toWire7", data); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageTest, toWireInParseMode) { + // toWire() isn't allowed in the parse mode. + EXPECT_THROW(message_parse.toWire(renderer), InvalidMessageOperation); +} + +// See dnssectime_unittest.cc +template <int64_t NOW> +int64_t +testGetTime() { + return (NOW); +} + +// bit-wise constant flags to configure DNS header flags for test +// messages. +const unsigned int QR_FLAG = 0x1; +const unsigned int AA_FLAG = 0x2; +const unsigned int RD_FLAG = 0x4; + +void +commonTSIGToWireCheck(Message& message, MessageRenderer& renderer, + TSIGContext& tsig_ctx, const char* const expected_file, + unsigned int message_flags = RD_FLAG, + RRType qtype = RRType::A(), + const vector<const char*>* answer_data = 0) { + message.setOpcode(Opcode::QUERY()); + message.setRcode(Rcode::NOERROR()); + if ((message_flags & QR_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_QR); + } + if ((message_flags & AA_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_AA); + } + if ((message_flags & RD_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_RD); + } + message.addQuestion(Question(Name("www.example.com"), RRClass::IN(), + qtype)); + + if (answer_data) { + RRsetPtr ans_rrset(new RRset(Name("www.example.com"), RRClass::IN(), + qtype, RRTTL(86400))); + for (auto const& it : *answer_data) { + ans_rrset->addRdata(createRdata(qtype, RRClass::IN(), it)); + } + message.addRRset(Message::SECTION_ANSWER, ans_rrset); + } + + message.toWire(renderer, &tsig_ctx); + vector<unsigned char> expected_data; + UnitTestUtil::readWireData(expected_file, expected_data); + matchWireData(&expected_data[0], expected_data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageTest, toWireWithTSIG) { + // Rendering a message with TSIG. Various special cases specific to + // TSIG are tested in the tsig tests. We only check the message contains + // a TSIG at the end and the ARCOUNT of the header is updated. + + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + message_render.setQid(0x2d65); + + { + SCOPED_TRACE("Message sign with TSIG"); + commonTSIGToWireCheck(message_render, renderer, tsig_ctx, + "message_toWire2.wire"); + } +} + +TEST_F(MessageTest, toWireWithEDNSAndTSIG) { + // Similar to the previous test, but with an EDNS before TSIG. + // The wire data check will confirm the ordering. + isc::util::detail::getTimeFunction = testGetTime<0x4db60d1f>; + + message_render.setQid(0x6cd); + + EDNSPtr edns(new EDNS()); + edns->setUDPSize(4096); + message_render.setEDNS(edns); + + { + SCOPED_TRACE("Message sign with TSIG and EDNS"); + commonTSIGToWireCheck(message_render, renderer, tsig_ctx, + "message_toWire3.wire"); + } +} + +// Some of the following tests involve truncation. We use the query name +// "www.example.com" and some TXT question/answers. The length of the +// header and question will be 33 bytes. If we also try to include a +// TSIG of the same key name (not compressed) with HMAC-MD5, the TSIG RR +// will be 85 bytes. + +// A long TXT RDATA. With a fully compressed owner name, the corresponding +// RR will be 268 bytes. +const char* const long_txt1 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde"; + +// With a fully compressed owner name, the corresponding RR will be 212 bytes. +// It should result in truncation even without TSIG (33 + 268 + 212 = 513) +const char* const long_txt2 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456"; + +// With a fully compressed owner name, the corresponding RR will be 127 bytes. +// So, it can fit in the standard 512 bytes with txt1 and without TSIG, but +// adding a TSIG would result in truncation (33 + 268 + 127 + 85 = 513) +const char* const long_txt3 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef01"; + +// This is 1 byte shorter than txt3, which will result in a possible longest +// message containing answer RRs and TSIG. +const char* const long_txt4 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0"; + +// Example output generated by +// "dig -y www.example.com:SFuWd/q99SzF8Yzd1QbB9g== www.example.com txt +// QID: 0x22c2 +// Time Signed: 0x00004e179212 +TEST_F(MessageTest, toWireTSIGTruncation) { + isc::util::detail::getTimeFunction = testGetTime<0x4e179212>; + + // Verify a validly signed query so that we can use the TSIG context + + factoryFromFile(message_parse, "message_fromWire17.wire"); + EXPECT_EQ(TSIGError::NOERROR(), + tsig_ctx.verify(message_parse.getTSIGRecord(), + &received_data[0], received_data.size())); + + message_render.setQid(0x22c2); + vector<const char*> answer_data; + answer_data.push_back(long_txt1); + answer_data.push_back(long_txt2); + { + SCOPED_TRACE("Message sign with TSIG and TC bit on"); + commonTSIGToWireCheck(message_render, renderer, tsig_ctx, + "message_toWire4.wire", + QR_FLAG|AA_FLAG|RD_FLAG, + RRType::TXT(), &answer_data); + } +} + +TEST_F(MessageTest, toWireTSIGTruncation2) { + // Similar to the previous test, but without TSIG it wouldn't cause + // truncation. + isc::util::detail::getTimeFunction = testGetTime<0x4e179212>; + factoryFromFile(message_parse, "message_fromWire17.wire"); + EXPECT_EQ(TSIGError::NOERROR(), + tsig_ctx.verify(message_parse.getTSIGRecord(), + &received_data[0], received_data.size())); + + message_render.setQid(0x22c2); + vector<const char*> answer_data; + answer_data.push_back(long_txt1); + answer_data.push_back(long_txt3); + { + SCOPED_TRACE("Message sign with TSIG and TC bit on (2)"); + commonTSIGToWireCheck(message_render, renderer, tsig_ctx, + "message_toWire4.wire", + QR_FLAG|AA_FLAG|RD_FLAG, + RRType::TXT(), &answer_data); + } +} + +TEST_F(MessageTest, toWireTSIGTruncation3) { + // Similar to previous ones, but truncation occurs due to too many + // Questions (very unusual, but not necessarily illegal). + + // We are going to create a message starting with a standard + // header (12 bytes) and multiple questions in the Question + // section of the same owner name (changing the RRType, just so + // that it would be the form that would be accepted by the BIND 9 + // parser). The first Question is 21 bytes in length, and the subsequent + // ones are 6 bytes. We'll also use a TSIG whose size is 85 bytes. + // Up to 66 questions can fit in the standard 512-byte buffer + // (12 + 21 + 6 * 65 + 85 = 508). If we try to add one more it would + // result in truncation. + message_render.setOpcode(Opcode::QUERY()); + message_render.setRcode(Rcode::NOERROR()); + for (int i = 1; i <= 67; ++i) { + message_render.addQuestion(Question(Name("www.example.com"), + RRClass::IN(), RRType(i))); + } + message_render.toWire(renderer, &tsig_ctx); + + // Check the rendered data by parsing it. We only check it has the + // TC bit on, has the correct number of questions, and has a TSIG RR. + // Checking the signature wouldn't be necessary for this rare case + // scenario. + InputBuffer buffer(renderer.getData(), renderer.getLength()); + message_parse.fromWire(buffer); + EXPECT_TRUE(message_parse.getHeaderFlag(Message::HEADERFLAG_TC)); + // Note that the number of questions are 66, not 67 as we tried to add. + EXPECT_EQ(66, message_parse.getRRCount(Message::SECTION_QUESTION)); + EXPECT_TRUE(message_parse.getTSIGRecord()); +} + +TEST_F(MessageTest, toWireTSIGNoTruncation) { + // A boundary case that shouldn't cause truncation: the resulting + // response message with a TSIG will be 512 bytes long. + isc::util::detail::getTimeFunction = testGetTime<0x4e17b38d>; + factoryFromFile(message_parse, "message_fromWire18.wire"); + EXPECT_EQ(TSIGError::NOERROR(), + tsig_ctx.verify(message_parse.getTSIGRecord(), + &received_data[0], received_data.size())); + + message_render.setQid(0xd6e2); + vector<const char*> answer_data; + answer_data.push_back(long_txt1); + answer_data.push_back(long_txt4); + { + SCOPED_TRACE("Message sign with TSIG, no truncation"); + commonTSIGToWireCheck(message_render, renderer, tsig_ctx, + "message_toWire5.wire", + QR_FLAG|AA_FLAG|RD_FLAG, + RRType::TXT(), &answer_data); + } +} + +// This is a buggy renderer for testing. It behaves like the straightforward +// MessageRenderer, but once it has some data, its setLengthLimit() ignores +// the given parameter and resets the limit to the current length, making +// subsequent insertion result in truncation, which would make TSIG RR +// rendering fail unexpectedly in the test that follows. +class BadRenderer : public MessageRenderer { +public: + virtual void setLengthLimit(size_t len) { + if (getLength() > 0) { + MessageRenderer::setLengthLimit(getLength()); + } else { + MessageRenderer::setLengthLimit(len); + } + } +}; + +TEST_F(MessageTest, toWireTSIGLengthErrors) { + // specify an unusual short limit that wouldn't be able to hold + // the TSIG. + renderer.setLengthLimit(tsig_ctx.getTSIGLength() - 1); + // Use commonTSIGToWireCheck() only to call toWire() with otherwise valid + // conditions. The checks inside it don't matter because we expect an + // exception before any of the checks. + EXPECT_THROW(commonTSIGToWireCheck(message_render, renderer, tsig_ctx, + "message_toWire2.wire"), + InvalidParameter); + + // This one is large enough for TSIG, but the remaining limit isn't + // even enough for the Header section. + renderer.clear(); + message_render.clear(Message::RENDER); + renderer.setLengthLimit(tsig_ctx.getTSIGLength() + 1); + EXPECT_THROW(commonTSIGToWireCheck(message_render, renderer, tsig_ctx, + "message_toWire2.wire"), + InvalidParameter); + + // Trying to render a message with TSIG using a buggy renderer. + BadRenderer bad_renderer; + bad_renderer.setLengthLimit(512); + message_render.clear(Message::RENDER); + EXPECT_THROW(commonTSIGToWireCheck(message_render, bad_renderer, tsig_ctx, + "message_toWire2.wire"), + Unexpected); +} + +TEST_F(MessageTest, toWireWithoutOpcode) { + message_render.setRcode(Rcode::NOERROR()); + EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation); +} + +TEST_F(MessageTest, toWireWithoutRcode) { + message_render.setOpcode(Opcode::QUERY()); + EXPECT_THROW(message_render.toWire(renderer), InvalidMessageOperation); +} + +TEST_F(MessageTest, toText) { + // Check toText() output for a typical DNS response with records in + // all sections + + factoryFromFile(message_parse, "message_toText1.wire"); + { + SCOPED_TRACE("Message toText test (basic case)"); + ifstream ifs; + unittests::openTestData("message_toText1.txt", ifs); + unittests::matchTextData(ifs, message_parse.toText()); + } + + // Another example with EDNS. The expected data was slightly modified + // from the dig output (other than replacing tabs with a space): adding + // a newline after the "OPT PSEUDOSECTION". This is an intentional change + // in our version for better readability. + message_parse.clear(Message::PARSE); + factoryFromFile(message_parse, "message_toText2.wire"); + { + SCOPED_TRACE("Message toText test with EDNS"); + ifstream ifs; + unittests::openTestData("message_toText2.txt", ifs); + unittests::matchTextData(ifs, message_parse.toText()); + } + + // Another example with TSIG. The expected data was slightly modified + // from the dig output (other than replacing tabs with a space): removing + // a redundant white space at the end of TSIG RDATA. We'd rather consider + // it a dig's defect than a feature. + message_parse.clear(Message::PARSE); + factoryFromFile(message_parse, "message_toText3.wire"); + { + SCOPED_TRACE("Message toText test with TSIG"); + ifstream ifs; + unittests::openTestData("message_toText3.txt", ifs); + unittests::matchTextData(ifs, message_parse.toText()); + } +} + +TEST_F(MessageTest, toTextWithoutOpcode) { + message_render.setRcode(Rcode::NOERROR()); + EXPECT_THROW(message_render.toText(), InvalidMessageOperation); +} + +TEST_F(MessageTest, toTextWithoutRcode) { + message_render.setOpcode(Opcode::QUERY()); + EXPECT_THROW(message_render.toText(), InvalidMessageOperation); +} +} diff --git a/src/lib/dns/tests/messagerenderer_unittest.cc b/src/lib/dns/tests/messagerenderer_unittest.cc new file mode 100644 index 0000000..784aa55 --- /dev/null +++ b/src/lib/dns/tests/messagerenderer_unittest.cc @@ -0,0 +1,292 @@ +// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <util/buffer.h> +#include <dns/name.h> +#include <dns/labelsequence.h> +#include <dns/messagerenderer.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +#include <gtest/gtest.h> + +#include <boost/lexical_cast.hpp> + +#include <string> +#include <vector> + +using isc::UnitTestUtil; +using isc::dns::Name; +using isc::dns::LabelSequence; +using isc::dns::MessageRenderer; +using isc::util::OutputBuffer; +using boost::lexical_cast; +using isc::util::unittests::matchWireData; + +namespace { +class MessageRendererTest : public ::testing::Test { +protected: + MessageRendererTest() : expected_size(0) { + data16 = (2 << 8) | 3; + data32 = (4 << 24) | (5 << 16) | (6 << 8) | 7; + } + size_t expected_size; + uint16_t data16; + uint32_t data32; + MessageRenderer renderer; + std::vector<unsigned char> data; + static const uint8_t testdata[5]; +}; + +const uint8_t MessageRendererTest::testdata[5] = {1, 2, 3, 4, 5}; + +// The test cases are borrowed from those for the OutputBuffer class. +TEST_F(MessageRendererTest, writeInteger) { + renderer.writeUint16(data16); + expected_size += sizeof(data16); + + matchWireData(&testdata[1], sizeof(data16), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, writeName) { + UnitTestUtil::readWireData("name_toWire1", data); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("b.example.com.")); + renderer.writeName(Name("a.example.org.")); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, writeNameInLargeBuffer) { + size_t offset = 0x3fff; + renderer.skip(offset); + + UnitTestUtil::readWireData("name_toWire2", data); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("b.example.com.")); + matchWireData(&data[0], data.size(), + static_cast<const uint8_t*>(renderer.getData()) + offset, + renderer.getLength() - offset); +} + +TEST_F(MessageRendererTest, writeNameWithUncompressed) { + UnitTestUtil::readWireData("name_toWire3", data); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("b.example.com."), false); + renderer.writeName(Name("b.example.com.")); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, writeNamePointerChain) { + UnitTestUtil::readWireData("name_toWire4", data); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("b.example.com.")); + renderer.writeName(Name("b.example.com.")); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, compressMode) { + // By default the render performs case insensitive compression. + EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode()); + + // The mode can be explicitly changed. + renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE); + EXPECT_EQ(MessageRenderer::CASE_SENSITIVE, renderer.getCompressMode()); + renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE); + EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode()); + + // The clear() method resets the mode to the default. + renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE); + renderer.clear(); + EXPECT_EQ(MessageRenderer::CASE_INSENSITIVE, renderer.getCompressMode()); +} + +TEST_F(MessageRendererTest, writeNameCaseCompress) { + // By default MessageRenderer performs case insensitive compression. + + UnitTestUtil::readWireData("name_toWire1", data); + renderer.writeName(Name("a.example.com.")); + // this should match the first name in terms of compression: + renderer.writeName(Name("b.exAmple.CoM.")); + renderer.writeName(Name("a.example.org.")); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, writeNameCaseSensitiveCompress) { + // name compression in case sensitive manner. See the data file + // description for details. + renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE); + UnitTestUtil::readWireData("name_toWire5.wire", data); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("b.eXample.com.")); + renderer.writeName(Name("c.eXample.com.")); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, writeNameMixedCaseCompress) { + renderer.setCompressMode(MessageRenderer::CASE_SENSITIVE); + UnitTestUtil::readWireData("name_toWire6.wire", data); + renderer.writeName(Name("a.example.com.")); + renderer.writeName(Name("b.eXample.com.")); + + // Change the compression mode in the middle of rendering. This is not + // allowed in this implementation. + EXPECT_THROW(renderer.setCompressMode(MessageRenderer::CASE_INSENSITIVE), + isc::InvalidParameter); + + // Once the renderer is cleared, it's okay again. + renderer.clear(); + EXPECT_NO_THROW(renderer.setCompressMode( + MessageRenderer::CASE_INSENSITIVE)); +} + +TEST_F(MessageRendererTest, writeRootName) { + // root name is special: it never causes compression or can (reasonably) + // be a compression pointer. So it makes sense to check this case + // explicitly. + Name example_name = Name("www.example.com"); + + OutputBuffer expected(0); + expected.writeUint8(0); // root name + example_name.toWire(expected); + + renderer.writeName(Name(".")); + renderer.writeName(example_name); + matchWireData(static_cast<const uint8_t*>(expected.getData()), + expected.getLength(), + static_cast<const uint8_t*>(renderer.getData()), + renderer.getLength()); +} + +TEST_F(MessageRendererTest, writeNameLabelSequence1) { + UnitTestUtil::readWireData("name_toWire7", data); + + Name n1("a.example.com"); + LabelSequence ls1(n1); + + // a.example.com. + renderer.writeName(ls1); + + ls1.stripLeft(1); + + // example.com. + renderer.writeName(ls1); + + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, writeNameLabelSequence2) { + UnitTestUtil::readWireData("name_toWire8", data); + + Name n1("a.example.com"); + LabelSequence ls1(n1); + + ls1.stripRight(1); + + // a.example.com (without root .) + renderer.writeName(ls1); + + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, writeNameLabelSequence3) { + UnitTestUtil::readWireData("name_toWire9", data); + + Name n1("a.example.com"); + LabelSequence ls1(n1); + + // a.example.com. + renderer.writeName(ls1); + + ls1.stripRight(1); + + // a.example.com (without root .) + renderer.writeName(ls1); + + ls1.stripRight(1); + + // a.example + renderer.writeName(ls1); + + ls1.stripLeft(1); + + // example + renderer.writeName(ls1); + + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(MessageRendererTest, setBuffer) { + OutputBuffer new_buffer(0); + renderer.setBuffer(&new_buffer); + EXPECT_EQ(0, new_buffer.getLength()); // the buffer should be still empty + renderer.writeUint32(42); + EXPECT_EQ(sizeof(uint32_t), new_buffer.getLength()); + EXPECT_EQ(sizeof(uint32_t), renderer.getLength()); + + // Change some other internal state for the reset test below. + EXPECT_EQ(512, renderer.getLengthLimit()); + renderer.setLengthLimit(4096); + EXPECT_EQ(4096, renderer.getLengthLimit()); + + // Reset the buffer to the default again. Other internal states and + // resources should be cleared. The used buffer should be intact. + renderer.setBuffer(0); + EXPECT_EQ(sizeof(uint32_t), new_buffer.getLength()); + EXPECT_EQ(0, renderer.getLength()); + EXPECT_EQ(512, renderer.getLengthLimit()); +} + +TEST_F(MessageRendererTest, setBufferErrors) { + OutputBuffer new_buffer(0); + + // Buffer cannot be reset when the renderer is in use. + renderer.writeUint32(10); + EXPECT_THROW(renderer.setBuffer(&new_buffer), isc::InvalidParameter); + + renderer.clear(); + renderer.setBuffer(&new_buffer); + renderer.writeUint32(10); + EXPECT_THROW(renderer.setBuffer(&new_buffer), isc::InvalidParameter); + + // Resetting the buffer isn't allowed for the default buffer. + renderer.setBuffer(0); + EXPECT_THROW(renderer.setBuffer(0), isc::InvalidParameter); + + // It's okay to reset a temporary buffer without using it. + renderer.setBuffer(&new_buffer); + EXPECT_NO_THROW(renderer.setBuffer(0)); +} + +TEST_F(MessageRendererTest, manyRRs) { + // Render a large number of names, and the confirm the resulting wire + // data store the expected names in the correct order (1000 is an + // arbitrary choice). + for (size_t i = 0; i < 1000; ++i) { + renderer.writeName(Name(lexical_cast<std::string>(i) + ".example")); + } + isc::util::InputBuffer b(renderer.getData(), renderer.getLength()); + for (size_t i = 0; i < 1000; ++i) { + EXPECT_EQ(Name(lexical_cast<std::string>(i) + ".example"), Name(b)); + } + // This will trigger trimming excessive hash items. It shouldn't cause + // any disruption. + EXPECT_NO_THROW(renderer.clear()); +} +} diff --git a/src/lib/dns/tests/name_unittest.cc b/src/lib/dns/tests/name_unittest.cc new file mode 100644 index 0000000..d241701 --- /dev/null +++ b/src/lib/dns/tests/name_unittest.cc @@ -0,0 +1,794 @@ +// Copyright (C) 2009-2024 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 <vector> +#include <string> +#include <sstream> +#include <iomanip> +#include <limits> +#include <stdexcept> + +#include <util/buffer.h> +#include <dns/exceptions.h> +#include <dns/name.h> +#include <dns/messagerenderer.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using isc::util::unittests::matchWireData; + +// +// XXX: these are defined as class static constants, but some compilers +// seemingly cannot find the symbols when used in the EXPECT_xxx macros. +// +const size_t Name::MAX_WIRE; +const size_t Name::MAX_LABELS; + +// This is a name of maximum allowed number of labels +const char* max_labels_str = "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 40 + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 80 + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 120 + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 160 + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 200 + "0.1.2.3.4.5.6.7.8.9.0.1.2.3.4.5.6.7.8.9." // 240 + "0.1.2.3.4.5.6"; +// This is a name of maximum allowed length +const char* max_len_str = "123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "123"; + +namespace { +class NameTest : public ::testing::Test { +protected: + NameTest() : example_name("www.example.com"), + example_name_upper("WWW.EXAMPLE.COM"), + small_name("aaa.example.com"), + large_name("zzz.example.com"), + origin_name("example.com."), + origin_name_upper("EXAMPLE.COM"), + buffer_actual(0), buffer_expected(0) + {} + + const Name example_name; + Name example_name_upper; // this will be modified and cannot be const + const Name small_name; + const Name large_name; + const Name origin_name; + const Name origin_name_upper; + OutputBuffer buffer_actual, buffer_expected; + + // + // helper methods + // + static Name nameFactoryFromWire(const char* datafile, size_t position, + bool downcase = false); + // construct a name including all non-upper-case-alphabet characters. + static Name nameFactoryLowerCase(); + void compareInWireFormat(const Name& name_actual, + const Name& name_expected); +}; + +const Name downcased_global("\\255.EXAMPLE.COM", true); + +Name +NameTest::nameFactoryFromWire(const char* datafile, size_t position, + bool downcase) +{ + vector<unsigned char> data; + UnitTestUtil::readWireData(datafile, data); + + InputBuffer buffer(&data[0], data.size()); + buffer.setPosition(position); + + return (Name(buffer, downcase)); +} + +Name +NameTest::nameFactoryLowerCase() { + string lowercase_namestr; + lowercase_namestr.reserve(Name::MAX_WIRE); + + unsigned int ch = 0; + unsigned int labelcount = 0; + do { + if (ch < 'A' || ch > 'Z') { + ostringstream ss; + ss.setf(ios_base::right, ios_base::adjustfield); + ss.width(3); + ss << setfill('0') << ch; + lowercase_namestr += '\\' + ss.str(); + + if (++labelcount == Name::MAX_LABELLEN) { + lowercase_namestr.push_back('.'); + labelcount = 0; + } + } + } while (++ch <= Name::MAX_WIRE); + + return (Name(lowercase_namestr)); +} + +void +NameTest::compareInWireFormat(const Name& name_actual, + const Name& name_expected) +{ + buffer_actual.clear(); + buffer_expected.clear(); + + name_actual.toWire(buffer_actual); + name_expected.toWire(buffer_expected); + + matchWireData(buffer_expected.getData(), buffer_expected.getLength(), + buffer_actual.getData(), buffer_actual.getLength()); +} + +TEST_F(NameTest, nonlocalObject) { + // A previous version of code relied on a non local static object for + // name construction, so a non local static Name object defined outside + // the name module might not be initialized correctly. This test detects + // that kind of bug. + EXPECT_EQ("\\255.example.com.", downcased_global.toText()); +} + +template <typename ExceptionType> +void +checkBadTextName(const string& txt) { + // Check it results in the specified type of exception as well as + // NameParserException. + EXPECT_THROW(Name(txt, false), ExceptionType); + EXPECT_THROW(Name(txt, false), NameParserException); + // The same is thrown when constructing by the master-file constructor + EXPECT_THROW(Name(txt.c_str(), txt.length(), &Name::ROOT_NAME()), + ExceptionType); + EXPECT_THROW(Name(txt.c_str(), txt.length(), &Name::ROOT_NAME()), + NameParserException); +} + +TEST_F(NameTest, checkExceptionsHierarchy) { + EXPECT_NO_THROW({ + const isc::dns::EmptyLabel exception("", 0, ""); + const isc::dns::NameParserException& exception_cast = + dynamic_cast<const isc::dns::NameParserException&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::TooLongName exception("", 0, ""); + const isc::dns::NameParserException& exception_cast = + dynamic_cast<const isc::dns::NameParserException&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::TooLongLabel exception("", 0, ""); + const isc::dns::NameParserException& exception_cast = + dynamic_cast<const isc::dns::NameParserException&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::BadLabelType exception("", 0, ""); + const isc::dns::NameParserException& exception_cast = + dynamic_cast<const isc::dns::NameParserException&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::BadEscape exception("", 0, ""); + const isc::dns::NameParserException& exception_cast = + dynamic_cast<const isc::dns::NameParserException&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::IncompleteName exception("", 0, ""); + const isc::dns::NameParserException& exception_cast = + dynamic_cast<const isc::dns::NameParserException&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); + + EXPECT_NO_THROW({ + const isc::dns::MissingNameOrigin exception("", 0, ""); + const isc::dns::NameParserException& exception_cast = + dynamic_cast<const isc::dns::NameParserException&>(exception); + // to avoid compiler warning + exception_cast.what(); + }); +} + +TEST_F(NameTest, fromText) { + vector<string> strnames; + strnames.push_back("www.example.com"); + strnames.push_back("www.example.com."); // with a trailing dot + strnames.push_back("wWw.exAmpLe.com"); // mixed cases + strnames.push_back("\\wWw.exAmpLe.com"); // escape with a backslash + // decimal representation for "WWW" + strnames.push_back("\\087\\087\\087.example.com"); + + for (auto const& it : strnames) { + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name, Name(it)); + } + + // root names + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, Name("@"), Name(".")); + + // downcase + EXPECT_EQ(Name("Www.eXample.coM", true).toText(), example_name.toText()); + + // + // Tests for bogus names. These should trigger exceptions. + // + // empty label cannot be followed by another label + checkBadTextName<EmptyLabel>(".a"); + // duplicate period + checkBadTextName<EmptyLabel>("a.."); + // label length must be < 64 + checkBadTextName<TooLongLabel>("012345678901234567890123456789" + "012345678901234567890123456789" + "0123"); + // now-unsupported bitstring labels + checkBadTextName<BadLabelType>("\\[b11010000011101]"); + // label length must be < 64 + checkBadTextName<TooLongLabel>("012345678901234567890123456789" + "012345678901234567890123456789" + "012\\x"); + // but okay as long as resulting len < 64 even if the original string is + // "too long" + EXPECT_NO_THROW(Name("012345678901234567890123456789" + "012345678901234567890123456789" + "01\\x")); + // incomplete \DDD pattern (exactly 3 D's must appear) + checkBadTextName<BadEscape>("\\12abc"); + // \DDD must not exceed 255 + checkBadTextName<BadEscape>("\\256"); + // Same tests for \111 as for \\x above + checkBadTextName<TooLongLabel>("012345678901234567890123456789" + "012345678901234567890123456789" + "012\\111"); + EXPECT_NO_THROW(Name("012345678901234567890123456789" + "012345678901234567890123456789" + "01\\111")); + // A domain name must be 255 octets or less + checkBadTextName<TooLongName>("123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789." + "123456789.1234"); + // This is a possible longest name and should be accepted + EXPECT_NO_THROW(Name(string(max_len_str))); + // \DDD must consist of 3 digits. + checkBadTextName<IncompleteName>("\\12"); + + // a name with the max number of labels. should be constructed without + // an error, and its length should be the max value. + Name maxlabels = Name(string(max_labels_str)); + EXPECT_EQ(Name::MAX_LABELS, maxlabels.getLabelCount()); +} + +// The following test uses a name data that was produced by +// fuzz testing and causes an unexpected condition in stringParser. +// Formerly this condition was trapped by an assert, but for +// robustness it has been replaced by a throw. +TEST_F(NameTest, unexpectedParseError) { + std::vector<uint8_t> badname { + 0xff,0xff,0x7f,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x04,0x63,0x82,0x53,0x63,0x35,0x01,0x01,0x3d,0x07,0x01,0x00,0x00,0x00, + 0x00,0x00,0x00,0x19,0x0c,0x4e,0x01,0x00,0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x04,0x00,0x00,0x07,0x08,0x3b,0x04,0x00, + 0x00,0x2e,0x3b,0x04,0x00,0x19,0x2e,0x00,0x00,0x00,0x0a,0x00,0x12,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x04,0x00,0x00,0x07,0x08,0x3b,0x04, + 0x00,0x00,0x2e,0x3b,0x04,0x00,0x19,0x2e,0x56,0x00,0x00,0x0a,0x00,0x12,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x19,0x0c, + 0x4e,0x01,0x05,0x3a,0x04,0xde,0x00,0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x35,0x01,0x05,0x3a,0x07,0x08,0x3b,0x04,0x00,0x00,0x2e,0x3b,0x04, + 0x00,0x19,0x2e,0x56,0x40,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0x12,0x00,0x00,0x00, + 0x00,0x00,0x19,0x00,0x0b,0x82,0x01,0xfc,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xfc,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x35,0x01,0x05,0xff,0xff,0x05,0x00,0x07,0x08,0x3b,0x04, + 0x00,0x00,0x2e,0x3b + }; + + std::string badnamestr(badname.begin(), badname.end()); + EXPECT_THROW(Name(badnamestr, false), Unexpected); +} + +// on the rest while we prepare it. +// Check the @ syntax is accepted and it just copies the origin. +TEST_F(NameTest, copyOrigin) { + EXPECT_EQ(origin_name, Name("@", 1, &origin_name)); + // The downcase works on the origin too. But only when we provide it. + EXPECT_EQ(origin_name, Name("@", 1, &origin_name_upper, true)); + EXPECT_EQ(origin_name_upper, Name("@", 1, &origin_name_upper, true)); + // If we don't provide the origin, it throws + EXPECT_THROW(Name("@", 1, 0), MissingNameOrigin); +} + +// Test the master-file constructor does not append the origin when the +// provided name is absolute +TEST_F(NameTest, dontAppendOrigin) { + EXPECT_EQ(example_name, Name("www.example.com.", 16, &origin_name)); + // The downcase works (only if provided, though) + EXPECT_EQ(example_name, Name("WWW.EXAMPLE.COM.", 16, &origin_name, true)); + EXPECT_EQ(example_name_upper, Name("WWW.EXAMPLE.COM.", 16, &origin_name)); + // And it does not require the origin to be provided + EXPECT_NO_THROW(Name("www.example.com.", 16, 0)); +} + +// Test the master-file constructor properly appends the origin when +// the provided name is relative. +TEST_F(NameTest, appendOrigin) { + EXPECT_EQ(example_name, Name("www", 3, &origin_name)); + // Check the downcase works (if provided) + EXPECT_EQ(example_name, Name("WWW", 3, &origin_name, true)); + EXPECT_EQ(example_name, Name("WWW", 3, &origin_name_upper, true)); + EXPECT_EQ(example_name_upper, Name("WWW", 3, &origin_name_upper)); + // Check we can prepend more than one label + EXPECT_EQ(Name("a.b.c.d.example.com."), Name("a.b.c.d", 7, &origin_name)); + // When the name is relative, we throw. + EXPECT_THROW(Name("www", 3, 0), MissingNameOrigin); +} + +// When we don't provide the data, it throws +TEST_F(NameTest, noDataProvided) { + EXPECT_THROW(Name(0, 10, 0), isc::InvalidParameter); + EXPECT_THROW(Name(0, 10, &origin_name), isc::InvalidParameter); + EXPECT_THROW(Name("www", 0, 0), isc::InvalidParameter); + EXPECT_THROW(Name("www", 0, &origin_name), isc::InvalidParameter); +} + +// When we combine the first part and the origin together, the resulting name +// is too long. It should throw. Other test checks this is valid when alone +// (without the origin appended). +TEST_F(NameTest, combinedTooLong) { + EXPECT_THROW(Name(max_len_str, strlen(max_len_str), &origin_name), + TooLongName); + EXPECT_THROW(Name(max_labels_str, strlen(max_labels_str), &origin_name), + TooLongName); + // Appending the root should be OK + EXPECT_NO_THROW(Name(max_len_str, strlen(max_len_str), + &Name::ROOT_NAME())); + EXPECT_NO_THROW(Name(max_labels_str, strlen(max_labels_str), + &Name::ROOT_NAME())); +} + +// Test the handling of @ in the name. If it is alone, it is the origin (when +// it exists) or the root. If it is somewhere else, it has no special meaning. +TEST_F(NameTest, atSign) { + // If it is alone, it is the origin + EXPECT_EQ(origin_name, Name("@", 1, &origin_name)); + EXPECT_THROW(Name("@", 1, 0), MissingNameOrigin); + EXPECT_EQ(Name::ROOT_NAME(), Name("@")); + + // It is not alone. It is taken verbatim. We check the name converted + // back to the textual form, since checking it against other name object + // may be wrong -- if we create it wrong the same way as the tested + // object. + EXPECT_EQ("\\@.", Name("@.").toText()); + EXPECT_EQ("\\@.", Name("@.", 2, 0).toText()); + EXPECT_EQ("\\@something.", Name("@something").toText()); + EXPECT_EQ("something\\@.", Name("something@").toText()); + EXPECT_EQ("\\@x.example.com.", Name("@x", 2, &origin_name).toText()); + EXPECT_EQ("x\\@.example.com.", Name("x@", 2, &origin_name).toText()); + + // An escaped at-sign isn't active + EXPECT_EQ("\\@.", Name("\\@").toText()); + EXPECT_EQ("\\@.example.com.", Name("\\@", 2, &origin_name).toText()); +} + +TEST_F(NameTest, fromWire) { + // + // test cases derived from BIND9 tests. + // + // normal case with a compression pointer + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, + nameFactoryFromWire("name_fromWire1", 25), + Name("vix.com")); + // bogus label character (looks like a local compression pointer) + EXPECT_THROW(nameFactoryFromWire("name_fromWire2", 25), DNSMessageFORMERR); + // a bad compression pointer (too big) + EXPECT_THROW(nameFactoryFromWire("name_fromWire3_1", 25), + DNSMessageFORMERR); + // forward reference + EXPECT_THROW(nameFactoryFromWire("name_fromWire3_2", 25), + DNSMessageFORMERR); + // invalid name length + EXPECT_THROW(nameFactoryFromWire("name_fromWire4", 550), DNSMessageFORMERR); + + // skip test for from Wire5. It's for disabling decompression, but our + // implementation always allows it. + + // bad pointer (too big) + EXPECT_THROW(nameFactoryFromWire("name_fromWire6", 25), DNSMessageFORMERR); + // input ends unexpectedly + EXPECT_THROW(nameFactoryFromWire("name_fromWire7", 25), DNSMessageFORMERR); + // many hops of compression but valid. should succeed. + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, + nameFactoryFromWire("name_fromWire8", 383), + Name("vix.com")); + + // + // Additional test cases + // + + // large names, a long but valid one, and invalid (too long) one. + EXPECT_EQ(Name::MAX_WIRE, + nameFactoryFromWire("name_fromWire9", 0).getLength()); + EXPECT_THROW(nameFactoryFromWire("name_fromWire10", 0).getLength(), + DNSMessageFORMERR); + + // A name with possible maximum number of labels; awkward but valid + EXPECT_EQ(nameFactoryFromWire("name_fromWire11", 0).getLabelCount(), + Name::MAX_LABELS); + + // Wire format including an invalid label length + EXPECT_THROW(nameFactoryFromWire("name_fromWire12", 0), DNSMessageFORMERR); + + // converting upper-case letters to down-case + EXPECT_EQ("vix.com.", + nameFactoryFromWire("name_fromWire1", 25, true).toText()); + EXPECT_EQ(3, nameFactoryFromWire("name_fromWire1", 25).getLabelCount()); +} + +TEST_F(NameTest, copyConstruct) { + Name copy(example_name); + EXPECT_EQ(copy, example_name); + + // Check the copied data is valid even after the original is deleted + Name* copy2 = new Name(example_name); + Name copy3(*copy2); + delete copy2; + EXPECT_EQ(copy3, example_name); +} + +TEST_F(NameTest, assignment) { + Name copy("."); + copy = example_name; + EXPECT_EQ(copy, example_name); + + // Check if the copied data is valid even after the original is deleted + Name* copy2 = new Name(example_name); + Name copy3("."); + copy3 = *copy2; + delete copy2; + EXPECT_EQ(copy3, example_name); + + // Self assignment + copy = *© + EXPECT_EQ(example_name, copy); +} + +TEST_F(NameTest, toText) { + // tests derived from BIND9 + EXPECT_EQ("a.b.c.d", Name("a.b.c.d").toText(true)); + EXPECT_EQ("a.\\\\[[.c.d", Name("a.\\\\[\\[.c.d").toText(true)); + EXPECT_EQ("a.b.C.d.", Name("a.b.C.d").toText(false)); + EXPECT_EQ("a.b.", Name("a.b.").toText(false)); + + // test omit_final_dot. It's false by default. + EXPECT_EQ("a.b.c.d", Name("a.b.c.d.").toText(true)); + EXPECT_EQ(Name("a.b.").toText(false), Name("a.b.").toText()); + + // the root name is a special case: omit_final_dot will be ignored. + EXPECT_EQ(".", Name(".").toText(true)); + + // test all printable characters to see whether special characters are + // escaped while the others are intact. note that the conversion is + // implementation specific; for example, it's not invalid to escape a + // "normal" character such as 'a' with regard to the standard. + string all_printable("!\\\"#\\$%&'\\(\\)*+,-\\./0123456789:\\;<=>?\\@" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "[\\\\]^_.`abcdefghijklmnopqrstuvwxyz{|}~."); + EXPECT_EQ(all_printable, + nameFactoryFromWire("name_fromWire13", 0).toText()); + + string all_nonprintable( + "\\000\\001\\002\\003\\004\\005\\006\\007\\008\\009" + "\\010\\011\\012\\013\\014\\015\\016\\017\\018\\019" + "\\020\\021\\022\\023\\024\\025\\026\\027\\028\\029" + "\\030\\031\\032\\127\\128\\129" + "\\130\\131\\132\\133\\134\\135\\136\\137\\138\\139" + "\\140\\141\\142\\143\\144\\145\\146\\147\\148\\149" + "\\150\\151\\152\\153\\154\\155\\156." + "\\157\\158\\159" + "\\160\\161\\162\\163\\164\\165\\166\\167\\168\\169" + "\\170\\171\\172\\173\\174\\175\\176\\177\\178\\179" + "\\180\\181\\182\\183\\184\\185\\186\\187\\188\\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\\244\\245\\246\\247\\248\\249" + "\\250\\251\\252\\253\\254\\255."); + EXPECT_EQ(all_nonprintable, + nameFactoryFromWire("name_fromWire14", 0).toText()); +} + +TEST_F(NameTest, toWireBuffer) { + vector<unsigned char> data; + OutputBuffer buffer(0); + + UnitTestUtil::readWireData(string("01610376697803636f6d00"), data); + Name("a.vix.com.").toWire(buffer); + matchWireData(&data[0], data.size(), + buffer.getData(), buffer.getLength()); +} + +// +// We test various corner cases in Renderer tests, but add this test case +// to fill the code coverage gap. +// +TEST_F(NameTest, toWireRenderer) { + vector<unsigned char> data; + MessageRenderer renderer; + + UnitTestUtil::readWireData(string("01610376697803636f6d00"), data); + Name("a.vix.com.").toWire(renderer); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +// +// Helper class to hold comparison test parameters. +// +struct CompareParameters { + CompareParameters(const Name& n1, const Name& n2, + NameComparisonResult::NameRelation r, int o, + unsigned int l) : + name1(n1), name2(n2), reln(r), order(o), labels(l) {} + static int normalizeOrder(int o) + { + if (o > 0) { + return (1); + } else if (o < 0) { + return (-1); + } + return (0); + } + Name name1; + Name name2; + NameComparisonResult::NameRelation reln; + int order; + unsigned int labels; +}; + +TEST_F(NameTest, compare) { + vector<CompareParameters> params; + params.push_back(CompareParameters(Name("c.d"), Name("a.b.c.d"), + NameComparisonResult::SUPERDOMAIN, + -1, 3)); + params.push_back(CompareParameters(Name("a.b.c.d"), Name("c.d"), + NameComparisonResult::SUBDOMAIN, 1, 3)); + params.push_back(CompareParameters(Name("a.b.c.d"), Name("c.d.e.f"), + NameComparisonResult::COMMONANCESTOR, + -1, 1)); + params.push_back(CompareParameters(Name("a.b.c.d"), Name("f.g.c.d"), + NameComparisonResult::COMMONANCESTOR, + -1, 3)); + params.push_back(CompareParameters(Name("a.b.c.d"), Name("A.b.C.d."), + NameComparisonResult::EQUAL, + 0, 5)); + + for (auto const& it : params) { + NameComparisonResult result = it.name1.compare(it.name2); + EXPECT_EQ(it.reln, result.getRelation()); + EXPECT_EQ(it.order, CompareParameters::normalizeOrder(result.getOrder())); + EXPECT_EQ(it.labels, result.getCommonLabels()); + } +} + +TEST_F(NameTest, equal) { + EXPECT_TRUE(example_name == Name("WWW.EXAMPLE.COM.")); + EXPECT_TRUE(example_name.equals(Name("WWW.EXAMPLE.COM."))); + EXPECT_TRUE(example_name != Name("www.example.org.")); + EXPECT_TRUE(example_name.nequals(Name("www.example.org."))); + // lengths don't match + EXPECT_TRUE(example_name != Name("www2.example.com.")); + EXPECT_TRUE(example_name.nequals(Name("www2.example.com."))); + // lengths are equal, but # of labels don't match (first test checks the + // prerequisite). + EXPECT_EQ(example_name.getLength(), Name("www\\.example.com.").getLength()); + EXPECT_TRUE(example_name != Name("www\\.example.com.")); + EXPECT_TRUE(example_name.nequals(Name("www\\.example.com."))); +} + +TEST_F(NameTest, isWildcard) { + EXPECT_FALSE(example_name.isWildcard()); + EXPECT_TRUE(Name("*.a.example.com").isWildcard()); + EXPECT_FALSE(Name("a.*.example.com").isWildcard()); +} + +TEST_F(NameTest, concatenate) { + NameComparisonResult result = + Name("aaa.www.example.com.").compare(Name("aaa").concatenate(example_name)); + EXPECT_EQ(NameComparisonResult::EQUAL, result.getRelation()); + + result = example_name.compare(Name(".").concatenate(example_name)); + EXPECT_EQ(NameComparisonResult::EQUAL, result.getRelation()); + + result = example_name.compare(example_name.concatenate(Name("."))); + EXPECT_EQ(NameComparisonResult::EQUAL, result.getRelation()); + + // concatenating two valid names would result in too long a name. + Name n1("123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789."); + Name n2("123456789.123456789.123456789.123456789.123456789." + "123456789.123456789.123456789.123456789.123456789." + "1234."); + EXPECT_THROW(n1.concatenate(n2), TooLongName); +} + +TEST_F(NameTest, reverse) { + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.reverse(), + Name("com.example.www.")); + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, Name(".").reverse(), + Name(".")); + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, + Name("a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s").reverse(), + Name("s.r.q.p.o.n.m.l.k.j.i.h.g.f.e.d.c.b.a")); +} + +TEST_F(NameTest, split) { + // normal cases with or without explicitly specifying the trailing dot. + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(1, 2), + Name("example.com.")); + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(1, 3), + Name("example.com.")); + // edge cases: only the first or last label. + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(0, 1), + Name("www.")); + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(3, 1), + Name(".")); + // invalid range: an exception should be thrown. + EXPECT_THROW(example_name.split(1, 0), OutOfRange); + EXPECT_THROW(example_name.split(2, 3), OutOfRange); + + // invalid range: the following parameters would cause overflow, + // bypassing naive validation. + EXPECT_THROW(example_name.split(1, numeric_limits<unsigned int>::max()), + OutOfRange); +} + +TEST_F(NameTest, split_for_suffix) { + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(1), + Name("example.com")); + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(0), + example_name); + EXPECT_PRED_FORMAT2(UnitTestUtil::matchName, example_name.split(3), + Name(".")); + + // Invalid case: the level must be less than the original label count. + EXPECT_THROW(example_name.split(4), OutOfRange); +} + +TEST_F(NameTest, downcase) { + // usual case: all-upper case name to all-lower case + compareInWireFormat(example_name_upper.downcase(), example_name); + // confirm that non upper-case characters are intact + compareInWireFormat(nameFactoryLowerCase().downcase(), + nameFactoryLowerCase()); + // confirm the calling object is actually modified + example_name_upper.downcase(); + compareInWireFormat(example_name_upper, example_name); +} + +TEST_F(NameTest, at) { + // Confirm at() produces the exact sequence of wire-format name data + vector<uint8_t> data; + + for (size_t i = 0; i < example_name.getLength(); i++) { + data.push_back(example_name.at(i)); + } + + example_name.toWire(buffer_expected); + matchWireData(&data[0], data.size(), + buffer_expected.getData(), buffer_expected.getLength()); + + // Out-of-range access: should trigger an exception. + EXPECT_THROW(example_name.at(example_name.getLength()), OutOfRange); +} + +// +// The following set of tests confirm the result of <=, <, >=, > +// The test logic is simple, and all tests are just straightforward variations +// of the first one. +// +TEST_F(NameTest, leq) { + // small <= large is true + EXPECT_TRUE(small_name.leq(large_name)); + EXPECT_TRUE(small_name <= large_name); + + // small <= small is true + EXPECT_TRUE(small_name.leq(small_name)); + EXPECT_LE(small_name, small_name); + + // large <= small is false + EXPECT_FALSE(large_name.leq(small_name)); + EXPECT_FALSE(large_name <= small_name); +} + +TEST_F(NameTest, geq) { + EXPECT_TRUE(large_name.geq(small_name)); + EXPECT_TRUE(large_name >= small_name); + + EXPECT_TRUE(large_name.geq(large_name)); + EXPECT_GE(large_name, large_name); + + EXPECT_FALSE(small_name.geq(large_name)); + EXPECT_FALSE(small_name >= large_name); +} + +TEST_F(NameTest, lthan) { + EXPECT_TRUE(small_name.lthan(large_name)); + EXPECT_TRUE(small_name < large_name); + + EXPECT_FALSE(small_name.lthan(small_name)); + // cppcheck-suppress duplicateExpression + EXPECT_FALSE(small_name < small_name); + + EXPECT_FALSE(large_name.lthan(small_name)); + EXPECT_FALSE(large_name < small_name); +} + +TEST_F(NameTest, gthan) { + EXPECT_TRUE(large_name.gthan(small_name)); + EXPECT_TRUE(large_name > small_name); + + EXPECT_FALSE(large_name.gthan(large_name)); + // cppcheck-suppress duplicateExpression + EXPECT_FALSE(large_name > large_name); + + EXPECT_FALSE(small_name.gthan(large_name)); + EXPECT_FALSE(small_name > large_name); +} + +TEST_F(NameTest, constants) { + EXPECT_EQ(Name("."), Name::ROOT_NAME()); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(NameTest, LeftShiftOperator) { + ostringstream oss; + oss << example_name; + EXPECT_EQ(example_name.toText(), oss.str()); +} + +// The following verifies that toRawText() returns a string +// actual characters in place of escape sequences. We do not +// bother with an exhaustive set of tests here as this is +// not a primary use case. +TEST_F(NameTest, toRawText) { + Name n("a bc.$exa(m)ple.@org"); + EXPECT_EQ("a bc.$exa(m)ple.@org", n.toRawText(true)); + EXPECT_EQ("a bc.$exa(m)ple.@org.", n.toRawText(false)); + // Verify default value of omit parameter is false. + EXPECT_EQ("a bc.$exa(m)ple.@org.", n.toRawText()); +} + +} diff --git a/src/lib/dns/tests/opcode_unittest.cc b/src/lib/dns/tests/opcode_unittest.cc new file mode 100644 index 0000000..f45b89e --- /dev/null +++ b/src/lib/dns/tests/opcode_unittest.cc @@ -0,0 +1,100 @@ +// Copyright (C) 2010-2024 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 <vector> +#include <sstream> + +#include <exceptions/exceptions.h> + +#include <dns/opcode.h> + +#include <gtest/gtest.h> + +using namespace std; +using namespace isc::dns; + +namespace { +TEST(OpcodeTest, construct) { + // This test also tests getCode() + EXPECT_EQ(0, Opcode(0).getCode()); + EXPECT_EQ(15, Opcode(Opcode::RESERVED15_CODE).getCode()); + + EXPECT_THROW(Opcode(16), isc::OutOfRange); +} + +TEST(OpcodeTest, constants) { + // We'll only test arbitrarily chosen subsets of the codes. + // This class is quite simple, so it should be suffice. + + EXPECT_EQ(Opcode::QUERY_CODE, Opcode(0).getCode()); + EXPECT_EQ(Opcode::IQUERY_CODE, Opcode(1).getCode()); + EXPECT_EQ(Opcode::NOTIFY_CODE, Opcode(4).getCode()); + EXPECT_EQ(Opcode::UPDATE_CODE, Opcode(5).getCode()); + EXPECT_EQ(Opcode::RESERVED15_CODE, Opcode(15).getCode()); + + EXPECT_EQ(Opcode::QUERY_CODE, Opcode::QUERY().getCode()); + EXPECT_EQ(Opcode::IQUERY_CODE, Opcode::IQUERY().getCode()); + EXPECT_EQ(Opcode::NOTIFY_CODE, Opcode::NOTIFY().getCode()); + EXPECT_EQ(Opcode::UPDATE_CODE, Opcode::UPDATE().getCode()); + EXPECT_EQ(Opcode::RESERVED15_CODE, Opcode::RESERVED15().getCode()); +} + +TEST(OpcodeTest, equal) { + EXPECT_TRUE(Opcode::QUERY() == Opcode(Opcode::QUERY_CODE)); + EXPECT_TRUE(Opcode::QUERY().equals(Opcode(Opcode::QUERY_CODE))); + EXPECT_TRUE(Opcode::IQUERY() == Opcode(Opcode::IQUERY_CODE)); + EXPECT_TRUE(Opcode::IQUERY().equals(Opcode(Opcode::IQUERY_CODE))); + EXPECT_TRUE(Opcode::NOTIFY() == Opcode(Opcode::NOTIFY_CODE)); + EXPECT_TRUE(Opcode::NOTIFY().equals(Opcode(Opcode::NOTIFY_CODE))); + EXPECT_TRUE(Opcode::UPDATE() == Opcode(Opcode::UPDATE_CODE)); + EXPECT_TRUE(Opcode::UPDATE().equals(Opcode(Opcode::UPDATE_CODE))); + EXPECT_TRUE(Opcode::RESERVED15() == Opcode(Opcode::RESERVED15())); + EXPECT_TRUE(Opcode::RESERVED15().equals(Opcode(Opcode::RESERVED15()))); +} + +TEST(OpcodeTest, nequal) { + EXPECT_TRUE(Opcode::QUERY() != Opcode::IQUERY()); + EXPECT_TRUE(Opcode::QUERY().nequals(Opcode::IQUERY())); + EXPECT_TRUE(Opcode::NOTIFY() != Opcode(1)); + EXPECT_TRUE(Opcode::NOTIFY().nequals(Opcode(1))); + EXPECT_TRUE(Opcode(10) != Opcode(11)); + EXPECT_TRUE(Opcode(10).nequals(Opcode(11))); +} + +TEST(OpcodeTest, toText) { + vector<const char*> expects; + expects.resize(Opcode::RESERVED15_CODE + 1); + expects[Opcode::QUERY_CODE] = "QUERY"; + expects[Opcode::IQUERY_CODE] = "IQUERY"; + expects[Opcode::STATUS_CODE] = "STATUS"; + expects[Opcode::RESERVED3_CODE] = "RESERVED3"; + expects[Opcode::NOTIFY_CODE] = "NOTIFY"; + expects[Opcode::UPDATE_CODE] = "UPDATE"; + expects[Opcode::RESERVED6_CODE] = "RESERVED6"; + expects[Opcode::RESERVED7_CODE] = "RESERVED7"; + expects[Opcode::RESERVED8_CODE] = "RESERVED8"; + expects[Opcode::RESERVED9_CODE] = "RESERVED9"; + expects[Opcode::RESERVED10_CODE] = "RESERVED10"; + expects[Opcode::RESERVED11_CODE] = "RESERVED11"; + expects[Opcode::RESERVED12_CODE] = "RESERVED12"; + expects[Opcode::RESERVED13_CODE] = "RESERVED13"; + expects[Opcode::RESERVED14_CODE] = "RESERVED14"; + expects[Opcode::RESERVED15_CODE] = "RESERVED15"; + + for (unsigned int i = 0; i <= Opcode::RESERVED15_CODE; ++i) { + EXPECT_EQ(expects.at(i), Opcode(i).toText()); + } +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST(OpcodeTest, LeftShiftOperator) { + ostringstream oss; + oss << Opcode::NOTIFY(); + EXPECT_EQ(Opcode::NOTIFY().toText(), oss.str()); +} +} diff --git a/src/lib/dns/tests/question_unittest.cc b/src/lib/dns/tests/question_unittest.cc new file mode 100644 index 0000000..443d900 --- /dev/null +++ b/src/lib/dns/tests/question_unittest.cc @@ -0,0 +1,198 @@ +// Copyright (C) 2010-2024 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 <vector> +#include <sstream> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> +#include <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/question.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { +class QuestionTest : public ::testing::Test { +protected: + QuestionTest() : obuffer(0), + example_name1(Name("foo.example.com")), + example_name2(Name("bar.example.com")), + test_question1(example_name1, RRClass::IN(), + RRType::NS()), + test_question2(example_name2, RRClass::CH(), + RRType::A()) { + } + OutputBuffer obuffer; + MessageRenderer renderer; + Name example_name1; + Name example_name2; + Question test_question1; + Question test_question2; + vector<unsigned char> wiredata; +}; + +Question +questionFromWire(const char* datafile, size_t position = 0) { + vector<unsigned char> data; + UnitTestUtil::readWireData(datafile, data); + + InputBuffer buffer(&data[0], data.size()); + buffer.setPosition(position); + + return (Question(buffer)); +} + +TEST_F(QuestionTest, fromWire) { + Question q = questionFromWire("question_fromWire"); + + EXPECT_EQ(example_name1, q.getName()); + EXPECT_EQ(RRClass::IN(), q.getClass()); + EXPECT_EQ(RRType::NS(), q.getType()); + + // owner name of the second Question is compressed. It's uncommon + // (to have multiple questions), but isn't prohibited by the protocol. + q = questionFromWire("question_fromWire", 21); + EXPECT_EQ(example_name2, q.getName()); + EXPECT_EQ(RRClass::CH(), q.getClass()); + EXPECT_EQ(RRType::A(), q.getType()); + + // Pathological cases: Corresponding exceptions will be thrown from + // the underlying parser. + EXPECT_THROW(questionFromWire("question_fromWire", 31), DNSMessageFORMERR); + EXPECT_THROW(questionFromWire("question_fromWire", 36), IncompleteRRClass); +} + +TEST_F(QuestionTest, toText) { + EXPECT_EQ("foo.example.com. IN NS", test_question1.toText()); + EXPECT_EQ("bar.example.com. CH A", test_question2.toText()); + + EXPECT_EQ("foo.example.com. IN NS", test_question1.toText(false)); + EXPECT_EQ("bar.example.com. CH A", test_question2.toText(false)); + + EXPECT_EQ("foo.example.com. IN NS\n", test_question1.toText(true)); + EXPECT_EQ("bar.example.com. CH A\n", test_question2.toText(true)); +} + +TEST_F(QuestionTest, toWireBuffer) { + test_question1.toWire(obuffer); + test_question2.toWire(obuffer); + UnitTestUtil::readWireData("question_toWire1", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(QuestionTest, toWireRenderer) { + test_question1.toWire(renderer); + test_question2.toWire(renderer); + UnitTestUtil::readWireData("question_toWire2", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(QuestionTest, toWireTruncated) { + // If the available length in the renderer is too small, it would require + // truncation. This won't happen in normal cases, but protocol wise it + // could still happen if and when we support some (possibly future) opcode + // that allows multiple questions. + + // Set the length limit to the qname length so that the whole question + // would request truncated + renderer.setLengthLimit(example_name1.getLength()); + + EXPECT_FALSE(renderer.isTruncated()); // check pre-render condition + EXPECT_EQ(0, test_question1.toWire(renderer)); + EXPECT_TRUE(renderer.isTruncated()); + EXPECT_EQ(0, renderer.getLength()); // renderer shouldn't have any data +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(QuestionTest, LeftShiftOperator) { + ostringstream oss; + oss << test_question1; + EXPECT_EQ(test_question1.toText(), oss.str()); +} + +TEST_F(QuestionTest, comparison) { + const Name a("a"); + const Name b("b"); + const RRClass in(RRClass::IN()); + const RRClass ch(RRClass::CH()); + const RRType ns(RRType::NS()); + const RRType aaaa(RRType::AAAA()); + + EXPECT_TRUE(Question(a, in, ns) < Question(a, in, aaaa)); + EXPECT_FALSE(Question(a, in, aaaa) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(a, ch, ns)); + EXPECT_FALSE(Question(a, ch, ns) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(a, ch, aaaa)); + EXPECT_FALSE(Question(a, ch, aaaa) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(b, in, ns)); + EXPECT_FALSE(Question(a, in, ns) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(b, ch, ns)); + EXPECT_FALSE(Question(b, ch, ns) < Question(a, in, ns)); + + EXPECT_TRUE(Question(a, in, ns) < Question(b, ch, aaaa)); + EXPECT_FALSE(Question(b, ch, aaaa) < Question(a, in, ns)); + + EXPECT_FALSE(Question(a, in, ns) < Question(a, in, ns)); + EXPECT_FALSE(Question(a, ch, ns) < Question(a, ch, ns)); + EXPECT_FALSE(Question(b, in, ns) < Question(b, in, ns)); + EXPECT_FALSE(Question(b, in, aaaa) < Question(b, in, aaaa)); + + // Identical questions are equal + + EXPECT_TRUE(Question(a, in, ns) == Question(a, in, ns)); + EXPECT_FALSE(Question(a, in, ns) != Question(a, in, ns)); + + // Components differing by one component are unequal... + + EXPECT_FALSE(Question(b, in, ns) == Question(a, in, ns)); + EXPECT_TRUE(Question(b, in, ns) != Question(a, in, ns)); + + EXPECT_FALSE(Question(a, ch, ns) == Question(a, in, ns)); + EXPECT_TRUE(Question(a, ch, ns) != Question(a, in, ns)); + + EXPECT_FALSE(Question(a, in, aaaa) == Question(a, in, ns)); + EXPECT_TRUE(Question(a, in, aaaa) != Question(a, in, ns)); + + // ... as are those differing by two components + + EXPECT_FALSE(Question(b, ch, ns) == Question(a, in, ns)); + EXPECT_TRUE(Question(b, ch, ns) != Question(a, in, ns)); + + EXPECT_FALSE(Question(b, in, aaaa) == Question(a, in, ns)); + EXPECT_TRUE(Question(b, in, aaaa) != Question(a, in, ns)); + + EXPECT_FALSE(Question(a, ch, aaaa) == Question(a, in, ns)); + EXPECT_TRUE(Question(a, ch, aaaa) != Question(a, in, ns)); + + // ... and question differing by all three + + EXPECT_FALSE(Question(b, ch, aaaa) == Question(a, in, ns)); + EXPECT_TRUE(Question(b, ch, aaaa) != Question(a, in, ns)); +} + +} diff --git a/src/lib/dns/tests/rcode_unittest.cc b/src/lib/dns/tests/rcode_unittest.cc new file mode 100644 index 0000000..7f7a3c8 --- /dev/null +++ b/src/lib/dns/tests/rcode_unittest.cc @@ -0,0 +1,126 @@ +// Copyright (C) 2010-2024 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 <vector> +#include <sstream> + +#include <exceptions/exceptions.h> + +#include <dns/rcode.h> + +#include <gtest/gtest.h> + +using namespace std; +using namespace isc::dns; + +namespace { +TEST(RcodeTest, constructFromCode) { + // Normal cases. This test also tests getCode() + EXPECT_EQ(0, Rcode(0).getCode()); + EXPECT_EQ(0xfff, Rcode(0xfff).getCode()); // possible max code + + // should fail on attempt of construction with an out of range code + EXPECT_THROW(Rcode(0x1000), isc::OutOfRange); + EXPECT_THROW(Rcode(0xffff), isc::OutOfRange); +} + +TEST(RcodeTest, constructFromCodePair) { + EXPECT_EQ(3, Rcode(Rcode::NXDOMAIN_CODE, 0).getCode()); + EXPECT_EQ(Rcode::BADVERS_CODE, Rcode(0, 1).getCode()); + EXPECT_EQ(0xfff, Rcode(0xf, 0xff).getCode()); + EXPECT_THROW(Rcode(0x10, 0xff), isc::OutOfRange); +} + +TEST(RcodeTest, getExtendedCode) { + EXPECT_EQ(0, Rcode::NOERROR().getExtendedCode()); + EXPECT_EQ(0, Rcode::YXRRSET().getExtendedCode()); + EXPECT_EQ(1, Rcode::BADVERS().getExtendedCode()); + EXPECT_EQ(0xab, Rcode(0xabf).getExtendedCode()); + EXPECT_EQ(0xff, Rcode(0xfff).getExtendedCode()); +} + +TEST(RcodeTest, constants) { + // We'll only test arbitrarily chosen subsets of the codes. + // This class is quite simple, so it should be suffice. + + EXPECT_EQ(Rcode::NOERROR_CODE, Rcode(0).getCode()); + EXPECT_EQ(Rcode::FORMERR_CODE, Rcode(1).getCode()); + EXPECT_EQ(Rcode::NOTIMP_CODE, Rcode(4).getCode()); + EXPECT_EQ(Rcode::REFUSED_CODE, Rcode(5).getCode()); + EXPECT_EQ(Rcode::RESERVED15_CODE, Rcode(15).getCode()); + EXPECT_EQ(Rcode::BADVERS_CODE, Rcode(16).getCode()); + + EXPECT_EQ(Rcode::NOERROR_CODE, Rcode::NOERROR().getCode()); + EXPECT_EQ(Rcode::FORMERR_CODE, Rcode::FORMERR().getCode()); + EXPECT_EQ(Rcode::NOTIMP_CODE, Rcode::NOTIMP().getCode()); + EXPECT_EQ(Rcode::REFUSED_CODE, Rcode::REFUSED().getCode()); + EXPECT_EQ(Rcode::RESERVED15_CODE, Rcode::RESERVED15().getCode()); + EXPECT_EQ(Rcode::BADVERS_CODE, Rcode::BADVERS().getCode()); +} + +TEST(RcodeTest, equal) { + EXPECT_TRUE(Rcode::NOERROR() == Rcode(Rcode::NOERROR_CODE)); + EXPECT_TRUE(Rcode::NOERROR().equals(Rcode(Rcode::NOERROR_CODE))); + EXPECT_TRUE(Rcode::FORMERR() == Rcode(Rcode::FORMERR_CODE)); + EXPECT_TRUE(Rcode::FORMERR().equals(Rcode(Rcode::FORMERR_CODE))); + EXPECT_TRUE(Rcode::NOTIMP() == Rcode(Rcode::NOTIMP_CODE)); + EXPECT_TRUE(Rcode::NOTIMP().equals(Rcode(Rcode::NOTIMP_CODE))); + EXPECT_TRUE(Rcode::REFUSED() == Rcode(Rcode::REFUSED_CODE)); + EXPECT_TRUE(Rcode::REFUSED().equals(Rcode(Rcode::REFUSED_CODE))); + EXPECT_TRUE(Rcode::RESERVED15() == Rcode(Rcode::RESERVED15())); + EXPECT_TRUE(Rcode::RESERVED15().equals(Rcode(Rcode::RESERVED15()))); + EXPECT_TRUE(Rcode::BADVERS() == Rcode(Rcode::BADVERS_CODE)); + EXPECT_TRUE(Rcode::BADVERS().equals(Rcode(Rcode::BADVERS_CODE))); +} + +TEST(RcodeTest, nequal) { + EXPECT_TRUE(Rcode::NOERROR() != Rcode::FORMERR()); + EXPECT_TRUE(Rcode::NOERROR().nequals(Rcode::FORMERR())); + EXPECT_TRUE(Rcode::NOTIMP() != Rcode(1)); + EXPECT_TRUE(Rcode::NOTIMP().nequals(Rcode(1))); + EXPECT_TRUE(Rcode(10) != Rcode(11)); + EXPECT_TRUE(Rcode(10).nequals(Rcode(11))); +} + +TEST(RcodeTest, toText) { + vector<const char*> expects; + expects.resize(Rcode::BADVERS_CODE + 1); + expects[Rcode::NOERROR_CODE] = "NOERROR"; + expects[Rcode::FORMERR_CODE] = "FORMERR"; + expects[Rcode::SERVFAIL_CODE] = "SERVFAIL"; + expects[Rcode::NXDOMAIN_CODE] = "NXDOMAIN"; + expects[Rcode::NOTIMP_CODE] = "NOTIMP"; + expects[Rcode::REFUSED_CODE] = "REFUSED"; + expects[Rcode::YXDOMAIN_CODE] = "YXDOMAIN"; + expects[Rcode::YXRRSET_CODE] = "YXRRSET"; + expects[Rcode::NXRRSET_CODE] = "NXRRSET"; + expects[Rcode::NOTAUTH_CODE] = "NOTAUTH"; + expects[Rcode::NOTZONE_CODE] = "NOTZONE"; + expects[Rcode::RESERVED11_CODE] = "RESERVED11"; + expects[Rcode::RESERVED12_CODE] = "RESERVED12"; + expects[Rcode::RESERVED13_CODE] = "RESERVED13"; + expects[Rcode::RESERVED14_CODE] = "RESERVED14"; + expects[Rcode::RESERVED15_CODE] = "RESERVED15"; + expects[Rcode::BADVERS_CODE] = "BADVERS"; + + for (unsigned int i = 0; i <= Rcode::BADVERS_CODE; ++i) { + EXPECT_EQ(expects.at(i), Rcode(i).toText()); + } + + // Non well-known Rcodes + EXPECT_EQ("17", Rcode(Rcode::BADVERS().getCode() + 1).toText()); + EXPECT_EQ("4095", Rcode(Rcode(0xfff)).toText()); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST(RcodeTest, LeftShiftOperator) { + ostringstream oss; + oss << Rcode::SERVFAIL(); + EXPECT_EQ(Rcode::SERVFAIL().toText(), oss.str()); +} +} diff --git a/src/lib/dns/tests/rdata_char_string_data_unittest.cc b/src/lib/dns/tests/rdata_char_string_data_unittest.cc new file mode 100644 index 0000000..22cf1a4 --- /dev/null +++ b/src/lib/dns/tests/rdata_char_string_data_unittest.cc @@ -0,0 +1,181 @@ +// Copyright (C) 2014-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <util/unittests/wiredata.h> + +#include <dns/exceptions.h> +#include <dns/rdata.h> +#include <dns/char_string.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +#include <string> +#include <vector> + +using namespace isc::dns; +using namespace isc::dns::rdata; +using isc::dns::rdata::generic::detail::CharStringData; +using isc::dns::rdata::generic::detail::stringToCharStringData; +using isc::dns::rdata::generic::detail::charStringDataToString; +using isc::dns::rdata::generic::detail::compareCharStringDatas; +using isc::util::unittests::matchWireData; + +namespace { +const uint8_t test_charstr[] = { + 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g' +}; + +class CharStringDataTest : public ::testing::Test { +protected: + CharStringDataTest() : + // char-string representation for test data using two types of escape + // ('r' = 114) + test_str("Test\\ St\\114ing") + { + str_region.beg = &test_str[0]; + str_region.len = test_str.size(); + } + CharStringData chstr; // place holder + const std::string test_str; + MasterToken::StringRegion str_region; +}; + +MasterToken::StringRegion +createStringRegion(const std::string& str) { + MasterToken::StringRegion region; + region.beg = &str[0]; // note std ensures this works even if str is empty + region.len = str.size(); + return (region); +} + +TEST_F(CharStringDataTest, normalConversion) { + uint8_t tmp[3]; // placeholder for expected sequence + + stringToCharStringData(str_region, chstr); + matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size()); + + // Empty string + chstr.clear(); + stringToCharStringData(createStringRegion(""), chstr); + EXPECT_TRUE(chstr.empty()); + + // Possible largest char string + chstr.clear(); + std::string long_str(255, 'x'); + stringToCharStringData(createStringRegion(long_str), chstr); + std::vector<uint8_t> expected; + expected.insert(expected.end(), long_str.begin(), long_str.end()); + matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size()); + + // Escaped '\' + chstr.clear(); + tmp[0] = '\\'; + stringToCharStringData(createStringRegion("\\\\"), chstr); + matchWireData(tmp, 1, &chstr[0], chstr.size()); + + // Boundary values for \DDD + chstr.clear(); + tmp[0] = 0; + stringToCharStringData(createStringRegion("\\000"), chstr); + matchWireData(tmp, 1, &chstr[0], chstr.size()); + + chstr.clear(); + stringToCharStringData(createStringRegion("\\255"), chstr); + tmp[0] = 255; + matchWireData(tmp, 1, &chstr[0], chstr.size()); + + // Another digit follows DDD; it shouldn't cause confusion + chstr.clear(); + stringToCharStringData(createStringRegion("\\2550"), chstr); + tmp[1] = '0'; + matchWireData(tmp, 2, &chstr[0], chstr.size()); +} + +TEST_F(CharStringDataTest, badConversion) { + // input string ending with (non escaped) '\' + chstr.clear(); + EXPECT_THROW(stringToCharStringData(createStringRegion("foo\\"), chstr), + InvalidRdataText); +} + +TEST_F(CharStringDataTest, badDDD) { + // Check various type of bad form of \DDD + + // Not a number + EXPECT_THROW(stringToCharStringData(createStringRegion("\\1a2"), chstr), + InvalidRdataText); + EXPECT_THROW(stringToCharStringData(createStringRegion("\\12a"), chstr), + InvalidRdataText); + + // Not in the range of uint8_t + EXPECT_THROW(stringToCharStringData(createStringRegion("\\256"), chstr), + InvalidRdataText); + + // Short buffer + EXPECT_THROW(stringToCharStringData(createStringRegion("\\42"), chstr), + InvalidRdataText); +} + +const struct TestData { + const char *data; + const char *expected; +} conversion_data[] = { + {"Test\"Test", "Test\\\"Test"}, + {"Test;Test", "Test\\;Test"}, + {"Test\\Test", "Test\\\\Test"}, + {"Test\x1fTest", "Test\\031Test"}, + {"Test ~ Test", "Test ~ Test"}, + {"Test\x7fTest", "Test\\127Test"}, + {0, 0} +}; + +TEST_F(CharStringDataTest, charStringDataToString) { + for (const TestData* cur = conversion_data; cur->data; ++cur) { + uint8_t idata[32]; + size_t length = std::strlen(cur->data); + ASSERT_LT(length, sizeof(idata)); + std::memcpy(idata, cur->data, length); + const CharStringData test_data(idata, idata + length); + EXPECT_EQ(cur->expected, charStringDataToString(test_data)); + } +} + +TEST_F(CharStringDataTest, compareCharStringData) { + CharStringData charstr; + CharStringData charstr2; + CharStringData charstr_small1; + CharStringData charstr_small2; + CharStringData charstr_large1; + CharStringData charstr_large2; + CharStringData charstr_empty; + + stringToCharStringData(createStringRegion("test string"), charstr); + stringToCharStringData(createStringRegion("test string"), charstr2); + stringToCharStringData(createStringRegion("test strin"), charstr_small1); + stringToCharStringData(createStringRegion("test strina"), charstr_small2); + stringToCharStringData(createStringRegion("test stringa"), charstr_large1); + stringToCharStringData(createStringRegion("test strinz"), charstr_large2); + + EXPECT_EQ(0, compareCharStringDatas(charstr, charstr2)); + EXPECT_EQ(0, compareCharStringDatas(charstr2, charstr)); + EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small1)); + EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_small2)); + EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large1)); + EXPECT_EQ(-1, compareCharStringDatas(charstr, charstr_large2)); + EXPECT_EQ(-1, compareCharStringDatas(charstr_small1, charstr)); + EXPECT_EQ(-1, compareCharStringDatas(charstr_small2, charstr)); + EXPECT_EQ(1, compareCharStringDatas(charstr_large1, charstr)); + EXPECT_EQ(1, compareCharStringDatas(charstr_large2, charstr)); + + EXPECT_EQ(-1, compareCharStringDatas(charstr_empty, charstr)); + EXPECT_EQ(1, compareCharStringDatas(charstr, charstr_empty)); + EXPECT_EQ(0, compareCharStringDatas(charstr_empty, charstr_empty)); +} + +} // unnamed namespace diff --git a/src/lib/dns/tests/rdata_char_string_unittest.cc b/src/lib/dns/tests/rdata_char_string_unittest.cc new file mode 100644 index 0000000..b0500d8 --- /dev/null +++ b/src/lib/dns/tests/rdata_char_string_unittest.cc @@ -0,0 +1,246 @@ +// Copyright (C) 2012-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <util/unittests/wiredata.h> + +#include <dns/exceptions.h> +#include <dns/rdata.h> +#include <dns/char_string.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +#include <string> +#include <vector> + +using namespace isc::dns; +using namespace isc::dns::rdata; +using isc::dns::rdata::generic::detail::CharString; +using isc::dns::rdata::generic::detail::bufferToCharString; +using isc::dns::rdata::generic::detail::stringToCharString; +using isc::dns::rdata::generic::detail::charStringToString; +using isc::dns::rdata::generic::detail::compareCharStrings; +using isc::util::unittests::matchWireData; + +namespace { +const uint8_t test_charstr[] = { + sizeof("Test String") - 1, + 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g' +}; + +class CharStringTest : public ::testing::Test { +protected: + CharStringTest() : + // char-string representation for test data using two types of escape + // ('r' = 114) + test_str("Test\\ St\\114ing") + { + str_region.beg = &test_str[0]; + str_region.len = test_str.size(); + } + CharString chstr; // place holder + const std::string test_str; + MasterToken::StringRegion str_region; +}; + +MasterToken::StringRegion +createStringRegion(const std::string& str) { + MasterToken::StringRegion region; + region.beg = &str[0]; // note std ensures this works even if str is empty + region.len = str.size(); + return (region); +} + +TEST_F(CharStringTest, normalConversion) { + uint8_t tmp[3]; // placeholder for expected sequence + + stringToCharString(str_region, chstr); + matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size()); + + // Empty string + chstr.clear(); + stringToCharString(createStringRegion(""), chstr); + tmp[0] = 0; + matchWireData(tmp, 1, &chstr[0], chstr.size()); + + // Possible largest char string + chstr.clear(); + std::string long_str(255, 'x'); + stringToCharString(createStringRegion(long_str), chstr); + std::vector<uint8_t> expected; + expected.push_back(255); // len of char string + expected.insert(expected.end(), long_str.begin(), long_str.end()); + matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size()); + + // Same data as the previous case, but the original string is longer than + // the max; this shouldn't be rejected + chstr.clear(); + long_str.at(254) = '\\'; // replace the last 'x' with '\' + long_str.append("120"); // 'x' = 120 + stringToCharString(createStringRegion(long_str), chstr); + matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size()); + + // Escaped '\' + chstr.clear(); + tmp[0] = 1; + tmp[1] = '\\'; + stringToCharString(createStringRegion("\\\\"), chstr); + matchWireData(tmp, 2, &chstr[0], chstr.size()); + + // Boundary values for \DDD + chstr.clear(); + tmp[0] = 1; + tmp[1] = 0; + stringToCharString(createStringRegion("\\000"), chstr); + matchWireData(tmp, 2, &chstr[0], chstr.size()); + + chstr.clear(); + stringToCharString(createStringRegion("\\255"), chstr); + tmp[0] = 1; + tmp[1] = 255; + matchWireData(tmp, 2, &chstr[0], chstr.size()); + + // Another digit follows DDD; it shouldn't cause confusion + chstr.clear(); + stringToCharString(createStringRegion("\\2550"), chstr); + tmp[0] = 2; // string len is now 2 + tmp[2] = '0'; + matchWireData(tmp, 3, &chstr[0], chstr.size()); +} + +TEST_F(CharStringTest, badConversion) { + // string cannot exceed 255 bytes + EXPECT_THROW(stringToCharString(createStringRegion(std::string(256, 'a')), + chstr), + CharStringTooLong); + + // input string ending with (non escaped) '\' + chstr.clear(); + EXPECT_THROW(stringToCharString(createStringRegion("foo\\"), chstr), + InvalidRdataText); +} + +TEST_F(CharStringTest, badDDD) { + // Check various type of bad form of \DDD + + // Not a number + EXPECT_THROW(stringToCharString(createStringRegion("\\1a2"), chstr), + InvalidRdataText); + EXPECT_THROW(stringToCharString(createStringRegion("\\12a"), chstr), + InvalidRdataText); + + // Not in the range of uint8_t + EXPECT_THROW(stringToCharString(createStringRegion("\\256"), chstr), + InvalidRdataText); + + // Short buffer + EXPECT_THROW(stringToCharString(createStringRegion("\\42"), chstr), + InvalidRdataText); +} + +const struct TestData { + const char *data; + const char *expected; +} conversion_data[] = { + {"Test\"Test", "Test\\\"Test"}, + {"Test;Test", "Test\\;Test"}, + {"Test\\Test", "Test\\\\Test"}, + {"Test\x1fTest", "Test\\031Test"}, + {"Test ~ Test", "Test ~ Test"}, + {"Test\x7fTest", "Test\\127Test"}, + {0, 0} +}; + +TEST_F(CharStringTest, charStringToString) { + for (const TestData* cur = conversion_data; cur->data; ++cur) { + uint8_t idata[32]; + size_t length = std::strlen(cur->data); + // length (1 byte) + string (length bytes) + ASSERT_TRUE(sizeof(idata) > length); + idata[0] = static_cast<uint8_t>(length); + std::memcpy(idata + 1, cur->data, length); + const CharString test_data(idata, idata + length + 1); + EXPECT_EQ(cur->expected, charStringToString(test_data)); + } +} + +TEST_F(CharStringTest, bufferToCharString) { + const size_t chstr_size = sizeof(test_charstr); + isc::util::InputBuffer buf(test_charstr, chstr_size); + size_t read = bufferToCharString(buf, chstr_size, chstr); + + EXPECT_EQ(chstr_size, read); + EXPECT_EQ("Test String", charStringToString(chstr)); +} + +TEST_F(CharStringTest, bufferToCharString_bad) { + const size_t chstr_size = sizeof(test_charstr); + isc::util::InputBuffer buf(test_charstr, chstr_size); + // Set valid data in both so we can make sure the charstr is not + // modified + bufferToCharString(buf, chstr_size, chstr); + ASSERT_EQ("Test String", charStringToString(chstr)); + + // Should be at end of buffer now, so it should fail + EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr), + DNSMessageFORMERR); + EXPECT_EQ("Test String", charStringToString(chstr)); + + // reset and try to read with too low rdata_len + buf.setPosition(0); + EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr), + DNSMessageFORMERR); + EXPECT_EQ("Test String", charStringToString(chstr)); + + // set internal charstring len too high + const uint8_t test_charstr_err[] = { + sizeof("Test String") + 1, + 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g' + }; + buf = isc::util::InputBuffer(test_charstr_err, sizeof(test_charstr_err)); + EXPECT_THROW(bufferToCharString(buf, chstr_size, chstr), + DNSMessageFORMERR); + EXPECT_EQ("Test String", charStringToString(chstr)); + +} + + + +TEST_F(CharStringTest, compareCharString) { + CharString charstr; + CharString charstr2; + CharString charstr_small1; + CharString charstr_small2; + CharString charstr_large1; + CharString charstr_large2; + CharString charstr_empty; + + stringToCharString(createStringRegion("test string"), charstr); + stringToCharString(createStringRegion("test string"), charstr2); + stringToCharString(createStringRegion("test strin"), charstr_small1); + stringToCharString(createStringRegion("test strina"), charstr_small2); + stringToCharString(createStringRegion("test stringa"), charstr_large1); + stringToCharString(createStringRegion("test strinz"), charstr_large2); + + EXPECT_EQ(0, compareCharStrings(charstr, charstr2)); + EXPECT_EQ(0, compareCharStrings(charstr2, charstr)); + EXPECT_EQ(1, compareCharStrings(charstr, charstr_small1)); + EXPECT_EQ(1, compareCharStrings(charstr, charstr_small2)); + EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large1)); + EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large2)); + EXPECT_EQ(-1, compareCharStrings(charstr_small1, charstr)); + EXPECT_EQ(-1, compareCharStrings(charstr_small2, charstr)); + EXPECT_EQ(1, compareCharStrings(charstr_large1, charstr)); + EXPECT_EQ(1, compareCharStrings(charstr_large2, charstr)); + + EXPECT_EQ(-1, compareCharStrings(charstr_empty, charstr)); + EXPECT_EQ(1, compareCharStrings(charstr, charstr_empty)); + EXPECT_EQ(0, compareCharStrings(charstr_empty, charstr_empty)); +} + +} // unnamed namespace diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc new file mode 100644 index 0000000..5c12829 --- /dev/null +++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc @@ -0,0 +1,165 @@ +// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> +#include <dns/rdataclass.h> +#include <util/encode/encode.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::util::encode; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { + +class Rdata_DHCID_Test : public RdataTest { +protected: + Rdata_DHCID_Test() : + dhcid_txt("0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA="), + rdata_dhcid(dhcid_txt) + {} + + void checkFromText_None(const string& rdata_str) { + checkFromText<in::DHCID, isc::Exception, isc::Exception>( + rdata_str, rdata_dhcid, false, false); + } + + void checkFromText_BadValue(const string& rdata_str) { + checkFromText<in::DHCID, BadValue, BadValue>( + rdata_str, rdata_dhcid, true, true); + } + + void checkFromText_LexerError(const string& rdata_str) { + checkFromText + <in::DHCID, InvalidRdataText, MasterLexer::LexerError>( + rdata_str, rdata_dhcid, true, true); + } + + void checkFromText_BadString(const string& rdata_str) { + checkFromText + <in::DHCID, InvalidRdataText, isc::Exception>( + rdata_str, rdata_dhcid, true, false); + } + + const string dhcid_txt; + const in::DHCID rdata_dhcid; +}; + +TEST_F(Rdata_DHCID_Test, fromText) { + EXPECT_EQ(dhcid_txt, rdata_dhcid.toText()); + + // Space in digest data is OK + checkFromText_None( + "0LIg0LvQtdGB0YMg 0YDQvtC00LjQu9Cw 0YHRjCDRkdC70L7R h9C60LA="); + + // Multi-line digest data is OK, if enclosed in parentheses + checkFromText_None( + "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA= )"); + + // Trailing garbage. This should cause only the string constructor + // to fail, but the lexer constructor must be able to continue + // parsing from it. + checkFromText_BadString( + "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=" + " ; comment\n" + "AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA="); +} + +TEST_F(Rdata_DHCID_Test, badText) { + // missing digest data + checkFromText_LexerError(""); + + // invalid base64 + checkFromText_BadValue("EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!="); + + // unterminated multi-line base64 + checkFromText_LexerError( + "( 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw\n0YHRjCDRkdC70L7R h9C60LA="); +} + +TEST_F(Rdata_DHCID_Test, copy) { + const in::DHCID rdata_dhcid2(rdata_dhcid); + EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid2)); +} + +TEST_F(Rdata_DHCID_Test, createFromWire) { + EXPECT_EQ(0, rdata_dhcid.compare( + *rdataFactoryFromFile(RRType("DHCID"), RRClass("IN"), + "rdata_dhcid_fromWire"))); + + InputBuffer buffer(0, 0); + EXPECT_THROW(in::DHCID(buffer, 0), InvalidRdataLength); + + // TBD: more tests +} + +TEST_F(Rdata_DHCID_Test, createFromLexer) { + EXPECT_EQ(0, rdata_dhcid.compare( + *test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(), + dhcid_txt))); +} + +TEST_F(Rdata_DHCID_Test, toWireRenderer) { + rdata_dhcid.toWire(renderer); + + vector<unsigned char> data; + UnitTestUtil::readWireData("rdata_dhcid_toWire", data); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_DHCID_Test, toWireBuffer) { + rdata_dhcid.toWire(obuffer); + + vector<unsigned char> data; + UnitTestUtil::readWireData("rdata_dhcid_toWire", data); + matchWireData(&data[0], data.size(), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_DHCID_Test, toText) { + EXPECT_EQ(dhcid_txt, rdata_dhcid.toText()); +} + +TEST_F(Rdata_DHCID_Test, getDHCIDDigest) { + const string dhcid_txt1(encodeBase64(rdata_dhcid.getDigest())); + + EXPECT_EQ(dhcid_txt, dhcid_txt1); +} + +TEST_F(Rdata_DHCID_Test, compare) { + // trivial case: self equivalence + // cppcheck-suppress uselessCallsCompare + EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid)); + + in::DHCID rdata_dhcid1("0YLQvtC/0L7Qu9GPINC00LLQsCDRgNGD0LHQu9GP"); + in::DHCID rdata_dhcid2("0YLQvtC/0L7Qu9GPINGC0YDQuCDRgNGD0LHQu9GP"); + in::DHCID rdata_dhcid3("0YLQvtC/0L7Qu9GPINGH0LXRgtGL0YDQtSDRgNGD0LHQu9GP"); + + EXPECT_LT(rdata_dhcid1.compare(rdata_dhcid2), 0); + EXPECT_GT(rdata_dhcid2.compare(rdata_dhcid1), 0); + + EXPECT_LT(rdata_dhcid2.compare(rdata_dhcid3), 0); + EXPECT_GT(rdata_dhcid3.compare(rdata_dhcid2), 0); + + // comparison attempt between incompatible RR types should be rejected + EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast); +} +} diff --git a/src/lib/dns/tests/rdata_in_a_unittest.cc b/src/lib/dns/tests/rdata_in_a_unittest.cc new file mode 100644 index 0000000..bc92c1c --- /dev/null +++ b/src/lib/dns/tests/rdata_in_a_unittest.cc @@ -0,0 +1,157 @@ +// Copyright (C) 2010-2024 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 <dns/rdataclass.h> +#include <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/master_lexer.h> +#include <dns/master_loader.h> +#include <dns/rdata.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/buffer.h> +#include <util/unittests/wiredata.h> + +#include <gtest/gtest.h> + +#include <sstream> + +#include <arpa/inet.h> +#include <sys/socket.h> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { +class Rdata_IN_A_Test : public RdataTest { +protected: + Rdata_IN_A_Test() : rdata_in_a("192.0.2.1") {} + + void checkFromTextIN_A(const std::string& rdata_txt, + bool throw_str_version = true, + bool throw_lexer_version = true) { + checkFromText<in::A, InvalidRdataText, InvalidRdataText>( + rdata_txt, rdata_in_a, throw_str_version, throw_lexer_version); + } + + const in::A rdata_in_a; +}; + +const uint8_t wiredata_in_a[] = { 192, 0, 2, 1 }; + +TEST_F(Rdata_IN_A_Test, createFromText) { + // Normal case: no exception for either case, so the exception type + // doesn't matter. + checkFromText<in::A, isc::Exception, isc::Exception>("192.0.2.1", + rdata_in_a, false, + false); + + // should reject an abbreviated form of IPv4 address + checkFromTextIN_A("10.1"); + // or an IPv6 address + checkFromTextIN_A("2001:db8::1234"); + // or any meaningless text as an IP address + checkFromTextIN_A("xxx"); + + // NetBSD's inet_pton accepts trailing space after an IPv4 address, which + // would confuse some of the tests below. We check the case differently + // in these cases depending on the strictness of inet_pton (most + // implementations seem to be stricter). + uint8_t v4addr_buf[4]; + const bool reject_extra_space = + inet_pton(AF_INET, "192.0.2.1 ", v4addr_buf) == 0; + + // trailing white space: only string version throws + checkFromTextIN_A("192.0.2.1 ", reject_extra_space, false); + // same for beginning white space. + checkFromTextIN_A(" 192.0.2.1", true, false); + // same for trailing non-space garbage (note that lexer version still + // ignore it; it's expected to be detected at a higher layer). + checkFromTextIN_A("192.0.2.1 xxx", reject_extra_space, false); + + // nul character after a valid textual representation. + string nul_after_addr = "192.0.2.1"; + nul_after_addr.push_back(0); + checkFromTextIN_A(nul_after_addr, true, true); + + // a valid address surrounded by parentheses; only okay with lexer + checkFromTextIN_A("(192.0.2.1)", true, false); + + // input that would cause lexer-specific error; it's bad text as an + // address so should result in the string version, too. + checkFromText<in::A, InvalidRdataText, MasterLexer::LexerError>( + ")192.0.2.1", rdata_in_a); +} + +TEST_F(Rdata_IN_A_Test, createFromWire) { + // Valid data + EXPECT_EQ(0, rdata_in_a.compare( + *rdataFactoryFromFile(RRType::A(), RRClass::IN(), + "rdata_in_a_fromWire"))); + // RDLENGTH is too short + EXPECT_THROW(rdataFactoryFromFile(RRType::A(), RRClass::IN(), + "rdata_in_a_fromWire", 6), + DNSMessageFORMERR); + // RDLENGTH is too long + EXPECT_THROW(rdataFactoryFromFile(RRType::A(), RRClass::IN(), + "rdata_in_a_fromWire", 12), + DNSMessageFORMERR); + // buffer too short. + EXPECT_THROW(rdataFactoryFromFile(RRType::A(), RRClass::IN(), + "rdata_in_a_fromWire", 19), + DNSMessageFORMERR); +} + +TEST_F(Rdata_IN_A_Test, toWireBuffer) { + rdata_in_a.toWire(obuffer); + matchWireData(wiredata_in_a, sizeof (wiredata_in_a), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_IN_A_Test, toWireRenderer) { + rdata_in_a.toWire(renderer); + matchWireData(wiredata_in_a, sizeof (wiredata_in_a), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_IN_A_Test, toText) { + EXPECT_EQ("192.0.2.1", rdata_in_a.toText()); + + // this shouldn't make the code crash + const string longaddr("255.255.255.255"); + EXPECT_EQ(longaddr, in::A(longaddr).toText()); +} + +TEST_F(Rdata_IN_A_Test, compare) { + const in::A small1("1.1.1.1"); + const in::A small2("1.2.3.4"); + const in::A large1("255.255.255.255"); + const in::A large2("4.3.2.1"); + + // trivial case: self equivalence + // cppcheck-suppress uselessCallsCompare + EXPECT_EQ(0, small1.compare(small1)); + + // confirm these are compared as unsigned values + EXPECT_GT(0, small1.compare(large1)); + EXPECT_LT(0, large1.compare(small1)); + + // confirm these are compared in network byte order + EXPECT_GT(0, small2.compare(large2)); + EXPECT_LT(0, large2.compare(small2)); + + // comparison attempt between incompatible RR types should be rejected + EXPECT_THROW(rdata_in_a.compare(*RdataTest::rdata_nomatch), bad_cast); +} +} diff --git a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc new file mode 100644 index 0000000..c75e829 --- /dev/null +++ b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc @@ -0,0 +1,151 @@ +// Copyright (C) 2010-2024 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 <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/buffer.h> +#include <util/unittests/wiredata.h> + +#include <gtest/gtest.h> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { +class Rdata_IN_AAAA_Test : public RdataTest { +protected: + Rdata_IN_AAAA_Test() : rdata_in_aaaa("2001:db8::1234") {} + + // Common check to see the result of in::A Rdata construction either from + // std::string or with MasterLexer object. If it's expected to succeed + // the result should be identical to the commonly used test data + // (rdata_in_a); otherwise it should result in the exception specified as + // the template parameter. + void checkFromTextIN_AAAA(const string& in_aaaa_txt, + bool throw_str_version = true, + bool throw_lexer_version = true) + { + checkFromText<in::AAAA, InvalidRdataText, InvalidRdataText>( + in_aaaa_txt, rdata_in_aaaa, throw_str_version, + throw_lexer_version); + } + + const in::AAAA rdata_in_aaaa; +}; + +const uint8_t wiredata_in_aaaa[] = { + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x12, 0x34 }; + +TEST_F(Rdata_IN_AAAA_Test, createFromText) { + // Normal case: no exception for either case, so the exception type + // doesn't matter. + checkFromText<in::AAAA, isc::Exception, isc::Exception>( + "2001:db8::1234", rdata_in_aaaa, false, false); + + // should reject an IP4 address. + checkFromTextIN_AAAA("192.0.2.1"); + // or any meaningless text as an IPv6 address + checkFromTextIN_AAAA("xxx"); + + // trailing white space: only string version throws + checkFromTextIN_AAAA("2001:db8::1234 ", true, false); + // same for beginning white space. + checkFromTextIN_AAAA(" 2001:db8::1234", true, false); + // same for trailing non-space garbage (note that lexer version still + // ignore it; it's expected to be detected at a higher layer). + checkFromTextIN_AAAA("2001:db8::1234 xxx", true, false); + + // nul character after a valid textual representation. + string nul_after_addr = "2001:db8::1234"; + nul_after_addr.push_back(0); + checkFromTextIN_AAAA(nul_after_addr, true, true); + + // a valid address surrounded by parentheses; only okay with lexer + checkFromTextIN_AAAA("(2001:db8::1234)", true, false); + + // input that would cause lexer-specific error; it's bad text as an + // address so should result in the string version, too. + checkFromText<in::AAAA, InvalidRdataText, MasterLexer::LexerError>( + ")2001:db8::1234", rdata_in_aaaa); +} + +TEST_F(Rdata_IN_AAAA_Test, createFromWire) { + // Valid data + EXPECT_EQ(0, rdata_in_aaaa.compare( + *rdataFactoryFromFile(RRType::AAAA(), RRClass::IN(), + "rdata_in_aaaa_fromWire"))); + // RDLENGTH is too short + EXPECT_THROW(rdataFactoryFromFile(RRType::AAAA(), RRClass::IN(), + "rdata_in_aaaa_fromWire", 18), + DNSMessageFORMERR); + // RDLENGTH is too long + EXPECT_THROW(rdataFactoryFromFile(RRType::AAAA(), RRClass::IN(), + "rdata_in_aaaa_fromWire", 36), + DNSMessageFORMERR); + // buffer too short. + EXPECT_THROW(rdataFactoryFromFile(RRType::AAAA(), RRClass::IN(), + "rdata_in_aaaa_fromWire", 55), + DNSMessageFORMERR); +} + +TEST_F(Rdata_IN_AAAA_Test, createFromLexer) { + EXPECT_EQ(0, rdata_in_aaaa.compare( + *test::createRdataUsingLexer(RRType::AAAA(), RRClass::IN(), + "2001:db8::1234"))); +} + +TEST_F(Rdata_IN_AAAA_Test, toWireBuffer) { + rdata_in_aaaa.toWire(obuffer); + matchWireData(wiredata_in_aaaa, sizeof (wiredata_in_aaaa), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_IN_AAAA_Test, toWireRenderer) { + rdata_in_aaaa.toWire(renderer); + matchWireData(wiredata_in_aaaa, sizeof (wiredata_in_aaaa), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_IN_AAAA_Test, toText) { + EXPECT_EQ("2001:db8::1234", rdata_in_aaaa.toText()); +} + +TEST_F(Rdata_IN_AAAA_Test, compare) { + in::AAAA small1("::1"); + in::AAAA small2("1:2:3:4:5:6:7:8"); + in::AAAA large1("ffff::"); + in::AAAA large2("8:7:6:5:4:3:2:1"); + + // trivial case: self equivalence + // cppcheck-suppress uselessCallsCompare + EXPECT_EQ(0, small1.compare(small1)); + + // confirm these are compared as unsigned values + EXPECT_GT(0, small1.compare(large1)); + EXPECT_LT(0, large1.compare(small1)); + + // confirm these are compared in network byte order + EXPECT_GT(0, small2.compare(large2)); + EXPECT_LT(0, large2.compare(small2)); + + // comparison attempt between incompatible RR types should be rejected + EXPECT_THROW(rdata_in_aaaa.compare(*RdataTest::rdata_nomatch), bad_cast); +} + +} diff --git a/src/lib/dns/tests/rdata_ns_unittest.cc b/src/lib/dns/tests/rdata_ns_unittest.cc new file mode 100644 index 0000000..cb7b160 --- /dev/null +++ b/src/lib/dns/tests/rdata_ns_unittest.cc @@ -0,0 +1,145 @@ +// Copyright (C) 2010-2024 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 <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/unittests/wiredata.h> +#include <util/buffer.h> + +#include <gtest/gtest.h> + +using namespace isc::dns; +using namespace isc::dns::rdata; +using namespace isc::util; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +using namespace std; + +namespace { +class Rdata_NS_Test : public RdataTest { +public: + Rdata_NS_Test() : + rdata_ns("ns.example.com."), + rdata_ns2("ns2.example.com.") { + } + + const generic::NS rdata_ns; + const generic::NS rdata_ns2; +}; + +const uint8_t wiredata_ns[] = { + 0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00 }; +const uint8_t wiredata_ns2[] = { + // first name: ns.example.com. + 0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, + // second name: ns2.example.com. all labels except the first should be + // compressed. + 0x03, 0x6e, 0x73, 0x32, 0xc0, 0x03 }; + +TEST_F(Rdata_NS_Test, createFromText) { + EXPECT_EQ(0, rdata_ns.compare(generic::NS("ns.example.com."))); + // explicitly add a trailing dot. should be the same RDATA. + EXPECT_EQ(0, rdata_ns.compare(generic::NS("ns.example.com."))); + // should be case sensitive. + EXPECT_EQ(0, rdata_ns.compare(generic::NS("NS.EXAMPLE.COM."))); + // RDATA of a class-independent type should be recognized for any + // "unknown" class. + EXPECT_EQ(0, rdata_ns.compare(*createRdata(RRType("NS"), RRClass(65000), + "ns.example.com."))); +} + +TEST_F(Rdata_NS_Test, badText) { + // Extra input at end of line + EXPECT_THROW(generic::NS("ns.example.com. extra."), InvalidRdataText); +} + +TEST_F(Rdata_NS_Test, createFromWire) { + EXPECT_EQ(0, rdata_ns.compare( + *rdataFactoryFromFile(RRType("NS"), RRClass("IN"), + "rdata_ns_fromWire"))); + // RDLENGTH is too short + EXPECT_THROW(rdataFactoryFromFile(RRType("NS"), RRClass("IN"), + "rdata_ns_fromWire", 18), + InvalidRdataLength); + // RDLENGTH is too long + EXPECT_THROW(rdataFactoryFromFile(RRType("NS"), RRClass("IN"), + "rdata_ns_fromWire", 36), + InvalidRdataLength); + // incomplete name. the error should be detected in the name constructor + EXPECT_THROW(rdataFactoryFromFile(RRType("NS"), RRClass("IN"), + "rdata_ns_fromWire", 71), + DNSMessageFORMERR); + + EXPECT_EQ(0, generic::NS("ns2.example.com.").compare( + *rdataFactoryFromFile(RRType("NS"), RRClass("IN"), + "rdata_ns_fromWire", 55))); + EXPECT_THROW(*rdataFactoryFromFile(RRType("NS"), RRClass("IN"), + "rdata_ns_fromWire", 63), + InvalidRdataLength); +} + +TEST_F(Rdata_NS_Test, createFromLexer) { + EXPECT_EQ(0, rdata_ns.compare( + *test::createRdataUsingLexer(RRType::NS(), RRClass::IN(), + "ns.example.com."))); + + // test::createRdataUsingLexer() constructs relative to + // "example.org." origin. + EXPECT_EQ(0, generic::NS("ns8.example.org.").compare( + *test::createRdataUsingLexer(RRType::NS(), RRClass::IN(), + "ns8"))); + + // Exceptions cause null to be returned. + EXPECT_FALSE(test::createRdataUsingLexer(RRType::NS(), RRClass::IN(), + "")); + + // Extra input at end of line + EXPECT_FALSE(test::createRdataUsingLexer(RRType::NS(), RRClass::IN(), + "ns.example.com. extra.")); +} + +TEST_F(Rdata_NS_Test, toWireBuffer) { + rdata_ns.toWire(obuffer); + matchWireData(wiredata_ns, sizeof(wiredata_ns), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_NS_Test, toWireRenderer) { + rdata_ns.toWire(renderer); + matchWireData(wiredata_ns, sizeof(wiredata_ns), + renderer.getData(), renderer.getLength()); + + rdata_ns2.toWire(renderer); + matchWireData(wiredata_ns2, sizeof(wiredata_ns2), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_NS_Test, toText) { + EXPECT_EQ("ns.example.com.", rdata_ns.toText()); +} + +TEST_F(Rdata_NS_Test, compare) { + generic::NS small("a.example."); + generic::NS large("example."); + EXPECT_TRUE(Name("a.example") > Name("example")); + EXPECT_GT(0, small.compare(large)); +} + +TEST_F(Rdata_NS_Test, getNSName) { + EXPECT_EQ(Name("ns.example.com."), rdata_ns.getNSName()); +} +} diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc new file mode 100644 index 0000000..d24a565 --- /dev/null +++ b/src/lib/dns/tests/rdata_opt_unittest.cc @@ -0,0 +1,198 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { +class Rdata_OPT_Test : public RdataTest { + // there's nothing to specialize +}; + +const uint8_t rdata_opt_wiredata[] = { + // Option code + 0x00, 0x2a, + // Option length + 0x00, 0x03, + // Option data + 0x00, 0x01, 0x02 +}; + +TEST_F(Rdata_OPT_Test, createFromText) { + // OPT RR cannot be created from text. + EXPECT_THROW(generic::OPT("this does not matter"), InvalidRdataText); +} + +TEST_F(Rdata_OPT_Test, createFromWire) { + // Valid cases: in the simple implementation with no supported options, + // we can only check these don't throw. + EXPECT_NO_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass("CLASS4096"), + "rdata_opt_fromWire1")); + EXPECT_NO_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::CH(), + "rdata_opt_fromWire1", 2)); + + // Short RDLEN. This throws InvalidRdataLength even if subsequent + // pseudo RRs cause RDLEN size to be exhausted. + EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(), + "rdata_opt_fromWire2"), + InvalidRdataLength); + EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(), + "rdata_opt_fromWire3"), + InvalidRdataLength); + // Option lengths can add up and overflow RDLEN. Unlikely when + // parsed from wire data, but we'll check for it anyway. + EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(), + "rdata_opt_fromWire4"), + InvalidRdataText); + + // short buffer case. + EXPECT_THROW(rdataFactoryFromFile(RRType::OPT(), RRClass::IN(), + "rdata_opt_fromWire1", 11), + isc::OutOfRange); +} + +TEST_F(Rdata_OPT_Test, createFromLexer) { + // OPT RR cannot be created from text. Exceptions cause null to be + // returned. + EXPECT_FALSE(test::createRdataUsingLexer(RRType::OPT(), RRClass::IN(), + "this does not matter")); +} + +TEST_F(Rdata_OPT_Test, toWireBuffer) { + const generic::OPT rdata_opt = + dynamic_cast<const generic::OPT&> + (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"), + "rdata_opt_fromWire1", 2)); + + obuffer.clear(); + rdata_opt.toWire(obuffer); + + matchWireData(rdata_opt_wiredata, sizeof(rdata_opt_wiredata), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_OPT_Test, toWireRenderer) { + const generic::OPT rdata_opt = + dynamic_cast<const generic::OPT&> + (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"), + "rdata_opt_fromWire1", 2)); + + renderer.clear(); + rdata_opt.toWire(renderer); + + matchWireData(rdata_opt_wiredata, sizeof(rdata_opt_wiredata), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_OPT_Test, toText) { + // empty OPT + const generic::OPT rdata_opt; + + EXPECT_THROW(rdata_opt.toText(), + isc::InvalidOperation); +} + +TEST_F(Rdata_OPT_Test, compare) { + // empty OPT + const generic::OPT rdata_opt; + + EXPECT_THROW(rdata_opt.compare( + *rdataFactoryFromFile(RRType::OPT(), RRClass::CH(), + "rdata_opt_fromWire1", 2)), + isc::InvalidOperation); + + // comparison attempt between incompatible RR types also results in + // isc::InvalidOperation. + EXPECT_THROW(rdata_opt.compare(*RdataTest::rdata_nomatch), + isc::InvalidOperation); +} + +TEST_F(Rdata_OPT_Test, appendPseudoRR) { + generic::OPT rdata_opt; + + // Append empty option data + rdata_opt.appendPseudoRR(0x0042, 0, 0); + + // Append simple option data + const uint8_t option_data[] = {'H', 'e', 'l', 'l', 'o'}; + rdata_opt.appendPseudoRR(0x0043, option_data, sizeof(option_data)); + + // Duplicate option codes are okay. + rdata_opt.appendPseudoRR(0x0042, option_data, sizeof(option_data)); + + // When option length may overflow RDLEN, append should throw. + const std::vector<uint8_t> buffer((1 << 16) - 1); + EXPECT_THROW(rdata_opt.appendPseudoRR(0x0044, &buffer[0], buffer.size()), + isc::InvalidParameter); + + const uint8_t rdata_opt_wiredata2[] = { + // OPTION #1 + // ` Option code + 0x00, 0x42, + // ` Option length + 0x00, 0x00, + + // OPTION #2 + // ` Option code + 0x00, 0x43, + // ` Option length + 0x00, 0x05, + // ` Option data + 'H', 'e', 'l', 'l', 'o', + + // OPTION #3 + // ` Option code + 0x00, 0x42, + // ` Option length + 0x00, 0x05, + // ` Option data + 'H', 'e', 'l', 'l', 'o' + }; + + obuffer.clear(); + rdata_opt.toWire(obuffer); + + matchWireData(rdata_opt_wiredata2, sizeof(rdata_opt_wiredata2), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_OPT_Test, getPseudoRRs) { + const generic::OPT rdf = + dynamic_cast<const generic::OPT&> + (*rdataFactoryFromFile(RRType("OPT"), RRClass("IN"), + "rdata_opt_fromWire1", 2)); + + const std::vector<generic::OPT::PseudoRR>& rrs = rdf.getPseudoRRs(); + ASSERT_FALSE(rrs.empty()); + EXPECT_EQ(1, rrs.size()); + EXPECT_EQ(0x2a, rrs.at(0).getCode()); + EXPECT_EQ(3, rrs.at(0).getLength()); + + const uint8_t expected_data[] = {0x00, 0x01, 0x02}; + const uint8_t* actual_data = rrs.at(0).getData(); + EXPECT_EQ(0, std::memcmp(expected_data, actual_data, + sizeof(expected_data))); +} +} diff --git a/src/lib/dns/tests/rdata_ptr_unittest.cc b/src/lib/dns/tests/rdata_ptr_unittest.cc new file mode 100644 index 0000000..b1db20b --- /dev/null +++ b/src/lib/dns/tests/rdata_ptr_unittest.cc @@ -0,0 +1,145 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <util/buffer.h> +#include <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/unittests/wiredata.h> + +using namespace isc::dns; +using namespace isc::dns::rdata; +using namespace isc::util; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +using namespace std; +// +// This test currently simply copies the NS RDATA tests. +// + +namespace { +class Rdata_PTR_Test : public RdataTest { +public: + Rdata_PTR_Test() : + rdata_ptr("ns.example.com."), + rdata_ptr2("ns2.example.com.") { + } + + const generic::PTR rdata_ptr; + const generic::PTR rdata_ptr2; +}; + +const uint8_t wiredata_ptr[] = { + 0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00 }; +const uint8_t wiredata_ptr2[] = { + // first name: ns.example.com. + 0x02, 0x6e, 0x73, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, + // second name: ns2.example.com. all labels except the first should be + // compressed. + 0x03, 0x6e, 0x73, 0x32, 0xc0, 0x03 }; + +TEST_F(Rdata_PTR_Test, createFromText) { + EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("ns.example.com."))); + // explicitly add a trailing dot. should be the same RDATA. + EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("ns.example.com."))); + // should be case sensitive. + EXPECT_EQ(0, rdata_ptr.compare(generic::PTR("NS.EXAMPLE.COM."))); + // RDATA of a class-independent type should be recognized for any + // "unknown" class. + EXPECT_EQ(0, rdata_ptr.compare(*createRdata(RRType("PTR"), RRClass(65000), + "ns.example.com."))); +} + +TEST_F(Rdata_PTR_Test, badText) { + // Extra text at end of line + EXPECT_THROW(generic::PTR("foo.example.com. extra."), InvalidRdataText); +} + +TEST_F(Rdata_PTR_Test, createFromWire) { + EXPECT_EQ(0, rdata_ptr.compare( + *rdataFactoryFromFile(RRType("PTR"), RRClass("IN"), + "rdata_ns_fromWire"))); + // RDLENGTH is too short + EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"), + "rdata_ns_fromWire", 18), + InvalidRdataLength); + // RDLENGTH is too long + EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"), + "rdata_ns_fromWire", 36), + InvalidRdataLength); + // incomplete name. the error should be detected in the name constructor + EXPECT_THROW(rdataFactoryFromFile(RRType("PTR"), RRClass("IN"), + "rdata_ns_fromWire", 71), + DNSMessageFORMERR); + + EXPECT_EQ(0, generic::PTR("ns2.example.com.").compare( + *rdataFactoryFromFile(RRType("PTR"), RRClass("IN"), + "rdata_ns_fromWire", 55))); + EXPECT_THROW(*rdataFactoryFromFile(RRType("PTR"), RRClass("IN"), + "rdata_ns_fromWire", 63), + InvalidRdataLength); +} + +TEST_F(Rdata_PTR_Test, createFromLexer) { + EXPECT_EQ(0, rdata_ptr.compare( + *test::createRdataUsingLexer(RRType::PTR(), RRClass::IN(), + "ns.example.com."))); + + // test::createRdataUsingLexer() constructs relative to + // "example.org." origin. + EXPECT_EQ(0, generic::PTR("foo0.example.org.").compare( + *test::createRdataUsingLexer(RRType::PTR(), RRClass::IN(), + "foo0"))); + + // Extra text at end of line + EXPECT_FALSE(test::createRdataUsingLexer(RRType::PTR(), RRClass::IN(), + "foo.example.com. extra.")); +} + +TEST_F(Rdata_PTR_Test, toWireBuffer) { + rdata_ptr.toWire(obuffer); + matchWireData(wiredata_ptr, sizeof(wiredata_ptr), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_PTR_Test, toWireRenderer) { + rdata_ptr.toWire(renderer); + matchWireData(wiredata_ptr, sizeof(wiredata_ptr), + renderer.getData(), renderer.getLength()); + + rdata_ptr2.toWire(renderer); + matchWireData(wiredata_ptr2, sizeof(wiredata_ptr2), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_PTR_Test, toText) { + EXPECT_EQ("ns.example.com.", rdata_ptr.toText()); +} + +TEST_F(Rdata_PTR_Test, compare) { + generic::PTR small("a.example."); + generic::PTR large("example."); + EXPECT_TRUE(Name("a.example") > Name("example")); + EXPECT_GT(0, small.compare(large)); +} + +TEST_F(Rdata_PTR_Test, getPTRName) { + EXPECT_EQ(Name("ns.example.com"), rdata_ptr.getPTRName()); +} +} diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc new file mode 100644 index 0000000..e0873e1 --- /dev/null +++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc @@ -0,0 +1,369 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/time_utils.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> +#include <dns/tests/rdata_unittest.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { + +const uint8_t wiredata_rrsig[] = { + // type covered = A + 0x00, 0x01, + // algorithm = 5 + 0x05, + // labels = 4 + 0x04, + // original TTL = 43200 (0x0000a8c0) + 0x00, 0x00, 0xa8, 0xc0, + // signature expiration = 1266961577 (0x4b844ca9) + 0x4b, 0x84, 0x4c, 0xa9, + // signature inception = 1266875177 (0x4b82fb29) + 0x4b, 0x82, 0xfb, 0x29, + // key tag = 8496 (0x2130) + 0x21, 0x30, + // signer's name (isc.org.) + // 3 i s c 3 o r g 0 + 0x03, 0x69, 0x73, 0x63, 0x03, 0x6f, 0x72, 0x67, 0x00, + // signature data follows + 0x7a, 0xfc, 0x61, 0x94, 0x6c, + 0x75, 0xde, 0x6a, 0x4a, 0x2d, 0x59, 0x0a, 0xb2, + 0x3a, 0x46, 0xcf, 0x27, 0x12, 0xe6, 0xdc, 0x2d, + 0x22, 0x8c, 0x4e, 0x9a, 0x53, 0x75, 0xe3, 0x0f, + 0x6d, 0xe4, 0x08, 0x33, 0x18, 0x19, 0xb3, 0x76, + 0x21, 0x9d, 0x2c, 0x8a, 0xc5, 0x69, 0xba, 0xab, + 0xef, 0x66, 0x9f, 0xda, 0xb5, 0x2a, 0xf9, 0x40, + 0xc1, 0x28, 0xc5, 0x97, 0xba, 0x3c, 0x19, 0x4d, + 0x95, 0x13, 0xc2, 0xcd, 0xf6, 0xb1, 0x59, 0x5d, + 0x0c, 0xf9, 0x3f, 0x35, 0xbb, 0x9a, 0x70, 0x93, + 0x36, 0xe5, 0xf4, 0x17, 0x7e, 0xfe, 0x66, 0x3b, + 0x70, 0x1f, 0xed, 0x33, 0xa8, 0xa3, 0x0d, 0xc0, + 0x8c, 0xc6, 0x95, 0x1b, 0xd8, 0x9c, 0x8c, 0x25, + 0xb4, 0x57, 0x9e, 0x56, 0x71, 0x64, 0x14, 0x7f, + 0x8f, 0x6d, 0xfa, 0xc5, 0xca, 0x3f, 0x36, 0xe2, + 0xa4, 0xdf, 0x60, 0xfa, 0xcd, 0x59, 0x3e, 0x22, + 0x32, 0xa1, 0xf7 +}; + +class Rdata_RRSIG_Test : public RdataTest { +protected: + Rdata_RRSIG_Test() : + rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="), + rdata_rrsig(rrsig_txt) + {} + + void checkFromText_None(const string& rdata_str) { + checkFromText<generic::RRSIG, isc::Exception, isc::Exception>( + rdata_str, rdata_rrsig, false, false); + } + + void checkFromText_InvalidText(const string& rdata_str) { + checkFromText<generic::RRSIG, InvalidRdataText, InvalidRdataText>( + rdata_str, rdata_rrsig, true, true); + } + + void checkFromText_InvalidType(const string& rdata_str) { + checkFromText<generic::RRSIG, InvalidRRType, InvalidRRType>( + rdata_str, rdata_rrsig, true, true); + } + + void checkFromText_InvalidTime(const string& rdata_str) { + checkFromText<generic::RRSIG, InvalidTime, InvalidTime>( + rdata_str, rdata_rrsig, true, true); + } + + void checkFromText_BadValue(const string& rdata_str) { + checkFromText<generic::RRSIG, BadValue, BadValue>( + rdata_str, rdata_rrsig, true, true); + } + + void checkFromText_LexerError(const string& rdata_str) { + checkFromText + <generic::RRSIG, InvalidRdataText, MasterLexer::LexerError>( + rdata_str, rdata_rrsig, true, true); + } + + void checkFromText_MissingOrigin(const string& rdata_str) { + checkFromText + <generic::RRSIG, MissingNameOrigin, MissingNameOrigin>( + rdata_str, rdata_rrsig, true, true); + } + + void checkFromText_BadString(const string& rdata_str) { + checkFromText + <generic::RRSIG, InvalidRdataText, isc::Exception>( + rdata_str, rdata_rrsig, true, false); + } + + const string rrsig_txt; + const generic::RRSIG rdata_rrsig; +}; + +TEST_F(Rdata_RRSIG_Test, fromText) { + EXPECT_EQ(rrsig_txt, rdata_rrsig.toText()); + EXPECT_EQ(isc::dns::RRType::A(), rdata_rrsig.typeCovered()); + + // Missing signature is OK + EXPECT_NO_THROW(const generic::RRSIG sig( + "A 5 4 43200 20100223214617 20100222214617 8496 isc.org.")); + + // Space in signature data is OK + checkFromText_None( + "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz " + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ " + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU " + "f49t+sXKPzbipN9g+s1ZPiIyofc="); + + // Multi-line signature data is OK, if enclosed in parentheses + checkFromText_None( + "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. " + "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n" + "f49t+sXKPzbipN9g+s1ZPiIyofc= )"); + + // Trailing garbage. This should cause only the string constructor + // to fail, but the lexer constructor must be able to continue + // parsing from it. + checkFromText_BadString( + "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc= ; comment\n" + "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, badText_missingFields) { + checkFromText_LexerError("A"); + checkFromText_LexerError("A 5"); + checkFromText_LexerError("A 5 4"); + checkFromText_LexerError("A 5 4 43200"); + checkFromText_LexerError("A 5 4 43200 20100223214617"); + checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617"); + checkFromText_LexerError("A 5 4 43200 20100223214617 20100222214617 " + "8496"); +} + +TEST_F(Rdata_RRSIG_Test, badText_coveredType) { + checkFromText_InvalidType("SPORK"); +} + +TEST_F(Rdata_RRSIG_Test, badText_algorithm) { + checkFromText_InvalidText( + "A 555 4 43200 " + "20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); + checkFromText_LexerError( + "A FIVE 4 43200 " + "20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, badText_labels) { + checkFromText_InvalidText( + "A 5 4444 43200 " + "20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); + checkFromText_LexerError( + "A 5 FOUR 43200 " + "20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, badText_ttl) { + checkFromText_LexerError( + "A 5 4 999999999999 " + "20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); + checkFromText_LexerError( + "A 5 4 TTL " + "20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); + + // alternate form of TTL is not okay + checkFromText_LexerError( + "A 5 4 12H 20100223214617 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz " + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/ " + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU " + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, badText_expiration) { + checkFromText_InvalidTime( + "A 5 4 43200 " + "201002232 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); + checkFromText_InvalidTime( + "A 5 4 43200 " + "EXPIRATION 20100222214617 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, badText_inception) { + checkFromText_InvalidTime( + "A 5 4 43200 " + "20100223214617 20100227 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); + checkFromText_InvalidTime( + "A 5 4 43200 " + "20100223214617 INCEPTION 8496 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, badText_keytag) { + checkFromText_InvalidText( + "A 5 4 43200 " + "20100223214617 20100222214617 999999 isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); + checkFromText_LexerError( + "A 5 4 43200 " + "20100223214617 20100222214617 TAG isc.org. " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, badText_signer) { + checkFromText_MissingOrigin( + "A 5 4 43200 " + "20100223214617 20100222214617 8496 isc.org " + "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, badText_signature) { + checkFromText_BadValue( + "A 5 4 43200 " + "20100223214617 20100222214617 8496 isc.org. " + "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!="); + + // no space between the tag and signer + checkFromText_LexerError( + "A 5 4 43200 20100223214617 20100222214617 " + "8496isc.org. ofc="); + + // unterminated multi-line base64 + checkFromText_LexerError( + "A 5 4 43200 20100223214617 20100222214617 8496 isc.org. " + "( evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz\n" + "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/\n" + "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU\n" + "f49t+sXKPzbipN9g+s1ZPiIyofc="); +} + +TEST_F(Rdata_RRSIG_Test, createFromLexer) { + EXPECT_EQ(0, rdata_rrsig.compare( + *test::createRdataUsingLexer(RRType::RRSIG(), RRClass::IN(), + rrsig_txt))); + + // Exceptions cause null to be returned. + EXPECT_FALSE(test::createRdataUsingLexer(RRType::RRSIG(), RRClass::IN(), + "INVALIDINPUT")); +} + +TEST_F(Rdata_RRSIG_Test, toWireRenderer) { + rdata_rrsig.toWire(renderer); + + matchWireData(wiredata_rrsig, sizeof(wiredata_rrsig), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_RRSIG_Test, toWireBuffer) { + rdata_rrsig.toWire(obuffer); + + matchWireData(wiredata_rrsig, sizeof(wiredata_rrsig), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_RRSIG_Test, createFromWire) { + const string rrsig_txt2( + "A 5 2 43200 20100327070149 20100225070149 2658 isc.org. " + "HkJk/xZTvzePU8NENl/ley8bbUumhk1hXciyqhLnz1VQFzkDooej6neX" + "ZgWZzQKeTKPOYWrnYtdZW4PnPQFeUl3orgLev7F8J6FZlDn0y/J/ThR5" + "m36Mo2/Gdxjj8lJ/IjPVkdpKyBpcnYND8KEIma5MyNCNeyO1UkfPQZGHNSQ="); + EXPECT_EQ(rrsig_txt2, + rdataFactoryFromFile(RRType("RRSIG"), RRClass("IN"), + "rdata_rrsig_fromWire1")->toText()); + const generic::RRSIG rdata_rrsig2(rrsig_txt2); + EXPECT_EQ(0, rdata_rrsig2.compare( + *rdataFactoryFromFile(RRType("RRSIG"), RRClass("IN"), + "rdata_rrsig_fromWire1"))); + + // RDLEN is too short + EXPECT_THROW(rdataFactoryFromFile(RRType::RRSIG(), RRClass::IN(), + "rdata_rrsig_fromWire2.wire"), + InvalidRdataLength); +} +} diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc new file mode 100644 index 0000000..33b1b4b --- /dev/null +++ b/src/lib/dns/tests/rdata_soa_unittest.cc @@ -0,0 +1,249 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { +class Rdata_SOA_Test : public RdataTest { +protected: + Rdata_SOA_Test() : + rdata_soa(Name("ns.example.com"), + Name("root.example.com"), + 2010012601, 3600, 300, 3600000, 1200) + {} + + template <typename ExForString, typename ExForLexer> + void checkFromTextSOA(const string& soa_txt, const Name* origin = 0, + bool throw_str_version = true, + bool throw_lexer_version = true) + { + checkFromText<generic::SOA, ExForString, ExForLexer>( + soa_txt, rdata_soa, throw_str_version, throw_lexer_version, + origin); + } + + const generic::SOA rdata_soa; +}; + +TEST_F(Rdata_SOA_Test, createFromText) { + // Below we specify isc::Exception as a dummy value for the exception type + // in case it's not expected to throw an exception; the type isn't used + // in the check code. + + // A simple case. + checkFromTextSOA<isc::Exception, isc::Exception>( + "ns.example.com. root.example.com. 2010012601 3600 300 3600000 1200", + 0, false, false); + + // Beginning and trailing space are ignored. + checkFromTextSOA<isc::Exception, isc::Exception>( + " ns.example.com. root.example.com. " + "2010012601 3600 300 3600000 1200 ", 0, false, false); + + // using extended TTL-like form for some parameters. + checkFromTextSOA<isc::Exception, isc::Exception>( + "ns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M", + 0, false, false); + + // multi-line. + checkFromTextSOA<isc::Exception, isc::Exception>( + "ns.example.com. (root.example.com.\n" + "2010012601 1H 5M 1000H) 20M", 0, false, false); + + // relative names for MNAME and RNAME with a separate origin (lexer + // version only) + const Name origin("example.com"); + checkFromTextSOA<MissingNameOrigin, isc::Exception>( + "ns root 2010012601 1H 5M 1000H 20M", &origin, true, false); + + // with the '@' notation with a separate origin (lexer version only; + // string version would throw) + const Name full_mname("ns.example.com"); + checkFromTextSOA<MissingNameOrigin, isc::Exception>( + "@ root.example.com. 2010012601 1H 5M 1000H 20M", &full_mname, true, + false); + + // bad MNAME/RNAMEs + checkFromTextSOA<EmptyLabel, EmptyLabel>( + "bad..example. . 2010012601 1H 5M 1000H 20M"); + checkFromTextSOA<EmptyLabel, EmptyLabel>( + ". bad..example. 2010012601 1H 5M 1000H 20M"); + + // Names shouldn't be quoted. + checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>( + "\".\" . 0 0 0 0 0"); + checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>( + ". \".\" 0 0 0 0 0"); + + // Missing MAME or RNAME: for the string version, the serial would be + // tried as RNAME and result in "not absolute". For the lexer version, + // it reaches the end-of-line, missing min TTL. + checkFromTextSOA<MissingNameOrigin, MasterLexer::LexerError>( + ". 2010012601 0 0 0 0", &Name::ROOT_NAME()); + + // bad serial. the string version converts lexer error to + // InvalidRdataText. + checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>( + ". . bad 0 0 0 0"); + + // bad serial; exceeding the uint32_t range (4294967296 = 2^32) + checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>( + ". . 4294967296 0 0 0 0"); + + // Bad format for other numeric parameters. These will be tried as a TTL, + // and result in an exception there. + checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>( + ". . 2010012601 bad 0 0 0"); + checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>( + ". . 2010012601 4294967296 0 0 0"); + checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>( + ". . 2010012601 0 bad 0 0"); + checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>( + ". . 2010012601 0 4294967296 0 0"); + checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>( + ". . 2010012601 0 0 bad 0"); + checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>( + ". . 2010012601 0 0 4294967296 0"); + checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>( + ". . 2010012601 0 0 0 bad"); + checkFromTextSOA<InvalidRRTTL, InvalidRRTTL>( + ". . 2010012601 0 0 0 4294967296"); + + // No space between RNAME and serial. This case is the same as missing + // M/RNAME. + checkFromTextSOA<MissingNameOrigin, MasterLexer::LexerError>( + ". example.0 0 0 0 0", &Name::ROOT_NAME()); + + // Extra parameter. string version immediately detects the error. + // lexer version defers the check to the upper layer (we pass origin + // to skip the check with the string version). + checkFromTextSOA<InvalidRdataText, isc::Exception>( + "ns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M " + "extra", &origin, true, false); + + // Likewise. Redundant newline is also considered an error. The lexer + // version accepts trailing newline, but not the beginning one (where + // the lexer expects a string excluding newline and EOF). + checkFromTextSOA<InvalidRdataText, isc::Exception>( + "ns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M\n", + 0, true, false); + checkFromTextSOA<InvalidRdataText, MasterLexer::LexerError>( + "\nns.example.com. root.example.com. 2010012601 1H 5M 1000H 20M", + 0, true, true); +} + +TEST_F(Rdata_SOA_Test, createFromWire) { + EXPECT_EQ(0, rdata_soa.compare( + *rdataFactoryFromFile(RRType("SOA"), RRClass("IN"), + "rdata_soa_fromWire"))); + // TBD: more tests +} + +TEST_F(Rdata_SOA_Test, createFromLexer) { + EXPECT_EQ(0, rdata_soa.compare( + *test::createRdataUsingLexer(RRType::SOA(), RRClass::IN(), + "ns.example.com. root.example.com. " + "2010012601 3600 300 3600000 1200"))); +} + +TEST_F(Rdata_SOA_Test, toWireRenderer) { + renderer.skip(2); + rdata_soa.toWire(renderer); + + vector<unsigned char> data; + UnitTestUtil::readWireData("rdata_soa_fromWire", data); + matchWireData(&data[2], data.size() - 2, + static_cast<const uint8_t *>(renderer.getData()) + 2, + renderer.getLength() - 2); +} + +TEST_F(Rdata_SOA_Test, toWireBuffer) { + obuffer.skip(2); + rdata_soa.toWire(obuffer); + vector<unsigned char> data; + UnitTestUtil::readWireData("rdata_soa_toWireUncompressed.wire", data); + matchWireData(&data[2], data.size() - 2, + static_cast<const uint8_t *>(obuffer.getData()) + 2, + obuffer.getLength() - 2); +} + +TEST_F(Rdata_SOA_Test, toText) { + EXPECT_EQ("ns.example.com. root.example.com. " + "2010012601 3600 300 3600000 1200", rdata_soa.toText()); +} + +TEST_F(Rdata_SOA_Test, getSerial) { + EXPECT_EQ(2010012601, rdata_soa.getSerial().getValue()); +} + +TEST_F(Rdata_SOA_Test, getMinimum) { + EXPECT_EQ(1200, rdata_soa.getMinimum()); + + // Also check with a very large number (with the MSB being 1). + EXPECT_EQ(2154848336u, generic::SOA(Name("ns.example.com"), + Name("root.example.com"), + 0, 0, 0, 0, 0x80706050).getMinimum()); +} + +void +compareCheck(const generic::SOA& small, const generic::SOA& large) { + EXPECT_GT(0, small.compare(large)); + EXPECT_LT(0, large.compare(small)); +} + +TEST_F(Rdata_SOA_Test, compare) { + // Check simple equivalence + EXPECT_EQ(0, rdata_soa.compare(generic::SOA( + "ns.example.com. root.example.com. " + "2010012601 3600 300 3600000 1200"))); + // Check name comparison is case insensitive + EXPECT_EQ(0, rdata_soa.compare(generic::SOA( + "NS.example.com. root.EXAMPLE.com. " + "2010012601 3600 300 3600000 1200"))); + + // Check names are compared in the RDATA comparison semantics (different + // from DNSSEC ordering for owner names) + compareCheck(generic::SOA("a.example. . 0 0 0 0 0"), + generic::SOA("example. . 0 0 0 0 0")); + compareCheck(generic::SOA(". a.example. 0 0 0 0 0"), + generic::SOA(". example. 0 0 0 0 0")); + + // Compare other numeric fields: 1076895760 = 0x40302010, + // 270544960 = 0x10203040. These are chosen to make sure that machine + // endianness doesn't confuse the comparison results. + compareCheck(generic::SOA(". . 270544960 0 0 0 0"), + generic::SOA(". . 1076895760 0 0 0 0")); + compareCheck(generic::SOA(". . 0 270544960 0 0 0"), + generic::SOA(". . 0 1076895760 0 0 0")); + compareCheck(generic::SOA(". . 0 0 270544960 0 0"), + generic::SOA(". . 0 0 1076895760 0 0")); + compareCheck(generic::SOA(". . 0 0 0 270544960 0"), + generic::SOA(". . 0 0 0 1076895760 0")); + compareCheck(generic::SOA(". . 0 0 0 0 270544960"), + generic::SOA(". . 0 0 0 0 1076895760")); +} + +} diff --git a/src/lib/dns/tests/rdata_tkey_unittest.cc b/src/lib/dns/tests/rdata_tkey_unittest.cc new file mode 100644 index 0000000..31c64bc --- /dev/null +++ b/src/lib/dns/tests/rdata_tkey_unittest.cc @@ -0,0 +1,450 @@ +// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <string> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> +#include <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/time_utils.h> +#include <dns/tsigerror.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { + +class Rdata_TKEY_Test : public RdataTest { +protected: + Rdata_TKEY_Test() : + // no Key or Other Data + valid_text1("gss-tsig. 20210501120000 20210501130000 GSS-API NOERROR 0 0"), + // Key but no Other Data + valid_text2("GSS-TSIG. 20210501120000 20210501130000 GSS-API BADSIG " + "12 FAKEFAKEFAKEFAKE 0"), + // Key and Other Data + valid_text3("gss.tsig. 20210501120000 20210501130000 GSS-API BADSIG " + "12 FAKEFAKEFAKEFAKE 6 FAKEFAKE"), + // Key and Other Data (with Error that doesn't expect Other Data) + valid_text4("gss.tsig. 20210501120000 20210501130000 3 BADSIG 12 " + "FAKEFAKEFAKEFAKE 6 FAKEFAKE"), + // numeric error code + valid_text5("GSS-TSIG. 20210501120000 20210501130000 GSS-API 2845 12 " + "FAKEFAKEFAKEFAKE 0"), + // GSS-API mode + valid_text6("gss-tsig. 20210501120000 20210501130000 GSS-API 0 12 " + "FAKEFAKEFAKEFAKE 0"), + rdata_tkey(valid_text1) + {} + + void checkFromText_None(const string& rdata_str) { + checkFromText<generic::TKEY, isc::Exception, isc::Exception>( + rdata_str, rdata_tkey, false, false); + } + + void checkFromText_InvalidTime(const string& rdata_str) { + checkFromText<generic::TKEY, InvalidTime, InvalidTime>( + rdata_str, rdata_tkey, true, true); + } + + void checkFromText_InvalidText(const string& rdata_str) { + checkFromText<generic::TKEY, InvalidRdataText, InvalidRdataText>( + rdata_str, rdata_tkey, true, true); + } + + void checkFromText_BadValue(const string& rdata_str) { + checkFromText<generic::TKEY, BadValue, BadValue>( + rdata_str, rdata_tkey, true, true); + } + + void checkFromText_LexerError(const string& rdata_str) { + checkFromText + <generic::TKEY, InvalidRdataText, MasterLexer::LexerError>( + rdata_str, rdata_tkey, true, true); + } + + void checkFromText_TooLongLabel(const string& rdata_str) { + checkFromText<generic::TKEY, TooLongLabel, TooLongLabel>( + rdata_str, rdata_tkey, true, true); + } + + void checkFromText_EmptyLabel(const string& rdata_str) { + checkFromText<generic::TKEY, EmptyLabel, EmptyLabel>( + rdata_str, rdata_tkey, true, true); + } + + void checkFromText_BadString(const string& rdata_str) { + checkFromText + <generic::TKEY, InvalidRdataText, isc::Exception>( + rdata_str, rdata_tkey, true, false); + } + + template <typename Output> + void toWireCommonChecks(Output& output) const; + + const string valid_text1; + const string valid_text2; + const string valid_text3; + const string valid_text4; + const string valid_text5; + const string valid_text6; + vector<uint8_t> expect_data; + const generic::TKEY rdata_tkey; // commonly used test RDATA +}; + +TEST_F(Rdata_TKEY_Test, fromText) { + // normal case. it also tests getter methods. + EXPECT_EQ(Name("gss-tsig"), rdata_tkey.getAlgorithm()); + EXPECT_EQ(1619870400, rdata_tkey.getInception()); + EXPECT_EQ("20210501120000", rdata_tkey.getInceptionDate()); + EXPECT_EQ(1619874000, rdata_tkey.getExpire()); + EXPECT_EQ("20210501130000", rdata_tkey.getExpireDate()); + EXPECT_EQ(3, rdata_tkey.getMode()); + EXPECT_EQ(0, rdata_tkey.getError()); + EXPECT_EQ(0, rdata_tkey.getKeyLen()); + EXPECT_FALSE(rdata_tkey.getKey()); + EXPECT_EQ(0, rdata_tkey.getOtherLen()); + EXPECT_FALSE(rdata_tkey.getOtherData()); + + generic::TKEY tkey2(valid_text2); + EXPECT_EQ(12, tkey2.getKeyLen()); + EXPECT_EQ(TSIGError::BAD_SIG_CODE, tkey2.getError()); + + generic::TKEY tkey3(valid_text3); + EXPECT_EQ(6, tkey3.getOtherLen()); + + // The other data is unusual, but we don't reject it. + EXPECT_NO_THROW(generic::TKEY tkey4(valid_text4)); + + // numeric representation of TKEY error + generic::TKEY tkey5(valid_text5); + EXPECT_EQ(2845, tkey5.getError()); + + // symbolic representation of TKEY mode + generic::TKEY tkey6(valid_text6); + EXPECT_EQ(generic::TKEY::GSS_API_MODE, tkey6.getMode()); + + // not fully qualified algorithm name + generic::TKEY tkey1("gss-tsig 20210501120000 20210501130000 3 0 0 0"); + EXPECT_EQ(0, tkey1.compare(rdata_tkey)); + + // multi-line rdata + checkFromText_None("gss-tsig. ( 20210501120000 20210501130000 GSS-API \n" + "NOERROR 0 0 )"); +}; + +TEST_F(Rdata_TKEY_Test, badText) { + // too many fields + checkFromText_BadString(valid_text1 + " 0 0"); + // not enough fields + checkFromText_LexerError("foo 20210501120000 20210501130000 0 BADKEY"); + // bad domain name + checkFromText_TooLongLabel( + "0123456789012345678901234567890123456789012345678901234567890123" + " 20210501120000 20210501130000 0 0 0 0"); + checkFromText_EmptyLabel("foo..bar 20210501120000 20210501130000 0 0 0 0"); + // invalid inception (no digit) + checkFromText_InvalidTime("foo TIME 20210501130000 0 0 0 0"); + // invalid inception (bad format) + checkFromText_InvalidTime("foo 0 20210501130000 0 0 0 0"); + // invalid expire (no digit) + checkFromText_InvalidTime("foo 20210501120000 TIME 0 0 0 0"); + // invalid expire (bad format) + checkFromText_InvalidTime("foo 20210501120000 0 0 0 0 0"); + // Unknown mode + checkFromText_InvalidText("foo 20210501120000 20210501130000 TEST 0 0 0"); + // Numeric mode is is too large + checkFromText_InvalidText("foo 20210501120000 20210501130000 65536 0 0 0"); + // Numeric mode is negative + checkFromText_InvalidText("foo 20210501120000 20210501130000 -1 0 0 0 0"); + // Unknown error code + checkFromText_InvalidText("foo 20210501120000 20210501130000 0 TEST 0 0"); + // Numeric error code is too large + checkFromText_InvalidText("foo 20210501120000 20210501130000 0 65536 0 0"); + // Numeric error code is negative + checkFromText_InvalidText("foo 20210501120000 20210501130000 0 -1 0 0"); + // Key len is too large + checkFromText_InvalidText("foo 20210501120000 20210501130000 0 0 65536 0"); + // invalid Key len (negative) + checkFromText_LexerError("foo 20210501120000 20210501130000 0 0 -1 0"); + // invalid Key len (not a number) + checkFromText_LexerError("foo 20210501120000 20210501130000 0 0 MACSIZE 0"); + // Key len and Key mismatch + checkFromText_InvalidText("foo 20210501120000 20210501130000 0 0 9 FAKE 0"); + // Key is bad base64 + checkFromText_BadValue("foo 20210501120000 20210501130000 0 0 3 FAK= 0"); + // Other len is too large + checkFromText_InvalidText("foo 20210501120000 20210501130000 0 0 0 65536 FAKE"); + // Other len is negative + checkFromText_LexerError("foo 20210501120000 20210501130000 0 0 0 -1 FAKE"); + // invalid Other len + checkFromText_LexerError("foo 20210501120000 20210501130000 0 0 0 LEN FAKE"); + // Other len and data mismatch + checkFromText_InvalidText("foo 20210501120000 20210501130000 0 0 0 9 FAKE"); +} + +void +fromWireCommonChecks(const generic::TKEY& tkey) { + EXPECT_EQ(Name("gss-tsig"), tkey.getAlgorithm()); + EXPECT_EQ(1619870400, tkey.getInception()); + EXPECT_EQ("20210501120000", tkey.getInceptionDate()); + EXPECT_EQ(1619874000, tkey.getExpire()); + EXPECT_EQ("20210501130000", tkey.getExpireDate()); + EXPECT_EQ(3, tkey.getMode()); + EXPECT_EQ(0, tkey.getError()); + + vector<uint8_t> expect_key(32, 'x'); + matchWireData(&expect_key[0], expect_key.size(), + tkey.getKey(), tkey.getKeyLen()); + + EXPECT_EQ(0, tkey.getOtherLen()); + EXPECT_FALSE(tkey.getOtherData()); +} + +TEST_F(Rdata_TKEY_Test, createFromWire) { + RdataPtr rdata(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire1.wire")); + fromWireCommonChecks(dynamic_cast<generic::TKEY&>(*rdata)); +} + +TEST_F(Rdata_TKEY_Test, createFromWireWithOtherData) { + RdataPtr rdata(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire2.wire")); + const generic::TKEY& tkey(dynamic_cast<generic::TKEY&>(*rdata)); + + vector<uint8_t> expect_key(32, 'x'); + matchWireData(&expect_key[0], expect_key.size(), + tkey.getKey(), tkey.getKeyLen()); + + vector<uint8_t> expect_data = { 'a', 'b', 'c', 'd', '0', '1', '2', '3' }; + matchWireData(&expect_data[0], expect_data.size(), + tkey.getOtherData(), tkey.getOtherLen()); +} + +TEST_F(Rdata_TKEY_Test, createFromWireWithoutKey) { + RdataPtr rdata(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire3.wire")); + const generic::TKEY& tkey(dynamic_cast<generic::TKEY&>(*rdata)); + EXPECT_EQ(0, tkey.getKeyLen()); + EXPECT_FALSE(tkey.getKey()); + + vector<uint8_t> expect_data = { 'a', 'b', 'c', 'd', '0', '1', '2', '3' }; + matchWireData(&expect_data[0], expect_data.size(), + tkey.getOtherData(), tkey.getOtherLen()); +} + +TEST_F(Rdata_TKEY_Test, createFromWireWithCompression) { + RdataPtr rdata(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire4.wire", + // we need to skip the dummy name: + Name("gss-tsig").getLength())); + fromWireCommonChecks(dynamic_cast<generic::TKEY&>(*rdata)); +} + +TEST_F(Rdata_TKEY_Test, badFromWire) { + // RDLENGTH is too short: + EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire5.wire"), + InvalidRdataLength); + // RDLENGTH is too long: + EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire6.wire"), + InvalidRdataLength); + // Algorithm name is broken: + EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire7.wire"), + DNSMessageFORMERR); + // Key length is bogus: + EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire8.wire"), + isc::OutOfRange); + // Other-data length is bogus: + EXPECT_THROW(rdataFactoryFromFile(RRType::TKEY(), RRClass::ANY(), + "rdata_tkey_fromWire9.wire"), + isc::OutOfRange); +} + +TEST_F(Rdata_TKEY_Test, copyConstruct) { + const generic::TKEY copy(rdata_tkey); + EXPECT_EQ(0, copy.compare(rdata_tkey)); + + // Check the copied data is valid even after the original is deleted + generic::TKEY* copy2 = new generic::TKEY(rdata_tkey); + generic::TKEY copy3(*copy2); + delete copy2; + EXPECT_EQ(0, copy3.compare(rdata_tkey)); +} + +TEST_F(Rdata_TKEY_Test, createFromParams) { + EXPECT_EQ(0, rdata_tkey.compare(generic::TKEY(Name("gss-tsig"), + 1619870400, + 1619874000, + 3, 0, 0, 0, 0, 0))); + + const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84, + 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 }; + EXPECT_EQ(0, generic::TKEY(valid_text2).compare( + generic::TKEY(Name("GSS-TSIG"), 1619870400, 1619874000, + 3, 16, 12, fake_data, 0, 0))); + + const uint8_t fake_data2[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 }; + EXPECT_EQ(0, generic::TKEY(valid_text3).compare( + generic::TKEY(Name("gss.tsig"), 1619870400, 1619874000, + 3, 16, 12, fake_data, 6, fake_data2))); + + EXPECT_THROW(generic::TKEY(Name("gss-tsig"), 0, 0, 0, 0, 0, fake_data, 0, 0), + isc::InvalidParameter); + EXPECT_THROW(generic::TKEY(Name("gss-tsig"), 0, 0, 0, 0, 12, 0, 0, 0), + isc::InvalidParameter); + EXPECT_THROW(generic::TKEY(Name("gss-tsig"), 0, 0, 0, 0, 0, 0, 0, fake_data), + isc::InvalidParameter); + EXPECT_THROW(generic::TKEY(Name("fake_data"), 0, 0, 0, 0, 0, 0, 6, 0), + isc::InvalidParameter); +} + +TEST_F(Rdata_TKEY_Test, assignment) { + generic::TKEY copy(valid_text2); + copy = rdata_tkey; + EXPECT_EQ(0, copy.compare(rdata_tkey)); + + // Check if the copied data is valid even after the original is deleted + generic::TKEY* copy2 = new generic::TKEY(rdata_tkey); + generic::TKEY copy3(valid_text2); + copy3 = *copy2; + delete copy2; + EXPECT_EQ(0, copy3.compare(rdata_tkey)); + + // Self assignment + copy = *© + EXPECT_EQ(0, copy.compare(rdata_tkey)); +} + +template <typename Output> +void +Rdata_TKEY_Test::toWireCommonChecks(Output& output) const { + vector<uint8_t> expect_data; + + output.clear(); + expect_data.clear(); + rdata_tkey.toWire(output); + // read the expected wire format data and trim the RDLEN part. + UnitTestUtil::readWireData("rdata_tkey_toWire1.wire", expect_data); + expect_data.erase(expect_data.begin(), expect_data.begin() + 2); + matchWireData(&expect_data[0], expect_data.size(), + output.getData(), output.getLength()); + + expect_data.clear(); + output.clear(); + generic::TKEY(valid_text2).toWire(output); + UnitTestUtil::readWireData("rdata_tkey_toWire2.wire", expect_data); + expect_data.erase(expect_data.begin(), expect_data.begin() + 2); + matchWireData(&expect_data[0], expect_data.size(), + output.getData(), output.getLength()); + + expect_data.clear(); + output.clear(); + generic::TKEY(valid_text3).toWire(output); + UnitTestUtil::readWireData("rdata_tkey_toWire3.wire", expect_data); + expect_data.erase(expect_data.begin(), expect_data.begin() + 2); + matchWireData(&expect_data[0], expect_data.size(), + output.getData(), output.getLength()); +} + +TEST_F(Rdata_TKEY_Test, toWireBuffer) { + toWireCommonChecks<OutputBuffer>(obuffer); +} + +TEST_F(Rdata_TKEY_Test, toWireRenderer) { + toWireCommonChecks<MessageRenderer>(renderer); + + // check algorithm name won't compressed when it would otherwise. + expect_data.clear(); + renderer.clear(); + renderer.writeName(Name("gss-tsig")); + renderer.writeUint16(26); // RDLEN + rdata_tkey.toWire(renderer); + UnitTestUtil::readWireData("rdata_tkey_toWire4.wire", expect_data); + matchWireData(&expect_data[0], expect_data.size(), + renderer.getData(), renderer.getLength()); + + // check algorithm can be used as a compression target. + expect_data.clear(); + renderer.clear(); + renderer.writeUint16(26); + rdata_tkey.toWire(renderer); + renderer.writeName(Name("gss-tsig")); + UnitTestUtil::readWireData("rdata_tkey_toWire5.wire", expect_data); + matchWireData(&expect_data[0], expect_data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_TKEY_Test, toText) { + EXPECT_EQ(valid_text1, rdata_tkey.toText()); + EXPECT_EQ(valid_text2, generic::TKEY(valid_text2).toText()); + EXPECT_EQ(valid_text3, generic::TKEY(valid_text3).toText()); + EXPECT_EQ(valid_text5, generic::TKEY(valid_text5).toText()); +} + +TEST_F(Rdata_TKEY_Test, compare) { + // test RDATAs, sorted in the ascending order. + // "AAAA" encoded in BASE64 corresponds to 0x000000, so it should be the + // smallest data of the same length. + vector<generic::TKEY> compare_set; + compare_set.push_back(generic::TKEY("a.example 20210501120000 " + "20210501130000 3 0 0 0")); + compare_set.push_back(generic::TKEY("example 20210501120000 " + "20210501130000 3 0 0 0")); + compare_set.push_back(generic::TKEY("example 20210501120001 " + "20210501130000 3 0 0 0")); + compare_set.push_back(generic::TKEY("example 20210501120001 " + "20210501130001 3 0 0 0")); + compare_set.push_back(generic::TKEY("example 20210501120001 " + "20210501130001 4 0 0 0")); + compare_set.push_back(generic::TKEY("example 20210501120001 " + "20210501130001 4 1 0 0")); + compare_set.push_back(generic::TKEY("example 20210501120001 " + "20210501130001 4 1 3 AAAA 0")); + compare_set.push_back(generic::TKEY("example 20210501120001 " + "20210501130001 4 1 3 FAKE 0")); + compare_set.push_back(generic::TKEY("example 20210501120001 " + "20210501130001 4 1 3 FAKE 3 AAAA")); + compare_set.push_back(generic::TKEY("example 20210501120001 " + "20210501130001 4 1 3 FAKE 3 FAKE")); + + EXPECT_EQ(0, + compare_set[0].compare(generic::TKEY("A.EXAMPLE 20210501120000 " + "20210501130000 3 0 0 0"))); + + vector<generic::TKEY>::const_iterator it; + vector<generic::TKEY>::const_iterator it_end = compare_set.end(); + for (it = compare_set.begin(); it != it_end - 1; ++it) { + EXPECT_GT(0, (*it).compare(*(it + 1))); + EXPECT_LT(0, (*(it + 1)).compare(*it)); + } + + // comparison attempt between incompatible RR types should be rejected + EXPECT_THROW(rdata_tkey.compare(*RdataTest::rdata_nomatch), bad_cast); +} +} diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc new file mode 100644 index 0000000..a9cab14 --- /dev/null +++ b/src/lib/dns/tests/rdata_tsig_unittest.cc @@ -0,0 +1,423 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <string> + +#include <exceptions/exceptions.h> + +#include <util/buffer.h> +#include <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/tsigerror.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using namespace isc::dns::rdata::any; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { + +class Rdata_TSIG_Test : public RdataTest { +protected: + Rdata_TSIG_Test() : + // no MAC or Other Data + valid_text1("hmac-md5.sig-alg.reg.int. 1286779327 300 " + "0 16020 BADKEY 0"), + // MAC but no Other Data + valid_text2("hmac-sha256. 1286779327 300 12 " + "FAKEFAKEFAKEFAKE 16020 BADSIG 0"), + // MAC and Other Data + valid_text3("hmac-sha1. 1286779327 300 12 " + "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE"), + // MAC and Other Data (with Error that doesn't expect Other Data) + valid_text4("hmac-sha1. 1286779327 300 12 " + "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE"), + // numeric error code + valid_text5("hmac-sha256. 1286779327 300 12 " + "FAKEFAKEFAKEFAKE 16020 2845 0"), + rdata_tsig(valid_text1) + {} + + void checkFromText_None(const string& rdata_str) { + checkFromText<TSIG, isc::Exception, isc::Exception>( + rdata_str, rdata_tsig, false, false); + } + + void checkFromText_InvalidText(const string& rdata_str) { + checkFromText<TSIG, InvalidRdataText, InvalidRdataText>( + rdata_str, rdata_tsig, true, true); + } + + void checkFromText_BadValue(const string& rdata_str) { + checkFromText<TSIG, BadValue, BadValue>( + rdata_str, rdata_tsig, true, true); + } + + void checkFromText_LexerError(const string& rdata_str) { + checkFromText + <TSIG, InvalidRdataText, MasterLexer::LexerError>( + rdata_str, rdata_tsig, true, true); + } + + void checkFromText_TooLongLabel(const string& rdata_str) { + checkFromText<TSIG, TooLongLabel, TooLongLabel>( + rdata_str, rdata_tsig, true, true); + } + + void checkFromText_EmptyLabel(const string& rdata_str) { + checkFromText<TSIG, EmptyLabel, EmptyLabel>( + rdata_str, rdata_tsig, true, true); + } + + void checkFromText_BadString(const string& rdata_str) { + checkFromText + <TSIG, InvalidRdataText, isc::Exception>( + rdata_str, rdata_tsig, true, false); + } + + template <typename Output> + void toWireCommonChecks(Output& output) const; + + const string valid_text1; + const string valid_text2; + const string valid_text3; + const string valid_text4; + const string valid_text5; + vector<uint8_t> expect_data; + const TSIG rdata_tsig; // commonly used test RDATA +}; + +TEST_F(Rdata_TSIG_Test, fromText) { + // normal case. it also tests getter methods. + EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), rdata_tsig.getAlgorithm()); + EXPECT_EQ(1286779327, rdata_tsig.getTimeSigned()); + EXPECT_EQ(300, rdata_tsig.getFudge()); + EXPECT_EQ(0, rdata_tsig.getMACSize()); + EXPECT_FALSE(rdata_tsig.getMAC()); + EXPECT_EQ(16020, rdata_tsig.getOriginalID()); + EXPECT_EQ(TSIGError::BAD_KEY_CODE, rdata_tsig.getError()); + EXPECT_EQ(0, rdata_tsig.getOtherLen()); + EXPECT_FALSE(rdata_tsig.getOtherData()); + + TSIG tsig2(valid_text2); + EXPECT_EQ(12, tsig2.getMACSize()); + EXPECT_EQ(TSIGError::BAD_SIG_CODE, tsig2.getError()); + + TSIG tsig3(valid_text3); + EXPECT_EQ(6, tsig3.getOtherLen()); + + // The other data is unusual, but we don't reject it. + EXPECT_NO_THROW(TSIG tsig4(valid_text4)); + + // numeric representation of TSIG error + TSIG tsig5(valid_text5); + EXPECT_EQ(2845, tsig5.getError()); + + // not fully qualified algorithm name + TSIG tsig1("hmac-md5.sig-alg.reg.int 1286779327 300 0 16020 BADKEY 0"); + EXPECT_EQ(0, tsig1.compare(rdata_tsig)); + + // multi-line rdata + checkFromText_None("hmac-md5.sig-alg.reg.int. ( 1286779327 300 \n" + "0 16020 BADKEY 0 )"); + + // short-form HMAC-MD5 name + const TSIG tsig6("hmac-md5. 1286779327 300 0 16020 BADKEY 0"); + EXPECT_EQ(0, tsig6.compare(rdata_tsig)); +}; + +TEST_F(Rdata_TSIG_Test, badText) { + // too many fields + checkFromText_BadString(valid_text1 + " 0 0"); + // not enough fields + checkFromText_LexerError("foo 0 0 0 0 BADKEY"); + // bad domain name + checkFromText_TooLongLabel( + "0123456789012345678901234567890123456789012345678901234567890123" + " 0 0 0 0 BADKEY 0"); + checkFromText_EmptyLabel("foo..bar 0 0 0 0 BADKEY"); + // time is too large (2814...6 is 2^48) + checkFromText_InvalidText("foo 281474976710656 0 0 0 BADKEY 0"); + // invalid time (negative) + checkFromText_InvalidText("foo -1 0 0 0 BADKEY 0"); + // invalid time (not a number) + checkFromText_InvalidText("foo TIME 0 0 0 BADKEY 0"); + // fudge is too large + checkFromText_InvalidText("foo 0 65536 0 0 BADKEY 0"); + // invalid fudge (negative) + checkFromText_LexerError("foo 0 -1 0 0 BADKEY 0"); + // invalid fudge (not a number) + checkFromText_LexerError("foo 0 FUDGE 0 0 BADKEY 0"); + // MAC size is too large + checkFromText_InvalidText("foo 0 0 65536 0 BADKEY 0"); + // invalid MAC size (negative) + checkFromText_LexerError("foo 0 0 -1 0 BADKEY 0"); + // invalid MAC size (not a number) + checkFromText_LexerError("foo 0 0 MACSIZE 0 BADKEY 0"); + // MAC size and MAC mismatch + checkFromText_InvalidText("foo 0 0 9 FAKE 0 BADKEY 0"); + // MAC is bad base64 + checkFromText_BadValue("foo 0 0 3 FAK= 0 BADKEY 0"); + // Unknown error code + checkFromText_InvalidText("foo 0 0 0 0 TEST 0"); + // Numeric error code is too large + checkFromText_InvalidText("foo 0 0 0 0 65536 0"); + // Numeric error code is negative + checkFromText_InvalidText("foo 0 0 0 0 -1 0"); + // Other len is too large + checkFromText_InvalidText("foo 0 0 0 0 NOERROR 65536 FAKE"); + // Other len is negative + checkFromText_LexerError("foo 0 0 0 0 NOERROR -1 FAKE"); + // invalid Other len + checkFromText_LexerError("foo 0 0 0 0 NOERROR LEN FAKE"); + // Other len and data mismatch + checkFromText_InvalidText("foo 0 0 0 0 NOERROR 9 FAKE"); +} + +void +fromWireCommonChecks(const TSIG& tsig) { + EXPECT_EQ(Name("hmac-sha256"), tsig.getAlgorithm()); + EXPECT_EQ(1286978795, tsig.getTimeSigned()); + EXPECT_EQ(300, tsig.getFudge()); + + vector<uint8_t> expect_mac(32, 'x'); + matchWireData(&expect_mac[0], expect_mac.size(), + tsig.getMAC(), tsig.getMACSize()); + + EXPECT_EQ(2845, tsig.getOriginalID()); + + EXPECT_EQ(0, tsig.getOtherLen()); + EXPECT_FALSE(tsig.getOtherData()); +} + +TEST_F(Rdata_TSIG_Test, createFromWire) { + RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire1.wire")); + fromWireCommonChecks(dynamic_cast<TSIG&>(*rdata)); +} + +TEST_F(Rdata_TSIG_Test, createFromWireWithOtherData) { + RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire2.wire")); + const TSIG& tsig(dynamic_cast<TSIG&>(*rdata)); + + EXPECT_EQ(18, tsig.getError()); + const uint64_t otherdata = 1286978795 + 300 + 1; // time-signed + fudge + 1 + expect_data.resize(6); + expect_data[0] = (otherdata >> 40); + expect_data[1] = ((otherdata >> 32) & 0xff); + expect_data[2] = ((otherdata >> 24) & 0xff); + expect_data[3] = ((otherdata >> 16) & 0xff); + expect_data[4] = ((otherdata >> 8) & 0xff); + expect_data[5] = (otherdata & 0xff); + matchWireData(&expect_data[0], expect_data.size(), + tsig.getOtherData(), tsig.getOtherLen()); +} + +TEST_F(Rdata_TSIG_Test, createFromWireWithoutMAC) { + RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire3.wire")); + const TSIG& tsig(dynamic_cast<TSIG&>(*rdata)); + EXPECT_EQ(16, tsig.getError()); + EXPECT_EQ(0, tsig.getMACSize()); + EXPECT_FALSE(tsig.getMAC()); +} + +TEST_F(Rdata_TSIG_Test, createFromWireWithCompression) { + RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire4.wire", + // we need to skip the dummy name: + Name("hmac-sha256").getLength())); + fromWireCommonChecks(dynamic_cast<TSIG&>(*rdata)); +} + +TEST_F(Rdata_TSIG_Test, badFromWire) { + // RDLENGTH is too short: + EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire5.wire"), + InvalidRdataLength); + // RDLENGTH is too long: + EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire6.wire"), + InvalidRdataLength); + // Algorithm name is broken: + EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire7.wire"), + DNSMessageFORMERR); + // MAC size is bogus: + EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire8.wire"), + isc::OutOfRange); + // Other-data length is bogus: + EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(), + "rdata_tsig_fromWire9.wire"), + isc::OutOfRange); +} + +TEST_F(Rdata_TSIG_Test, copyConstruct) { + const TSIG copy(rdata_tsig); + EXPECT_EQ(0, copy.compare(rdata_tsig)); + + // Check the copied data is valid even after the original is deleted + TSIG* copy2 = new TSIG(rdata_tsig); + TSIG copy3(*copy2); + delete copy2; + EXPECT_EQ(0, copy3.compare(rdata_tsig)); +} + +TEST_F(Rdata_TSIG_Test, createFromParams) { + EXPECT_EQ(0, rdata_tsig.compare(TSIG(Name("hmac-md5.sig-alg.reg.int"), + 1286779327, 300, 0, 0, 16020, 17, 0, 0))); + + const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84, + 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 }; + EXPECT_EQ(0, TSIG(valid_text2).compare(TSIG(Name("hmac-sha256"), 1286779327, 300, 12, + fake_data, 16020, 16, 0, 0))); + + const uint8_t fake_data2[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 }; + EXPECT_EQ(0, TSIG(valid_text3).compare(TSIG(Name("hmac-sha1"), 1286779327, 300, 12, + fake_data, 16020, 18, 6, fake_data2))); + + EXPECT_THROW(TSIG(Name("hmac-sha256"), 1ULL << 48, 300, 12, fake_data, 16020, 18, 6, fake_data2), + isc::OutOfRange); + EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 0, fake_data, 16020, 18, 0, 0), + isc::InvalidParameter); + EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 12, 0, 16020, 18, 0, 0), + isc::InvalidParameter); + EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 0, 0, 16020, 18, 0, fake_data), + isc::InvalidParameter); + EXPECT_THROW(TSIG(Name("hmac-sha256"), 0, 300, 0, 0, 16020, 18, 6, 0), + isc::InvalidParameter); +} + +TEST_F(Rdata_TSIG_Test, assignment) { + TSIG copy(valid_text2); + copy = rdata_tsig; + EXPECT_EQ(0, copy.compare(rdata_tsig)); + + // Check if the copied data is valid even after the original is deleted + TSIG* copy2 = new TSIG(rdata_tsig); + TSIG copy3(valid_text2); + copy3 = *copy2; + delete copy2; + EXPECT_EQ(0, copy3.compare(rdata_tsig)); + + // Self assignment + copy = *© + EXPECT_EQ(0, copy.compare(rdata_tsig)); +} + +template <typename Output> +void +Rdata_TSIG_Test::toWireCommonChecks(Output& output) const { + vector<uint8_t> expect_data; + + output.clear(); + expect_data.clear(); + rdata_tsig.toWire(output); + // read the expected wire format data and trim the RDLEN part. + UnitTestUtil::readWireData("rdata_tsig_toWire1.wire", expect_data); + expect_data.erase(expect_data.begin(), expect_data.begin() + 2); + matchWireData(&expect_data[0], expect_data.size(), + output.getData(), output.getLength()); + + expect_data.clear(); + output.clear(); + TSIG(valid_text2).toWire(output); + UnitTestUtil::readWireData("rdata_tsig_toWire2.wire", expect_data); + expect_data.erase(expect_data.begin(), expect_data.begin() + 2); + matchWireData(&expect_data[0], expect_data.size(), + output.getData(), output.getLength()); + + expect_data.clear(); + output.clear(); + TSIG(valid_text3).toWire(output); + UnitTestUtil::readWireData("rdata_tsig_toWire3.wire", expect_data); + expect_data.erase(expect_data.begin(), expect_data.begin() + 2); + matchWireData(&expect_data[0], expect_data.size(), + output.getData(), output.getLength()); +} + +TEST_F(Rdata_TSIG_Test, toWireBuffer) { + toWireCommonChecks<OutputBuffer>(obuffer); +} + +TEST_F(Rdata_TSIG_Test, toWireRenderer) { + toWireCommonChecks<MessageRenderer>(renderer); + + // check algorithm name won't compressed when it would otherwise. + expect_data.clear(); + renderer.clear(); + renderer.writeName(Name("hmac-md5.sig-alg.reg.int")); + renderer.writeUint16(42); // RDLEN + rdata_tsig.toWire(renderer); + UnitTestUtil::readWireData("rdata_tsig_toWire4.wire", expect_data); + matchWireData(&expect_data[0], expect_data.size(), + renderer.getData(), renderer.getLength()); + + // check algorithm can be used as a compression target. + expect_data.clear(); + renderer.clear(); + renderer.writeUint16(42); + rdata_tsig.toWire(renderer); + renderer.writeName(Name("hmac-md5.sig-alg.reg.int")); + UnitTestUtil::readWireData("rdata_tsig_toWire5.wire", expect_data); + matchWireData(&expect_data[0], expect_data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_TSIG_Test, toText) { + EXPECT_EQ(valid_text1, rdata_tsig.toText()); + EXPECT_EQ(valid_text2, TSIG(valid_text2).toText()); + EXPECT_EQ(valid_text3, TSIG(valid_text3).toText()); + EXPECT_EQ(valid_text5, TSIG(valid_text5).toText()); +} + +TEST_F(Rdata_TSIG_Test, compare) { + // test RDATAs, sorted in the ascending order. + // "AAAA" encoded in BASE64 corresponds to 0x000000, so it should be the + // smallest data of the same length. + vector<TSIG> compare_set; + compare_set.push_back(TSIG("a.example 0 300 0 16020 0 0")); + compare_set.push_back(TSIG("example 0 300 0 16020 0 0")); + compare_set.push_back(TSIG("example 1 300 0 16020 0 0")); + compare_set.push_back(TSIG("example 1 600 0 16020 0 0")); + compare_set.push_back(TSIG("example 1 600 3 AAAA 16020 0 0")); + compare_set.push_back(TSIG("example 1 600 3 FAKE 16020 0 0")); + compare_set.push_back(TSIG("example 1 600 3 FAKE 16021 0 0")); + compare_set.push_back(TSIG("example 1 600 3 FAKE 16021 1 0")); + compare_set.push_back(TSIG("example 1 600 3 FAKE 16021 1 3 AAAA")); + compare_set.push_back(TSIG("example 1 600 3 FAKE 16021 1 3 FAKE")); + + EXPECT_EQ(0, compare_set[0].compare(TSIG("A.EXAMPLE 0 300 0 16020 0 0"))); + + vector<TSIG>::const_iterator it; + vector<TSIG>::const_iterator it_end = compare_set.end(); + for (it = compare_set.begin(); it != it_end - 1; ++it) { + EXPECT_GT(0, (*it).compare(*(it + 1))); + EXPECT_LT(0, (*(it + 1)).compare(*it)); + } + + // comparison attempt between incompatible RR types should be rejected + EXPECT_THROW(rdata_tsig.compare(*RdataTest::rdata_nomatch), bad_cast); +} +} diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc new file mode 100644 index 0000000..91f2735 --- /dev/null +++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc @@ -0,0 +1,394 @@ +// Copyright (C) 2011-2024 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/. + +// This is the common code for TXT tests. + +#include <config.h> + +#include <util/buffer.h> +#include <dns/exceptions.h> +#include <dns/rdataclass.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> + +#include <util/unittests/wiredata.h> + +#include <gtest/gtest.h> + +#include <string> +#include <sstream> +#include <vector> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { + +template<class T> +class RRTYPE : public RRType { +public: + RRTYPE(); +}; + +template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {} + +const uint8_t wiredata_txt_like[] = { + sizeof("Test-String") - 1, + 'T', 'e', 's', 't', '-', 'S', 't', 'r', 'i', 'n', 'g' +}; + +const uint8_t wiredata_nulltxt[] = { 0 }; + +template<class TXT_LIKE> +class Rdata_TXT_LIKE_Test : public RdataTest { +protected: + Rdata_TXT_LIKE_Test() : + wiredata_longesttxt(256, 'a'), + rdata_txt_like("Test-String"), + rdata_txt_like_empty("\"\""), + rdata_txt_like_quoted("\"Test-String\"") + { + wiredata_longesttxt[0] = 255; // adjust length + } + +protected: + vector<uint8_t> wiredata_longesttxt; + const TXT_LIKE rdata_txt_like; + const TXT_LIKE rdata_txt_like_empty; + const TXT_LIKE rdata_txt_like_quoted; +}; + +// The list of types we want to test. +typedef testing::Types<generic::TXT> Implementations; + +#ifdef TYPED_TEST_SUITE +TYPED_TEST_SUITE(Rdata_TXT_LIKE_Test, Implementations); +#else +TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations); +#endif + +TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) { + // Below we check the behavior for the "from text" constructors, both + // from std::string and with MasterLexer. The underlying implementation + // is the same, so both should work exactly same, but we confirm both + // cases. + + const std::string multi_line = "(\n \"Test-String\" )"; + const std::string escaped_txt = "Test\\045Strin\\g"; + + // test input for the lexer version + std::stringstream ss; + ss << "Test-String\n"; + ss << "\"Test-String\"\n"; // explicitly surrounded by '"'s + ss << multi_line << "\n"; // multi-line text with () + ss << escaped_txt << "\n"; // using the two types of escape with '\' + ss << "\"\"\n"; // empty string (note: still valid char-str) + ss << string(255, 'a') << "\n"; // Longest possible character-string. + ss << string(256, 'a') << "\n"; // char-string too long + ss << "\"Test-String\\\"\n"; // unbalanced quote + ss << "\"Test-String\\\"\"\n"; + this->lexer.pushSource(ss); + + // commonly used Rdata to compare below, created from wire + ConstRdataPtr const rdata = + this->rdataFactoryFromFile(RRTYPE<TypeParam>(), + RRClass("IN"), "rdata_txt_fromWire1"); + + // normal case is covered in toWireBuffer. First check the std::string + // case, then with MasterLexer. For the latter, we need to read and skip + // '\n'. These apply to most of the other cases below. + EXPECT_EQ(0, this->rdata_txt_like.compare(*rdata)); + EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // surrounding double-quotes shouldn't change the result. + EXPECT_EQ(0, this->rdata_txt_like_quoted.compare(*rdata)); + EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // multi-line input with () + EXPECT_EQ(0, TypeParam(multi_line).compare(*rdata)); + EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // for the same data using escape + EXPECT_EQ(0, TypeParam(escaped_txt).compare(*rdata)); + EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // Null character-string. + this->obuffer.clear(); + TypeParam(string("\"\"")).toWire(this->obuffer); + matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt), + this->obuffer.getData(), this->obuffer.getLength()); + + this->obuffer.clear(); + TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, this->loader_cb). + toWire(this->obuffer); + matchWireData(wiredata_nulltxt, sizeof(wiredata_nulltxt), + this->obuffer.getData(), this->obuffer.getLength()); + + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // Longest possible character-string. + this->obuffer.clear(); + TypeParam(string(255, 'a')).toWire(this->obuffer); + matchWireData(&this->wiredata_longesttxt[0], + this->wiredata_longesttxt.size(), + this->obuffer.getData(), this->obuffer.getLength()); + + this->obuffer.clear(); + TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, this->loader_cb). + toWire(this->obuffer); + matchWireData(&this->wiredata_longesttxt[0], + this->wiredata_longesttxt.size(), + this->obuffer.getData(), this->obuffer.getLength()); + + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // Too long text for a valid character-string. + EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong); + EXPECT_THROW(TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, + this->loader_cb), CharStringTooLong); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); + + // The escape character makes the double quote a part of character-string, + // so this is invalid input and should be rejected. + EXPECT_THROW(TypeParam("\"Test-String\\\""), InvalidRdataText); + EXPECT_THROW(TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, + this->loader_cb), MasterLexer::LexerError); + EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType()); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, createMultiStringsFromText) { + // Tests for "from text" variants construction with various forms of + // multi character-strings. + + std::vector<std::string > texts; + texts.push_back("\"Test-String\" \"Test-String\""); // most common form + texts.push_back("\"Test-String\"\"Test-String\""); // no space between'em + texts.push_back("\"Test-String\" Test-String"); // no '"' for one + texts.push_back("\"Test-String\"Test-String"); // and no space either + texts.push_back("Test-String \"Test-String\""); // no '"' for the other + texts.push_back("Test-String\"Test-String\""); // and no space either + + std::stringstream ss; + for (auto const& it : texts) { + ss << it << "\n"; + } + this->lexer.pushSource(ss); + + // The corresponding Rdata built from wire to compare in the checks below. + ConstRdataPtr const rdata = + this->rdataFactoryFromFile(RRTYPE<TypeParam>(), + RRClass("IN"), "rdata_txt_fromWire3.wire"); + + // Confirm we can construct the Rdata from the test text, both from + // std::string and with lexer, and that matches the from-wire data. + for (auto const& it : texts) { + SCOPED_TRACE(it); + EXPECT_EQ(0, TypeParam(it).compare(*rdata)); + + EXPECT_EQ(0, TypeParam(this->lexer, 0, MasterLoader::MANY_ERRORS, + this->loader_cb).compare(*rdata)); + EXPECT_EQ(MasterToken::END_OF_LINE, + this->lexer.getNextToken().getType()); + } +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, createFromTextExtra) { + // This is for the std::string version only: the input must end with EOF; + // an extra new-line will result in an exception. + EXPECT_THROW(TypeParam("\"Test-String\"\n"), InvalidRdataText); + // Same if there's a space before '\n' + EXPECT_THROW(TypeParam("\"Test-String\" \n"), InvalidRdataText); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, fromTextEmpty) { + // If the input text doesn't contain any character-string, it should be + // rejected + EXPECT_THROW(TypeParam(""), InvalidRdataText); + EXPECT_THROW(TypeParam(" "), InvalidRdataText); // even with a space + EXPECT_THROW(TypeParam("(\n)"), InvalidRdataText); // or multi-line with () +} + +void +makeLargest(vector<uint8_t>& data) { + uint8_t ch = 0; + + // create 255 sets of character-strings, each of which has the longest + // length (255bytes string + 1-byte length field) + for (int i = 0; i < 255; ++i, ++ch) { + data.push_back(255); + data.insert(data.end(), 255, ch); + } + // the last character-string should be 255 bytes (including the one-byte + // length field) in length so that the total length should be in the range + // of 16-bit integers. + data.push_back(254); + data.insert(data.end(), 254, ch); + + ASSERT_TRUE(data.size() == 65535); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) { + EXPECT_EQ(0, this->rdata_txt_like.compare( + *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), + RRClass("IN"), + "rdata_txt_fromWire1"))); + + // Empty character string + EXPECT_EQ(0, this->rdata_txt_like_empty.compare( + *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), + RRClass("IN"), + "rdata_txt_fromWire2.wire"))); + + // Multiple character strings + this->obuffer.clear(); + this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"), + "rdata_txt_fromWire3.wire")->toWire(this->obuffer); + // the result should be 'wiredata_txt' repeated twice + vector<uint8_t> expected_data(wiredata_txt_like, wiredata_txt_like + + sizeof(wiredata_txt_like)); + expected_data.insert(expected_data.end(), wiredata_txt_like, + wiredata_txt_like + sizeof(wiredata_txt_like)); + matchWireData(&expected_data[0], expected_data.size(), + this->obuffer.getData(), this->obuffer.getLength()); + + // Largest length of data. There's nothing special, but should be + // constructed safely, and the content should be identical to the original + // data. + vector<uint8_t> largest_txt_like_data; + makeLargest(largest_txt_like_data); + InputBuffer ibuffer(&largest_txt_like_data[0], + largest_txt_like_data.size()); + TypeParam largest_txt_like(ibuffer, largest_txt_like_data.size()); + this->obuffer.clear(); + largest_txt_like.toWire(this->obuffer); + matchWireData(&largest_txt_like_data[0], largest_txt_like_data.size(), + this->obuffer.getData(), this->obuffer.getLength()); + + // rdlen parameter is out of range. This is a rare event because we'd + // normally call the constructor via a polymorphic wrapper, where the + // length is validated. But this should be checked explicitly. + InputBuffer ibuffer2(&largest_txt_like_data[0], + largest_txt_like_data.size()); + EXPECT_THROW(TypeParam(ibuffer2, 65536), InvalidRdataLength); + + // RDATA is empty, which is invalid for TXT_LIKE. + EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"), + "rdata_txt_fromWire4.wire"), + DNSMessageFORMERR); + + // character-string length is too large, which could cause overrun. + EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"), + "rdata_txt_fromWire5.wire"), + DNSMessageFORMERR); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) { + EXPECT_EQ(0, this->rdata_txt_like.compare( + *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(), + "Test-String"))); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) { + this->rdata_txt_like.toWire(this->obuffer); + matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like), + this->obuffer.getData(), this->obuffer.getLength()); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) { + this->rdata_txt_like.toWire(this->renderer); + matchWireData(wiredata_txt_like, sizeof(wiredata_txt_like), + this->renderer.getData(), this->renderer.getLength()); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, toText) { + EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText()); + EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText()); + EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText()); + + // Check escape behavior + const TypeParam double_quotes("Test-String\"Test-String\""); + EXPECT_EQ("\"Test-String\" \"Test-String\"", double_quotes.toText()); + const TypeParam semicolon("Test-String\\;Test-String"); + EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText()); + const TypeParam backslash("Test-String\\\\Test-String"); + EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText()); + const TypeParam before_x20("Test-String\\031Test-String"); + EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText()); + const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\""); + EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText()); + const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String"); + EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText()); + const TypeParam after_x7e("Test-String\\127Test-String"); + EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText()); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) { + TypeParam rdata1("assignment1"); + TypeParam rdata2("assignment2"); + rdata1 = rdata2; + EXPECT_EQ(0, rdata2.compare(rdata1)); + + // Check if the copied data is valid even after the original is deleted + TypeParam* rdata3 = new TypeParam(rdata1); + TypeParam rdata4("assignment3"); + rdata4 = *rdata3; + delete rdata3; + EXPECT_EQ(0, rdata4.compare(rdata1)); + + // Self assignment + rdata2 = *&rdata2; + EXPECT_EQ(0, rdata2.compare(rdata1)); +} + +TYPED_TEST(Rdata_TXT_LIKE_Test, compare) { + string const txt1("aaaaaaaa"); + string const txt2("aaaaaaaaaa"); + string const txt3("bbbbbbbb"); + string const txt4(129, 'a'); + string const txt5(128, 'b'); + + EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0); + + EXPECT_LT(TypeParam("\"\"").compare(TypeParam(txt1)), 0); + EXPECT_GT(TypeParam(txt1).compare(TypeParam("\"\"")), 0); + + EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0); + EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0); + + EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt3)), 0); + EXPECT_GT(TypeParam(txt3).compare(TypeParam(txt1)), 0); + + // we're comparing the data raw, starting at the length octet, so a shorter + // string sorts before a longer one no matter the lexicopraphical order + EXPECT_LT(TypeParam(txt3).compare(TypeParam(txt2)), 0); + EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt3)), 0); + + // to make sure the length octet compares unsigned + EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt4)), 0); + EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt1)), 0); + + EXPECT_LT(TypeParam(txt5).compare(TypeParam(txt4)), 0); + EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt5)), 0); + + // comparison attempt between incompatible RR types should be rejected + EXPECT_THROW(TypeParam(txt1).compare(*this->rdata_nomatch), + bad_cast); +} + +} diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc new file mode 100644 index 0000000..b52d7cb --- /dev/null +++ b/src/lib/dns/tests/rdata_unittest.cc @@ -0,0 +1,471 @@ +// Copyright (C) 2010-2024 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 <functional> +#include <iomanip> +#include <vector> +#include <string> +#include <sstream> + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> + +#include <gtest/gtest.h> + +#include <dns/tests/unittest_util.h> +#include <dns/tests/rdata_unittest.h> + +#include <util/unittests/wiredata.h> + +#include <boost/lexical_cast.hpp> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; +namespace ph = std::placeholders; + +namespace isc { +namespace dns { +namespace rdata { + +namespace { +void +nullCallback(const std::string&, size_t, const std::string&) { +} +} + +RdataTest::RdataTest() : + obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0")), + loader_cb(MasterLoaderCallbacks(nullCallback, nullCallback)) { +} + +RdataPtr +RdataTest::rdataFactoryFromFile(const RRType& rrtype, const RRClass& rrclass, + const char* datafile, size_t position) { + std::vector<unsigned char> data; + UnitTestUtil::readWireData(datafile, data); + + InputBuffer buffer(&data[0], data.size()); + buffer.setPosition(position); + + uint16_t rdlen = buffer.readUint16(); + return (createRdata(rrtype, rrclass, buffer, rdlen)); +} + +namespace test { + +RdataPtr +createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass, + const std::string& str) { + std::stringstream ss(str); + MasterLexer lexer; + lexer.pushSource(ss); + + MasterLoaderCallbacks callbacks(nullCallback, nullCallback); + const Name origin("example.org."); + + return (createRdata(rrtype, rrclass, lexer, &origin, + MasterLoader::MANY_ERRORS, callbacks)); +} + +} // end of namespace isc::dns::rdata::test + +// A mock class to check parameters passed via loader callbacks. Its callback +// records the passed parameters, allowing the test to check them later via +// the check() method. +class CreateRdataCallback { +public: + enum CallbackType { NONE, ERROR, WARN }; + CreateRdataCallback() : type_(NONE), line_(0) {} + void callback(CallbackType type, const string& source, size_t line, + const string& reason_txt) { + type_ = type; + source_ = source; + line_ = line; + reason_txt_ = reason_txt; + } + + void clear() { + type_ = NONE; + source_.clear(); + line_ = 0; + reason_txt_.clear(); + } + + // Return if callback is called since the previous call to clear(). + bool isCalled() const { return (type_ != NONE); } + + void check(const string& expected_srcname, size_t expected_line, + CallbackType expected_type, const string& expected_reason) + const + { + EXPECT_EQ(expected_srcname, source_); + EXPECT_EQ(expected_line, line_); + EXPECT_EQ(expected_type, type_); + EXPECT_EQ(expected_reason, reason_txt_); + } + +private: + CallbackType type_; + string source_; + size_t line_; + string reason_txt_; +}; + +// Test class/type-independent behavior of createRdata(). +TEST_F(RdataTest, createRdataWithLexer) { + const in::AAAA aaaa_rdata("2001:db8::1"); + + stringstream ss; + const string src_name = "stream-" + boost::lexical_cast<string>(&ss); + ss << aaaa_rdata.toText() << "\n"; // valid case + ss << aaaa_rdata.toText() << "; comment, should be ignored\n"; + ss << aaaa_rdata.toText() << " extra-token\n"; // extra token + ss << aaaa_rdata.toText() << " extra token\n"; // 2 extra tokens + ss << ")\n"; // causing lexer error in parsing the RDATA text + ss << "192.0.2.1\n"; // semantics error: IPv4 address is given for AAAA + ss << aaaa_rdata.toText(); // valid, but end with EOF, not EOL + lexer.pushSource(ss); + + CreateRdataCallback callback; + MasterLoaderCallbacks callbacks( + std::bind(&CreateRdataCallback::callback, &callback, + CreateRdataCallback::ERROR, ph::_1, ph::_2, ph::_3), + std::bind(&CreateRdataCallback::callback, &callback, + CreateRdataCallback::WARN, ph::_1, ph::_2, ph::_3)); + + size_t line = 0; + + // Valid case. + ++line; + ConstRdataPtr rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, + 0, MasterLoader::MANY_ERRORS, + callbacks); + EXPECT_EQ(0, aaaa_rdata.compare(*rdata)); + EXPECT_FALSE(callback.isCalled()); + + // Similar to the previous case, but RDATA is followed by a comment. + // It should cause any confusion. + ++line; + callback.clear(); + rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0, + MasterLoader::MANY_ERRORS, callbacks); + EXPECT_EQ(0, aaaa_rdata.compare(*rdata)); + EXPECT_FALSE(callback.isCalled()); + + // Broken RDATA text: extra token. createRdata() returns null, error + // callback is called. + ++line; + callback.clear(); + EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0, + MasterLoader::MANY_ERRORS, callbacks)); + callback.check(src_name, line, CreateRdataCallback::ERROR, + "createRdata from text failed near 'extra-token': " + "extra input text"); + + // Similar to the previous case, but only the first extra token triggers + // callback. + ++line; + callback.clear(); + EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0, + MasterLoader::MANY_ERRORS, callbacks)); + callback.check(src_name, line, CreateRdataCallback::ERROR, + "createRdata from text failed near 'extra': " + "extra input text"); + + // Lexer error will happen, corresponding error callback will be triggered. + ++line; + callback.clear(); + EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0, + MasterLoader::MANY_ERRORS, callbacks)); + callback.check(src_name, line, CreateRdataCallback::ERROR, + "createRdata from text failed: unbalanced parentheses"); + + // Semantics level error will happen, corresponding error callback will be + // triggered. + ++line; + callback.clear(); + EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0, + MasterLoader::MANY_ERRORS, callbacks)); + callback.check(src_name, line, CreateRdataCallback::ERROR, + "createRdata from text failed: Bad IN/AAAA RDATA text: " + "'192.0.2.1'"); + + // Input is valid and parse will succeed, but with a warning that the + // file is not ended with a newline. + ++line; + callback.clear(); + rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, 0, + MasterLoader::MANY_ERRORS, callbacks); + EXPECT_EQ(0, aaaa_rdata.compare(*rdata)); + callback.check(src_name, line, CreateRdataCallback::WARN, + "file does not end with newline"); +} + +TEST_F(RdataTest, getLength) { + const in::AAAA aaaa_rdata("2001:db8::1"); + EXPECT_EQ(16, aaaa_rdata.getLength()); + + const generic::TXT txt_rdata("Hello World"); + EXPECT_EQ(12, txt_rdata.getLength()); +} + +} +} +} + +namespace { + +// Wire-format data correspond to rdata_unknown. Note that it doesn't +// include RDLENGTH. +const uint8_t wiredata_unknown[] = { 0xa1, 0xb2, 0xc3, 0x0d }; + +class Rdata_Unknown_Test : public RdataTest { +public: + Rdata_Unknown_Test() : + // "Unknown" RR Type used for the test cases below. If/when we + // use this type number as a "well-known" (probably + // experimental) type, we'll need to renumber it. + unknown_rrtype(RRType(65000)), + rdata_unknowntxt("\\# 4 a1b2c30d"), + rdata_unknown(rdata_unknowntxt) + {} +protected: + static string getLongestRdataTxt(); + static void getLongestRdataWire(vector<uint8_t>& v); + + const RRType unknown_rrtype; + const std::string rdata_unknowntxt; + const generic::Generic rdata_unknown; +}; + +string +Rdata_Unknown_Test::getLongestRdataTxt() { + ostringstream oss; + + oss << "\\# " << MAX_RDLENGTH << " "; + oss.fill('0'); + oss << right << hex; + for (int i = 0; i < MAX_RDLENGTH; i++) { + oss << setw(2) << (i & 0xff); + } + + return (oss.str()); +} + +void +Rdata_Unknown_Test::getLongestRdataWire(vector<uint8_t>& v) { + unsigned char ch = 0; + for (int i = 0; i < MAX_RDLENGTH; ++i, ++ch) { + v.push_back(ch); + } +} + +TEST_F(Rdata_Unknown_Test, createFromText) { + // valid construction. This also tests a normal case of "FromWire". + EXPECT_EQ(0, generic::Generic("\\# 4 a1b2c30d").compare( + *rdataFactoryFromFile(unknown_rrtype, RRClass::IN(), + "rdata_unknown_fromWire"))); + // upper case hexadecimal digits should also be okay. + EXPECT_EQ(0, generic::Generic("\\# 4 A1B2C30D").compare( + *rdataFactoryFromFile(unknown_rrtype, RRClass::IN(), + "rdata_unknown_fromWire"))); + // 0-length RDATA should be accepted + EXPECT_EQ(0, generic::Generic("\\# 0").compare( + *rdataFactoryFromFile(unknown_rrtype, RRClass::IN(), + "rdata_unknown_fromWire", 6))); + // hex encoding can be space-separated + EXPECT_EQ(0, generic::Generic("\\# 4 a1 b2c30d").compare(rdata_unknown)); + EXPECT_EQ(0, generic::Generic("\\# 4 a1b2 c30d").compare(rdata_unknown)); + EXPECT_EQ(0, generic::Generic("\\# 4 a1 b2 c3 0d").compare(rdata_unknown)); + EXPECT_EQ(0, generic::Generic("\\# 4 a1\tb2c3 0d").compare(rdata_unknown)); + + // Max-length RDATA + vector<uint8_t> v; + getLongestRdataWire(v); + InputBuffer ibuffer(&v[0], v.size()); + EXPECT_EQ(0, generic::Generic(getLongestRdataTxt()).compare( + generic::Generic(ibuffer, v.size()))); + + // the length field must match the encoding data length. + EXPECT_THROW(generic::Generic("\\# 4 1080c0ff00"), InvalidRdataLength); + EXPECT_THROW(generic::Generic("\\# 5 1080c0ff"), InvalidRdataLength); + // RDATA encoding part must consist of an even number of hex digits. + EXPECT_THROW(generic::Generic("\\# 1 1"), InvalidRdataText); + EXPECT_THROW(generic::Generic("\\# 1 ax"), InvalidRdataText); + // the length should be 16-bit unsigned integer + EXPECT_THROW(generic::Generic("\\# 65536 a1b2c30d"), InvalidRdataLength); + EXPECT_THROW(generic::Generic("\\# -1 a1b2c30d"), InvalidRdataLength); + EXPECT_THROW(generic::Generic("\\# 1.1 a1"), InvalidRdataLength); + EXPECT_THROW(generic::Generic("\\# 0a 00010203040506070809"), + InvalidRdataLength); + // should reject if the special token is missing. + EXPECT_THROW(generic::Generic("4 a1b2c30d"), InvalidRdataText); + // the special token, the RDLENGTH and the data must be space separated. + EXPECT_THROW(generic::Generic("\\#0"), InvalidRdataText); + EXPECT_THROW(generic::Generic("\\# 1ff"), InvalidRdataLength); +} + +TEST_F(Rdata_Unknown_Test, createFromWire) { + // normal case (including 0-length data) is covered in createFromText. + + // buffer too short. the error should be detected in buffer read + EXPECT_THROW(rdataFactoryFromFile(unknown_rrtype, RRClass::IN(), + "rdata_unknown_fromWire", 8), + isc::OutOfRange); + + // too large data + vector<uint8_t> v; + getLongestRdataWire(v); + v.push_back(0); // making it too long + InputBuffer ibuffer(&v[0], v.size()); + EXPECT_THROW(generic::Generic(ibuffer, v.size()), InvalidRdataLength); +} + +// The following 3 sets of tests check the behavior of createRdata() variants +// with the "unknown" RRtype. The result should be RRclass independent. +TEST_F(Rdata_Unknown_Test, createRdataFromString) { + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass::IN(), + rdata_unknowntxt))); + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass::CH(), + rdata_unknowntxt))); + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass("CLASS65000"), + rdata_unknowntxt))); +} + +TEST_F(Rdata_Unknown_Test, createRdataFromWire) { + InputBuffer ibuffer(wiredata_unknown, sizeof(wiredata_unknown)); + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass::IN(), + ibuffer, sizeof(wiredata_unknown)))); + + InputBuffer ibuffer2(wiredata_unknown, sizeof(wiredata_unknown)); + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass::CH(), + ibuffer2, sizeof(wiredata_unknown)))); + + InputBuffer ibuffer3(wiredata_unknown, sizeof(wiredata_unknown)); + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass(65000), + ibuffer3, sizeof(wiredata_unknown)))); +} + +TEST_F(Rdata_Unknown_Test, createRdataByCopy) { + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass::IN(), rdata_unknown))); + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass::CH(), rdata_unknown))); + EXPECT_EQ(0, rdata_unknown.compare( + *createRdata(unknown_rrtype, RRClass(65000), + rdata_unknown))); +} + +TEST_F(Rdata_Unknown_Test, copyConstruct) { + generic::Generic copy(rdata_unknown); + EXPECT_EQ(0, copy.compare(rdata_unknown)); + + // Check the copied data is valid even after the original is deleted + generic::Generic* copy2 = new generic::Generic(rdata_unknown); + generic::Generic copy3(*copy2); + delete copy2; + EXPECT_EQ(0, copy3.compare(rdata_unknown)); +} + +TEST_F(Rdata_Unknown_Test, assignment) { + generic::Generic copy("\\# 1 10"); + copy = rdata_unknown; + EXPECT_EQ(0, copy.compare(rdata_unknown)); + + // Check if the copied data is valid even after the original is deleted + generic::Generic* copy2 = new generic::Generic(rdata_unknown); + generic::Generic copy3("\\# 1 10"); + copy3 = *copy2; + delete copy2; + EXPECT_EQ(0, copy3.compare(rdata_unknown)); + + // Self assignment + copy = *© + EXPECT_EQ(0, copy.compare(rdata_unknown)); +} + +TEST_F(Rdata_Unknown_Test, toText) { + EXPECT_EQ(rdata_unknowntxt, rdata_unknown.toText()); + EXPECT_EQ(getLongestRdataTxt(), + generic::Generic(getLongestRdataTxt()).toText()); +} + +TEST_F(Rdata_Unknown_Test, toWireBuffer) { + rdata_unknown.toWire(obuffer); + matchWireData(wiredata_unknown, sizeof(wiredata_unknown), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(Rdata_Unknown_Test, toWireRenderer) { + rdata_unknown.toWire(renderer); + matchWireData(wiredata_unknown, sizeof(wiredata_unknown), + renderer.getData(), renderer.getLength()); +} + +TEST_F(Rdata_Unknown_Test, compare) { + // comparison as left-justified unsigned octet sequences: + // cppcheck-suppress uselessCallsCompare + EXPECT_EQ(0, rdata_unknown.compare(rdata_unknown)); + + generic::Generic rdata_unknown_small("\\# 4 00b2c3ff"); + EXPECT_GT(0, rdata_unknown_small.compare(rdata_unknown)); + EXPECT_LT(0, rdata_unknown.compare(rdata_unknown_small)); + + generic::Generic rdata_unknown_large("\\# 4 ffb2c300"); + EXPECT_LT(0, rdata_unknown_large.compare(rdata_unknown)); + EXPECT_GT(0, rdata_unknown.compare(rdata_unknown_large)); + + // the absence of an octet sorts before a zero octet. + generic::Generic rdata_unknown_short("\\# 3 a1b2c3"); + EXPECT_GT(0, rdata_unknown_short.compare(rdata_unknown)); + EXPECT_LT(0, rdata_unknown.compare(rdata_unknown_short)); +} + +TEST_F(Rdata_Unknown_Test, LeftShiftOperator) { + ostringstream oss; + oss << rdata_unknown; + EXPECT_EQ(rdata_unknown.toText(), oss.str()); +} + +// +// Tests for global utility functions +// +TEST_F(RdataTest, compareNames) { + Name small("a.example"); + Name large("example"); + + // Check the case where the order is different from the owner name + // comparison: + EXPECT_TRUE(small > large); + EXPECT_EQ(-1, compareNames(small, large)); + EXPECT_EQ(1, compareNames(large, small)); + + // Check case insensitive comparison: + Name small_upper("A.EXAMPLE"); + EXPECT_EQ(0, compareNames(small, small_upper)); + + // the absence of an octet sorts before a zero octet. + Name large2("a.example2"); + EXPECT_EQ(-1, compareNames(small, large2)); + EXPECT_EQ(1, compareNames(large2, small)); +} +} diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h new file mode 100644 index 0000000..b2ba483 --- /dev/null +++ b/src/lib/dns/tests/rdata_unittest.h @@ -0,0 +1,87 @@ +// Copyright (C) 2010-2024 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 RDATA_UNITTEST_H +#define RDATA_UNITTEST_H + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/rdata.h> +#include <dns/master_lexer.h> + +#include <gtest/gtest.h> + +#include <string> +#include <sstream> + +namespace isc { +namespace dns { +namespace rdata { +class RdataTest : public ::testing::Test { +protected: + RdataTest(); + static RdataPtr rdataFactoryFromFile(const RRType& rrtype, + const RRClass& rrclass, + const char* datafile, + size_t position = 0); + + // Common check to see the result of Rdata construction of given type + // (template parameter RdataType) either from std::string or with + // MasterLexer object. If it's expected to succeed the result should be + // identical to the commonly used test data (rdata_expected); otherwise it + // should result in the exception specified as the template parameter: + // ExForString for the string version, and ExForLexer for the lexer + // version. throw_str_version and throw_lexer_version are set to true + // iff the string/lexer version is expected to throw, respectively. + // Parameter origin can be set to non null for the origin parameter of + // the lexer version of Rdata constructor. + template <typename RdataType, typename ExForString, typename ExForLexer> + void checkFromText(const std::string& rdata_txt, + const RdataType& rdata_expected, + bool throw_str_version = true, + bool throw_lexer_version = true, + const Name* origin = 0) { + SCOPED_TRACE(rdata_txt); + + if (throw_str_version) { + EXPECT_THROW(RdataType rdata(rdata_txt), ExForString); + } else { + EXPECT_EQ(0, RdataType(rdata_txt).compare(rdata_expected)); + } + + std::stringstream ss(rdata_txt); + MasterLexer lexer; + lexer.pushSource(ss); + if (throw_lexer_version) { + EXPECT_THROW(RdataType rdata(lexer, origin, MasterLoader::DEFAULT, + loader_cb), ExForLexer); + } else { + EXPECT_EQ(0, RdataType(lexer, origin, MasterLoader::DEFAULT, + loader_cb).compare(rdata_expected)); + } + } + + isc::util::OutputBuffer obuffer; + MessageRenderer renderer; + /// This is an RDATA object of some "unknown" RR type so that it can be + /// used to test the compare() method against a well-known RR type. + RdataPtr rdata_nomatch; + MasterLexer lexer; + MasterLoaderCallbacks loader_cb; +}; + +namespace test { +RdataPtr +createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass, + const std::string& str); +} + +} +} +} +#endif // RDATA_UNITTEST_H diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc new file mode 100644 index 0000000..bf71e92 --- /dev/null +++ b/src/lib/dns/tests/rrclass_unittest.cc @@ -0,0 +1,204 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <gtest/gtest.h> + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/rrclass.h> +#include <dns/rrparamregistry.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +#include <boost/scoped_ptr.hpp> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using boost::scoped_ptr; +using isc::util::unittests::matchWireData; + +namespace { +class RRClassTest : public ::testing::Test { +protected: + RRClassTest() : obuffer(0) {} + + OutputBuffer obuffer; + MessageRenderer renderer; + + static RRClass rrclassFactoryFromWire(const char* datafile); + static const RRClass rrclass_1, rrclass_0x80, rrclass_0x800, + rrclass_0x8000, rrclass_max; + static const uint8_t wiredata[]; +}; + +const RRClass RRClassTest::rrclass_1(1); +const RRClass RRClassTest::rrclass_0x80(0x80); +const RRClass RRClassTest::rrclass_0x800(0x800); +const RRClass RRClassTest::rrclass_0x8000(0x8000); +const RRClass RRClassTest::rrclass_max(0xffff); +// This is wire-format data for the above sample RRClass rendered in the +// appearing order. +const uint8_t RRClassTest::wiredata[] = { 0x00, 0x01, 0x00, 0x80, 0x08, + 0x00, 0x80, 0x00, 0xff, 0xff }; + +RRClass +RRClassTest::rrclassFactoryFromWire(const char* datafile) { + std::vector<unsigned char> data; + UnitTestUtil::readWireData(datafile, data); + + InputBuffer buffer(&data[0], data.size()); + + return (RRClass(buffer)); +} + +TEST_F(RRClassTest, fromTextConstructor) { + EXPECT_EQ("IN", RRClass("IN").toText()); + EXPECT_EQ("CH", RRClass("CH").toText()); + + EXPECT_EQ("CLASS65535", RRClass("CLASS65535").toText()); + + // some uncommon cases: see the corresponding RRType tests. + EXPECT_EQ(53, RRClass("CLASS00053").getCode()); + EXPECT_THROW(RRClass("CLASS000053"), InvalidRRClass); + + // bogus CLASSnnn representations: should trigger an exception + EXPECT_THROW(RRClass("CLASS"), InvalidRRClass); + EXPECT_THROW(RRClass("CLASS-1"), InvalidRRClass); + EXPECT_THROW(RRClass("CLASSxxx"), InvalidRRClass); + EXPECT_THROW(RRClass("CLASS65536"), InvalidRRClass); + EXPECT_THROW(RRClass("CLASS6500x"), InvalidRRClass); + EXPECT_THROW(RRClass("CLASS65000 "), InvalidRRClass); +} + +TEST_F(RRClassTest, fromWire) { + EXPECT_EQ(0x1234, + rrclassFactoryFromWire("rrcode16_fromWire1").getCode()); + EXPECT_THROW(rrclassFactoryFromWire("rrcode16_fromWire2"), + IncompleteRRClass); +} + +TEST_F(RRClassTest, caseConstruct) { + EXPECT_EQ("IN", RRClass("in").toText()); + EXPECT_EQ("CH", RRClass("ch").toText()); + EXPECT_EQ("CLASS65535", RRClass("class65535").toText()); +} + +TEST_F(RRClassTest, toText) { + EXPECT_EQ("IN", RRClass(1).toText()); + EXPECT_EQ("CLASS65000", RRClass(65000).toText()); +} + +TEST_F(RRClassTest, createFromText) { + scoped_ptr<RRClass> chclass(RRClass::createFromText("CH")); + EXPECT_TRUE(chclass); + EXPECT_EQ("CH", chclass->toText()); + + scoped_ptr<RRClass> zzclass(RRClass::createFromText("ZZ")); + EXPECT_FALSE(zzclass); +} + +TEST_F(RRClassTest, toWireBuffer) { + rrclass_1.toWire(obuffer); + rrclass_0x80.toWire(obuffer); + rrclass_0x800.toWire(obuffer); + rrclass_0x8000.toWire(obuffer); + rrclass_max.toWire(obuffer); + + matchWireData(wiredata, sizeof (wiredata), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(RRClassTest, toWireRenderer) { + rrclass_1.toWire(renderer); + rrclass_0x80.toWire(renderer); + rrclass_0x800.toWire(renderer); + rrclass_0x8000.toWire(renderer); + rrclass_max.toWire(renderer); + + matchWireData(wiredata, sizeof (wiredata), + renderer.getData(), renderer.getLength()); +} + +TEST_F(RRClassTest, wellKnownClass) { + EXPECT_EQ(1, RRClass::IN().getCode()); + EXPECT_EQ("IN", RRClass::IN().toText()); +} + +TEST_F(RRClassTest, compare) { + EXPECT_TRUE(RRClass(1) == RRClass("IN")); + EXPECT_TRUE(RRClass(1).equals(RRClass("IN"))); + EXPECT_TRUE(RRClass(0).nequals(RRClass("IN"))); + + EXPECT_TRUE(RRClass("IN") < RRClass("CH")); + EXPECT_TRUE(RRClass(100) < RRClass(65535)); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(RRClassTest, LeftShiftOperator) { + ostringstream oss; + oss << RRClass::IN(); + EXPECT_EQ(RRClass::IN().toText(), oss.str()); +} + +// Below, we'll check definitions for all well-known RR classes; whether they +// are defined and have the correct parameter values. Test data are generated +// from the list available at: +// http://www.iana.org/assignments/dns-parameters/dns-parameters.xml +struct WellKnownClassParam { + const char* const txt; // "IN", "CH", etc + const uint16_t code; // 1, 3, etc + const RRClass& (*obj)(); // RRClass::IN(), RRClass::CH(), etc +} well_known_classes[] = { + {"IN", 1, RRClass::IN}, + {"CH", 3, RRClass::CH}, + {"NONE", 254, RRClass::NONE}, + {"ANY", 255, RRClass::ANY}, + {0, 0, 0} +}; + +TEST(RRClassConstTest, wellKnowns) { + for (size_t i = 0; well_known_classes[i].txt; ++i) { + SCOPED_TRACE("Checking well known RRClass: " + + string(well_known_classes[i].txt)); + EXPECT_EQ(well_known_classes[i].code, + RRClass(well_known_classes[i].txt).getCode()); + EXPECT_EQ(well_known_classes[i].code, + (*well_known_classes[i].obj)().getCode()); + } +} + +// Below, we'll check definitions for all registered RR classes. +struct RegisteredClassParam { + const char* const txt; // "IN", "CH", etc + const uint16_t code; // 1, 3, etc +} registered_classes[] = { + {"IN", 1}, + {"CH", 3}, + {"HS", 4}, + {"NONE", 254}, + {"ANY", 255}, + {0, 0} +}; + +TEST(RRClassConstTest, registered) { + for (size_t i = 0; registered_classes[i].txt; ++i) { + SCOPED_TRACE("Checking registered RRClass: " + + string(registered_classes[i].txt)); + uint16_t code = 0; + EXPECT_NO_THROW(RRParamRegistry::getRegistry().textToClassCode(registered_classes[i].txt, code)); + EXPECT_EQ(code, registered_classes[i].code); + string txt; + EXPECT_NO_THROW(txt = RRParamRegistry::getRegistry().codeToClassText(registered_classes[i].code)); + EXPECT_EQ(txt, registered_classes[i].txt); + } +} + +} diff --git a/src/lib/dns/tests/rrparamregistry_unittest.cc b/src/lib/dns/tests/rrparamregistry_unittest.cc new file mode 100644 index 0000000..b60106d --- /dev/null +++ b/src/lib/dns/tests/rrparamregistry_unittest.cc @@ -0,0 +1,189 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <string> +#include <sstream> + +#include <stdint.h> + +#include <gtest/gtest.h> + +#include <dns/rrclass.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrparamregistry.h> +#include <dns/rrtype.h> +#include <dns/master_loader.h> + +#include <boost/scoped_ptr.hpp> + +using namespace std; +using namespace isc::dns; +using namespace isc::dns::rdata; +using namespace isc::util; + +namespace { +void +nullCallback(const std::string&, size_t, const std::string&) { +} + +class RRParamRegistryTest : public ::testing::Test { +protected: + RRParamRegistryTest() + { + ostringstream oss1; + oss1 << test_class_code; + // cppcheck-suppress useInitializationList + test_class_unknown_str = "CLASS" + oss1.str(); + + ostringstream oss2; + oss2 << test_type_code; + test_type_unknown_str = "TYPE" + oss2.str(); + } + ~RRParamRegistryTest() + { + // cleanup any non well-known parameters that possibly remain + // as a side effect. + RRParamRegistry::getRegistry().removeType(test_type_code); + RRParamRegistry::getRegistry().removeClass(test_class_code); + RRParamRegistry::getRegistry().removeRdataFactory( + RRType(test_type_code), RRClass(test_class_code)); + RRParamRegistry::getRegistry().removeRdataFactory( + RRType(test_type_code)); + } + + string test_class_unknown_str; + string test_type_unknown_str; + + // we assume class/type numbers are officially unassigned. If not we'll + // need to update the test cases. + static const uint16_t test_class_code = 65533; + static const uint16_t test_type_code = 65534; + static const string test_class_str; + static const string test_type_str; +}; + +const string RRParamRegistryTest::test_class_str("TESTCLASS"); +const string RRParamRegistryTest::test_type_str("TESTTYPE"); + +TEST_F(RRParamRegistryTest, addRemove) { + RRParamRegistry::getRegistry().addType(test_type_str, test_type_code); + RRParamRegistry::getRegistry().addClass(test_class_str, test_class_code); + EXPECT_EQ(65533, RRClass("TESTCLASS").getCode()); + EXPECT_EQ(65534, RRType("TESTTYPE").getCode()); + + // the first removal attempt should succeed + EXPECT_TRUE(RRParamRegistry::getRegistry().removeType(test_type_code)); + // then toText() should treat it as an "unknown" + EXPECT_EQ(test_type_unknown_str, RRType(test_type_code).toText()); + // attempt of removing non-existent mapping should result in 'false' + EXPECT_FALSE(RRParamRegistry::getRegistry().removeType(test_type_code)); + + // same set of tests for RR class. + EXPECT_TRUE(RRParamRegistry::getRegistry().removeClass(test_class_code)); + EXPECT_EQ(test_class_unknown_str, RRClass(test_class_code).toText()); + EXPECT_FALSE(RRParamRegistry::getRegistry().removeClass(test_class_code)); +} + +TEST_F(RRParamRegistryTest, addError) { + // An attempt to override a pre-registered class should fail with an + // exception, and the pre-registered one should remain in the registry. + EXPECT_THROW(RRParamRegistry::getRegistry().addClass(test_class_str, 1), + RRClassExists); + EXPECT_EQ("IN", RRClass(1).toText()); + + // Same for RRType + EXPECT_THROW(RRParamRegistry::getRegistry().addType(test_type_str, 1), + RRTypeExists); + EXPECT_EQ("A", RRType(1).toText()); +} + +class TestRdataFactory : public AbstractRdataFactory { +public: + virtual RdataPtr create(const string& rdata_str) const + { return (RdataPtr(new in::A(rdata_str))); } + virtual RdataPtr create(InputBuffer& buffer, size_t rdata_len) const + { return (RdataPtr(new in::A(buffer, rdata_len))); } + virtual RdataPtr create(const Rdata& source) const + { return (RdataPtr(new in::A(dynamic_cast<const in::A&>(source)))); } + virtual RdataPtr create(MasterLexer& lexer, const Name* origin, + MasterLoader::Options options, + MasterLoaderCallbacks& callbacks) const + { return (RdataPtr(new in::A(lexer, origin, options, callbacks))); } +}; + +TEST_F(RRParamRegistryTest, addRemoveFactory) { + // By default, the test type/code pair should be considered "unknown", + // so the following should trigger an exception. + EXPECT_THROW(createRdata(RRType(test_type_code), RRClass(test_class_code), + "192.0.2.1"), + InvalidRdataText); + // Add factories so that we can treat this pair just like in::A. + RRParamRegistry::getRegistry().add(test_type_str, test_type_code, + test_class_str, test_class_code, + RdataFactoryPtr(new TestRdataFactory())); + // Now it should be accepted, and should be identical to the same data of + // in::A. + EXPECT_EQ(0, in::A("192.0.2.1").compare( + *createRdata(RRType(test_type_code), RRClass(test_class_code), + "192.0.2.1"))); + // It should still fail with other classes as we specified the factories + // as class-specific. + EXPECT_THROW(createRdata(RRType(test_type_code), RRClass("IN"), + "192.0.2.1"), + InvalidRdataText); + // Add the factories also as a class independent RRtype + RRParamRegistry::getRegistry().add(test_type_str, test_type_code, + RdataFactoryPtr(new TestRdataFactory())); + // Now it should be okay for other classes than the test class. + EXPECT_EQ(0, in::A("192.0.2.1").compare( + *createRdata(RRType(test_type_code), RRClass("IN"), + "192.0.2.1"))); + + // Remove the added factories: first attempt should succeed; the second + // should return false as there's no match + EXPECT_TRUE(RRParamRegistry::getRegistry().removeRdataFactory( + RRType(test_type_code), RRClass(test_class_code))); + EXPECT_FALSE(RRParamRegistry::getRegistry().removeRdataFactory( + RRType(test_type_code), RRClass(test_class_code))); + EXPECT_TRUE(RRParamRegistry::getRegistry().removeRdataFactory( + RRType(test_type_code))); + EXPECT_FALSE(RRParamRegistry::getRegistry().removeRdataFactory( + RRType(test_type_code))); +} + +RdataPtr +createRdataHelper(const std::string& str) { + boost::scoped_ptr<AbstractRdataFactory> rdf(new TestRdataFactory()); + + std::stringstream ss(str); + MasterLexer lexer; + lexer.pushSource(ss); + + MasterLoaderCallbacks callbacks(nullCallback, nullCallback); + const Name origin("example.org."); + + return (rdf->create(lexer, &origin, + MasterLoader::MANY_ERRORS, + callbacks)); +} + +TEST_F(RRParamRegistryTest, createFromLexer) { + // This test basically checks that the string version of + // AbstractRdataFactory::create() is called by the MasterLexer + // variant of create(). + EXPECT_EQ(0, in::A("192.168.0.1").compare( + *createRdataHelper("192.168.0.1"))); + + // This should parse only up to the end of line. Everything that + // comes afterwards is not parsed. + EXPECT_EQ(0, in::A("192.168.0.42").compare( + *createRdataHelper("192.168.0.42\na b c d e f"))); +} + +} diff --git a/src/lib/dns/tests/rrset_unittest.cc b/src/lib/dns/tests/rrset_unittest.cc new file mode 100644 index 0000000..53cadec --- /dev/null +++ b/src/lib/dns/tests/rrset_unittest.cc @@ -0,0 +1,441 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/rrttl.h> +#include <dns/rrset.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +#include <gtest/gtest.h> + +#include <stdexcept> +#include <sstream> + +using namespace std; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::dns::rdata; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { +class RRsetTest : public ::testing::Test { +protected: + RRsetTest() : buffer(0), + test_name("test.example.com"), + test_domain("example.com"), + test_nsname("ns.example.com"), + rrset_a(test_name, RRClass::IN(), RRType::A(), RRTTL(3600)), + rrset_a_empty(test_name, RRClass::IN(), RRType::A(), + RRTTL(3600)), + rrset_any_a_empty(test_name, RRClass::ANY(), RRType::A(), + RRTTL(3600)), + rrset_none_a_empty(test_name, RRClass::NONE(), RRType::A(), + RRTTL(3600)), + rrset_ns(test_domain, RRClass::IN(), RRType::NS(), + RRTTL(86400)), + rrset_ch_txt(test_domain, RRClass::CH(), RRType::TXT(), + RRTTL(0)) { + rrset_a.addRdata(in::A("192.0.2.1")); + rrset_a.addRdata(in::A("192.0.2.2")); + } + + OutputBuffer buffer; + MessageRenderer renderer; + Name test_name; + Name test_domain; + Name test_nsname; + RRset rrset_a; + RRset rrset_a_empty; + RRset rrset_any_a_empty; + RRset rrset_none_a_empty; + RRset rrset_ns; + RRset rrset_ch_txt; + std::vector<unsigned char> wiredata; + + // max number of Rdata objects added to a test RRset object. + // this is an arbitrary chosen limit, but should be sufficiently large + // in practice and reasonable even as an extreme test case. + static const int MAX_RDATA_COUNT = 100; +}; + +TEST_F(RRsetTest, getRdataCount) { + for (int i = 0; i < MAX_RDATA_COUNT; ++i) { + EXPECT_EQ(i, rrset_a_empty.getRdataCount()); + rrset_a_empty.addRdata(in::A("192.0.2.1")); + } +} + +TEST_F(RRsetTest, getName) { + EXPECT_EQ(test_name, rrset_a.getName()); + EXPECT_EQ(test_domain, rrset_ns.getName()); +} + +TEST_F(RRsetTest, getClass) { + EXPECT_EQ(RRClass("IN"), rrset_a.getClass()); + EXPECT_EQ(RRClass("CH"), rrset_ch_txt.getClass()); +} + +TEST_F(RRsetTest, getType) { + EXPECT_EQ(RRType("A"), rrset_a.getType()); + EXPECT_EQ(RRType("NS"), rrset_ns.getType()); + EXPECT_EQ(RRType("TXT"), rrset_ch_txt.getType()); +} + +TEST_F(RRsetTest, getTTL) { + EXPECT_EQ(RRTTL(3600), rrset_a.getTTL()); + EXPECT_EQ(RRTTL(86400), rrset_ns.getTTL()); + EXPECT_EQ(RRTTL(0), rrset_ch_txt.getTTL()); +} + +TEST_F(RRsetTest, setTTL) { + rrset_a.setTTL(RRTTL(86400)); + EXPECT_EQ(RRTTL(86400), rrset_a.getTTL()); + rrset_a.setTTL(RRTTL(0)); + EXPECT_EQ(RRTTL(0), rrset_a.getTTL()); +} + +TEST_F(RRsetTest, isSameKind) { + RRset rrset_w(test_name, RRClass::IN(), RRType::A(), RRTTL(3600)); + RRset rrset_x(test_name, RRClass::IN(), RRType::A(), RRTTL(3600)); + RRset rrset_y(test_name, RRClass::IN(), RRType::NS(), RRTTL(3600)); + RRset rrset_z(test_name, RRClass::CH(), RRType::A(), RRTTL(3600)); + RRset rrset_p(test_nsname, RRClass::IN(), RRType::A(), RRTTL(3600)); + + EXPECT_TRUE(rrset_w.isSameKind(rrset_w)); + EXPECT_TRUE(rrset_w.isSameKind(rrset_x)); + EXPECT_FALSE(rrset_w.isSameKind(rrset_y)); + EXPECT_FALSE(rrset_w.isSameKind(rrset_z)); + EXPECT_FALSE(rrset_w.isSameKind(rrset_p)); +} + +void +addRdataTestCommon(const RRset& rrset) { + ASSERT_EQ(2, rrset.getRdataCount()); + + RdataIteratorPtr it = rrset.getRdataIterator(); // cursor is set to the 1st + EXPECT_FALSE(it->isLast()); + EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1"))); + it->next(); + EXPECT_FALSE(it->isLast()); + EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.2"))); + it->next(); + EXPECT_TRUE(it->isLast()); +} + +TEST_F(RRsetTest, addRdata) { + addRdataTestCommon(rrset_a); + + // Reference version of addRdata() doesn't allow to add a different + // type of Rdata. + EXPECT_THROW(rrset_a.addRdata(generic::NS(test_nsname)), std::bad_cast); +} + +TEST_F(RRsetTest, addRdataPtr) { + rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(), + rrset_a_empty.getClass(), + "192.0.2.1")); + rrset_a_empty.addRdata(createRdata(rrset_a_empty.getType(), + rrset_a_empty.getClass(), + "192.0.2.2")); + addRdataTestCommon(rrset_a_empty); +} + +TEST_F(RRsetTest, addRdataPtrMismatched) { + // Pointer version of addRdata() doesn't type check and does allow to + //add a different type of Rdata as a result. + + // Type mismatch + rrset_a_empty.addRdata(createRdata(RRType::NS(), RRClass::IN(), + "ns.example.com.")); + EXPECT_EQ(1, rrset_a_empty.getRdataCount()); + + // Class mismatch + rrset_ch_txt.addRdata(createRdata(RRType::TXT(), RRClass::IN(), + "Test String")); + EXPECT_EQ(1, rrset_ch_txt.getRdataCount()); +} + +TEST_F(RRsetTest, addRdataString) { + rrset_a_empty.addRdata("192.0.2.1"); + rrset_a_empty.addRdata("192.0.2.2"); + + addRdataTestCommon(rrset_a_empty); + + // String version of addRdata() will throw for bad RDATA for + // RRType::A(). + EXPECT_THROW(rrset_a_empty.addRdata("ns.example.com."), InvalidRdataText); + addRdataTestCommon(rrset_a_empty); +} + +TEST_F(RRsetTest, iterator) { + // Iterator for an empty RRset. + RdataIteratorPtr it = rrset_a_empty.getRdataIterator(); + EXPECT_TRUE(it->isLast()); + + // Normal case (already tested, but do it again just in case) + rrset_a_empty.addRdata(in::A("192.0.2.1")); + rrset_a_empty.addRdata(in::A("192.0.2.2")); + addRdataTestCommon(rrset_a_empty); + + // Rewind test: should be repeat the iteration by calling first(). + for (int i = 0; i < 2; ++i) { + it = rrset_a_empty.getRdataIterator(); + it->first(); + EXPECT_FALSE(it->isLast()); + it->next(); + EXPECT_FALSE(it->isLast()); + it->next(); + EXPECT_TRUE(it->isLast()); + } +} + +TEST_F(RRsetTest, toText) { + EXPECT_EQ("test.example.com. 3600 IN A 192.0.2.1\n" + "test.example.com. 3600 IN A 192.0.2.2\n", + rrset_a.toText()); + + // toText() cannot be performed for an empty RRset + EXPECT_THROW(rrset_a_empty.toText(), EmptyRRset); + + // Unless it is type ANY or NONE + EXPECT_EQ("test.example.com. 3600 ANY A\n", + rrset_any_a_empty.toText()); + EXPECT_EQ("test.example.com. 3600 NONE A\n", + rrset_none_a_empty.toText()); +} + +TEST_F(RRsetTest, getLength) { + // Empty RRset should throw + EXPECT_THROW(rrset_a_empty.getLength(), EmptyRRset); + + // Unless it is type ANY or NONE: + // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets + // TYPE field = 2 octets + // CLASS field = 2 octets + // TTL field = 4 octets + // RDLENGTH field = 2 octets + // Total = 18 + 2 + 2 + 4 + 2 = 28 octets + EXPECT_EQ(28, rrset_any_a_empty.getLength()); + EXPECT_EQ(28, rrset_none_a_empty.getLength()); + + // RRset with single RDATA + // 28 (above) + 4 octets (A RDATA) = 32 octets + rrset_a_empty.addRdata(in::A("192.0.2.1")); + EXPECT_EQ(32, rrset_a_empty.getLength()); + + // 2 A RRs + rrset_a_empty.addRdata(in::A("192.0.2.2")); + EXPECT_EQ(32 + 32, rrset_a_empty.getLength()); +} + +TEST_F(RRsetTest, toWireBuffer) { + rrset_a.toWire(buffer); + + UnitTestUtil::readWireData("rrset_toWire1", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + buffer.getData(), buffer.getLength()); + + // toWire() cannot be performed for an empty RRset except when + // class=ANY or class=NONE. + buffer.clear(); + EXPECT_THROW(rrset_a_empty.toWire(buffer), EmptyRRset); + + // When class=ANY or class=NONE, toWire() can also be performed for + // an empty RRset. + buffer.clear(); + rrset_any_a_empty.toWire(buffer); + wiredata.clear(); + UnitTestUtil::readWireData("rrset_toWire3", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + buffer.getData(), buffer.getLength()); + + buffer.clear(); + rrset_none_a_empty.toWire(buffer); + wiredata.clear(); + UnitTestUtil::readWireData("rrset_toWire4", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + buffer.getData(), buffer.getLength()); +} + +TEST_F(RRsetTest, toWireRenderer) { + rrset_ns.addRdata(generic::NS(test_nsname)); + + rrset_a.toWire(renderer); + rrset_ns.toWire(renderer); + + UnitTestUtil::readWireData("rrset_toWire2", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); + + // toWire() cannot be performed for an empty RRset except when + // class=ANY or class=NONE. + renderer.clear(); + EXPECT_THROW(rrset_a_empty.toWire(renderer), EmptyRRset); + + // When class=ANY or class=NONE, toWire() can also be performed for + // an empty RRset. + renderer.clear(); + rrset_any_a_empty.toWire(renderer); + wiredata.clear(); + UnitTestUtil::readWireData("rrset_toWire3", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); + + renderer.clear(); + rrset_none_a_empty.toWire(renderer); + wiredata.clear(); + UnitTestUtil::readWireData("rrset_toWire4", wiredata); + matchWireData(&wiredata[0], wiredata.size(), + renderer.getData(), renderer.getLength()); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(RRsetTest, LeftShiftOperator) { + ostringstream oss; + oss << rrset_a; + EXPECT_EQ(rrset_a.toText(), oss.str()); +} + +class RRsetRRSIGTest : public ::testing::Test { +protected: + RRsetRRSIGTest() : test_name("test.example.com") + { + rrset_a = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::A(), RRTTL(3600))); + rrset_a->addRdata(in::A("192.0.2.1")); + rrset_a->addRdata(in::A("192.0.2.2")); + + rrset_aaaa = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::AAAA(), RRTTL(3600))); + rrset_aaaa->addRdata(in::AAAA("2001:db8::1234")); + + rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::RRSIG(), RRTTL(3600))); + rrset_rrsig->addRdata(generic::RRSIG("AAAA 5 3 7200 20100322084538 " + "20100220084538 1 example.com. " + "FAKEFAKEFAKEFAKE")); + rrset_aaaa->addRRsig(rrset_rrsig); + } + + const Name test_name; + RRsetPtr rrset_a; // A RRset with two RDATAs + RRsetPtr rrset_aaaa; // AAAA RRset with one RDATA with RRSIG + RRsetPtr rrset_rrsig; // RRSIG for the AAAA RRset +}; + +TEST_F(RRsetRRSIGTest, getRRsig) { + RRsetPtr sp = rrset_a->getRRsig(); + EXPECT_FALSE(sp); + + sp = rrset_aaaa->getRRsig(); + EXPECT_TRUE(sp); +} + +TEST_F(RRsetRRSIGTest, addRRsig) { + RRsetPtr sp = rrset_a->getRRsig(); + EXPECT_FALSE(sp); + + rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::RRSIG(), RRTTL(3600))); + // one signature algorithm (5 = RSA/SHA-1) + rrset_rrsig->addRdata(generic::RRSIG("A 5 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + // another signature algorithm (3 = DSA/SHA-1) + rrset_rrsig->addRdata(generic::RRSIG("A 3 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + rrset_a->addRRsig(rrset_rrsig); + + sp = rrset_a->getRRsig(); + EXPECT_TRUE(sp); + EXPECT_EQ(2, sp->getRdataCount()); + + // add to existing RRSIG + rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::RRSIG(), RRTTL(3600))); + // another signature algorithm (4 = ECC) + rrset_rrsig->addRdata(generic::RRSIG("A 4 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + rrset_a->addRRsig(rrset_rrsig); + EXPECT_EQ(3, sp->getRdataCount()); +} + +TEST_F(RRsetRRSIGTest, getRRsigDataCount) { + EXPECT_EQ(1, rrset_aaaa->getRRsigDataCount()); + EXPECT_EQ(0, rrset_a->getRRsigDataCount()); + + rrset_rrsig = RRsetPtr(new RRset(test_name, RRClass::IN(), + RRType::RRSIG(), RRTTL(3600))); + // one signature algorithm (5 = RSA/SHA-1) + rrset_rrsig->addRdata(generic::RRSIG("A 5 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + // another signature algorithm (3 = DSA/SHA-1) + rrset_rrsig->addRdata(generic::RRSIG("A 3 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + rrset_a->addRRsig(rrset_rrsig); + EXPECT_EQ(2, rrset_a->getRRsigDataCount()); + + rrset_a->removeRRsig(); + EXPECT_EQ(0, rrset_a->getRRsigDataCount()); +} + +TEST_F(RRsetRRSIGTest, toText) { + // toText() should also return the associated RRSIG. + EXPECT_EQ("test.example.com. 3600 IN AAAA 2001:db8::1234\n" + "test.example.com. 3600 IN RRSIG AAAA 5 3 7200 " + "20100322084538 20100220084538 1 example.com. FAKEFAKEFAKEFAKE\n", + rrset_aaaa->toText()); +} + +TEST_F(RRsetRRSIGTest, getLength) { + // A RR + // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets + // TYPE field = 2 octets + // CLASS field = 2 octets + // TTL field = 4 octets + // RDLENGTH field = 2 octets + // A RDATA = 4 octets + // Total = 18 + 2 + 2 + 4 + 2 + 4 = 32 octets + + // 2 A RRs + EXPECT_EQ(32 + 32, rrset_a->getLength()); + + // RRSIG + // test.example.com = 1 + 4 + 1 + 7 + 1 + 3 + 1 = 18 octets + // TYPE field = 2 octets + // CLASS field = 2 octets + // TTL field = 4 octets + // RDLENGTH field = 2 octets + // RRSIG RDATA = 40 octets + // Total = 18 + 2 + 2 + 4 + 2 + 40 = 68 octets + RRsetPtr my_rrsig(new RRset(test_name, RRClass::IN(), + RRType::RRSIG(), RRTTL(3600))); + my_rrsig->addRdata(generic::RRSIG("A 4 3 3600 " + "20000101000000 20000201000000 " + "12345 example.com. FAKEFAKEFAKE")); + EXPECT_EQ(68, my_rrsig->getLength()); + + // RRset with attached RRSIG + rrset_a->addRRsig(my_rrsig); + + EXPECT_EQ(32 + 32 + 68, rrset_a->getLength()); +} +} diff --git a/src/lib/dns/tests/rrttl_unittest.cc b/src/lib/dns/tests/rrttl_unittest.cc new file mode 100644 index 0000000..49e48fc --- /dev/null +++ b/src/lib/dns/tests/rrttl_unittest.cc @@ -0,0 +1,279 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <gtest/gtest.h> + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/rrttl.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +#include <boost/scoped_ptr.hpp> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using boost::scoped_ptr; +using isc::util::unittests::matchWireData; + +namespace { +class RRTTLTest : public ::testing::Test { +protected: + RRTTLTest() : obuffer(0) {} + + OutputBuffer obuffer; + MessageRenderer renderer; + + static RRTTL rrttlFactoryFromWire(const char* datafile); + static const RRTTL ttl_0, ttl_1h, ttl_1d, ttl_32bit, ttl_max; + static const RRTTL ttl_small, ttl_large; + static const uint8_t wiredata[20]; +}; + +const RRTTL RRTTLTest::ttl_0(0); +const RRTTL RRTTLTest::ttl_1h(3600); +const RRTTL RRTTLTest::ttl_1d(86400); +const RRTTL RRTTLTest::ttl_32bit(0x12345678); +const RRTTL RRTTLTest::ttl_max(0xffffffff); + +const RRTTL RRTTLTest::ttl_small(1); +const RRTTL RRTTLTest::ttl_large(0x80000001); +// This is wire-format data for the above sample RRTTLs rendered in the +// appearing order. +const uint8_t RRTTLTest::wiredata[20] = { 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0e, 0x10, + 0x00, 0x01, 0x51, 0x80, + 0x12, 0x34, 0x56, 0x78, + 0xff, 0xff, 0xff, 0xff }; + +RRTTL +RRTTLTest::rrttlFactoryFromWire(const char* datafile) { + std::vector<unsigned char> data; + UnitTestUtil::readWireData(datafile, data); + + InputBuffer buffer(&data[0], data.size()); + + return (RRTTL(buffer)); +} + +TEST_F(RRTTLTest, getValue) { + EXPECT_EQ(0, ttl_0.getValue()); + EXPECT_EQ(3600, ttl_1h.getValue()); + EXPECT_EQ(86400, ttl_1d.getValue()); + EXPECT_EQ(0x12345678, ttl_32bit.getValue()); + EXPECT_EQ(0xffffffff, ttl_max.getValue()); +} + +TEST_F(RRTTLTest, copyConstruct) { + const RRTTL ttl1(3600); + const RRTTL ttl2(ttl1); + EXPECT_EQ(ttl1.getValue(), ttl2.getValue()); +} + +TEST_F(RRTTLTest, fromText) { + // Border cases + EXPECT_EQ(0, RRTTL("0").getValue()); + EXPECT_EQ(4294967295U, RRTTL("4294967295").getValue()); + + // Invalid cases + EXPECT_THROW(RRTTL("0xdeadbeef"), InvalidRRTTL); // must be decimal + EXPECT_THROW(RRTTL("-1"), InvalidRRTTL); // must be positive + EXPECT_THROW(RRTTL("1.1"), InvalidRRTTL); // must be integer + EXPECT_THROW(RRTTL("4294967296"), InvalidRRTTL); // must be 32-bit +} + +TEST_F(RRTTLTest, createFromText) { + // It returns an actual RRTTL iff the given text is recognized as a + // valid RR TTL. + scoped_ptr<RRTTL> good_ttl(RRTTL::createFromText("3600")); + EXPECT_TRUE(good_ttl); + EXPECT_EQ(RRTTL(3600), *good_ttl); + + scoped_ptr<RRTTL> bad_ttl(RRTTL::createFromText("bad")); + EXPECT_FALSE(bad_ttl); +} + +void +checkUnit(unsigned multiply, char suffix) { + SCOPED_TRACE(string("Unit check with suffix ") + suffix); + const uint32_t value = 10 * multiply; + const string num = "10"; + // Check both lower and upper version of the suffix + EXPECT_EQ(value, + RRTTL(num + static_cast<char>(tolower(suffix))).getValue()); + EXPECT_EQ(value, + RRTTL(num + static_cast<char>(toupper(suffix))).getValue()); +} + +// Check parsing the unit form (1D, etc) +TEST_F(RRTTLTest, fromTextUnit) { + // Check each of the units separately + checkUnit(1, 'S'); + checkUnit(60, 'M'); + checkUnit(60 * 60, 'H'); + checkUnit(24 * 60 * 60, 'D'); + checkUnit(7 * 24 * 60 * 60, 'W'); + + // Some border cases (with units) + EXPECT_EQ(4294967295U, RRTTL("4294967295S").getValue()); + EXPECT_EQ(0, RRTTL("0W0D0H0M0S").getValue()); + EXPECT_EQ(4294967295U, RRTTL("1193046H1695S").getValue()); + // Leading zeroes are accepted + EXPECT_EQ(4294967295U, RRTTL("0000000000000004294967295S").getValue()); + + // Now some compound ones. We allow any order (it would be much work to + // check the order anyway). + EXPECT_EQ(60 * 60 + 3, RRTTL("1H3S").getValue()); + + // Awkward, but allowed case - the same unit used twice. + EXPECT_EQ(20 * 3600, RRTTL("12H8H").getValue()); + + // Negative number in part of the expression, but the total is positive. + // Rejected. + EXPECT_THROW(RRTTL("-1S1H"), InvalidRRTTL); + + // Some things out of range in the ttl, but it wraps to number in range + // in int64_t. Should still not get fooled and reject it. + + // First part out of range + EXPECT_THROW(RRTTL("9223372036854775807S9223372036854775807S2S"), + InvalidRRTTL); + // Second part out of range, but it immediately wraps (2S+2^64-2S) + EXPECT_THROW(RRTTL("2S18446744073709551614S"), InvalidRRTTL); + // The whole thing wraps right away (2^64S) + EXPECT_THROW(RRTTL("18446744073709551616S"), InvalidRRTTL); + // Second part out of range, and will become negative with the unit, + EXPECT_THROW(RRTTL("256S307445734561825856M"), InvalidRRTTL); + + // Missing before unit. + EXPECT_THROW(RRTTL("W5H"), InvalidRRTTL); + EXPECT_THROW(RRTTL("5hW"), InvalidRRTTL); + + // Empty string is not allowed + EXPECT_THROW(RRTTL(""), InvalidRRTTL); + // Missing the last unit is not allowed + EXPECT_THROW(RRTTL("3D5"), InvalidRRTTL); + + // There are some wrong units + EXPECT_THROW(RRTTL("13X"), InvalidRRTTL); + EXPECT_THROW(RRTTL("3D5F"), InvalidRRTTL); +} + +TEST_F(RRTTLTest, fromWire) { + EXPECT_EQ(0x12345678, + rrttlFactoryFromWire("rrcode32_fromWire1").getValue()); + EXPECT_THROW(rrttlFactoryFromWire("rrcode32_fromWire2"), + IncompleteRRTTL); +} + +TEST_F(RRTTLTest, toText) { + EXPECT_EQ("0", ttl_0.toText()); + EXPECT_EQ("3600", ttl_1h.toText()); + EXPECT_EQ("86400", ttl_1d.toText()); + EXPECT_EQ("305419896", ttl_32bit.toText()); + EXPECT_EQ("4294967295", ttl_max.toText()); +} + +TEST_F(RRTTLTest, toWireBuffer) { + ttl_0.toWire(obuffer); + ttl_1h.toWire(obuffer); + ttl_1d.toWire(obuffer); + ttl_32bit.toWire(obuffer); + ttl_max.toWire(obuffer); + + matchWireData(wiredata, sizeof(wiredata), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(RRTTLTest, toWireRenderer) { + ttl_0.toWire(renderer); + ttl_1h.toWire(renderer); + ttl_1d.toWire(renderer); + ttl_32bit.toWire(renderer); + ttl_max.toWire(renderer); + + matchWireData(wiredata, sizeof(wiredata), + renderer.getData(), renderer.getLength()); +} + +TEST_F(RRTTLTest, equal) { + EXPECT_TRUE(RRTTL("3600") == ttl_1h); + EXPECT_TRUE(RRTTL("86400").equals(ttl_1d)); + + EXPECT_TRUE(ttl_1d != ttl_1h); + EXPECT_TRUE(ttl_1d.nequals(ttl_max)); +} + +// +// The following set of tests confirm the result of <=, <, >=, > +// The test logic is simple, and all tests are just straightforward variations +// of the first one. +// +TEST_F(RRTTLTest, leq) { + // small <= large is true + EXPECT_TRUE(ttl_small.leq(ttl_large)); + EXPECT_TRUE(ttl_small <= ttl_large); + + // small <= small is true + EXPECT_TRUE(ttl_small.leq(ttl_small)); + EXPECT_LE(ttl_small, ttl_small); + + // large <= small is false + EXPECT_FALSE(ttl_large.leq(ttl_small)); + EXPECT_FALSE(ttl_large <= ttl_small); +} + +TEST_F(RRTTLTest, geq) { + EXPECT_TRUE(ttl_large.geq(ttl_small)); + EXPECT_TRUE(ttl_large >= ttl_small); + + EXPECT_TRUE(ttl_large.geq(ttl_large)); + EXPECT_GE(ttl_large, ttl_large); + + EXPECT_FALSE(ttl_small.geq(ttl_large)); + EXPECT_FALSE(ttl_small >= ttl_large); +} + +TEST_F(RRTTLTest, lthan) { + EXPECT_TRUE(ttl_small.lthan(ttl_large)); + EXPECT_TRUE(ttl_small < ttl_large); + + EXPECT_FALSE(ttl_small.lthan(ttl_small)); + // cppcheck-suppress duplicateExpression + EXPECT_FALSE(ttl_small < ttl_small); + + EXPECT_FALSE(ttl_large.lthan(ttl_small)); + EXPECT_FALSE(ttl_large < ttl_small); +} + +TEST_F(RRTTLTest, gthan) { + EXPECT_TRUE(ttl_large.gthan(ttl_small)); + EXPECT_TRUE(ttl_large > ttl_small); + + EXPECT_FALSE(ttl_large.gthan(ttl_large)); + // cppcheck-suppress duplicateExpression + EXPECT_FALSE(ttl_large > ttl_large); + + EXPECT_FALSE(ttl_small.gthan(ttl_large)); + EXPECT_FALSE(ttl_small > ttl_large); +} + +TEST_F(RRTTLTest, maxTTL) { + EXPECT_EQ((1u << 31) - 1, RRTTL::MAX_TTL().getValue()); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(RRTTLTest, LeftShiftOperator) { + ostringstream oss; + oss << ttl_1h; + EXPECT_EQ(ttl_1h.toText(), oss.str()); +} +} diff --git a/src/lib/dns/tests/rrtype_unittest.cc b/src/lib/dns/tests/rrtype_unittest.cc new file mode 100644 index 0000000..d874ebc --- /dev/null +++ b/src/lib/dns/tests/rrtype_unittest.cc @@ -0,0 +1,290 @@ +// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <gtest/gtest.h> + +#include <util/buffer.h> +#include <dns/messagerenderer.h> +#include <dns/rrparamregistry.h> +#include <dns/rrtype.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using isc::util::unittests::matchWireData; + +namespace { +class RRTypeTest : public ::testing::Test { +protected: + RRTypeTest() : obuffer(0) {} + + OutputBuffer obuffer; + MessageRenderer renderer; + + static RRType rrtypeFactoryFromWire(const char* datafile); + static const RRType rrtype_1, rrtype_0x80, rrtype_0x800, rrtype_0x8000, + rrtype_max; + static const uint8_t wiredata[]; +}; + +const RRType RRTypeTest::rrtype_1(1); +const RRType RRTypeTest::rrtype_0x80(0x80); +const RRType RRTypeTest::rrtype_0x800(0x800); +const RRType RRTypeTest::rrtype_0x8000(0x8000); +const RRType RRTypeTest::rrtype_max(0xffff); +// This is wire-format data for the above sample RRTypes rendered in the +// appearing order. +const uint8_t RRTypeTest::wiredata[] = { 0x00, 0x01, 0x00, 0x80, 0x08, + 0x00, 0x80, 0x00, 0xff, 0xff }; + +RRType +RRTypeTest::rrtypeFactoryFromWire(const char* datafile) { + std::vector<unsigned char> data; + UnitTestUtil::readWireData(datafile, data); + + InputBuffer buffer(&data[0], data.size()); + + return (RRType(buffer)); +} + +TEST_F(RRTypeTest, fromText) { + EXPECT_EQ("A", RRType("A").toText()); + EXPECT_EQ("NS", RRType("NS").toText()); + + EXPECT_EQ("TYPE65535", RRType("TYPE65535").toText()); + + // something unusual, but existing implementations accept this form, + // so do we. + EXPECT_EQ(53, RRType("TYPE00053").getCode()); + // again, unusual, and the majority of other implementations reject it. + // In any case, there should be no reasonable reason to accept such a + // ridiculously long input. + EXPECT_THROW(RRType("TYPE000053"), InvalidRRType); + + // bogus TYPEnnn representations: should trigger an exception + EXPECT_THROW(RRType("TYPE"), InvalidRRType); + EXPECT_THROW(RRType("TYPE-1"), InvalidRRType); + EXPECT_THROW(RRType("TYPExxx"), InvalidRRType); + EXPECT_THROW(RRType("TYPE65536"), InvalidRRType); + EXPECT_THROW(RRType("TYPE6500x"), InvalidRRType); + EXPECT_THROW(RRType("TYPE65000 "), InvalidRRType); +} + +TEST_F(RRTypeTest, fromWire) { + EXPECT_EQ(0x1234, + rrtypeFactoryFromWire("rrcode16_fromWire1").getCode()); + EXPECT_THROW(rrtypeFactoryFromWire("rrcode16_fromWire2"), IncompleteRRType); +} + +// from string, lower case +TEST_F(RRTypeTest, caseConstruct) { + EXPECT_EQ("A", RRType("a").toText()); + EXPECT_EQ("NS", RRType("ns").toText()); + EXPECT_EQ("TYPE65535", RRType("type65535").toText()); +} + +TEST_F(RRTypeTest, toText) { + EXPECT_EQ("A", RRType(1).toText()); + EXPECT_EQ("TYPE65000", RRType(65000).toText()); +} + +TEST_F(RRTypeTest, toWireBuffer) { + rrtype_1.toWire(obuffer); + rrtype_0x80.toWire(obuffer); + rrtype_0x800.toWire(obuffer); + rrtype_0x8000.toWire(obuffer); + rrtype_max.toWire(obuffer); + + matchWireData(wiredata, sizeof(wiredata), + obuffer.getData(), obuffer.getLength()); +} + +TEST_F(RRTypeTest, toWireRenderer) { + rrtype_1.toWire(renderer); + rrtype_0x80.toWire(renderer); + rrtype_0x800.toWire(renderer); + rrtype_0x8000.toWire(renderer); + rrtype_max.toWire(renderer); + + matchWireData(wiredata, sizeof(wiredata), + renderer.getData(), renderer.getLength()); +} + +TEST_F(RRTypeTest, wellKnownTypes) { + EXPECT_EQ(1, RRType::A().getCode()); + EXPECT_EQ("A", RRType::A().toText()); +} + +TEST_F(RRTypeTest, compare) { + EXPECT_TRUE(RRType(1) == RRType("A")); + EXPECT_TRUE(RRType(1).equals(RRType("A"))); + EXPECT_TRUE(RRType(0) != RRType("A")); + EXPECT_TRUE(RRType(0).nequals(RRType("A"))); + + EXPECT_TRUE(RRType("A") < RRType("NS")); + EXPECT_TRUE(RRType(100) < RRType(65535)); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(RRTypeTest, LeftShiftOperator) { + ostringstream oss; + oss << RRType::A(); + EXPECT_EQ(RRType::A().toText(), oss.str()); +} + +// Below, we'll check definitions for all well-known RR types; whether they +// are defined and have the correct parameter values. Test data are generated +// from the list available at: +// http://www.iana.org/assignments/dns-parameters/dns-parameters.xml +struct WellKnownTypeParam { + const char* const txt; // "A", "AAAA", "NS", etc + const uint16_t code; // 1, 28, 2, etc + const RRType& (*obj)(); // RRType::A(), etc +} well_known_types[] = { + {"A", 1, RRType::A}, + {"NS", 2, RRType::NS}, + {"SOA", 6, RRType::SOA}, + {"PTR", 12, RRType::PTR}, + {"TXT", 16, RRType::TXT}, + {"AAAA", 28, RRType::AAAA}, + {"OPT", 41, RRType::OPT}, + {"RRSIG", 46, RRType::RRSIG}, + {"DHCID", 49, RRType::DHCID}, + {"TKEY", 249, RRType::TKEY}, + {"TSIG", 250, RRType::TSIG}, + {"ANY", 255, RRType::ANY}, + {0, 0, 0} +}; + +TEST(RRTypeConstTest, wellKnowns) { + for (size_t i = 0; well_known_types[i].txt; ++i) { + SCOPED_TRACE("Checking well known RRType: " + + string(well_known_types[i].txt)); + EXPECT_EQ(well_known_types[i].code, + RRType(well_known_types[i].txt).getCode()); + EXPECT_EQ(well_known_types[i].code, + (*well_known_types[i].obj)().getCode()); + } +} + +// Below, we'll check definitions for all registered RR types. +struct RegisteredTypeParam { + const char* const txt; // "A", "AAAA", "NS", etc + const uint16_t code; // 1, 28, 2, etc +} registered_types[] = { + {"A", 1}, + {"NS", 2}, + {"MD", 3}, + {"MF", 4}, + {"CNAME", 5}, + {"SOA", 6}, + {"MB", 7}, + {"MG", 8}, + {"MR", 9}, + {"NULL", 10}, + {"WKS", 11}, + {"PTR", 12}, + {"HINFO", 13}, + {"MINFO", 14}, + {"MX", 15}, + {"TXT", 16}, + {"RP", 17}, + {"AFSDB", 18}, + {"X25", 19}, + {"ISDN", 20}, + {"RT", 21}, + {"NSAP", 22}, + {"NSAP-PTR", 23}, + {"SIG", 24}, + {"KEY", 25}, + {"PX", 26}, + {"GPOS", 27}, + {"AAAA", 28}, + {"LOC", 29}, + {"NXT", 30}, + {"EID", 31}, + {"NIMLOC", 32}, + {"SRV", 33}, + {"ATMA", 34}, + {"NAPTR", 35}, + {"KX", 36}, + {"CERT", 37}, + {"A6", 38}, + {"DNAME", 39}, + {"SINK", 40}, + {"OPT", 41}, + {"APL", 42}, + {"DS", 43}, + {"SSHFP", 44}, + {"IPSECKEY", 45}, + {"RRSIG", 46}, + {"NSEC", 47}, + {"DNSKEY", 48}, + {"DHCID", 49}, + {"NSEC3", 50}, + {"NSEC3PARAM", 51}, + {"TLSA", 52}, + {"SMIMEA", 53}, + {"HIP", 55}, + {"NINFO", 56}, + {"RKEY", 57}, + {"TALINK", 58}, + {"CDS", 59}, + {"CDNSKEY", 60}, + {"OPENPGPKEY", 61}, + {"CSYNC", 62}, + {"ZONEMD", 63}, + {"SVCB", 64}, + {"HTTPS", 65}, + {"SPF", 99}, + {"UINFO", 100}, + {"UID", 101}, + {"GID", 102}, + {"UNSPEC", 103}, + {"NID", 104}, + {"L32", 105}, + {"L64", 106}, + {"LP", 107}, + {"EUI48", 108}, + {"EUI64", 109}, + {"TKEY", 249}, + {"TSIG", 250}, + {"IXFR", 251}, + {"AXFR", 252}, + {"MAILB", 253}, + {"MAILA", 254}, + {"ANY", 255}, + {"URI", 256}, + {"CAA", 257}, + {"AVC", 258}, + {"DOA", 259}, + {"AMTRELAY", 260}, + {"RESINFO", 261}, + {"TA", 32768}, + {"DLV", 32769}, + {0, 0} +}; + +TEST(RRTypeConstTest, registered) { + for (size_t i = 0; registered_types[i].txt; ++i) { + SCOPED_TRACE("Checking registered RRType: " + + string(registered_types[i].txt)); + uint16_t code = 0; + EXPECT_NO_THROW(RRParamRegistry::getRegistry().textToTypeCode(registered_types[i].txt, code)); + EXPECT_EQ(code, registered_types[i].code); + string txt; + EXPECT_NO_THROW(txt = RRParamRegistry::getRegistry().codeToTypeText(registered_types[i].code)); + EXPECT_EQ(txt, registered_types[i].txt); + } +} + +} diff --git a/src/lib/dns/tests/run_unittests.cc b/src/lib/dns/tests/run_unittests.cc new file mode 100644 index 0000000..de396fa --- /dev/null +++ b/src/lib/dns/tests/run_unittests.cc @@ -0,0 +1,24 @@ +// Copyright (C) 2009-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <gtest/gtest.h> +#include <util/unittests/run_all.h> + +#include <util/unittests/testdata.h> +#include <dns/tests/unittest_util.h> + +int +main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + isc::UnitTestUtil::addDataPath(TEST_DATA_SRCDIR); + isc::util::unittests::addTestDataPath(TEST_DATA_SRCDIR); + isc::UnitTestUtil::addDataPath(TEST_DATA_BUILDDIR); + isc::util::unittests::addTestDataPath(TEST_DATA_BUILDDIR); + + return (isc::util::unittests::run_all()); +} diff --git a/src/lib/dns/tests/serial_unittest.cc b/src/lib/dns/tests/serial_unittest.cc new file mode 100644 index 0000000..07cd142 --- /dev/null +++ b/src/lib/dns/tests/serial_unittest.cc @@ -0,0 +1,173 @@ +// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <gtest/gtest.h> + +#include <dns/serial.h> + +using namespace isc::dns; + +class SerialTest : public ::testing::Test { +public: + SerialTest() : one(1), one_2(1), two(2), + date_zero(1980120100), date_one(1980120101), + min(0), max(4294967295u), + number_low(12345), + number_medium(2000000000), + number_high(4000000000u) + {} + Serial one, one_2, two, date_zero, date_one, min, max, number_low, number_medium, number_high; +}; + +// +// Basic tests +// + +TEST_F(SerialTest, get_value) { + EXPECT_EQ(1, one.getValue()); + EXPECT_NE(2, one.getValue()); + EXPECT_EQ(2, two.getValue()); + EXPECT_EQ(1980120100, date_zero.getValue()); + EXPECT_EQ(1980120101, date_one.getValue()); + EXPECT_EQ(0, min.getValue()); + EXPECT_EQ(4294967295u, max.getValue()); + EXPECT_EQ(12345, number_low.getValue()); + EXPECT_EQ(2000000000, number_medium.getValue()); + EXPECT_EQ(4000000000u, number_high.getValue()); +} + +TEST_F(SerialTest, equals) { + EXPECT_EQ(one, one); + EXPECT_EQ(one, one_2); + EXPECT_NE(one, two); + EXPECT_NE(two, one); + EXPECT_EQ(Serial(12345), number_low); + EXPECT_NE(Serial(12346), number_low); +} + +TEST_F(SerialTest, comparison) { + // These should be true/false even without serial arithmetic + EXPECT_LE(one, one); + EXPECT_LE(one, one_2); + EXPECT_LT(one, two); + EXPECT_LE(one, two); + EXPECT_GE(two, two); + EXPECT_GT(two, one); + EXPECT_GE(two, one); + EXPECT_LT(one, number_low); + EXPECT_LT(number_low, number_medium); + EXPECT_LT(number_medium, number_high); + + // now let's try some that 'wrap', as it were + EXPECT_GT(min, max); + EXPECT_LT(max, min); + EXPECT_LT(number_high, number_low); +} + +// +// RFC 1982 Section 3.1 +// +TEST_F(SerialTest, addition) { + EXPECT_EQ(two, one + one); + EXPECT_EQ(two, one + one_2); + EXPECT_EQ(max, max + min); + EXPECT_EQ(min, max + one); + EXPECT_EQ(one, max + two); + EXPECT_EQ(one, max + one + one); + + EXPECT_EQ(one + 100, max + 102); + EXPECT_EQ(min + 2147483645, max + 2147483646); + EXPECT_EQ(min + 2147483646, max + MAX_SERIAL_INCREMENT); +} + +// +// RFC 1982 Section 3.2 has been checked by the basic tests above +// + +// +// RFC 1982 Section 4.1 +// + +// Helper function for addition_always_larger test, add some numbers +// and check that the result is always larger than the original +void do_addition_larger_test(const Serial& number) { + EXPECT_GE(number + 0, number); + EXPECT_EQ(number + 0, number); + EXPECT_GT(number + 1, number); + EXPECT_GT(number + 2, number); + EXPECT_GT(number + 100, number); + EXPECT_GT(number + 1111111, number); + EXPECT_GT(number + 2147483646, number); + EXPECT_GT(number + MAX_SERIAL_INCREMENT, number); + // Try MAX_SERIAL_INCREMENT as a hardcoded number as well + EXPECT_GT(number + 2147483647, number); +} + +TEST_F(SerialTest, addition_always_larger) { + do_addition_larger_test(one); + do_addition_larger_test(two); + do_addition_larger_test(date_zero); + do_addition_larger_test(date_one); + do_addition_larger_test(min); + do_addition_larger_test(max); + do_addition_larger_test(number_low); + do_addition_larger_test(number_medium); + do_addition_larger_test(number_high); +} + +// +// RFC 1982 Section 4.2 +// + +// Helper function to do the second addition +void +do_two_additions_test_second(const Serial &original, + const Serial &number) +{ + EXPECT_NE(original, number); + EXPECT_NE(original, number + 0); + EXPECT_NE(original, number + 1); + EXPECT_NE(original, number + 2); + EXPECT_NE(original, number + 100); + EXPECT_NE(original, number + 1111111); + EXPECT_NE(original, number + 2147483646); + EXPECT_NE(original, number + MAX_SERIAL_INCREMENT); + EXPECT_NE(original, number + 2147483647); +} + +void do_two_additions_test_first(const Serial &number) { + do_two_additions_test_second(number, number + 1); + do_two_additions_test_second(number, number + 2); + do_two_additions_test_second(number, number + 100); + do_two_additions_test_second(number, number + 1111111); + do_two_additions_test_second(number, number + 2147483646); + do_two_additions_test_second(number, number + MAX_SERIAL_INCREMENT); + do_two_additions_test_second(number, number + 2147483647); +} + +TEST_F(SerialTest, two_additions_never_equal) { + do_two_additions_test_first(one); + do_two_additions_test_first(two); + do_two_additions_test_first(date_zero); + do_two_additions_test_first(date_one); + do_two_additions_test_first(min); + do_two_additions_test_first(max); + do_two_additions_test_first(number_low); + do_two_additions_test_first(number_medium); + do_two_additions_test_first(number_high); +} + +// +// RFC 1982 Section 4.3 and 4.4 have nothing to test +// + +// +// Tests from RFC 1982 examples +// +TEST(SerialTextRFCExamples, rfc_example_tests) { +} diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am new file mode 100644 index 0000000..bd56c11 --- /dev/null +++ b/src/lib/dns/tests/testdata/Makefile.am @@ -0,0 +1,122 @@ +CLEANFILES = + +# NOTE: keep this in sync with real file listing +# so is included in tarball +EXTRA_DIST = edns_toWire1.spec edns_toWire2.spec +EXTRA_DIST += edns_toWire3.spec edns_toWire4.spec +EXTRA_DIST += masterload.txt +EXTRA_DIST += message_fromWire1 message_fromWire2 +EXTRA_DIST += message_fromWire3 message_fromWire4 +EXTRA_DIST += message_fromWire5 message_fromWire6 +EXTRA_DIST += message_fromWire7 message_fromWire8 +EXTRA_DIST += message_fromWire9 message_fromWire10.spec +EXTRA_DIST += message_fromWire11.spec message_fromWire12.spec +EXTRA_DIST += message_fromWire13.spec message_fromWire14.spec +EXTRA_DIST += message_fromWire15.spec message_fromWire16.spec +EXTRA_DIST += message_fromWire17.spec message_fromWire18.spec +EXTRA_DIST += message_fromWire19.spec message_fromWire20.spec +EXTRA_DIST += message_fromWire21.spec message_fromWire22.spec +EXTRA_DIST += message_toWire1 message_toWire2.spec message_toWire3.spec +EXTRA_DIST += message_toWire4.spec message_toWire5.spec +EXTRA_DIST += message_toWire6 message_toWire7 +EXTRA_DIST += message_toText1.txt message_toText1.spec +EXTRA_DIST += message_toText2.txt message_toText2.spec +EXTRA_DIST += message_toText3.txt message_toText3.spec +EXTRA_DIST += name_fromWire1 name_fromWire2 name_fromWire3_1 name_fromWire3_2 +EXTRA_DIST += name_fromWire4 name_fromWire6 name_fromWire7 name_fromWire8 +EXTRA_DIST += name_fromWire9 name_fromWire10 name_fromWire11 name_fromWire12 +EXTRA_DIST += name_fromWire13 name_fromWire14 +EXTRA_DIST += name_toWire1 name_toWire2 name_toWire3 name_toWire4 +EXTRA_DIST += name_toWire5.spec name_toWire6.spec +EXTRA_DIST += name_toWire7 name_toWire8 name_toWire9 +EXTRA_DIST += question_fromWire question_toWire1 question_toWire2 +EXTRA_DIST += rdata_dhcid_fromWire rdata_dhcid_toWire +EXTRA_DIST += rdata_ns_fromWire +EXTRA_DIST += rdata_in_a_fromWire rdata_in_aaaa_fromWire +EXTRA_DIST += rdata_opt_fromWire1 rdata_opt_fromWire2 +EXTRA_DIST += rdata_opt_fromWire3 rdata_opt_fromWire4 +EXTRA_DIST += rdata_rrsig_fromWire1 +EXTRA_DIST += rdata_rrsig_fromWire2.spec +EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.spec +EXTRA_DIST += rdata_txt_fromWire1 rdata_txt_fromWire2.spec +EXTRA_DIST += rdata_txt_fromWire3.spec rdata_txt_fromWire4.spec +EXTRA_DIST += rdata_txt_fromWire5.spec rdata_unknown_fromWire +EXTRA_DIST += rrcode16_fromWire1 rrcode16_fromWire2 +EXTRA_DIST += rrcode32_fromWire1 rrcode32_fromWire2 +EXTRA_DIST += rrset_toWire1 rrset_toWire2 +EXTRA_DIST += rrset_toWire3 rrset_toWire4 +EXTRA_DIST += rdata_tkey_fromWire1.spec rdata_tkey_fromWire2.spec +EXTRA_DIST += rdata_tkey_fromWire3.spec rdata_tkey_fromWire4.spec +EXTRA_DIST += rdata_tkey_fromWire5.spec rdata_tkey_fromWire6.spec +EXTRA_DIST += rdata_tkey_fromWire7.spec rdata_tkey_fromWire8.spec +EXTRA_DIST += rdata_tkey_fromWire9.spec +EXTRA_DIST += rdata_tkey_toWire1.spec rdata_tkey_toWire2.spec +EXTRA_DIST += rdata_tkey_toWire3.spec rdata_tkey_toWire4.spec +EXTRA_DIST += rdata_tkey_toWire5.spec +EXTRA_DIST += rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec +EXTRA_DIST += rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec +EXTRA_DIST += rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec +EXTRA_DIST += rdata_tsig_fromWire7.spec rdata_tsig_fromWire8.spec +EXTRA_DIST += rdata_tsig_fromWire9.spec +EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec +EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec +EXTRA_DIST += rdata_tsig_toWire5.spec +EXTRA_DIST += tsigrecord_toWire1.spec tsigrecord_toWire2.spec +EXTRA_DIST += tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec +EXTRA_DIST += tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec +EXTRA_DIST += tsig_verify7.spec tsig_verify8.spec tsig_verify9.spec +EXTRA_DIST += tsig_verify10.spec tsig_verify11.spec +EXTRA_DIST += example.org +EXTRA_DIST += broken.zone +EXTRA_DIST += origincheck.txt +EXTRA_DIST += omitcheck.txt + +# Generated .wire files +EXTRA_DIST += edns_toWire1.wire edns_toWire2.wire +EXTRA_DIST += edns_toWire3.wire edns_toWire4.wire +EXTRA_DIST += message_fromWire10.wire +EXTRA_DIST += message_fromWire11.wire message_fromWire12.wire +EXTRA_DIST += message_fromWire13.wire message_fromWire14.wire +EXTRA_DIST += message_fromWire15.wire message_fromWire16.wire +EXTRA_DIST += message_fromWire17.wire message_fromWire18.wire +EXTRA_DIST += message_fromWire19.wire message_fromWire20.wire +EXTRA_DIST += message_fromWire21.wire message_fromWire22.wire +EXTRA_DIST += message_toWire1 message_toWire2.wire message_toWire3.wire +EXTRA_DIST += message_toWire4.wire message_toWire5.wire +EXTRA_DIST += message_toText1.txt message_toText1.wire +EXTRA_DIST += message_toText2.txt message_toText2.wire +EXTRA_DIST += message_toText3.txt message_toText3.wire +EXTRA_DIST += name_toWire5.wire name_toWire6.wire +EXTRA_DIST += rdata_rrsig_fromWire2.wire +EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.wire +EXTRA_DIST += rdata_txt_fromWire1 rdata_txt_fromWire2.wire +EXTRA_DIST += rdata_txt_fromWire3.wire rdata_txt_fromWire4.wire +EXTRA_DIST += rdata_txt_fromWire5.wire rdata_unknown_fromWire +EXTRA_DIST += rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire +EXTRA_DIST += rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire +EXTRA_DIST += rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire +EXTRA_DIST += rdata_tsig_fromWire7.wire rdata_tsig_fromWire8.wire +EXTRA_DIST += rdata_tsig_fromWire9.wire +EXTRA_DIST += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire +EXTRA_DIST += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire +EXTRA_DIST += rdata_tsig_toWire5.wire +EXTRA_DIST += rdata_tkey_fromWire1.wire rdata_tkey_fromWire2.wire +EXTRA_DIST += rdata_tkey_fromWire3.wire rdata_tkey_fromWire4.wire +EXTRA_DIST += rdata_tkey_fromWire5.wire rdata_tkey_fromWire6.wire +EXTRA_DIST += rdata_tkey_fromWire7.wire rdata_tkey_fromWire8.wire +EXTRA_DIST += rdata_tkey_fromWire9.wire +EXTRA_DIST += rdata_tkey_toWire1.wire rdata_tkey_toWire2.wire +EXTRA_DIST += rdata_tkey_toWire3.wire rdata_tkey_toWire4.wire +EXTRA_DIST += rdata_tkey_toWire5.wire +EXTRA_DIST += tsigrecord_toWire1.wire tsigrecord_toWire2.wire +EXTRA_DIST += tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire +EXTRA_DIST += tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire +EXTRA_DIST += tsig_verify7.wire tsig_verify8.wire tsig_verify9.wire +EXTRA_DIST += tsig_verify10.wire tsig_verify11.wire + +# We no longer use gen_wiredata.py during build process, so the +# dependency is no longer needed. However, we'll keep this dependency +# commented till the gen_wiredata.py script is removed. + +#.spec.wire: +# $(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $< diff --git a/src/lib/dns/tests/testdata/Makefile.in b/src/lib/dns/tests/testdata/Makefile.in new file mode 100644 index 0000000..955f1bd --- /dev/null +++ b/src/lib/dns/tests/testdata/Makefile.in @@ -0,0 +1,654 @@ +# Makefile.in generated by automake 1.16.5 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2021 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/dns/tests/testdata +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4macros/ax_boost_for_kea.m4 \ + $(top_srcdir)/m4macros/ax_cpp14.m4 \ + $(top_srcdir)/m4macros/ax_cpp20.m4 \ + $(top_srcdir)/m4macros/ax_crypto.m4 \ + $(top_srcdir)/m4macros/ax_find_library.m4 \ + $(top_srcdir)/m4macros/ax_gssapi.m4 \ + $(top_srcdir)/m4macros/ax_gtest.m4 \ + $(top_srcdir)/m4macros/ax_isc_rpath.m4 \ + $(top_srcdir)/m4macros/ax_netconf.m4 \ + $(top_srcdir)/m4macros/libtool.m4 \ + $(top_srcdir)/m4macros/ltoptions.m4 \ + $(top_srcdir)/m4macros/ltsugar.m4 \ + $(top_srcdir)/m4macros/ltversion.m4 \ + $(top_srcdir)/m4macros/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +SOURCES = +DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +am__DIST_COMMON = $(srcdir)/Makefile.in +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +ASCIIDOC = @ASCIIDOC@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BOOST_INCLUDES = @BOOST_INCLUDES@ +BOOST_LIBS = @BOOST_LIBS@ +BOTAN_TOOL = @BOTAN_TOOL@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CONTRIB_DIR = @CONTRIB_DIR@ +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@ +CSCOPE = @CSCOPE@ +CTAGS = @CTAGS@ +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@ +DPKG = @DPKG@ +DPKGQUERY = @DPKGQUERY@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ETAGS = @ETAGS@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +FILECMD = @FILECMD@ +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@ +XMLLINT = @XMLLINT@ +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@ +CLEANFILES = + +# NOTE: keep this in sync with real file listing +# so is included in tarball + +# Generated .wire files +EXTRA_DIST = edns_toWire1.spec edns_toWire2.spec edns_toWire3.spec \ + edns_toWire4.spec masterload.txt message_fromWire1 \ + message_fromWire2 message_fromWire3 message_fromWire4 \ + message_fromWire5 message_fromWire6 message_fromWire7 \ + message_fromWire8 message_fromWire9 message_fromWire10.spec \ + message_fromWire11.spec message_fromWire12.spec \ + message_fromWire13.spec message_fromWire14.spec \ + message_fromWire15.spec message_fromWire16.spec \ + message_fromWire17.spec message_fromWire18.spec \ + message_fromWire19.spec message_fromWire20.spec \ + message_fromWire21.spec message_fromWire22.spec \ + message_toWire1 message_toWire2.spec message_toWire3.spec \ + message_toWire4.spec message_toWire5.spec message_toWire6 \ + message_toWire7 message_toText1.txt message_toText1.spec \ + message_toText2.txt message_toText2.spec message_toText3.txt \ + message_toText3.spec name_fromWire1 name_fromWire2 \ + name_fromWire3_1 name_fromWire3_2 name_fromWire4 \ + name_fromWire6 name_fromWire7 name_fromWire8 name_fromWire9 \ + name_fromWire10 name_fromWire11 name_fromWire12 \ + name_fromWire13 name_fromWire14 name_toWire1 name_toWire2 \ + name_toWire3 name_toWire4 name_toWire5.spec name_toWire6.spec \ + name_toWire7 name_toWire8 name_toWire9 question_fromWire \ + question_toWire1 question_toWire2 rdata_dhcid_fromWire \ + rdata_dhcid_toWire rdata_ns_fromWire rdata_in_a_fromWire \ + rdata_in_aaaa_fromWire rdata_opt_fromWire1 rdata_opt_fromWire2 \ + rdata_opt_fromWire3 rdata_opt_fromWire4 rdata_rrsig_fromWire1 \ + rdata_rrsig_fromWire2.spec rdata_soa_fromWire \ + rdata_soa_toWireUncompressed.spec rdata_txt_fromWire1 \ + rdata_txt_fromWire2.spec rdata_txt_fromWire3.spec \ + rdata_txt_fromWire4.spec rdata_txt_fromWire5.spec \ + rdata_unknown_fromWire rrcode16_fromWire1 rrcode16_fromWire2 \ + rrcode32_fromWire1 rrcode32_fromWire2 rrset_toWire1 \ + rrset_toWire2 rrset_toWire3 rrset_toWire4 \ + rdata_tkey_fromWire1.spec rdata_tkey_fromWire2.spec \ + rdata_tkey_fromWire3.spec rdata_tkey_fromWire4.spec \ + rdata_tkey_fromWire5.spec rdata_tkey_fromWire6.spec \ + rdata_tkey_fromWire7.spec rdata_tkey_fromWire8.spec \ + rdata_tkey_fromWire9.spec rdata_tkey_toWire1.spec \ + rdata_tkey_toWire2.spec rdata_tkey_toWire3.spec \ + rdata_tkey_toWire4.spec rdata_tkey_toWire5.spec \ + rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec \ + rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec \ + rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec \ + rdata_tsig_fromWire7.spec rdata_tsig_fromWire8.spec \ + rdata_tsig_fromWire9.spec rdata_tsig_toWire1.spec \ + rdata_tsig_toWire2.spec rdata_tsig_toWire3.spec \ + rdata_tsig_toWire4.spec rdata_tsig_toWire5.spec \ + tsigrecord_toWire1.spec tsigrecord_toWire2.spec \ + tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec \ + tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec \ + tsig_verify7.spec tsig_verify8.spec tsig_verify9.spec \ + tsig_verify10.spec tsig_verify11.spec example.org broken.zone \ + origincheck.txt omitcheck.txt edns_toWire1.wire \ + edns_toWire2.wire edns_toWire3.wire edns_toWire4.wire \ + message_fromWire10.wire message_fromWire11.wire \ + message_fromWire12.wire message_fromWire13.wire \ + message_fromWire14.wire message_fromWire15.wire \ + message_fromWire16.wire message_fromWire17.wire \ + message_fromWire18.wire message_fromWire19.wire \ + message_fromWire20.wire message_fromWire21.wire \ + message_fromWire22.wire message_toWire1 message_toWire2.wire \ + message_toWire3.wire message_toWire4.wire message_toWire5.wire \ + message_toText1.txt message_toText1.wire message_toText2.txt \ + message_toText2.wire message_toText3.txt message_toText3.wire \ + name_toWire5.wire name_toWire6.wire rdata_rrsig_fromWire2.wire \ + rdata_soa_fromWire rdata_soa_toWireUncompressed.wire \ + rdata_txt_fromWire1 rdata_txt_fromWire2.wire \ + rdata_txt_fromWire3.wire rdata_txt_fromWire4.wire \ + rdata_txt_fromWire5.wire rdata_unknown_fromWire \ + rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire \ + rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire \ + rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire \ + rdata_tsig_fromWire7.wire rdata_tsig_fromWire8.wire \ + rdata_tsig_fromWire9.wire rdata_tsig_toWire1.wire \ + rdata_tsig_toWire2.wire rdata_tsig_toWire3.wire \ + rdata_tsig_toWire4.wire rdata_tsig_toWire5.wire \ + rdata_tkey_fromWire1.wire rdata_tkey_fromWire2.wire \ + rdata_tkey_fromWire3.wire rdata_tkey_fromWire4.wire \ + rdata_tkey_fromWire5.wire rdata_tkey_fromWire6.wire \ + rdata_tkey_fromWire7.wire rdata_tkey_fromWire8.wire \ + rdata_tkey_fromWire9.wire rdata_tkey_toWire1.wire \ + rdata_tkey_toWire2.wire rdata_tkey_toWire3.wire \ + rdata_tkey_toWire4.wire rdata_tkey_toWire5.wire \ + tsigrecord_toWire1.wire tsigrecord_toWire2.wire \ + tsig_verify1.wire tsig_verify2.wire tsig_verify3.wire \ + tsig_verify4.wire tsig_verify5.wire tsig_verify6.wire \ + tsig_verify7.wire tsig_verify8.wire tsig_verify9.wire \ + tsig_verify10.wire tsig_verify11.wire +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/lib/dns/tests/testdata/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign src/lib/dns/tests/testdata/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs +tags TAGS: + +ctags CTAGS: + +cscope cscopelist: + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + -test -z "$(CLEANFILES)" || rm -f $(CLEANFILES) + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: + +.MAKE: install-am install-strip + +.PHONY: all all-am check check-am clean clean-generic clean-libtool \ + cscopelist-am ctags-am distclean distclean-generic \ + distclean-libtool distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am install-man \ + install-pdf install-pdf-am install-ps install-ps-am \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags-am uninstall uninstall-am + +.PRECIOUS: Makefile + + +# We no longer use gen_wiredata.py during build process, so the +# dependency is no longer needed. However, we'll keep this dependency +# commented till the gen_wiredata.py script is removed. + +#.spec.wire: +# $(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $< + +# 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/dns/tests/testdata/broken.zone b/src/lib/dns/tests/testdata/broken.zone new file mode 100644 index 0000000..70f4540 --- /dev/null +++ b/src/lib/dns/tests/testdata/broken.zone @@ -0,0 +1,3 @@ +; This should fail due to broken TTL +; The file should _NOT_ end with EOLN +broken. 3600X IN A 192.0.2.2 More data
\ No newline at end of file diff --git a/src/lib/dns/tests/testdata/edns_toWire1.spec b/src/lib/dns/tests/testdata/edns_toWire1.spec new file mode 100644 index 0000000..483aefa --- /dev/null +++ b/src/lib/dns/tests/testdata/edns_toWire1.spec @@ -0,0 +1,5 @@ +# +# A simplest form of EDNS: all default parameters +# +[edns] + diff --git a/src/lib/dns/tests/testdata/edns_toWire1.wire b/src/lib/dns/tests/testdata/edns_toWire1.wire new file mode 100644 index 0000000..2884e29 --- /dev/null +++ b/src/lib/dns/tests/testdata/edns_toWire1.wire @@ -0,0 +1,9 @@ +### +### This data file was auto-generated from edns_toWire1.spec +### + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=0 +00 0029 1000 0000 0000 +# RDLEN=0 +0000 diff --git a/src/lib/dns/tests/testdata/edns_toWire2.spec b/src/lib/dns/tests/testdata/edns_toWire2.spec new file mode 100644 index 0000000..7fe1ffd --- /dev/null +++ b/src/lib/dns/tests/testdata/edns_toWire2.spec @@ -0,0 +1,5 @@ +# +# Same as edns_toWire1 but setting the DO bit +# +[edns] +do: 1 diff --git a/src/lib/dns/tests/testdata/edns_toWire2.wire b/src/lib/dns/tests/testdata/edns_toWire2.wire new file mode 100644 index 0000000..cb09000 --- /dev/null +++ b/src/lib/dns/tests/testdata/edns_toWire2.wire @@ -0,0 +1,9 @@ +### +### This data file was auto-generated from edns_toWire2.spec +### + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=1 +00 0029 1000 0000 8000 +# RDLEN=0 +0000 diff --git a/src/lib/dns/tests/testdata/edns_toWire3.spec b/src/lib/dns/tests/testdata/edns_toWire3.spec new file mode 100644 index 0000000..0332097 --- /dev/null +++ b/src/lib/dns/tests/testdata/edns_toWire3.spec @@ -0,0 +1,7 @@ +# +# Same as edns_toWire1 but setting the DO bit, and extended Rcode being non 0 +# (for BADVER) +# +[edns] +do: 1 +extrcode: 0x1 diff --git a/src/lib/dns/tests/testdata/edns_toWire3.wire b/src/lib/dns/tests/testdata/edns_toWire3.wire new file mode 100644 index 0000000..b8d0775 --- /dev/null +++ b/src/lib/dns/tests/testdata/edns_toWire3.wire @@ -0,0 +1,9 @@ +### +### This data file was auto-generated from edns_toWire3.spec +### + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=1 Version=0 DO=1 +00 0029 1000 0100 8000 +# RDLEN=0 +0000 diff --git a/src/lib/dns/tests/testdata/edns_toWire4.spec b/src/lib/dns/tests/testdata/edns_toWire4.spec new file mode 100644 index 0000000..ea1f5e3 --- /dev/null +++ b/src/lib/dns/tests/testdata/edns_toWire4.spec @@ -0,0 +1,7 @@ +# +# Same as edns_toWire1 but setting the DO bit, and using an unusual +# UDP payload size +# +[edns] +do: 1 +udpsize = 511 diff --git a/src/lib/dns/tests/testdata/edns_toWire4.wire b/src/lib/dns/tests/testdata/edns_toWire4.wire new file mode 100644 index 0000000..73bf757 --- /dev/null +++ b/src/lib/dns/tests/testdata/edns_toWire4.wire @@ -0,0 +1,9 @@ +### +### This data file was auto-generated from edns_toWire4.spec +### + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=511 ExtRcode=0 Version=0 DO=1 +00 0029 01ff 0000 8000 +# RDLEN=0 +0000 diff --git a/src/lib/dns/tests/testdata/example.org b/src/lib/dns/tests/testdata/example.org new file mode 100644 index 0000000..2708ef4 --- /dev/null +++ b/src/lib/dns/tests/testdata/example.org @@ -0,0 +1,17 @@ +example.org. 3600 IN SOA ( ; The SOA, split across lines for testing + ns1.example.org. + admin.example.org. + 1234 + 3600 + 1800 + 2419200 + 7200 + ) +; Check it accepts quoted name too +"\101xample.org." 3600 IN NS ns1.example.org. + + +; Some empty lines here. They are to make sure the loader can skip them. +www 3600 IN A 192.0.2.1 ; Test a relative name as well. + 3600 IN AAAA 2001:db8::1 ; And initial whitespace handling + ; Here be just some space, no RRs diff --git a/src/lib/dns/tests/testdata/masterload.txt b/src/lib/dns/tests/testdata/masterload.txt new file mode 100644 index 0000000..0d2f942 --- /dev/null +++ b/src/lib/dns/tests/testdata/masterload.txt @@ -0,0 +1,5 @@ +;; a simple (incomplete) zone file + +example.com. 3600 IN TXT "test data" +www.example.com. 60 IN A 192.0.2.1 +www.example.com. 60 IN A 192.0.2.2 diff --git a/src/lib/dns/tests/testdata/message_fromWire1 b/src/lib/dns/tests/testdata/message_fromWire1 new file mode 100644 index 0000000..5b76e3f --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire1 @@ -0,0 +1,22 @@ +# +# A simple DNS response message +# ID = 0x1035 +# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=2, other COUNTS=0 +# Question: test.example.com. IN A +# Answer: +# test.example.com. 3600 IN A 192.0.2.1 +# test.example.com. 7200 IN A 192.0.2.2 +# +1035 8500 +0001 0002 0000 0000 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# same name, fully compressed +c0 0c +# TTL=3600, A, IN, RDLENGTH=4, RDATA +0001 0001 00000e10 0004 c0 00 02 01 +# mostly same, with the slight difference in RDATA and TTL +c0 0c +0001 0001 00001c20 0004 c0 00 02 02 diff --git a/src/lib/dns/tests/testdata/message_fromWire10.spec b/src/lib/dns/tests/testdata/message_fromWire10.spec new file mode 100644 index 0000000..d3fb014 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire10.spec @@ -0,0 +1,13 @@ +# +# A simple DNS response message with an EDNS0 indicating a BADVERS error +# + +[header] +qr: response +rd: 1 +arcount: 1 +[question] +# use default +[edns] +do: 1 +extrcode: 1 diff --git a/src/lib/dns/tests/testdata/message_fromWire10.wire b/src/lib/dns/tests/testdata/message_fromWire10.wire new file mode 100644 index 0000000..fa76b92 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire10.wire @@ -0,0 +1,19 @@ +### +### This data file was auto-generated from message_fromWire10.spec +### + +# Header Section +# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) RD +1035 8100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1) +076578616d706c6503636f6d00 0001 0001 + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=1 Version=0 DO=1 +00 0029 1000 0100 8000 +# RDLEN=0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire11.spec b/src/lib/dns/tests/testdata/message_fromWire11.spec new file mode 100644 index 0000000..5f31746 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire11.spec @@ -0,0 +1,15 @@ +# +# A simple DNS response message with an EDNS0 indicating the maximum error code +# (0xfff) +# + +[header] +qr: response +rd: 1 +rcode: 0xf +arcount: 1 +[question] +# use default +[edns] +do: 1 +extrcode: 0xff diff --git a/src/lib/dns/tests/testdata/message_fromWire11.wire b/src/lib/dns/tests/testdata/message_fromWire11.wire new file mode 100644 index 0000000..f20132c --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire11.wire @@ -0,0 +1,19 @@ +### +### This data file was auto-generated from message_fromWire11.spec +### + +# Header Section +# ID=4149 QR=Response Opcode=QUERY(0) Rcode=15 RD +1035 810f +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1) +076578616d706c6503636f6d00 0001 0001 + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=255 Version=0 DO=1 +00 0029 1000 ff00 8000 +# RDLEN=0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire12.spec b/src/lib/dns/tests/testdata/message_fromWire12.spec new file mode 100644 index 0000000..4eadeed --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire12.spec @@ -0,0 +1,21 @@ +# +# A simple DNS response message with TSIG signed, but the owner name of TSIG +# is compressed +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +rr_name: ptr=12 +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x227026ad297beee721ce6c6fff1e9ef3 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/message_fromWire12.wire b/src/lib/dns/tests/testdata/message_fromWire12.wire new file mode 100644 index 0000000..9ceb356 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire12.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from message_fromWire12.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=ptr=12 Class=ANY(255) TTL=0, RDLEN=58) +c00c 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire13.spec b/src/lib/dns/tests/testdata/message_fromWire13.spec new file mode 100644 index 0000000..e81ec4c --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire13.spec @@ -0,0 +1,20 @@ +# +# Invalid TSIG: containing 2 TSIG RRs. +# + +[custom] +sections: header:question:tsig:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 2 +[question] +name: www.example.com +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x227026ad297beee721ce6c6fff1e9ef3 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/message_fromWire13.wire b/src/lib/dns/tests/testdata/message_fromWire13.wire new file mode 100644 index 0000000..05b064a --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire13.wire @@ -0,0 +1,35 @@ +### +### This data file was auto-generated from message_fromWire13.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=2 +0001 0000 0000 0002 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire14.spec b/src/lib/dns/tests/testdata/message_fromWire14.spec new file mode 100644 index 0000000..bf68a93 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire14.spec @@ -0,0 +1,21 @@ +# +# Invalid TSIG: not in the additional section. +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +# TSIG goes to the answer section +ancount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x227026ad297beee721ce6c6fff1e9ef3 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/message_fromWire14.wire b/src/lib/dns/tests/testdata/message_fromWire14.wire new file mode 100644 index 0000000..17d0e21 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire14.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from message_fromWire14.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=0 +0001 0001 0000 0000 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire15.spec b/src/lib/dns/tests/testdata/message_fromWire15.spec new file mode 100644 index 0000000..25d810f --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire15.spec @@ -0,0 +1,22 @@ +# +# Invalid TSIG: not at the end of the message +# + +[custom] +sections: header:question:tsig:edns +[header] +id: 0x2d65 +rd: 1 +arcount: 2 +[question] +name: www.example.com +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x227026ad297beee721ce6c6fff1e9ef3 +original_id: 0x2d65 +[edns] +# (all default) diff --git a/src/lib/dns/tests/testdata/message_fromWire15.wire b/src/lib/dns/tests/testdata/message_fromWire15.wire new file mode 100644 index 0000000..e3f36d0 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire15.wire @@ -0,0 +1,30 @@ +### +### This data file was auto-generated from message_fromWire15.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=2 +0001 0000 0000 0002 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=0 +00 0029 1000 0000 0000 +# RDLEN=0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire16.spec b/src/lib/dns/tests/testdata/message_fromWire16.spec new file mode 100644 index 0000000..be0abc3 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire16.spec @@ -0,0 +1,21 @@ +# +# Invalid TSIG: not in the additional section. +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +rr_class: IN +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x227026ad297beee721ce6c6fff1e9ef3 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/message_fromWire16.wire b/src/lib/dns/tests/testdata/message_fromWire16.wire new file mode 100644 index 0000000..04a791a --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire16.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from message_fromWire16.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=IN(1) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 0001 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire17.spec b/src/lib/dns/tests/testdata/message_fromWire17.spec new file mode 100644 index 0000000..366cf05 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire17.spec @@ -0,0 +1,22 @@ +# +# A simple DNS query message with TSIG signed +# + +[custom] +sections: header:question:tsig +[header] +id: 0x22c2 +rd: 1 +arcount: 1 +[question] +name: www.example.com +rrtype: TXT +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4e179212 +mac_size: 16 +mac: 0x8214b04634e32323d651ac60b08e6388 +original_id: 0x22c2 diff --git a/src/lib/dns/tests/testdata/message_fromWire17.wire b/src/lib/dns/tests/testdata/message_fromWire17.wire new file mode 100644 index 0000000..e607c52 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire17.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from message_fromWire17.spec +### + +# Header Section +# ID=8898 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +22c2 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=TXT(16) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0010 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1310167570 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004e179212 012c +# MAC Size=16 MAC=(see hex) +0010 8214b04634e32323d651ac60b08e6388 +# Original-ID=8898 Error=0 +22c2 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire18.spec b/src/lib/dns/tests/testdata/message_fromWire18.spec new file mode 100644 index 0000000..0b2592a --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire18.spec @@ -0,0 +1,23 @@ +# +# Another simple DNS query message with TSIG signed. Only ID and time signed +# (and MAC as a result) are different. +# + +[custom] +sections: header:question:tsig +[header] +id: 0xd6e2 +rd: 1 +arcount: 1 +[question] +name: www.example.com +rrtype: TXT +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4e17b38d +mac_size: 16 +mac: 0x903b5b194a799b03a37718820c2404f2 +original_id: 0xd6e2 diff --git a/src/lib/dns/tests/testdata/message_fromWire18.wire b/src/lib/dns/tests/testdata/message_fromWire18.wire new file mode 100644 index 0000000..82bdf6b --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire18.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from message_fromWire18.spec +### + +# Header Section +# ID=55010 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +d6e2 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=TXT(16) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0010 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1310176141 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004e17b38d 012c +# MAC Size=16 MAC=(see hex) +0010 903b5b194a799b03a37718820c2404f2 +# Original-ID=55010 Error=0 +d6e2 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire19.spec b/src/lib/dns/tests/testdata/message_fromWire19.spec new file mode 100644 index 0000000..8212dbf --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire19.spec @@ -0,0 +1,20 @@ +# +# A non realistic DNS response message containing mixed types of RRs in the +# answer section in a mixed order. +# + +[custom] +sections: header:question:a/1:aaaa:a/2 +[header] +qr: 1 +ancount: 3 +[question] +name: www.example.com +rrtype: A +[a/1] +as_rr: True +[aaaa] +as_rr: True +[a/2] +as_rr: True +address: 192.0.2.2 diff --git a/src/lib/dns/tests/testdata/message_fromWire19.wire b/src/lib/dns/tests/testdata/message_fromWire19.wire new file mode 100644 index 0000000..d154244 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire19.wire @@ -0,0 +1,28 @@ +### +### This data file was auto-generated from message_fromWire19.spec +### + +# Header Section +# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) +1035 8000 +# QDCNT=1, ANCNT=3, NSCNT=0, ARCNT=0 +0001 0003 0000 0000 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4) +076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.1 +c0000201 + +# AAAA RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16) +076578616d706c6503636f6d00 001c 0001 00015180 0010 +# Address=2001:db8::1 +20010db8000000000000000000000001 + +# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4) +076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.2 +c0000202 diff --git a/src/lib/dns/tests/testdata/message_fromWire2 b/src/lib/dns/tests/testdata/message_fromWire2 new file mode 100644 index 0000000..194cbf2 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire2 @@ -0,0 +1,22 @@ +# +# A simple DNS query message with a valid EDNS0 OPT RR +# ID = 0x1035 +# QR=0 (query), Opcode=0, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1 +# Question: test.example.com. IN A +1035 0100 +0001 0000 0000 0001 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# EDNS0 OPT RR +# owner name: "." +00 +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 4096 +1000 +# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO +0000 8000 +# RDLEN = 0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire20.spec b/src/lib/dns/tests/testdata/message_fromWire20.spec new file mode 100644 index 0000000..91986e4 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire20.spec @@ -0,0 +1,20 @@ +# +# A non realistic DNS response message containing mixed types of RRs in the +# authority section in a mixed order. +# + +[custom] +sections: header:question:a/1:aaaa:a/2 +[header] +qr: 1 +nscount: 3 +[question] +name: www.example.com +rrtype: A +[a/1] +as_rr: True +[aaaa] +as_rr: True +[a/2] +as_rr: True +address: 192.0.2.2 diff --git a/src/lib/dns/tests/testdata/message_fromWire20.wire b/src/lib/dns/tests/testdata/message_fromWire20.wire new file mode 100644 index 0000000..887dd1e --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire20.wire @@ -0,0 +1,28 @@ +### +### This data file was auto-generated from message_fromWire20.spec +### + +# Header Section +# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) +1035 8000 +# QDCNT=1, ANCNT=0, NSCNT=3, ARCNT=0 +0001 0000 0003 0000 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4) +076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.1 +c0000201 + +# AAAA RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16) +076578616d706c6503636f6d00 001c 0001 00015180 0010 +# Address=2001:db8::1 +20010db8000000000000000000000001 + +# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4) +076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.2 +c0000202 diff --git a/src/lib/dns/tests/testdata/message_fromWire21.spec b/src/lib/dns/tests/testdata/message_fromWire21.spec new file mode 100644 index 0000000..cd6aac9 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire21.spec @@ -0,0 +1,20 @@ +# +# A non realistic DNS response message containing mixed types of RRs in the +# additional section in a mixed order. +# + +[custom] +sections: header:question:a/1:aaaa:a/2 +[header] +qr: 1 +arcount: 3 +[question] +name: www.example.com +rrtype: A +[a/1] +as_rr: True +[aaaa] +as_rr: True +[a/2] +as_rr: True +address: 192.0.2.2 diff --git a/src/lib/dns/tests/testdata/message_fromWire21.wire b/src/lib/dns/tests/testdata/message_fromWire21.wire new file mode 100644 index 0000000..14cfcc0 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire21.wire @@ -0,0 +1,28 @@ +### +### This data file was auto-generated from message_fromWire21.spec +### + +# Header Section +# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) +1035 8000 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=3 +0001 0000 0000 0003 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4) +076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.1 +c0000201 + +# AAAA RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16) +076578616d706c6503636f6d00 001c 0001 00015180 0010 +# Address=2001:db8::1 +20010db8000000000000000000000001 + +# A RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=4) +076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.2 +c0000202 diff --git a/src/lib/dns/tests/testdata/message_fromWire22.spec b/src/lib/dns/tests/testdata/message_fromWire22.spec new file mode 100644 index 0000000..a52523b --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire22.spec @@ -0,0 +1,14 @@ +# +# A simple DNS message containing one SOA RR in the answer section. This is +# intended to be trimmed to emulate a bogus message. +# + +[custom] +sections: header:question:soa +[header] +qr: 1 +ancount: 1 +[question] +rrtype: SOA +[soa] +as_rr: True diff --git a/src/lib/dns/tests/testdata/message_fromWire22.wire b/src/lib/dns/tests/testdata/message_fromWire22.wire new file mode 100644 index 0000000..69c3254 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire22.wire @@ -0,0 +1,20 @@ +### +### This data file was auto-generated from message_fromWire22.spec +### + +# Header Section +# ID=4149 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) +1035 8000 +# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=0 +0001 0001 0000 0000 + +# Question Section +# QNAME=example.com. QTYPE=SOA(6) QCLASS=IN(1) +076578616d706c6503636f6d00 0006 0001 + +# SOA RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=54) +076578616d706c6503636f6d00 0006 0001 00015180 0036 +# NNAME=ns.example.com RNAME=root.example.com +026e73076578616d706c6503636f6d00 04726f6f74076578616d706c6503636f6d00 +# SERIAL(2010012601) REFRESH(3600) RETRY(300) EXPIRE(3600000) MINIMUM(1200) +77ce5bb9 00000e10 0000012c 0036ee80 000004b0 diff --git a/src/lib/dns/tests/testdata/message_fromWire3 b/src/lib/dns/tests/testdata/message_fromWire3 new file mode 100644 index 0000000..9bd536a --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire3 @@ -0,0 +1,22 @@ +# +# A simple DNS query message with a valid EDNS0 OPT RR, DO bit off +# ID = 0x1035 +# QR=0 (query), Opcode=0, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1 +# Question: test.example.com. IN A +1035 0100 +0001 0000 0000 0001 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# EDNS0 OPT RR +# owner name: "." +00 +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 4096 +1000 +# TTL (extended RCODE and flags): RCODE=0, version=0, flags=0 +0000 0000 +# RDLEN = 0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire4 b/src/lib/dns/tests/testdata/message_fromWire4 new file mode 100644 index 0000000..23eb7cf --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire4 @@ -0,0 +1,23 @@ +# +# A simple DNS query message with a bogus EDNS0 OPT RR (included in the +# answer section) +# ID = 0x1035 +# QR=0 (query), Opcode=0, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=1, NSCOUNT=0, ARCOUNT=0 +# Question: test.example.com. IN A +1035 0100 +0001 0001 0000 0000 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# EDNS0 OPT RR +# owner name: "." +00 +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 4096 +1000 +# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO +0000 8000 +# RDLEN = 0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire5 b/src/lib/dns/tests/testdata/message_fromWire5 new file mode 100644 index 0000000..6f08d22 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire5 @@ -0,0 +1,33 @@ +# +# A simple DNS query message with multiple EDNS0 OPT RRs (bogus) +# ID = 0x1035 +# QR=0 (query), Opcode=0, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=2 +# Question: test.example.com. IN A +1035 0100 +0001 0000 0000 0002 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# EDNS0 OPT RR (1st) +# owner name: "." +00 +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 4096 +1000 +# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO +0000 8000 +# RDLEN = 0 +0000 +# EDNS0 OPT RR (2nd) +# owner name: "." +00 +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 4096 +1000 +# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO +0000 8000 +# RDLEN = 0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire6 b/src/lib/dns/tests/testdata/message_fromWire6 new file mode 100644 index 0000000..2783fd0 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire6 @@ -0,0 +1,23 @@ +# +# A simple DNS query message with EDNS0 OPT RRs of non root name (bogus) +# ID = 0x1035 +# QR=0 (query), Opcode=0, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1 +1035 0100 +0001 0000 0000 0001 +# Question: test.example.com. IN A +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# EDNS0 OPT RR +# owner name: "example.com" +#(7) e x a m p l e (3) c o m . + 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 4096 +1000 +# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO +0000 8000 +# RDLEN = 0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire7 b/src/lib/dns/tests/testdata/message_fromWire7 new file mode 100644 index 0000000..4d85314 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire7 @@ -0,0 +1,27 @@ +# +# A simple DNS query message with EDNS0 OPT RRs of compressed owner name +# pointing to root (is this bogus?) +# ID = 0x1035 +# QR=0 (query), Opcode=0, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1 +#0 1 2 3 +1035 0100 +#4 5 6 7 8 9 10 1 +0001 0000 0000 0001 +# Question: test.example.com. IN A +# 2 3 4 5 6 7 8 9 20 1 2 3 4 5 6 7 8 9 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# EDNS0 OPT RR +# owner name: "example.com" +# pointer = 29 (end of question section) + c0 1d +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 4096 +1000 +# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO +0000 8000 +# RDLEN = 0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire8 b/src/lib/dns/tests/testdata/message_fromWire8 new file mode 100644 index 0000000..c950b5e --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire8 @@ -0,0 +1,22 @@ +# +# A simple DNS query message with a valid EDNS0 OPT RR (but unusual UDP size) +# ID = 0x1035 +# QR=0 (query), Opcode=0, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1 +# Question: test.example.com. IN A +1035 0100 +0001 0000 0000 0001 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# EDNS0 OPT RR +# owner name: "." +00 +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 500 +01f4 +# TTL (extended RCODE and flags): RCODE=0, version=0, flags=DO +0000 8000 +# RDLEN = 0 +0000 diff --git a/src/lib/dns/tests/testdata/message_fromWire9 b/src/lib/dns/tests/testdata/message_fromWire9 new file mode 100644 index 0000000..f9ff950 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_fromWire9 @@ -0,0 +1,22 @@ +# +# A simple DNS query message with an unsupported version of EDNS0 +# ID = 0x1035 +# QR=0 (query), Opcode=0, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=0, NSCOUNT=0, ARCOUNT=1 +# Question: test.example.com. IN A +1035 0100 +0001 0000 0000 0001 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# EDNS0 OPT RR +# owner name: "." +00 +# TYPE: OPT (41 = 0x29) +00 29 +# CLASS (= UDP size): 4096 +1000 +# TTL (extended RCODE and flags): RCODE=0, version=1, flags=DO +0001 8000 +# RDLEN = 0 +0000 diff --git a/src/lib/dns/tests/testdata/message_toText1.spec b/src/lib/dns/tests/testdata/message_toText1.spec new file mode 100644 index 0000000..b31310e --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText1.spec @@ -0,0 +1,24 @@ +# +# A standard DNS message (taken from an invocation of dig) +# + +[custom] +sections: header:question:a/1:ns:a/2 +[header] +id: 29174 +qr: 1 +aa: 1 +ancount: 1 +nscount: 1 +arcount: 1 +[question] +name: www.example.com +[a/1] +as_rr: True +rr_name: www.example.com +address: 192.0.2.80 +[ns] +as_rr: True +[a/2] +as_rr: True +rr_name: ns.example.com diff --git a/src/lib/dns/tests/testdata/message_toText1.txt b/src/lib/dns/tests/testdata/message_toText1.txt new file mode 100644 index 0000000..58c7239 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText1.txt @@ -0,0 +1,14 @@ +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29174 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1 + +;; QUESTION SECTION: +;www.example.com. IN A + +;; ANSWER SECTION: +www.example.com. 86400 IN A 192.0.2.80 + +;; AUTHORITY SECTION: +example.com. 86400 IN NS ns.example.com. + +;; ADDITIONAL SECTION: +ns.example.com. 86400 IN A 192.0.2.1 diff --git a/src/lib/dns/tests/testdata/message_toText1.wire b/src/lib/dns/tests/testdata/message_toText1.wire new file mode 100644 index 0000000..2a959bd --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText1.wire @@ -0,0 +1,28 @@ +### +### This data file was auto-generated from message_toText1.spec +### + +# Header Section +# ID=29174 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA +71f6 8400 +# QDCNT=1, ANCNT=1, NSCNT=1, ARCNT=1 +0001 0001 0001 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# A RR (QNAME=www.example.com Class=IN(1) TTL=86400, RDLEN=4) +03777777076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.80 +c0000250 + +# NS RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16) +076578616d706c6503636f6d00 0002 0001 00015180 0010 +# NS name=ns.example.com +026e73076578616d706c6503636f6d00 + +# A RR (QNAME=ns.example.com Class=IN(1) TTL=86400, RDLEN=4) +026e73076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.1 +c0000201 diff --git a/src/lib/dns/tests/testdata/message_toText2.spec b/src/lib/dns/tests/testdata/message_toText2.spec new file mode 100644 index 0000000..978aab3 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText2.spec @@ -0,0 +1,14 @@ +# +# A standard DNS message with EDNS (taken from an invocation of dig) +# + +[custom] +sections: header:question:edns +[header] +id: 45981 +qr: 1 +rcode: refused +arcount: 1 +[question] +[edns] +do: 1 diff --git a/src/lib/dns/tests/testdata/message_toText2.txt b/src/lib/dns/tests/testdata/message_toText2.txt new file mode 100644 index 0000000..42cc2c1 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText2.txt @@ -0,0 +1,8 @@ +;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 45981 +;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 + +;; OPT PSEUDOSECTION: +; EDNS: version: 0, flags: do; udp: 4096 + +;; QUESTION SECTION: +;example.com. IN A diff --git a/src/lib/dns/tests/testdata/message_toText2.wire b/src/lib/dns/tests/testdata/message_toText2.wire new file mode 100644 index 0000000..1047b63 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText2.wire @@ -0,0 +1,19 @@ +### +### This data file was auto-generated from message_toText2.spec +### + +# Header Section +# ID=45981 QR=Response Opcode=QUERY(0) Rcode=REFUSED(5) +b39d 8005 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=example.com. QTYPE=A(1) QCLASS=IN(1) +076578616d706c6503636f6d00 0001 0001 + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=1 +00 0029 1000 0000 8000 +# RDLEN=0 +0000 diff --git a/src/lib/dns/tests/testdata/message_toText3.spec b/src/lib/dns/tests/testdata/message_toText3.spec new file mode 100644 index 0000000..a74ea1b --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText3.spec @@ -0,0 +1,31 @@ +# +# A standard DNS message with TSIG (taken from an invocation of dig) +# + +[custom] +sections: header:question:a/1:ns:a/2:tsig +[header] +id: 10140 +qr: 1 +aa: 1 +ancount: 1 +nscount: 1 +arcount: 2 +[question] +name: www.example.com +[a/1] +as_rr: True +rr_name: www.example.com +address: 192.0.2.80 +[ns] +as_rr: True +[a/2] +as_rr: True +rr_name: ns.example.com +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 1304384318 +original_id: 10140 +mac: 0x5257c80396f2fa95b20c77ae9a652fb2 diff --git a/src/lib/dns/tests/testdata/message_toText3.txt b/src/lib/dns/tests/testdata/message_toText3.txt new file mode 100644 index 0000000..359b9c5 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText3.txt @@ -0,0 +1,17 @@ +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10140 +;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 2 + +;; QUESTION SECTION: +;www.example.com. IN A + +;; ANSWER SECTION: +www.example.com. 86400 IN A 192.0.2.80 + +;; AUTHORITY SECTION: +example.com. 86400 IN NS ns.example.com. + +;; ADDITIONAL SECTION: +ns.example.com. 86400 IN A 192.0.2.1 + +;; TSIG PSEUDOSECTION: +www.example.com. 0 ANY TSIG hmac-md5.sig-alg.reg.int. 1304384318 300 16 UlfIA5by+pWyDHeummUvsg== 10140 NOERROR 0 diff --git a/src/lib/dns/tests/testdata/message_toText3.wire b/src/lib/dns/tests/testdata/message_toText3.wire new file mode 100644 index 0000000..eb3632b --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toText3.wire @@ -0,0 +1,39 @@ +### +### This data file was auto-generated from message_toText3.spec +### + +# Header Section +# ID=10140 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA +279c 8400 +# QDCNT=1, ANCNT=1, NSCNT=1, ARCNT=2 +0001 0001 0001 0002 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# A RR (QNAME=www.example.com Class=IN(1) TTL=86400, RDLEN=4) +03777777076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.80 +c0000250 + +# NS RR (QNAME=example.com Class=IN(1) TTL=86400, RDLEN=16) +076578616d706c6503636f6d00 0002 0001 00015180 0010 +# NS name=ns.example.com +026e73076578616d706c6503636f6d00 + +# A RR (QNAME=ns.example.com Class=IN(1) TTL=86400, RDLEN=4) +026e73076578616d706c6503636f6d00 0001 0001 00015180 0004 +# Address=192.0.2.1 +c0000201 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1304384318 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004dbf533e 012c +# MAC Size=16 MAC=(see hex) +0010 5257c80396f2fa95b20c77ae9a652fb2 +# Original-ID=10140 Error=0 +279c 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_toWire1 b/src/lib/dns/tests/testdata/message_toWire1 new file mode 100644 index 0000000..daeb85a --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire1 @@ -0,0 +1,22 @@ +# +# A simple DNS query message +# ID = 0x1035 +# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=2, other COUNTS=0 +# Question: test.example.com. IN A +# Answer: +# test.example.com. 3600 IN A 192.0.2.1 +# test.example.com. 7200 IN A 192.0.2.2 +# +1035 8500 +0001 0002 0000 0000 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# same name, fully compressed +c0 0c +# TTL=3600, A, IN, RDLENGTH=4, RDATA +0001 0001 00000e10 0004 c0 00 02 01 +# mostly same, with the slight difference in RDATA +c0 0c +0001 0001 00000e10 0004 c0 00 02 02 diff --git a/src/lib/dns/tests/testdata/message_toWire2.spec b/src/lib/dns/tests/testdata/message_toWire2.spec new file mode 100644 index 0000000..d256052 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire2.spec @@ -0,0 +1,21 @@ +# +# A simple DNS query message with TSIG signed +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x227026ad297beee721ce6c6fff1e9ef3 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/message_toWire2.wire b/src/lib/dns/tests/testdata/message_toWire2.wire new file mode 100644 index 0000000..a495253 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire2.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from message_toWire2.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_toWire3.spec b/src/lib/dns/tests/testdata/message_toWire3.spec new file mode 100644 index 0000000..c8e9453 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire3.spec @@ -0,0 +1,22 @@ +# +# A simple DNS query message with EDNS and TSIG +# + +[custom] +sections: header:question:edns:tsig +[header] +id: 0x06cd +rd: 1 +arcount: 2 +[question] +name: www.example.com +[edns] +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4db60d1f +mac_size: 16 +mac: 0x93444053881c83d7eb120e86f25b369e +original_id: 0x06cd diff --git a/src/lib/dns/tests/testdata/message_toWire3.wire b/src/lib/dns/tests/testdata/message_toWire3.wire new file mode 100644 index 0000000..46808b9 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire3.wire @@ -0,0 +1,30 @@ +### +### This data file was auto-generated from message_toWire3.spec +### + +# Header Section +# ID=1741 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +06cd 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=2 +0001 0000 0000 0002 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# EDNS OPT RR +# NAME=. TYPE=OPT(41) UDPSize=4096 ExtRcode=0 Version=0 DO=0 +00 0029 1000 0000 0000 +# RDLEN=0 +0000 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1303776543 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004db60d1f 012c +# MAC Size=16 MAC=(see hex) +0010 93444053881c83d7eb120e86f25b369e +# Original-ID=1741 Error=0 +06cd 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_toWire4.spec b/src/lib/dns/tests/testdata/message_toWire4.spec new file mode 100644 index 0000000..aab7e10 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire4.spec @@ -0,0 +1,27 @@ +# +# Truncated DNS response with TSIG signed +# This is expected to be a response to "fromWire17" +# + +[custom] +sections: header:question:tsig +[header] +id: 0x22c2 +rd: 1 +qr: 1 +aa: 1 +# It's "truncated": +tc: 1 +arcount: 1 +[question] +name: www.example.com +rrtype: TXT +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4e179212 +mac_size: 16 +mac: 0x88adc3811d1d6bec7c684438906fc694 +original_id: 0x22c2 diff --git a/src/lib/dns/tests/testdata/message_toWire4.wire b/src/lib/dns/tests/testdata/message_toWire4.wire new file mode 100644 index 0000000..d2cda30 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire4.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from message_toWire4.spec +### + +# Header Section +# ID=8898 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA TC RD +22c2 8700 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=TXT(16) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0010 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1310167570 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004e179212 012c +# MAC Size=16 MAC=(see hex) +0010 88adc3811d1d6bec7c684438906fc694 +# Original-ID=8898 Error=0 +22c2 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_toWire5.spec b/src/lib/dns/tests/testdata/message_toWire5.spec new file mode 100644 index 0000000..e316833 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire5.spec @@ -0,0 +1,36 @@ +# +# A longest possible (without EDNS) DNS response with TSIG, i.e. total +# length should be 512 bytes. +# + +[custom] +sections: header:question:txt/1:txt/2:tsig +[header] +id: 0xd6e2 +rd: 1 +qr: 1 +aa: 1 +ancount: 2 +arcount: 1 +[question] +name: www.example.com +rrtype: TXT +[txt/1] +as_rr: True +# QNAME is fully compressed +rr_name: ptr=12 +string: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde +[txt/2] +as_rr: True +# QNAME is fully compressed +rr_name: ptr=12 +string: 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0 +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4e17b38d +mac_size: 16 +mac: 0xbe2ba477373d2496891e2fda240ee4ec +original_id: 0xd6e2 diff --git a/src/lib/dns/tests/testdata/message_toWire5.wire b/src/lib/dns/tests/testdata/message_toWire5.wire new file mode 100644 index 0000000..ef7ee6e --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire5.wire @@ -0,0 +1,34 @@ +### +### This data file was auto-generated from message_toWire5.spec +### + +# Header Section +# ID=55010 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA RD +d6e2 8500 +# QDCNT=1, ANCNT=2, NSCNT=0, ARCNT=1 +0001 0002 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=TXT(16) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0010 0001 + +# TXT RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=256) +c00c 0010 0001 00015180 0100 +# String Len=255, String="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde" +ff 303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465 + +# TXT RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=114) +c00c 0010 0001 00015180 0072 +# String Len=113, String="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0" +71 3031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630313233343536373839616263646566303132333435363738396162636465663031323334353637383961626364656630 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1310176141 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004e17b38d 012c +# MAC Size=16 MAC=(see hex) +0010 be2ba477373d2496891e2fda240ee4ec +# Original-ID=55010 Error=0 +d6e2 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/message_toWire6 b/src/lib/dns/tests/testdata/message_toWire6 new file mode 100644 index 0000000..996c99c --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire6 @@ -0,0 +1,48 @@ +# +# A simple DNS query message (with a signed response) +# ID = 0x75c1 +# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=4, other COUNTS=0 +# Question: test.example.com. IN A +# Answer: +# test.example.com. 3600 IN A 192.0.2.1 +# test.example.com. 7200 IN A 192.0.2.2 +# +75c1 8500 +0001 0004 0000 0000 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0001 0001 +# same name, fully compressed +c0 0c +# TTL=3600, A, IN, RDLENGTH=4, RDATA +0001 0001 00000e10 0004 c0 00 02 01 +# mostly same, with the slight difference in RDATA +c0 0c +0001 0001 00000e10 0004 c0 00 02 02 + +# signature 1 + +# same name +c0 0c +# RRSIG, IN, TTL=3600, RDLENGTH=0x28 TYPE_COV=A ALGO=5 (RSA/SHA-1) LABELS=3 ORIG_TTL=3600 +002e 0001 00000e10 0028 0001 05 03 00000e10 +# SIG_EXPIRY=20000101000000 SIG_INCEP=20000201000000 KEY_ID=12345 +386d4380 38962200 3039 +#(7) e x a m p l e (3) c o m . + 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# FAKEFAKEFAKE +14 02 84 14 02 84 14 02 84 + +# signature 2 + +# same name +c0 0c +# RRSIG, IN, TTL=3600, RDLENGTH=0x28 TYPE_COV=A ALGO=3 (DSA/SHA-1) LABELS=3 ORIG_TTL=3600 +002e 0001 00000e10 0028 0001 03 03 00000e10 +# SIG_EXPIRY=20000101000000 SIG_INCEP=20000201000000 KEY_ID=12345 +386d4380 38962200 3039 +#(7) e x a m p l e (3) c o m . + 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# FAKEFAKEFAKE +14 02 84 14 02 84 14 02 84 diff --git a/src/lib/dns/tests/testdata/message_toWire7 b/src/lib/dns/tests/testdata/message_toWire7 new file mode 100644 index 0000000..ba22634 --- /dev/null +++ b/src/lib/dns/tests/testdata/message_toWire7 @@ -0,0 +1,35 @@ +# +# A simple DNS query message (with a signed response) +# ID = 0x75c1 +# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0) +# QDCOUNT=1, ANCOUNT=1, ADCOUNT=0 +# Question: test.example.com. IN TXT +# Answer: +# test.example.com. 3600 IN TXT aaaaa... +# +75c1 8700 +0001 0001 0000 0000 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +0010 0001 +# same name, fully compressed +c0 0c +# TTL=3600, TXT, IN, RDLENGTH=256, RDATA +0010 0001 00000e10 0100 ff +# 'a' repeated 255 times +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 +61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 diff --git a/src/lib/dns/tests/testdata/name_fromWire1 b/src/lib/dns/tests/testdata/name_fromWire1 new file mode 100644 index 0000000..42fc61d --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire1 @@ -0,0 +1,14 @@ +# +# a global14 compression pointer +# +000a85800001000300000003 +# V i x c o m +0356697803636f6d0000020001c00c00 +02000100000e10000b05697372763102 +7061c00cc00c0002000100000e100009 +066e732d657874c00cc00c0002000100 +000e10000e036e733104676e61630363 +6f6d00c0250001000100000e100004cc +98b886c03c0001000100000e100004cc +98b840c051000100010002a14a0004c6 +97f8f6 diff --git a/src/lib/dns/tests/testdata/name_fromWire10 b/src/lib/dns/tests/testdata/name_fromWire10 new file mode 100644 index 0000000..65be775 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire10 @@ -0,0 +1,12 @@ +# +# Too large name; should trigger an exception. +# +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 040102030400 diff --git a/src/lib/dns/tests/testdata/name_fromWire11 b/src/lib/dns/tests/testdata/name_fromWire11 new file mode 100644 index 0000000..32184f6 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire11 @@ -0,0 +1,12 @@ +# +# A name with possible maximum number of labels; should be accepted safely. +# +01000100010001000100 01000100010001000100 01000100010001000100 +01000100010001000100 01000100010001000100 01000100010001000100 +01000100010001000100 01000100010001000100 01000100010001000100 +01000100010001000100 01000100010001000100 01000100010001000100 +01000100010001000100 01000100010001000100 01000100010001000100 +01000100010001000100 01000100010001000100 01000100010001000100 +01000100010001000100 01000100010001000100 01000100010001000100 +01000100010001000100 01000100010001000100 01000100010001000100 +01000100010001000100 0100010000 diff --git a/src/lib/dns/tests/testdata/name_fromWire12 b/src/lib/dns/tests/testdata/name_fromWire12 new file mode 100644 index 0000000..073adda --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire12 @@ -0,0 +1,13 @@ +# +# Wire format including an invalid label length +# +#(1) a (7) e x a m p l e + 01 61 07 65 78 61 6d 70 6c 65 +# invalid label length: 64 +40 +# a "label" of 64 characters: shouldn't be parsed +00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f +10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f +20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f +30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f +00 diff --git a/src/lib/dns/tests/testdata/name_fromWire13 b/src/lib/dns/tests/testdata/name_fromWire13 new file mode 100644 index 0000000..447f54b --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire13 @@ -0,0 +1,5 @@ +# +# A name including all "printable" characters +# + +3f2122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f1f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e00 diff --git a/src/lib/dns/tests/testdata/name_fromWire14 b/src/lib/dns/tests/testdata/name_fromWire14 new file mode 100644 index 0000000..3123aec --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire14 @@ -0,0 +1,7 @@ +# +# A name including all "non-printable" characters +# + +3f000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f207f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c +3f9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadb +24dcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff00 diff --git a/src/lib/dns/tests/testdata/name_fromWire2 b/src/lib/dns/tests/testdata/name_fromWire2 new file mode 100644 index 0000000..0758a68 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire2 @@ -0,0 +1,15 @@ +# +# bogus label character (looks like a local compression pointer) +# +000a85800001000300000003 +#this is the bogus label character: +83 +76697803636f6d0000020001c00c00 +02000100000e10000b05697372763102 +7061c00cc00c0002000100000e100009 +066e732d657874c00cc00c0002000100 +000e10000e036e733104676e61630363 +6f6d00c0250001000100000e100004cc +98b886c03c0001000100000e100004cc +98b840c051000100010002a14a0004c6 +97f8f6 diff --git a/src/lib/dns/tests/testdata/name_fromWire3_1 b/src/lib/dns/tests/testdata/name_fromWire3_1 new file mode 100644 index 0000000..e38efcc --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire3_1 @@ -0,0 +1,11 @@ +# +# a bad compression pointer starting with the bits 1111 (too big pointer) +# +000a85800001000300000003 +03766978 03636f6d 00 0002 0001 +f00c 0002 0001 0000 0e10 000b 056973727631 027061 c00c +c00c 0002 0001 0000 0e10 0009 066e732d657874 c00c +c00c 0002 0001 0000 0e10 000e 036e7331 04676e6163 03636f6d 00 +c025 0001 0001 0000 0e10 0004 cc98b886 +c03c 0001 0001 0000 0e10 0004 cc98b840 +c051 0001 0001 0002 a14a 0004 c697f8f6 diff --git a/src/lib/dns/tests/testdata/name_fromWire3_2 b/src/lib/dns/tests/testdata/name_fromWire3_2 new file mode 100644 index 0000000..c377bb1 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire3_2 @@ -0,0 +1,13 @@ +# +# a bad compression pointer due to forward reference of 0x30 to +# another compression pointer with a valid backreference +# +000a85800001000300000003 +03766978 03636f6d 00 0002 0001 +#'30' is the forward reference, 'c00c' at the end is the valid pointer: +c030 0002 0001 0000 0e10 000b 056973727631 027061 c00c +c00c 0002 0001 0000 0e10 0009 066e732d657874 c00c +c00c 0002 0001 0000 0e10 000e 036e7331 04676e6163 03636f6d 00 +c025 0001 0001 0000 0e10 0004 cc98b886 +c03c 0001 0001 0000 0e10 0004 cc98b840 +c051 0001 0001 0002 a14a 0004 c697f8f6 diff --git a/src/lib/dns/tests/testdata/name_fromWire4 b/src/lib/dns/tests/testdata/name_fromWire4 new file mode 100644 index 0000000..dba6035 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire4 @@ -0,0 +1,45 @@ +# +# invalid name length, pointer at offset 0x0226 points to +# long name at offset 0x25 +# +000a 8580 0001 0003 0000 0001 +03 766978 03 636f6d 00 0002 0001 +c00c 0002 0001 00000e10 +0101 +# long name starts here +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a +03616263 0358595a 03616263 0358595a +03414243 0378797a 03414243 0378797a 00 +# compression pointer start here and refers back to long name +c023 0002 0001 00000e10 0009 066e732d657874 c00c +c00c 0002 0001 00000e10 000e 036e733104676e616303636f6d00 +c025 0001 0001 00000e10 0004 cc98b886 diff --git a/src/lib/dns/tests/testdata/name_fromWire6 b/src/lib/dns/tests/testdata/name_fromWire6 new file mode 100644 index 0000000..fa1abe6 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire6 @@ -0,0 +1,14 @@ +# +# a bad pointer +# +000a85800001000300000003 +# the bad pointer is f00c (offset = 300c) which is too large +0376697803636f6d0000020001 f00c 00 +02000100000e10000b05697372763102 +7061c00cc00c0002000100000e100009 +066e732d657874c00cc00c0002000100 +000e10000e036e733104676e61630363 +6f6d00c0250001000100000e100004cc +98b886c03c0001000100000e100004cc +98b840c051000100010002a14a0004c6 +97f8f6 diff --git a/src/lib/dns/tests/testdata/name_fromWire7 b/src/lib/dns/tests/testdata/name_fromWire7 new file mode 100644 index 0000000..2dedd4a --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire7 @@ -0,0 +1,6 @@ +# +# input ends unexpectedly +# +000a85800001000300000003 +# parser will start at the ending 'c0', which is an incomplete sequence. +0376697803636f6d0000020001 c0 diff --git a/src/lib/dns/tests/testdata/name_fromWire8 b/src/lib/dns/tests/testdata/name_fromWire8 new file mode 100644 index 0000000..575563d --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire8 @@ -0,0 +1,27 @@ +# +# many hops of compression. absolutely many, but should be decompressed. +# +000a85800001000300000013 +03 766978 03 636f6d 00 0002 0001 +c00c 0002 0001 00000e10 000b 056973727631027061 c00c +c019 0002 0001 00000e10 0009 066e732d657874 c00c +c030 0002 0001 00000e10 000e 036e7331 04676e6163 03636f6d 00 +c045 0001 0001 00000e10 0004 cc98b886 +c05f 0001 0001 00000e10 0004 cc98b840 +c06f 0001 0001 0002a14a 0004 c697f8f6 +c07f 0001 0001 0002a14a 0004 c697f8f6 +c08f 0001 0001 0002a14a 0004 c697f8f6 +c09f 0001 0001 0002a14a 0004 c697f8f6 +c0af 0001 0001 0002a14a 0004 c697f8f6 +c0bf 0001 0001 0002a14a 0004 c697f8f6 +c0cf 0001 0001 0002a14a 0004 c697f8f6 +c0df 0001 0001 0002a14a 0004 c697f8f6 +c0ef 0001 0001 0002a14a 0004 c697f8f6 +c0ff 0001 0001 0002a14a 0004 c697f8f6 +c10f 0001 0001 0002a14a 0004 c697f8f6 +c11f 0001 0001 0002a14a 0004 c697f8f6 +c12f 0001 0001 0002a14a 0004 c697f8f6 +c13f 0001 0001 0002a14a 0004 c697f8f6 +c14f 0001 0001 0002a14a 0004 c697f8f6 +c15f 0001 0001 0002a14a 0004 c697f8f6 +c16f 0001 0001 0002a14a 0004 c697f8f6 diff --git a/src/lib/dns/tests/testdata/name_fromWire9 b/src/lib/dns/tests/testdata/name_fromWire9 new file mode 100644 index 0000000..79b2978 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_fromWire9 @@ -0,0 +1,12 @@ +# +# A possible longest name; should be accepted safely. +# +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 09010203040506070809 09010203040506070809 +09010203040506070809 0301020300 diff --git a/src/lib/dns/tests/testdata/name_toWire1 b/src/lib/dns/tests/testdata/name_toWire1 new file mode 100644 index 0000000..c06ec4b --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire1 @@ -0,0 +1,12 @@ +# +# Rendering 3 names with compression. [x] means a compression pointer pointing +# to offset 'x'. +# +#bytes: +# 0 1 2 3 4 5 6 7 8 9 a b c d e +#(1)a(7)e x a m p l e(3)c o m . + 0161076578616d706c6503636f6d00 +#(1)b [2] + 0162c002 +# a . e x a m p l e . o r g . + 0161076578616d706c65036f726700 diff --git a/src/lib/dns/tests/testdata/name_toWire2 b/src/lib/dns/tests/testdata/name_toWire2 new file mode 100644 index 0000000..2377121 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire2 @@ -0,0 +1,14 @@ +# +# Rendering names in a large buffer. [x] means a compression pointer pointing +# to offset 'x'. +# +#bytes: +#3f 40 +#ff 00 +#(1) a (7) e x a m p l e (3) c o m . + 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +#[3fff] = a.example.com: can be compressed + ffff +#(1) b(7) e x a m p l e (3) c o m .; cannot compress as the pointer +# (0x4001) would exceed 0x4000 + 01 62 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 diff --git a/src/lib/dns/tests/testdata/name_toWire3 b/src/lib/dns/tests/testdata/name_toWire3 new file mode 100644 index 0000000..08c1474 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire3 @@ -0,0 +1,14 @@ +# +# Rendering names including one explicitly uncompressed. +# [x] means a compression pointer pointing to offset 'x'. +# +# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (bytes) +#(1) a (7) e x a m p l e (3) c o m . + 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 + +#15 29 (bytes) +#(1) b (7) e x a m p l e (3) c o m .; specified to be not compressed, +# but can be pointed to from others + 01 62 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +#[0f] referring to the second (uncompressed name) + c0 0f diff --git a/src/lib/dns/tests/testdata/name_toWire4 b/src/lib/dns/tests/testdata/name_toWire4 new file mode 100644 index 0000000..740d718 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire4 @@ -0,0 +1,16 @@ +# +# Rendering 3 names with compression, including one resulting in a chain of +# pointers (the last one). +# legend: [x] means a compression pointer pointing to offset 'x'. +#bytes: +#00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e +#(1) a (7) e x a m p l e (3) c o m . + 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 + +#0f 10 11 12 +#(1) b [2] (b.example.com.) + 01 62 c0 02 + +#13 14 +# [0f]: (b.example.com.) + c0 0f diff --git a/src/lib/dns/tests/testdata/name_toWire5.spec b/src/lib/dns/tests/testdata/name_toWire5.spec new file mode 100644 index 0000000..87e140d --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire5.spec @@ -0,0 +1,19 @@ +# +# A sequence of names that would be compressed case-sensitive manner. +# First name: "a.example.com" +# Second name: "b.eXample.com". Due to case-sensitive comparison only "com" +# can be compressed. +# Third name: "c.eXample.com". "eXample.com" part matches that of the second +# name and can be compressed. +# + +[custom] +sections: name/1:name/2:name/3 +[name/1] +name: a.example.com +[name/2] +name: b.eXample +pointer: 10 +[name/3] +name: c +pointer: 17 diff --git a/src/lib/dns/tests/testdata/name_toWire5.wire b/src/lib/dns/tests/testdata/name_toWire5.wire new file mode 100644 index 0000000..c6e62ed --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire5.wire @@ -0,0 +1,12 @@ +### +### This data file was auto-generated from name_toWire5.spec +### + +# DNS Name: a.example.com +0161076578616d706c6503636f6d00 + +# DNS Name: b.eXample + compression pointer: 10 +0162076558616d706c65c00a + +# DNS Name: c + compression pointer: 17 +0163c011 diff --git a/src/lib/dns/tests/testdata/name_toWire6.spec b/src/lib/dns/tests/testdata/name_toWire6.spec new file mode 100644 index 0000000..a536f5d --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire6.spec @@ -0,0 +1,19 @@ +# +# A sequence of names that would be compressed both case-sensitive and +# case-insensitive manner (unusual, but allowed). +# First and second name: see name_toWire5.spec. +# Third name: "c.b.EXAMPLE.com". This is rendered with case-insensitive +# compression, so "b.EXAMPLE.com" part of the name matches that of the +# second name. +# + +[custom] +sections: name/1:name/2:name/3 +[name/1] +name: a.example.com +[name/2] +name: b.eXample +pointer: 10 +[name/3] +name: c +pointer: 15 diff --git a/src/lib/dns/tests/testdata/name_toWire6.wire b/src/lib/dns/tests/testdata/name_toWire6.wire new file mode 100644 index 0000000..dcaa39f --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire6.wire @@ -0,0 +1,12 @@ +### +### This data file was auto-generated from name_toWire6.spec +### + +# DNS Name: a.example.com +0161076578616d706c6503636f6d00 + +# DNS Name: b.eXample + compression pointer: 10 +0162076558616d706c65c00a + +# DNS Name: c + compression pointer: 15 +0163c00f diff --git a/src/lib/dns/tests/testdata/name_toWire7 b/src/lib/dns/tests/testdata/name_toWire7 new file mode 100644 index 0000000..bff599f --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire7 @@ -0,0 +1,10 @@ +# +# Rendering names including one explicitly uncompressed. +# [x] means a compression pointer pointing to offset 'x'. +# +# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (bytes) +#(1) a (7) e x a m p l e (3) c o m . + 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 + +#[02] pointing to -> "example.com." + c0 02 diff --git a/src/lib/dns/tests/testdata/name_toWire8 b/src/lib/dns/tests/testdata/name_toWire8 new file mode 100644 index 0000000..d01093b --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire8 @@ -0,0 +1,7 @@ +# +# Rendering names. +# [x] means a compression pointer pointing to offset 'x'. +# +# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 (bytes) +#(1) a (7) e x a m p l e (3) c o m + 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d diff --git a/src/lib/dns/tests/testdata/name_toWire9 b/src/lib/dns/tests/testdata/name_toWire9 new file mode 100644 index 0000000..51a1987 --- /dev/null +++ b/src/lib/dns/tests/testdata/name_toWire9 @@ -0,0 +1,13 @@ +# +# Rendering names including one explicitly uncompressed. +# [x] means a compression pointer pointing to offset 'x'. +# +# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 (bytes) +#(1) a (7) e x a m p l e (3) c o m . + 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +#(1) a (7) e x a m p l e (3) c o m + 01 61 07 65 78 61 6d 70 6c 65 03 63 6f 6d +#(1) a (7) e x a m p l e + 01 61 07 65 78 61 6d 70 6c 65 +#[1f] pointing to ^^ "example" + c0 1f diff --git a/src/lib/dns/tests/testdata/omitcheck.txt b/src/lib/dns/tests/testdata/omitcheck.txt new file mode 100644 index 0000000..580cab4 --- /dev/null +++ b/src/lib/dns/tests/testdata/omitcheck.txt @@ -0,0 +1 @@ + 1H IN A 192.0.2.1 diff --git a/src/lib/dns/tests/testdata/origincheck.txt b/src/lib/dns/tests/testdata/origincheck.txt new file mode 100644 index 0000000..c370ed2 --- /dev/null +++ b/src/lib/dns/tests/testdata/origincheck.txt @@ -0,0 +1,5 @@ +; We change the origin here. We want to check it is not propagated +; outside of the included file. +$ORIGIN www.example.org. + +@ 1H IN A 192.0.2.1 diff --git a/src/lib/dns/tests/testdata/question_fromWire b/src/lib/dns/tests/testdata/question_fromWire new file mode 100644 index 0000000..cbc28c0 --- /dev/null +++ b/src/lib/dns/tests/testdata/question_fromWire @@ -0,0 +1,33 @@ +# +# Wire-format data of a sequence of DNS questions. +# foo.example.com. IN NS +# bar.example.com. CH A (owner name is compressed) +# and some pathological cases +# +# 0 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 (-th byte) +#(3) f o o (7) e x a m p l e (3) c o m . + 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +#7 8 9 20 +# type/class: NS = 2, IN = 1 +00 02 00 01 + +# 1 2 3 4 5 6 +#(3) b a r [ptr=0x04] + 03 62 61 72 c0 04 +#7 8 9 30 +# type/class: A = 1, CH = 3 +00 01 00 03 + +# owner name is broken +#1 +# invalid label type +ff +#2 3 4 5 +#type/class IN/A +00 01 00 01 + +# short buffer +# (root name) +00 +#class is missing +00 01 diff --git a/src/lib/dns/tests/testdata/question_toWire1 b/src/lib/dns/tests/testdata/question_toWire1 new file mode 100644 index 0000000..77886db --- /dev/null +++ b/src/lib/dns/tests/testdata/question_toWire1 @@ -0,0 +1,14 @@ +# +# Rendering two DNS Questions without name compression +# foo.example.com. IN NS +# bar.example.com. CH A +# +#(3) f o o (7) e x a m p l e (3) c o m . + 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# type/class: NS = 2, IN = 1 +00 02 00 01 +#(3) b a r (7) e x a m p l e (3) c o m . + 03 62 61 72 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +#7 8 9 30 +# type/class: A = 1, CH = 3 +00 01 00 03 diff --git a/src/lib/dns/tests/testdata/question_toWire2 b/src/lib/dns/tests/testdata/question_toWire2 new file mode 100644 index 0000000..9117ab2 --- /dev/null +++ b/src/lib/dns/tests/testdata/question_toWire2 @@ -0,0 +1,14 @@ +# +# Rendering two DNS Questions with name compression +# foo.example.com. IN NS +# bar.example.com. CH A +# +# 0 1 2 3 4 ... (-th byte) +#(3) f o o (7) e x a m p l e (3) c o m . + 03 66 6f 6f 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# type/class: NS = 2, IN = 1 +00 02 00 01 +#(3) b a r [ptr=0x04] + 03 62 61 72 c0 04 +# type/class: A = 1, CH = 3 +00 01 00 03 diff --git a/src/lib/dns/tests/testdata/rdata_dhcid_fromWire b/src/lib/dns/tests/testdata/rdata_dhcid_fromWire new file mode 100644 index 0000000..b28b5b3 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_dhcid_fromWire @@ -0,0 +1,12 @@ +# +# DHCID RDATA stored in an input buffer +# +# Valid RDATA for 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA= +# +# RDLENGTH=41 bytes +# 0 1 + 00 29 +# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA= +d0 b2 20 d0 bb d0 b5 d1 81 d1 83 20 d1 80 d0 be +d0 b4 d0 b8 d0 bb d0 b0 d1 81 d1 8c 20 d1 91 d0 +bb d0 be d1 87 d0 ba d0 b0 diff --git a/src/lib/dns/tests/testdata/rdata_dhcid_toWire b/src/lib/dns/tests/testdata/rdata_dhcid_toWire new file mode 100644 index 0000000..99ec229 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_dhcid_toWire @@ -0,0 +1,7 @@ +# +# DHCID RDATA stored in an output buffer +# +# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA= +d0 b2 20 d0 bb d0 b5 d1 81 d1 83 20 d1 80 d0 be +d0 b4 d0 b8 d0 bb d0 b0 d1 81 d1 8c 20 d1 91 d0 +bb d0 be d1 87 d0 ba d0 b0 diff --git a/src/lib/dns/tests/testdata/rdata_in_a_fromWire b/src/lib/dns/tests/testdata/rdata_in_a_fromWire new file mode 100644 index 0000000..12f508b --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_in_a_fromWire @@ -0,0 +1,19 @@ +# +# various kinds of IN/A RDATA stored in an input buffer +# +# valid RDATA for 192.0.2.1 +# +# 0 1 2 3 4 5 (bytes) + 00 04 c0 00 02 01 +# +# short length +# 6 7 8 9 10 11 (bytes) + 00 03 c0 00 02 01 +# +# length too long +#12 13 14 15 16 17 18 + 00 05 c0 00 02 01 00 +# +# short buffer (this can be tested only at the end of the buffer) +#19 20 21 22 23 + 00 04 c0 00 02 diff --git a/src/lib/dns/tests/testdata/rdata_in_aaaa_fromWire b/src/lib/dns/tests/testdata/rdata_in_aaaa_fromWire new file mode 100644 index 0000000..22fdd1f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_in_aaaa_fromWire @@ -0,0 +1,18 @@ +# +# various kinds of IN/AAAA RDATA stored in an input buffer +# +# valid RDATA for 2001:db8::1234 +# +#RDLENGTH=16 +0010 +#IPv6 address (18 bytes) +2001 0db8 0000 0000 0000 0000 0000 1234 +# +# short length (36 bytes) +0008 2001 0db8 0000 0000 0000 0000 0000 1234 +# +# length too long (55 bytes) +0011 2001 0db8 0000 0000 0000 0000 0000 1234 ff +# +# short buffer (this can be tested only at the end of the buffer) +0010 2001 0db8 diff --git a/src/lib/dns/tests/testdata/rdata_ns_fromWire b/src/lib/dns/tests/testdata/rdata_ns_fromWire new file mode 100644 index 0000000..973365f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_ns_fromWire @@ -0,0 +1,44 @@ +# +# various kinds of NS RDATA stored in an input buffer +# +# Valid non-compressed RDATA for ns.example.com. +# RDLENGTH=16 bytes +# 0 1 + 00 10 +# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7(bytes) +#(2) n s (7) e x a m p l e (3) c o m . + 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# +# short length +# 8 9 + 00 0f +#20 1 2 3 4 5 6 7 8 9 30 1 2 3 4 5 + 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# +# length too long +# 6 7 + 00 11 +# +# 8 9 40 1 2 3 4 5 6 7 8 9 50 1 2 3 4 + 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 +# +# Valid compressed NS name: 'ns2' + pointer +# 5 6 + 00 06 +# 7 8 9 60 1 2 +#(3) n s 2 ptr=5 + 03 6e 73 32 c0 05 +# +# Valid compressed NS name but RDLENGTH is incorrect: it must be the length +# of the sequence from the head to the pointer, not the decompressed name +# length. +# 3 4 + 00 11 +# 5 6 7 8 9 70 + 03 6e 73 32 c0 05 +# incomplete name (no trailing dot). this can be tested only at the end of +# the buffer. +# 1 2 + 00 0f +# 3 4 5 6 7 8 9 80 1 2 3 4 5 6 7 + 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire1 b/src/lib/dns/tests/testdata/rdata_opt_fromWire1 new file mode 100644 index 0000000..f2eb680 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire1 @@ -0,0 +1,15 @@ +# Various kinds of OPT RDATA stored in an input buffer +# +# Empty RDATA (which is okay) +# +# 0 1 (bytes) + 00 00 +# +# An OPT RR containing an NSID Option +# code=3 len=3 ID value (opaque) +# 2 3 4 5 6 7 8 9 10 + 00 07 00 2a 00 03 00 01 02 +# +# Short buffer (this can be tested only at the end of the buffer) +# 1 2 3 4 5 + 00 04 c0 00 02 diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire2 b/src/lib/dns/tests/testdata/rdata_opt_fromWire2 new file mode 100644 index 0000000..2c5a11f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire2 @@ -0,0 +1,4 @@ +# Short RDATA length +# +# OPT RDATA, RDLEN=1 +0001 diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire3 b/src/lib/dns/tests/testdata/rdata_opt_fromWire3 new file mode 100644 index 0000000..52db1d8 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire3 @@ -0,0 +1,8 @@ +# Short RDATA length (in second pseudo RR) +# +# OPT RDATA, RDLEN=8 +0008 +# Pseudo RR 1 of size 7 (code=3, len=3) +00 03 00 03 00 01 02 +# Pseudo RR 2 of size 7 exhausts RDLEN (code=4, len=3) +00 04 00 03 00 01 02 diff --git a/src/lib/dns/tests/testdata/rdata_opt_fromWire4 b/src/lib/dns/tests/testdata/rdata_opt_fromWire4 new file mode 100644 index 0000000..a302127 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_opt_fromWire4 @@ -0,0 +1,9 @@ +# Sum of option lengths would overflow RDLEN +# +# OPT RDATA, RDLEN=14 (0x000e) +000e +# Pseudo RR 1 (code=3, len=3) +00 03 00 03 00 01 02 +# Pseudo RR 2 (code=4, len=65535 overflows RDLEN) +00 04 ff ff 00 01 02 +# Rest of option data is omitted... diff --git a/src/lib/dns/tests/testdata/rdata_rrsig_fromWire1 b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire1 new file mode 100644 index 0000000..1b799c2 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire1 @@ -0,0 +1,13 @@ +# RDLENGTH 155 bytes +00 9b +# RRSIG record +00 01 05 02 00 00 a8 c0 4b ad ad 5d 4b 86 20 5d +0a 62 03 69 73 63 03 6f 72 67 00 1e 42 64 ff 16 +53 bf 37 8f 53 c3 44 36 5f e5 7b 2f 1b 6d 4b a6 +86 4d 61 5d c8 b2 aa 12 e7 cf 55 50 17 39 03 a2 +87 a3 ea 77 97 66 05 99 cd 02 9e 4c a3 ce 61 6a +e7 62 d7 59 5b 83 e7 3d 01 5e 52 5d e8 ae 02 de +bf b1 7c 27 a1 59 94 39 f4 cb f2 7f 4e 14 79 9b +7e 8c a3 6f c6 77 18 e3 f2 52 7f 22 33 d5 91 da +4a c8 1a 5c 9d 83 43 f0 a1 08 99 ae 4c c8 d0 8d +7b 23 b5 52 47 cf 41 91 87 35 24 diff --git a/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.spec new file mode 100644 index 0000000..582975a --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.spec @@ -0,0 +1,8 @@ +# +# RRSIG RDATA with a bogus RDLEN (too short) +# + +[custom] +sections: rrsig +[rrsig] +rdlen: 19 diff --git a/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.wire new file mode 100644 index 0000000..b3601a1 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_rrsig_fromWire2.wire @@ -0,0 +1,12 @@ +### +### This data file was auto-generated from rdata_rrsig_fromWire2.spec +### + +# RRSIG RDATA, RDLEN=19 +0013 +# Covered=A(1) Algorithm=RSASHA1(5) Labels=2 OrigTTL=3600 +0001 05 02 00000e10 +# Expiration=1264935600, Inception=1262343600 +4b6562b0 4b3dd5b0 +# Tag=4149 Signer=example.com and Signature +1035 076578616d706c6503636f6d00 123456789abcdef123456789abcdef diff --git a/src/lib/dns/tests/testdata/rdata_soa_fromWire b/src/lib/dns/tests/testdata/rdata_soa_fromWire new file mode 100644 index 0000000..5cd67f0 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_soa_fromWire @@ -0,0 +1,20 @@ +# +# A common style of SOA RDATA stored in an input buffer +# +# Valid compressed RDATA for "(ns.example.com. root.example.com. +# 2010012601 3600 300 3600000 1200)" +# RDLENGTH=43 bytes +# 0 1 + 00 2b +# MNAME: non compressed +# 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7(bytes) +#(2) n s (7) e x a m p l e (3) c o m . + 02 6e 73 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# RNAME: compressed +# 8 9 20 1 2 3 4 +#(4) r o o t ptr=5 + 04 72 6f 6f 74 c0 05 +# other numeric parameters +# 28 32 36 40 44 +# serial, refresh, retry, expire, minimum + 77ce5bb9 00000e10 0000012c 0036ee80 000004b0 diff --git a/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.spec b/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.spec new file mode 100644 index 0000000..389cec9 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.spec @@ -0,0 +1,7 @@ +# +# A simple SOA RDATA without name compression +# + +[custom] +sections: soa +[soa] diff --git a/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.wire b/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.wire new file mode 100644 index 0000000..4b6442f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_soa_toWireUncompressed.wire @@ -0,0 +1,10 @@ +### +### This data file was auto-generated from rdata_soa_toWireUncompressed.spec +### + +# SOA RDATA, RDLEN=54 +0036 +# NNAME=ns.example.com RNAME=root.example.com +026e73076578616d706c6503636f6d00 04726f6f74076578616d706c6503636f6d00 +# SERIAL(2010012601) REFRESH(3600) RETRY(300) EXPIRE(3600000) MINIMUM(1200) +77ce5bb9 00000e10 0000012c 0036ee80 000004b0 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.spec new file mode 100644 index 0000000..e46d9b3 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.spec @@ -0,0 +1,6 @@ +# +# A simplest form of TKEY: all default parameters +# +[custom] +sections: tkey +[tkey] diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.wire new file mode 100644 index 0000000..e8ee944 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire1.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire1.spec +### + +# TKEY RDATA, RDLEN=58 +003a +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=32 Key=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.spec new file mode 100644 index 0000000..e4a1920 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.spec @@ -0,0 +1,8 @@ +# +# TKEY with other data +# +[custom] +sections: tkey +[tkey] +other_len: 8 +other_data: abcd0123 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.wire new file mode 100644 index 0000000..614844f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire2.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire2.spec +### + +# TKEY RDATA, RDLEN=66 +0042 +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=32 Key=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Other-Len=8 Other-Data=(see hex) +0008 6162636430313233 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.spec new file mode 100644 index 0000000..2566b58 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.spec @@ -0,0 +1,9 @@ +# +# TKEY without Key +# +[custom] +sections: tkey +[tkey] +key_len: 0 +other_len: 8 +other_data: abcd0123 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.wire new file mode 100644 index 0000000..df27910 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire3.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire3.spec +### + +# TKEY RDATA, RDLEN=34 +0022 +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=0 Key=(see hex) +0000 +# Other-Len=8 Other-Data=(see hex) +0008 6162636430313233 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.spec new file mode 100644 index 0000000..33459eb --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.spec @@ -0,0 +1,11 @@ +# +# A simplest form of TKEY, but the algorithm name is compressed (quite +# pathological, but we accept it) +# +[custom] +sections: name:tkey +[name] +name: gss-tsig +[tkey] +algorithm: ptr=0 +key_len: 32 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.wire new file mode 100644 index 0000000..550052e --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire4.wire @@ -0,0 +1,17 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire4.spec +### + +# DNS Name: gss-tsig +086773732d7473696700 + +# TKEY RDATA, RDLEN=50 +0032 +# Algorithm=ptr=0 +c000 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=32 Key=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.spec new file mode 100644 index 0000000..6cfa4b4 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.spec @@ -0,0 +1,7 @@ +# +# TKEY-like RDATA but RDLEN is too short. +# +[custom] +sections: tkey +[tkey] +rdlen: 57 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.wire new file mode 100644 index 0000000..fa32566 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire5.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire5.spec +### + +# TKEY RDATA, RDLEN=57 +0039 +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=32 Key=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.spec new file mode 100644 index 0000000..87460a2 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.spec @@ -0,0 +1,7 @@ +# +# TKEY-like RDATA but RDLEN is too long. +# +[custom] +sections: tkey +[tkey] +rdlen: 60 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.wire new file mode 100644 index 0000000..7f5f112 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire6.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire6.spec +### + +# TKEY RDATA, RDLEN=60 +003c +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=32 Key=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.spec new file mode 100644 index 0000000..3fc0929 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.spec @@ -0,0 +1,8 @@ +# +# TKEY-like RDATA but algorithm name is broken. +# +[custom] +sections: tkey +[tkey] +algorithm: "01234567890123456789012345678901234567890123456789012345678901234" +key_len: 32 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.wire new file mode 100644 index 0000000..73b277c --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire7.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire7.spec +### + +# TKEY RDATA, RDLEN=117 +0075 +# Algorithm="01234567890123456789012345678901234567890123456789012345678901234" +432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=32 Key=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.spec new file mode 100644 index 0000000..8338279 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.spec @@ -0,0 +1,8 @@ +# +# TKEY-like RDATA but Key len is bogus +# +[custom] +sections: tkey +[tkey] +key_len: 65535 +key: "dummy data" diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.wire new file mode 100644 index 0000000..abeb95b --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire8.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire8.spec +### + +# TKEY RDATA, RDLEN=38 +0026 +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=65535 Key=(see hex) +ffff 2264756d6d79206461746122 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.spec new file mode 100644 index 0000000..9fb63e0 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.spec @@ -0,0 +1,8 @@ +# +# TKEY-like RDATA but Other-Data length is bogus +# +[custom] +sections: tkey +[tkey] +other_len: 65535 +otherdata: "dummy data" diff --git a/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.wire b/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.wire new file mode 100644 index 0000000..8e5f943 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_fromWire9.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_fromWire9.spec +### + +# TKEY RDATA, RDLEN=58 +003a +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=32 Key=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Other-Len=65535 Other-Data=(see hex) +ffff diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire1.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire1.spec new file mode 100644 index 0000000..42521a3 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire1.spec @@ -0,0 +1,8 @@ +# +# An artificial TKEY RDATA for toWire test. +# +[custom] +sections: tkey +[tkey] +algorithm: gss-tsig +key_len: 0 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire1.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire1.wire new file mode 100644 index 0000000..3f49a69 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire1.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_toWire1.spec +### + +# TKEY RDATA, RDLEN=26 +001a +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=0 Key=(see hex) +0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire2.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire2.spec new file mode 100644 index 0000000..25d47e5 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire2.spec @@ -0,0 +1,11 @@ +# +# An artificial TKEY RDATA for toWire test. +# +[custom] +sections: tkey +[tkey] +algorithm: GSS-TSIG +error: 16 +key_len: 12 +# 0x1402... would be FAKEFAKE... if encoded in BASE64 +key: 0x140284140284140284140284 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire2.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire2.wire new file mode 100644 index 0000000..a1fdb9a --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire2.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_toWire2.spec +### + +# TKEY RDATA, RDLEN=38 +0026 +# Algorithm=GSS-TSIG +084753532d5453494700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=16 +608d42c0 608d50d0 0003 0010 +# Key Len=12 Key=(see hex) +000c 140284140284140284140284 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire3.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire3.spec new file mode 100644 index 0000000..b3ea3db --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire3.spec @@ -0,0 +1,13 @@ +# +# An artificial TKEY RDATA for toWire test. +# +[custom] +sections: tkey +[tkey] +algorithm: gss.tsig +error: 16 +key_len: 12 +# 0x1402... would be FAKEFAKE... if encoded in BASE64 +key: 0x140284140284140284140284 +other_len: 6 +other_data: 0x140284140284 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire3.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire3.wire new file mode 100644 index 0000000..f2f8a6f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire3.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tkey_toWire3.spec +### + +# TKEY RDATA, RDLEN=44 +002c +# Algorithm=gss.tsig +03677373047473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=16 +608d42c0 608d50d0 0003 0010 +# Key Len=12 Key=(see hex) +000c 140284140284140284140284 +# Other-Len=6 Other-Data=(see hex) +0006 140284140284 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire4.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire4.spec new file mode 100644 index 0000000..e403c00 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire4.spec @@ -0,0 +1,10 @@ +# +# An artificial TKEY RDATA for toWire test. +# +[custom] +sections: name:tkey +[name] +name: gss-tsig +[tkey] +algorithm: gss-tsig +key_len: 0 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire4.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire4.wire new file mode 100644 index 0000000..81a1443 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire4.wire @@ -0,0 +1,17 @@ +### +### This data file was auto-generated from rdata_tkey_toWire4.spec +### + +# DNS Name: gss-tsig +086773732d7473696700 + +# TKEY RDATA, RDLEN=26 +001a +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=0 Key=(see hex) +0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire5.spec b/src/lib/dns/tests/testdata/rdata_tkey_toWire5.spec new file mode 100644 index 0000000..b4a1bca --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire5.spec @@ -0,0 +1,10 @@ +# +# An artificial TKEY RDATA for toWire test. +# +[custom] +sections: tkey:name +[tkey] +algorithm: gss-tsig +key_len: 0 +[name] +name: ptr=2 diff --git a/src/lib/dns/tests/testdata/rdata_tkey_toWire5.wire b/src/lib/dns/tests/testdata/rdata_tkey_toWire5.wire new file mode 100644 index 0000000..e59a1f0 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tkey_toWire5.wire @@ -0,0 +1,17 @@ +### +### This data file was auto-generated from rdata_tkey_toWire5.spec +### + +# TKEY RDATA, RDLEN=26 +001a +# Algorithm=gss-tsig +086773732d7473696700 +# Inception=1619870400 Expire=1619874000 Mode=3 Error=0 +608d42c0 608d50d0 0003 0000 +# Key Len=0 Key=(see hex) +0000 +# Other-Len=0 Other-Data=(see hex) +0000 + +# DNS Name: ptr=2 +c002 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec new file mode 100644 index 0000000..a30c371 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec @@ -0,0 +1,6 @@ +# +# A simplest form of TSIG: all default parameters +# +[custom] +sections: tsig +[tsig] diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.wire new file mode 100644 index 0000000..cec3a0f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire1.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire1.spec +### + +# TSIG RDATA, RDLEN=61 +003d +# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300 +0b686d61632d73686132353600 00004cb5bceb 012c +# MAC Size=32 MAC=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Original-ID=2845 Error=0 +0b1d 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec new file mode 100644 index 0000000..d1e49a5 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec @@ -0,0 +1,8 @@ +# +# TSIG with other data (error = BADTIME(18)) +# +[custom] +sections: tsig +[tsig] +mac_size: 0 +error: 18 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.wire new file mode 100644 index 0000000..ca85df4 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire2.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire2.spec +### + +# TSIG RDATA, RDLEN=35 +0023 +# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300 +0b686d61632d73686132353600 00004cb5bceb 012c +# MAC Size=0 MAC=(see hex) +0000 +# Original-ID=2845 Error=18 +0b1d 0012 +# Other-Len=6 Other-Data=(see hex) +0006 00004cb5be18 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec new file mode 100644 index 0000000..57f8e83 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec @@ -0,0 +1,8 @@ +# +# TSIG without MAC (error = BADSIG(16)) +# +[custom] +sections: tsig +[tsig] +mac_size: 0 +error: 16 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.wire new file mode 100644 index 0000000..16c3e39 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire3.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire3.spec +### + +# TSIG RDATA, RDLEN=29 +001d +# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300 +0b686d61632d73686132353600 00004cb5bceb 012c +# MAC Size=0 MAC=(see hex) +0000 +# Original-ID=2845 Error=16 +0b1d 0010 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec new file mode 100644 index 0000000..8c38e9e --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec @@ -0,0 +1,11 @@ +# +# A simplest form of TSIG, but the algorithm name is compressed (quite +# pathological, but we accept it) +# +[custom] +sections: name:tsig +[name] +name: hmac-sha256 +[tsig] +algorithm: ptr=0 +mac_size: 32 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.wire new file mode 100644 index 0000000..6b8e0f3 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire4.wire @@ -0,0 +1,17 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire4.spec +### + +# DNS Name: hmac-sha256 +0b686d61632d73686132353600 + +# TSIG RDATA, RDLEN=50 +0032 +# Algorithm=ptr=0 Time-Signed=1286978795 Fudge=300 +c000 00004cb5bceb 012c +# MAC Size=32 MAC=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Original-ID=2845 Error=0 +0b1d 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec new file mode 100644 index 0000000..da90b18 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec @@ -0,0 +1,7 @@ +# +# TSIG-like RDATA but RDLEN is too short. +# +[custom] +sections: tsig +[tsig] +rdlen: 60 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.wire new file mode 100644 index 0000000..9656ce2 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire5.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire5.spec +### + +# TSIG RDATA, RDLEN=60 +003c +# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300 +0b686d61632d73686132353600 00004cb5bceb 012c +# MAC Size=32 MAC=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Original-ID=2845 Error=0 +0b1d 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec new file mode 100644 index 0000000..9d2f627 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec @@ -0,0 +1,7 @@ +# +# TSIG-like RDATA but RDLEN is too long. +# +[custom] +sections: tsig +[tsig] +rdlen: 63 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.wire new file mode 100644 index 0000000..bb1ecfb --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire6.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire6.spec +### + +# TSIG RDATA, RDLEN=63 +003f +# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300 +0b686d61632d73686132353600 00004cb5bceb 012c +# MAC Size=32 MAC=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Original-ID=2845 Error=0 +0b1d 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec new file mode 100644 index 0000000..ed7a81c --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec @@ -0,0 +1,8 @@ +# +# TSIG-like RDATA but algorithm name is broken. +# +[custom] +sections: tsig +[tsig] +algorithm: "01234567890123456789012345678901234567890123456789012345678901234" +mac_size: 32 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.wire new file mode 100644 index 0000000..327211f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire7.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire7.spec +### + +# TSIG RDATA, RDLEN=117 +0075 +# Algorithm="01234567890123456789012345678901234567890123456789012345678901234" Time-Signed=1286978795 Fudge=300 +432230313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233342200 00004cb5bceb 012c +# MAC Size=32 MAC=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Original-ID=2845 Error=0 +0b1d 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec new file mode 100644 index 0000000..0b44f87 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec @@ -0,0 +1,8 @@ +# +# TSIG-like RDATA but MAC size is bogus +# +[custom] +sections: tsig +[tsig] +mac_size: 65535 +mac: "dummy data" diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.wire new file mode 100644 index 0000000..a4209f9 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire8.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire8.spec +### + +# TSIG RDATA, RDLEN=41 +0029 +# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300 +0b686d61632d73686132353600 00004cb5bceb 012c +# MAC Size=65535 MAC=(see hex) +ffff 2264756d6d79206461746122 +# Original-ID=2845 Error=0 +0b1d 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec new file mode 100644 index 0000000..f512fb4 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec @@ -0,0 +1,8 @@ +# +# TSIG-like RDATA but Other-Data length is bogus +# +[custom] +sections: tsig +[tsig] +other_len: 65535 +otherdata: "dummy data" diff --git a/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.wire b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.wire new file mode 100644 index 0000000..b356825 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_fromWire9.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_fromWire9.spec +### + +# TSIG RDATA, RDLEN=61 +003d +# Algorithm=hmac-sha256 Time-Signed=1286978795 Fudge=300 +0b686d61632d73686132353600 00004cb5bceb 012c +# MAC Size=32 MAC=(see hex) +0020 7878787878787878787878787878787878787878787878787878787878787878 +# Original-ID=2845 Error=0 +0b1d 0000 +# Other-Len=65535 Other-Data=(see hex) +ffff diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec new file mode 100644 index 0000000..eb74000 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec @@ -0,0 +1,11 @@ +# +# An artificial TSIG RDATA for toWire test. +# +[custom] +sections: tsig +[tsig] +algorithm: hmac-md5 +time_signed: 1286779327 +mac_size: 0 +original_id: 16020 +error: 17 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire1.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.wire new file mode 100644 index 0000000..c368853 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire1.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_toWire1.spec +### + +# TSIG RDATA, RDLEN=42 +002a +# Algorithm=hmac-md5 Time-Signed=1286779327 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004cb2b1bf 012c +# MAC Size=0 MAC=(see hex) +0000 +# Original-ID=16020 Error=17 +3e94 0011 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec new file mode 100644 index 0000000..b2c38e9 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec @@ -0,0 +1,13 @@ +# +# An artificial TSIG RDATA for toWire test. +# +[custom] +sections: tsig +[tsig] +algorithm: hmac-sha256 +time_signed: 1286779327 +mac_size: 12 +# 0x1402... would be FAKEFAKE... if encoded in BASE64 +mac: 0x140284140284140284140284 +original_id: 16020 +error: 16 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire2.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.wire new file mode 100644 index 0000000..7ea336d --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire2.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_toWire2.spec +### + +# TSIG RDATA, RDLEN=41 +0029 +# Algorithm=hmac-sha256 Time-Signed=1286779327 Fudge=300 +0b686d61632d73686132353600 00004cb2b1bf 012c +# MAC Size=12 MAC=(see hex) +000c 140284140284140284140284 +# Original-ID=16020 Error=16 +3e94 0010 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec new file mode 100644 index 0000000..6520a08 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec @@ -0,0 +1,15 @@ +# +# An artificial TSIG RDATA for toWire test. +# +[custom] +sections: tsig +[tsig] +algorithm: hmac-sha1 +time_signed: 1286779327 +mac_size: 12 +# 0x1402... would be FAKEFAKE... if encoded in BASE64 +mac: 0x140284140284140284140284 +original_id: 16020 +error: 18 +other_len: 6 +other_data: 0x140284140284 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire3.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.wire new file mode 100644 index 0000000..535a83b --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire3.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from rdata_tsig_toWire3.spec +### + +# TSIG RDATA, RDLEN=45 +002d +# Algorithm=hmac-sha1 Time-Signed=1286779327 Fudge=300 +09686d61632d7368613100 00004cb2b1bf 012c +# MAC Size=12 MAC=(see hex) +000c 140284140284140284140284 +# Original-ID=16020 Error=18 +3e94 0012 +# Other-Len=6 Other-Data=(see hex) +0006 140284140284 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec new file mode 100644 index 0000000..d95cd23 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec @@ -0,0 +1,13 @@ +# +# An artificial TSIG RDATA for toWire test. +# +[custom] +sections: name:tsig +[name] +name: hmac-md5.sig-alg.reg.int. +[tsig] +algorithm: hmac-md5 +time_signed: 1286779327 +mac_size: 0 +original_id: 16020 +error: 17 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire4.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.wire new file mode 100644 index 0000000..5e8f68b --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire4.wire @@ -0,0 +1,17 @@ +### +### This data file was auto-generated from rdata_tsig_toWire4.spec +### + +# DNS Name: hmac-md5.sig-alg.reg.int. +08686d61632d6d6435077369672d616c670372656703696e7400 + +# TSIG RDATA, RDLEN=42 +002a +# Algorithm=hmac-md5 Time-Signed=1286779327 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004cb2b1bf 012c +# MAC Size=0 MAC=(see hex) +0000 +# Original-ID=16020 Error=17 +3e94 0011 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec new file mode 100644 index 0000000..81e3a78 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec @@ -0,0 +1,13 @@ +# +# An artificial TSIG RDATA for toWire test. +# +[custom] +sections: tsig:name +[tsig] +algorithm: hmac-md5 +time_signed: 1286779327 +mac_size: 0 +original_id: 16020 +error: 17 +[name] +name: ptr=2 diff --git a/src/lib/dns/tests/testdata/rdata_tsig_toWire5.wire b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.wire new file mode 100644 index 0000000..bc83de6 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_tsig_toWire5.wire @@ -0,0 +1,17 @@ +### +### This data file was auto-generated from rdata_tsig_toWire5.spec +### + +# TSIG RDATA, RDLEN=42 +002a +# Algorithm=hmac-md5 Time-Signed=1286779327 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004cb2b1bf 012c +# MAC Size=0 MAC=(see hex) +0000 +# Original-ID=16020 Error=17 +3e94 0011 +# Other-Len=0 Other-Data=(see hex) +0000 + +# DNS Name: ptr=2 +c002 diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire1 b/src/lib/dns/tests/testdata/rdata_txt_fromWire1 new file mode 100644 index 0000000..547d76f --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire1 @@ -0,0 +1,9 @@ +# +# various kinds of TXT RDATA stored in an input buffer +# +# Valid RDATA for "Test-String" +# +# RDLENGTH=12 bytes + 00 0c +# T e s t - S t r i n g + 0b 54 65 73 74 2d 53 74 72 69 6e 67 diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_txt_fromWire2.spec new file mode 100644 index 0000000..c5829d9 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire2.spec @@ -0,0 +1,8 @@ +# +# TXT RDATA with empty character-string. unusual, but valid. +# + +[custom] +sections: txt +[txt] +string: '' diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire2.wire b/src/lib/dns/tests/testdata/rdata_txt_fromWire2.wire new file mode 100644 index 0000000..83ebea3 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire2.wire @@ -0,0 +1,8 @@ +### +### This data file was auto-generated from rdata_txt_fromWire2.spec +### + +# TXT RDATA, RDLEN=1 +0001 +# String Len=0, String="" +00 diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_txt_fromWire3.spec new file mode 100644 index 0000000..fe5c129 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire3.spec @@ -0,0 +1,8 @@ +# +# TXT RDATA with multiple character-strings. +# + +[custom] +sections: txt +[txt] +nstring: 2 diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire3.wire b/src/lib/dns/tests/testdata/rdata_txt_fromWire3.wire new file mode 100644 index 0000000..42fdf76 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire3.wire @@ -0,0 +1,10 @@ +### +### This data file was auto-generated from rdata_txt_fromWire3.spec +### + +# TXT RDATA, RDLEN=24 +0018 +# String Len=11, String="Test-String" +0b 546573742d537472696e67 +# String Len=11, String="Test-String" +0b 546573742d537472696e67 diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_txt_fromWire4.spec new file mode 100644 index 0000000..0e015d4 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire4.spec @@ -0,0 +1,9 @@ +# +# Malformed TXT RDATA: RDLEN is 0 +# + +[custom] +sections: txt +[txt] +rdlen: 0 +# following data is provided, but that doesn't matter. diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire4.wire b/src/lib/dns/tests/testdata/rdata_txt_fromWire4.wire new file mode 100644 index 0000000..6ac73b8 --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire4.wire @@ -0,0 +1,8 @@ +### +### This data file was auto-generated from rdata_txt_fromWire4.spec +### + +# TXT RDATA, RDLEN=0 +0000 +# String Len=11, String="Test-String" +0b 546573742d537472696e67 diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_txt_fromWire5.spec new file mode 100644 index 0000000..7710cdf --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire5.spec @@ -0,0 +1,9 @@ +# +# Malformed TXT RDATA: character-string length is too large +# + +[custom] +sections: txt +[txt] +stringlen: 255 +string: 'too short' diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire5.wire b/src/lib/dns/tests/testdata/rdata_txt_fromWire5.wire new file mode 100644 index 0000000..ea7a9cf --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire5.wire @@ -0,0 +1,8 @@ +### +### This data file was auto-generated from rdata_txt_fromWire5.spec +### + +# TXT RDATA, RDLEN=10 +000a +# String Len=255, String="too short" +ff 746f6f2073686f7274 diff --git a/src/lib/dns/tests/testdata/rdata_unknown_fromWire b/src/lib/dns/tests/testdata/rdata_unknown_fromWire new file mode 100644 index 0000000..69ea8ff --- /dev/null +++ b/src/lib/dns/tests/testdata/rdata_unknown_fromWire @@ -0,0 +1,13 @@ +# +# various kinds of "unknown" RDATA stored in an input buffer +# +# 0 1 2 3 4 5 (bytes) + 00 04 a1 b2 c3 0d +# +# 0-length data +# 6 7 + 00 00 +# +# short buffer (this can be tested only at the end of the buffer) +# 8 9 10 1 2 + 00 04 a1 b2 c3 diff --git a/src/lib/dns/tests/testdata/rrcode16_fromWire1 b/src/lib/dns/tests/testdata/rrcode16_fromWire1 new file mode 100644 index 0000000..df2a177 --- /dev/null +++ b/src/lib/dns/tests/testdata/rrcode16_fromWire1 @@ -0,0 +1,4 @@ +# +# a 16 bit wire-format data (network byte order) +# +1234 diff --git a/src/lib/dns/tests/testdata/rrcode16_fromWire2 b/src/lib/dns/tests/testdata/rrcode16_fromWire2 new file mode 100644 index 0000000..fec2dd0 --- /dev/null +++ b/src/lib/dns/tests/testdata/rrcode16_fromWire2 @@ -0,0 +1,4 @@ +# +# an incomplete segment for a 16 bit wire-format data +# +12 diff --git a/src/lib/dns/tests/testdata/rrcode32_fromWire1 b/src/lib/dns/tests/testdata/rrcode32_fromWire1 new file mode 100644 index 0000000..fb2818f --- /dev/null +++ b/src/lib/dns/tests/testdata/rrcode32_fromWire1 @@ -0,0 +1,4 @@ +# +# a 32 bit wire-format data (network byte order) +# +12345678 diff --git a/src/lib/dns/tests/testdata/rrcode32_fromWire2 b/src/lib/dns/tests/testdata/rrcode32_fromWire2 new file mode 100644 index 0000000..504d9d2 --- /dev/null +++ b/src/lib/dns/tests/testdata/rrcode32_fromWire2 @@ -0,0 +1,4 @@ +# +# an incomplete segment for a 32 bit wire-format data +# +123456 diff --git a/src/lib/dns/tests/testdata/rrset_toWire1 b/src/lib/dns/tests/testdata/rrset_toWire1 new file mode 100644 index 0000000..8f81a0e --- /dev/null +++ b/src/lib/dns/tests/testdata/rrset_toWire1 @@ -0,0 +1,23 @@ +# +# Rendering an IN/A RRset containing 2 RRs: +# test.example.com. 3600 IN A 192.0.2.1 +# test.example.com. 3600 IN A 192.0.2.2 +# +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# type/class: A = 1, IN = 1 +00 01 00 01 +# TTL: 3600 +00 00 0e 10 +#6 7 +# RDLENGTH: 4 +00 04 +# RDATA: 192.0.2.1 +c0 00 02 01 +# +# 2nd RR: mostly the same except the RDATA +04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +00 01 00 01 +00 00 0e 10 +00 04 +c0 00 02 02 diff --git a/src/lib/dns/tests/testdata/rrset_toWire2 b/src/lib/dns/tests/testdata/rrset_toWire2 new file mode 100644 index 0000000..ca6483f --- /dev/null +++ b/src/lib/dns/tests/testdata/rrset_toWire2 @@ -0,0 +1,38 @@ +# +# Rendering an IN/A RRset and NS RRset as follows: +# test.example.com. 3600 IN A 192.0.2.1 +# test.example.com. 3600 IN A 192.0.2.2 +# example.com. 1D IN NS ns.example.com. +# Names will be compressed when possible. +# +# 0 1 2 3 4 5 +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# type/class: A = 1, IN = 1 +00 01 00 01 +# TTL: 3600 +00 00 0e 10 +#6 7 +# RDLENGTH: 4 +00 04 +# RDATA: 192.0.2.1 +c0 00 02 01 +# +# 2nd RR: the owner name is compressed +c0 00 +00 01 00 01 +00 00 0e 10 +00 04 +c0 00 02 02 +# 3rd RR: the owner name and NS name are compressed +# pointing to the 5th octet of the owner name of the 1st RR +c0 05 +# type/class: NS = 2, IN = 1 +00 02 00 01 +# TTL: 1D = 86400sec = 0x15180 +00 01 51 80 +# RDLENGTH: 5 octets +00 05 +# NSDNAME: "ns." + compression pointer +#(2) n s + 02 6e 73 c0 05 diff --git a/src/lib/dns/tests/testdata/rrset_toWire3 b/src/lib/dns/tests/testdata/rrset_toWire3 new file mode 100644 index 0000000..47f8e6b --- /dev/null +++ b/src/lib/dns/tests/testdata/rrset_toWire3 @@ -0,0 +1,12 @@ +# +# Rendering an empty IN/A RRset +# +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# type/class: A = 1, ANY = 255 +00 01 00 ff +# TTL: 3600 +00 00 0e 10 +#6 7 +# RDLENGTH: 0 +00 00 diff --git a/src/lib/dns/tests/testdata/rrset_toWire4 b/src/lib/dns/tests/testdata/rrset_toWire4 new file mode 100644 index 0000000..6fb409c --- /dev/null +++ b/src/lib/dns/tests/testdata/rrset_toWire4 @@ -0,0 +1,12 @@ +# +# Rendering an empty IN/A RRset +# +#(4) t e s t (7) e x a m p l e (3) c o m . + 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 +# type/class: A = 1, ANY = 255 +00 01 00 fe +# TTL: 3600 +00 00 0e 10 +#6 7 +# RDLENGTH: 0 +00 00 diff --git a/src/lib/dns/tests/testdata/tsig_verify1.spec b/src/lib/dns/tests/testdata/tsig_verify1.spec new file mode 100644 index 0000000..687013a --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify1.spec @@ -0,0 +1,19 @@ +# +# An example of signed AXFR request +# + +[custom] +sections: header:question:tsig +[header] +id: 0x3410 +arcount: 1 +[question] +rrtype: AXFR +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8e951 +mac_size: 16 +mac: 0x35b2fd08268781634400c7c8a5533b13 +original_id: 0x3410 diff --git a/src/lib/dns/tests/testdata/tsig_verify1.wire b/src/lib/dns/tests/testdata/tsig_verify1.wire new file mode 100644 index 0000000..b2d12f1 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify1.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from tsig_verify1.spec +### + +# Header Section +# ID=13328 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) +3410 0000 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=example.com. QTYPE=AXFR(252) QCLASS=IN(1) +076578616d706c6503636f6d00 00fc 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302915409 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8e951 012c +# MAC Size=16 MAC=(see hex) +0010 35b2fd08268781634400c7c8a5533b13 +# Original-ID=13328 Error=0 +3410 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify10.spec b/src/lib/dns/tests/testdata/tsig_verify10.spec new file mode 100644 index 0000000..33ce83e --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify10.spec @@ -0,0 +1,22 @@ +# +# A simple DNS query message with TSIG signed whose MAC is too short +# (only 1 byte) +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 1 +mac: 0x22 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsig_verify10.wire b/src/lib/dns/tests/testdata/tsig_verify10.wire new file mode 100644 index 0000000..9ffe4ea --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify10.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from tsig_verify10.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=43) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 002b +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=1 MAC=(see hex) +0001 22 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify11.spec b/src/lib/dns/tests/testdata/tsig_verify11.spec new file mode 100644 index 0000000..9927b48 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify11.spec @@ -0,0 +1,24 @@ +# +# A simple DNS query message with TSIG signed with truncated MAC +# using common HMAC-SHA512-256 +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-sha512 +time_signed: 0x4da8877a +#mac_size: 64 +mac_size: 32 +#mac: 0xc4bc4053572d62dd1b26998111565c18056be773dedc6ecea60dff31db2f25966e5d9bafbaaed56efbd5ee2d7e2a12ede4caad630ddf69846c980409724da34e +mac: 0xc4bc4053572d62dd1b26998111565c18056be773dedc6ecea60dff31db2f2596 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsig_verify11.wire b/src/lib/dns/tests/testdata/tsig_verify11.wire new file mode 100644 index 0000000..f0a8f4f --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify11.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from tsig_verify11.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=61) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003d +# Algorithm=hmac-sha512 Time-Signed=1302890362 Fudge=300 +0b686d61632d73686135313200 00004da8877a 012c +# MAC Size=32 MAC=(see hex) +0020 c4bc4053572d62dd1b26998111565c18056be773dedc6ecea60dff31db2f2596 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify2.spec b/src/lib/dns/tests/testdata/tsig_verify2.spec new file mode 100644 index 0000000..ff98ca3 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify2.spec @@ -0,0 +1,32 @@ +# +# An example of signed AXFR response +# + +[custom] +sections: header:question:soa:tsig +[header] +id: 0x3410 +aa: 1 +qr: 1 +ancount: 1 +arcount: 1 +[question] +rrtype: AXFR +[soa] +# note that names are compressed in this RR +as_rr: True +rr_name: ptr=12 +mname: ns.ptr=12 +rname: root.ptr=12 +serial: 2011041503 +refresh: 7200 +retry: 3600 +expire: 2592000 +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8e951 +mac_size: 16 +mac: 0xbdd612cd2c7f9e0648bd6dc23713e83c +original_id: 0x3410 diff --git a/src/lib/dns/tests/testdata/tsig_verify2.wire b/src/lib/dns/tests/testdata/tsig_verify2.wire new file mode 100644 index 0000000..65f5f89 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify2.wire @@ -0,0 +1,31 @@ +### +### This data file was auto-generated from tsig_verify2.spec +### + +# Header Section +# ID=13328 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA +3410 8400 +# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=1 +0001 0001 0000 0001 + +# Question Section +# QNAME=example.com. QTYPE=AXFR(252) QCLASS=IN(1) +076578616d706c6503636f6d00 00fc 0001 + +# SOA RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=32) +c00c 0006 0001 00015180 0020 +# NNAME=ns.ptr=12 RNAME=root.ptr=12 +026e73c00c 04726f6f74c00c +# SERIAL(2011041503) REFRESH(7200) RETRY(3600) EXPIRE(2592000) MINIMUM(1200) +77de0edf 00001c20 00000e10 00278d00 000004b0 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302915409 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8e951 012c +# MAC Size=16 MAC=(see hex) +0010 bdd612cd2c7f9e0648bd6dc23713e83c +# Original-ID=13328 Error=0 +3410 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify3.spec b/src/lib/dns/tests/testdata/tsig_verify3.spec new file mode 100644 index 0000000..7e2f797 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify3.spec @@ -0,0 +1,26 @@ +# +# An example of signed AXFR response (continued) +# + +[custom] +sections: header:ns:tsig +[header] +id: 0x3410 +aa: 1 +qr: 1 +qdcount: 0 +ancount: 1 +arcount: 1 +[ns] +# note that names are compressed in this RR +as_rr: True +rr_name: example.com. +nsname: ns.ptr=12 +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8e951 +mac_size: 16 +mac: 0x102458f7f62ddd7d638d746034130968 +original_id: 0x3410 diff --git a/src/lib/dns/tests/testdata/tsig_verify3.wire b/src/lib/dns/tests/testdata/tsig_verify3.wire new file mode 100644 index 0000000..c479ac3 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify3.wire @@ -0,0 +1,25 @@ +### +### This data file was auto-generated from tsig_verify3.spec +### + +# Header Section +# ID=13328 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA +3410 8400 +# QDCNT=0, ANCNT=1, NSCNT=0, ARCNT=1 +0000 0001 0000 0001 + +# NS RR (QNAME=example.com. Class=IN(1) TTL=86400, RDLEN=5) +076578616d706c6503636f6d00 0002 0001 00015180 0005 +# NS name=ns.ptr=12 +026e73c00c + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302915409 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8e951 012c +# MAC Size=16 MAC=(see hex) +0010 102458f7f62ddd7d638d746034130968 +# Original-ID=13328 Error=0 +3410 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify4.spec b/src/lib/dns/tests/testdata/tsig_verify4.spec new file mode 100644 index 0000000..4ffbbcf --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify4.spec @@ -0,0 +1,27 @@ +# +# An example of signed DNS response with bogus MAC +# + +[custom] +sections: header:question:a:tsig +[header] +id: 0x2d65 +aa: 1 +qr: 1 +rd: 1 +ancount: 1 +arcount: 1 +[question] +name: www.example.com +[a] +as_rr: True +rr_name: ptr=12 +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +# bogus MAC +mac: 0xdeadbeefdeadbeefdeadbeefdeadbeef +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsig_verify4.wire b/src/lib/dns/tests/testdata/tsig_verify4.wire new file mode 100644 index 0000000..de5e050 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify4.wire @@ -0,0 +1,29 @@ +### +### This data file was auto-generated from tsig_verify4.spec +### + +# Header Section +# ID=11621 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA RD +2d65 8500 +# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=1 +0001 0001 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# A RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=4) +c00c 0001 0001 00015180 0004 +# Address=192.0.2.1 +c0000201 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 deadbeefdeadbeefdeadbeefdeadbeef +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify5.spec b/src/lib/dns/tests/testdata/tsig_verify5.spec new file mode 100644 index 0000000..a6cc643 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify5.spec @@ -0,0 +1,26 @@ +# +# An example of signed DNS response +# + +[custom] +sections: header:question:a:tsig +[header] +id: 0x2d65 +aa: 1 +qr: 1 +rd: 1 +ancount: 1 +arcount: 1 +[question] +name: www.example.com +[a] +as_rr: True +rr_name: ptr=12 +[tsig] +as_rr: True +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x8fcda66a7cd1a3b9948eb1869d384a9f +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsig_verify5.wire b/src/lib/dns/tests/testdata/tsig_verify5.wire new file mode 100644 index 0000000..3cfb89b --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify5.wire @@ -0,0 +1,29 @@ +### +### This data file was auto-generated from tsig_verify5.spec +### + +# Header Section +# ID=11621 QR=Response Opcode=QUERY(0) Rcode=NOERROR(0) AA RD +2d65 8500 +# QDCNT=1, ANCNT=1, NSCNT=0, ARCNT=1 +0001 0001 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# A RR (QNAME=ptr=12 Class=IN(1) TTL=86400, RDLEN=4) +c00c 0001 0001 00015180 0004 +# Address=192.0.2.1 +c0000201 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 8fcda66a7cd1a3b9948eb1869d384a9f +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify6.spec b/src/lib/dns/tests/testdata/tsig_verify6.spec new file mode 100644 index 0000000..32e0818 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify6.spec @@ -0,0 +1,21 @@ +# +# Forwarded DNS query message with TSIG signed (header ID != orig ID) +# + +[custom] +sections: header:question:tsig +[header] +id: 0x1035 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x227026ad297beee721ce6c6fff1e9ef3 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsig_verify6.wire b/src/lib/dns/tests/testdata/tsig_verify6.wire new file mode 100644 index 0000000..9e3e5b4 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify6.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from tsig_verify6.spec +### + +# Header Section +# ID=4149 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +1035 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify7.spec b/src/lib/dns/tests/testdata/tsig_verify7.spec new file mode 100644 index 0000000..377578e --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify7.spec @@ -0,0 +1,21 @@ +# +# DNS query message with TSIG that has empty MAC (invalidly) +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 0 +mac: '' +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsig_verify7.wire b/src/lib/dns/tests/testdata/tsig_verify7.wire new file mode 100644 index 0000000..4de7d24 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify7.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from tsig_verify7.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=42) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 002a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=0 MAC=(see hex) +0000 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify8.spec b/src/lib/dns/tests/testdata/tsig_verify8.spec new file mode 100644 index 0000000..5432d4a --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify8.spec @@ -0,0 +1,23 @@ +# +# DNS query message with TSIG that has empty MAC + BADKEY error +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 0 +mac: '' +# 17: BADKEY +error: 17 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsig_verify8.wire b/src/lib/dns/tests/testdata/tsig_verify8.wire new file mode 100644 index 0000000..50845eb --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify8.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from tsig_verify8.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=42) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 002a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=0 MAC=(see hex) +0000 +# Original-ID=11621 Error=17 +2d65 0011 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsig_verify9.spec b/src/lib/dns/tests/testdata/tsig_verify9.spec new file mode 100644 index 0000000..5888455 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify9.spec @@ -0,0 +1,21 @@ +# +# A simple DNS query message with TSIG signed, but TSIG key and algorithm +# names have upper case characters (unusual) +# + +[custom] +sections: header:question:tsig +[header] +id: 0x2d65 +rd: 1 +arcount: 1 +[question] +name: www.example.com +[tsig] +as_rr: True +rr_name: WWW.EXAMPLE.COM +algorithm: HMAC-MD5.SIG-ALG.REG.INT +time_signed: 0x4da8877a +mac_size: 16 +mac: 0x227026ad297beee721ce6c6fff1e9ef3 +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsig_verify9.wire b/src/lib/dns/tests/testdata/tsig_verify9.wire new file mode 100644 index 0000000..163fcee --- /dev/null +++ b/src/lib/dns/tests/testdata/tsig_verify9.wire @@ -0,0 +1,24 @@ +### +### This data file was auto-generated from tsig_verify9.spec +### + +# Header Section +# ID=11621 QR=Query Opcode=QUERY(0) Rcode=NOERROR(0) RD +2d65 0100 +# QDCNT=1, ANCNT=0, NSCNT=0, ARCNT=1 +0001 0000 0000 0001 + +# Question Section +# QNAME=www.example.com QTYPE=A(1) QCLASS=IN(1) +03777777076578616d706c6503636f6d00 0001 0001 + +# TSIG RR (QNAME=WWW.EXAMPLE.COM Class=ANY(255) TTL=0, RDLEN=58) +03575757074558414d504c4503434f4d00 00fa 00ff 00000000 003a +# Algorithm=HMAC-MD5.SIG-ALG.REG.INT Time-Signed=1302890362 Fudge=300 +08484d41432d4d4435075349472d414c470352454703494e5400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 227026ad297beee721ce6c6fff1e9ef3 +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsigrecord_toWire1.spec b/src/lib/dns/tests/testdata/tsigrecord_toWire1.spec new file mode 100644 index 0000000..a25dc46 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsigrecord_toWire1.spec @@ -0,0 +1,16 @@ +# +# A simple TSIG RR (some of the parameters are taken from a live example +# and don't have a specific meaning) +# + +[custom] +sections: tsig +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0xdadadadadadadadadadadadadadadada +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsigrecord_toWire1.wire b/src/lib/dns/tests/testdata/tsigrecord_toWire1.wire new file mode 100644 index 0000000..a125e3d --- /dev/null +++ b/src/lib/dns/tests/testdata/tsigrecord_toWire1.wire @@ -0,0 +1,14 @@ +### +### This data file was auto-generated from tsigrecord_toWire1.spec +### + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 dadadadadadadadadadadadadadadada +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/testdata/tsigrecord_toWire2.spec b/src/lib/dns/tests/testdata/tsigrecord_toWire2.spec new file mode 100644 index 0000000..f667e4c --- /dev/null +++ b/src/lib/dns/tests/testdata/tsigrecord_toWire2.spec @@ -0,0 +1,19 @@ +# +# TSIG RR after some names that could (unexpectedly) cause name compression +# + +[custom] +sections: name/1:name/2:tsig +[name/1] +name: hmac-md5.sig-alg.reg.int +[name/2] +name: foo.example.com +[tsig] +as_rr: True +# TSIG QNAME won't be compressed +rr_name: www.example.com +algorithm: hmac-md5 +time_signed: 0x4da8877a +mac_size: 16 +mac: 0xdadadadadadadadadadadadadadadada +original_id: 0x2d65 diff --git a/src/lib/dns/tests/testdata/tsigrecord_toWire2.wire b/src/lib/dns/tests/testdata/tsigrecord_toWire2.wire new file mode 100644 index 0000000..980e1f1 --- /dev/null +++ b/src/lib/dns/tests/testdata/tsigrecord_toWire2.wire @@ -0,0 +1,20 @@ +### +### This data file was auto-generated from tsigrecord_toWire2.spec +### + +# DNS Name: hmac-md5.sig-alg.reg.int +08686d61632d6d6435077369672d616c670372656703696e7400 + +# DNS Name: foo.example.com +03666f6f076578616d706c6503636f6d00 + +# TSIG RR (QNAME=www.example.com Class=ANY(255) TTL=0, RDLEN=58) +03777777076578616d706c6503636f6d00 00fa 00ff 00000000 003a +# Algorithm=hmac-md5 Time-Signed=1302890362 Fudge=300 +08686d61632d6d6435077369672d616c670372656703696e7400 00004da8877a 012c +# MAC Size=16 MAC=(see hex) +0010 dadadadadadadadadadadadadadadada +# Original-ID=11621 Error=0 +2d65 0000 +# Other-Len=0 Other-Data=(see hex) +0000 diff --git a/src/lib/dns/tests/time_utils_unittest.cc b/src/lib/dns/tests/time_utils_unittest.cc new file mode 100644 index 0000000..320b6ce --- /dev/null +++ b/src/lib/dns/tests/time_utils_unittest.cc @@ -0,0 +1,147 @@ +#include <config.h> + +#include <dns/time_utils.h> + +#include <ctime> +#include <gtest/gtest.h> +#include <string> + +using namespace std; +using namespace isc::util; + +// See time_utilities.cc +namespace isc { +namespace util { +namespace detail { +extern int64_t (*getTimeFunction)(); +} +} // namespace util +} // namespace isc + +namespace { + +class DNSSECTimeTest : public ::testing::Test { +protected: + ~DNSSECTimeTest() { + detail::getTimeFunction = 0; + } +}; + +TEST_F(DNSSECTimeTest, fromText) { + // In most cases (in practice) the 32-bit and 64-bit versions should + // behave identically, so we'll mainly test the 32-bit version, which + // will be more commonly used in actual code (because many of the wire + // format time field are 32-bit). The subtle cases where these two + // return different values will be tested at the end of this test case. + + // These are bogus and should be rejected + EXPECT_THROW(timeFromText32("2011 101120000"), InvalidTime); + EXPECT_THROW(timeFromText32("201101011200-0"), InvalidTime); + + // Short length (or "decimal integer" version of representation; + // it's valid per RFC4034, but is not supported in this implementation) + EXPECT_THROW(timeFromText32("20100223"), InvalidTime); + + // Leap year checks + EXPECT_THROW(timeFromText32("20110229120000"), InvalidTime); + EXPECT_THROW(timeFromText32("21000229120000"), InvalidTime); + EXPECT_NO_THROW(timeFromText32("20000229120000")); + EXPECT_NO_THROW(timeFromText32("20120229120000")); + + // unusual case: this implementation allows SS=60 for "leap seconds" + EXPECT_NO_THROW(timeFromText32("20110101120060")); + + // Out of range parameters + EXPECT_THROW(timeFromText32("19100223214617"), InvalidTime); // YY<1970 + EXPECT_THROW(timeFromText32("20110001120000"), InvalidTime); // MM=00 + EXPECT_THROW(timeFromText32("20111301120000"), InvalidTime); // MM=13 + EXPECT_THROW(timeFromText32("20110100120000"), InvalidTime); // DD=00 + EXPECT_THROW(timeFromText32("20110132120000"), InvalidTime); // DD=32 + EXPECT_THROW(timeFromText32("20110431120000"), InvalidTime); // 'Apr31' + EXPECT_THROW(timeFromText32("20110101250000"), InvalidTime); // HH=25 + EXPECT_THROW(timeFromText32("20110101126000"), InvalidTime); // mm=60 + EXPECT_THROW(timeFromText32("20110101120061"), InvalidTime); // SS=61 + + // Feb 7, 06:28:15 UTC 2106 is the possible maximum time that can be + // represented as an unsigned 32bit integer without overflow. + EXPECT_EQ(4294967295LU, timeFromText32("21060207062815")); + + // After that, timeFromText32() should start returning the second count + // modulo 2^32. + EXPECT_EQ(0, timeFromText32("21060207062816")); + EXPECT_EQ(10, timeFromText32("21060207062826")); + + // On the other hand, the 64-bit version should return monotonically + // increasing counters. + EXPECT_EQ(4294967296LL, timeFromText64("21060207062816")); + EXPECT_EQ(4294967306LL, timeFromText64("21060207062826")); +} + +// This helper templated function tells timeToText32 a faked current time. +// The template parameter is that faked time in the form of int64_t seconds +// since epoch. +template <int64_t NOW> +int64_t +testGetTime() { + return (NOW); +} + +// Seconds since epoch for the year 10K eve. Commonly used in some tests +// below. +constexpr uint64_t YEAR10K_EVE = 253402300799LL; + +TEST_F(DNSSECTimeTest, toText) { + // Check a basic case with the default (normal) getTimeFunction + // based on the "real current time". + // Note: this will fail after year 2078, but at that point we won't use + // this program anyway:-) + EXPECT_EQ("20100311233000", timeToText32(1268350200)); + + // Set the current time to: Feb 18 09:04:14 UTC 2012 (an arbitrary choice + // in the range of the first half of uint32 since epoch). + detail::getTimeFunction = testGetTime<1329555854LL>; + + // Test the "year 2038" problem. + // Check the result of toText() for "INT_MIN" in int32_t. It's in the + // 68-year range from the faked current time, so the result should be + // in year 2038, instead of 1901. + EXPECT_EQ("20380119031408", timeToText64(0x80000000L)); + EXPECT_EQ("20380119031408", timeToText32(0x80000000L)); + + // A controversial case: what should we do with "-1"? It's out of range + // in future, but according to RFC time before epoch doesn't seem to be + // considered "in-range" either. Our toText() implementation handles + // this range as a special case and always treats them as future time + // until year 2038. This won't be a real issue in practice, though, + // since such too large values won't be used in actual deployment by then. + EXPECT_EQ("21060207062815", timeToText32(0xffffffffL)); + + // After the singular point of year 2038, the first half of uint32 can + // point to a future time. + // Set the current time to: Apr 1 00:00:00 UTC 2038: + detail::getTimeFunction = testGetTime<2153692800LL>; + // then time "10" is Feb 7 06:28:26 UTC 2106 + EXPECT_EQ("21060207062826", timeToText32(10)); + // in 64-bit, it's 2^32 + 10 + EXPECT_EQ("21060207062826", timeToText64(0x10000000aLL)); + + // After year 2106, the upper half of uint32 can point to past time + // (as it should). + detail::getTimeFunction = testGetTime<0x10000000aLL>; + EXPECT_EQ("21060207062815", timeToText32(0xffffffffL)); + + // Try very large time value. Actually it's the possible farthest time + // that can be represented in the form of YYYYMMDDHHmmSS. + EXPECT_EQ("99991231235959", timeToText64(YEAR10K_EVE)); + detail::getTimeFunction = testGetTime<YEAR10K_EVE - 10>; + EXPECT_EQ("99991231235959", timeToText32(4294197631LU)); +} + +TEST_F(DNSSECTimeTest, overflow) { + // Jan 1, Year 10,000. + EXPECT_THROW(timeToText64(253402300800LL), InvalidTime); + detail::getTimeFunction = testGetTime<YEAR10K_EVE - 10>; + EXPECT_THROW(timeToText32(4294197632LU), InvalidTime); +} + +} // namespace
\ No newline at end of file diff --git a/src/lib/dns/tests/tsig_unittest.cc b/src/lib/dns/tests/tsig_unittest.cc new file mode 100644 index 0000000..2eb418b --- /dev/null +++ b/src/lib/dns/tests/tsig_unittest.cc @@ -0,0 +1,1177 @@ +// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <dns/message.h> +#include <dns/messagerenderer.h> +#include <dns/question.h> +#include <dns/opcode.h> +#include <dns/rcode.h> +#include <dns/rrclass.h> +#include <dns/rrtype.h> +#include <dns/tsig.h> +#include <dns/tsigkey.h> +#include <dns/tsigrecord.h> +#include <dns/tests/unittest_util.h> +#include <util/buffer.h> +#include <util/encode/encode.h> +#include <util/unittests/newhook.h> +#include <util/unittests/wiredata.h> + +#include <time.h> +#include <string> +#include <stdexcept> +#include <vector> + +#include <boost/scoped_ptr.hpp> + +#include <gtest/gtest.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; +using namespace isc::util; +using namespace isc::util::encode; +using namespace isc::dns::rdata; +using namespace isc::dns::rdata::any; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +// @note: blocks and SCOPED_TRACE can make buggy cppchecks raise +// a spurious syntax error... + +// See dnssectime.cc +namespace isc { +namespace util { +namespace detail { +extern int64_t (*getTimeFunction)(); +} +} +} + +namespace { +// See dnssectime_unittest.cc +template <int64_t NOW> +int64_t +testGetTime() { + return (NOW); +} + +// Thin wrapper around TSIGContext to allow access to the +// update method. +class TestTSIGContext : public TSIGContext { +public: + TestTSIGContext(const TSIGKey& key) : + TSIGContext(key) { + } + TestTSIGContext(const Name& key_name, const Name& algorithm_name, + const TSIGKeyRing& keyring) : + TSIGContext(key_name, algorithm_name, keyring) { + } + void update(const void* const data, size_t len) { + TSIGContext::update(data, len); + } +}; + +class TSIGTest : public ::testing::Test { +protected: + TSIGTest() : + tsig_ctx(0), qid(0x2d65), test_name("www.example.com"), + badkey_name("badkey.example.com"), test_class(RRClass::IN()), + test_ttl(86400), message(Message::RENDER), + dummy_data(1024, 0xdd), // should be sufficiently large for all tests + dummy_record(badkey_name, TSIG(TSIGKey::HMACMD5_NAME(), 0x4da8877a, + TSIGContext::DEFAULT_FUDGE, 0, 0, qid, 0, 0, 0)) { + // Make sure we use the system time by default so that we won't be + // confused due to other tests that tweak the time. + isc::util::detail::getTimeFunction = 0; + + decodeBase64("SFuWd/q99SzF8Yzd1QbB9g==", secret); + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACMD5_NAME(), + &secret[0], + secret.size()))); + tsig_verify_ctx.reset(new TSIGContext(TSIGKey(test_name, + TSIGKey::HMACMD5_NAME(), + &secret[0], + secret.size()))); + } + ~TSIGTest() { + isc::util::detail::getTimeFunction = 0; + } + + // Many of the tests below create some DNS message and sign it under + // some specific TSIG context. This helper method unifies the common + // logic with slightly different parameters. + ConstTSIGRecordPtr createMessageAndSign(uint16_t qid, const Name& qname, + TSIGContext* ctx, + unsigned int message_flags = + RD_FLAG, + RRType qtype = RRType::A(), + const char* answer_data = 0, + const RRType* answer_type = 0, + bool add_question = true, + Rcode rcode = Rcode::NOERROR()); + + void createMessageFromFile(const char* datafile); + + // bit-wise constant flags to configure DNS header flags for test + // messages. + static const unsigned int QR_FLAG = 0x1; + static const unsigned int AA_FLAG = 0x2; + static const unsigned int RD_FLAG = 0x4; + + boost::scoped_ptr<TestTSIGContext> tsig_ctx; + boost::scoped_ptr<TSIGContext> tsig_verify_ctx; + TSIGKeyRing keyring; + const uint16_t qid; + const Name test_name; + const Name badkey_name; + const RRClass test_class; + const RRTTL test_ttl; + Message message; + MessageRenderer renderer; + vector<uint8_t> secret; + vector<uint8_t> dummy_data; + const TSIGRecord dummy_record; + vector<uint8_t> received_data; +}; + +ConstTSIGRecordPtr +TSIGTest::createMessageAndSign(uint16_t id, const Name& qname, + TSIGContext* ctx, unsigned int message_flags, + RRType qtype, const char* answer_data, + const RRType* answer_type, bool add_question, + Rcode rcode) { + message.clear(Message::RENDER); + message.setQid(id); + message.setOpcode(Opcode::QUERY()); + message.setRcode(rcode); + if ((message_flags & QR_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_QR); + } + if ((message_flags & AA_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_AA); + } + if ((message_flags & RD_FLAG) != 0) { + message.setHeaderFlag(Message::HEADERFLAG_RD); + } + if (add_question) { + message.addQuestion(Question(qname, test_class, qtype)); + } + if (answer_data) { + if (!answer_type) { + answer_type = &qtype; + } + RRsetPtr answer_rrset(new RRset(qname, test_class, *answer_type, + test_ttl)); + answer_rrset->addRdata(createRdata(*answer_type, test_class, + answer_data)); + message.addRRset(Message::SECTION_ANSWER, answer_rrset); + } + renderer.clear(); + + TSIGContext::State expected_new_state = + (ctx->getState() == TSIGContext::INIT) ? + TSIGContext::SENT_REQUEST : TSIGContext::SENT_RESPONSE; + + message.toWire(renderer, ctx); + + message.clear(Message::PARSE); + InputBuffer buffer(renderer.getData(), renderer.getLength()); + message.fromWire(buffer); + + EXPECT_EQ(expected_new_state, ctx->getState()); + + return (ConstTSIGRecordPtr(new TSIGRecord(*message.getTSIGRecord()))); +} + +void +TSIGTest::createMessageFromFile(const char* datafile) { + message.clear(Message::PARSE); + received_data.clear(); + UnitTestUtil::readWireData(datafile, received_data); + InputBuffer buffer(&received_data[0], received_data.size()); + message.fromWire(buffer); +} + +void +commonSignChecks(ConstTSIGRecordPtr tsig, uint16_t expected_qid, + uint64_t expected_timesigned, + const uint8_t* expected_mac, size_t expected_maclen, + uint16_t expected_error = 0, + uint16_t expected_otherlen = 0, + const uint8_t* expected_otherdata = 0, + const Name& expected_algorithm = TSIGKey::HMACMD5_NAME()) { + ASSERT_TRUE(tsig); + const TSIG& tsig_rdata = tsig->getRdata(); + + EXPECT_EQ(expected_algorithm, tsig_rdata.getAlgorithm()); + EXPECT_EQ(expected_timesigned, tsig_rdata.getTimeSigned()); + EXPECT_EQ(300, tsig_rdata.getFudge()); + EXPECT_EQ(expected_maclen, tsig_rdata.getMACSize()); + matchWireData(expected_mac, expected_maclen, + tsig_rdata.getMAC(), tsig_rdata.getMACSize()); + + EXPECT_EQ(expected_qid, tsig_rdata.getOriginalID()); + EXPECT_EQ(expected_error, tsig_rdata.getError()); + EXPECT_EQ(expected_otherlen, tsig_rdata.getOtherLen()); + matchWireData(expected_otherdata, expected_otherlen, + tsig_rdata.getOtherData(), tsig_rdata.getOtherLen()); +} + +void +commonVerifyChecks(TSIGContext& ctx, const TSIGRecord* record, + const void* data, size_t data_len, TSIGError expected_error, + TSIGContext::State expected_new_state = + TSIGContext::VERIFIED_RESPONSE, + bool last_should_throw = false) { + EXPECT_EQ(expected_error, ctx.verify(record, data, data_len)); + EXPECT_EQ(expected_error, ctx.getError()); + EXPECT_EQ(expected_new_state, ctx.getState()); + if (last_should_throw) { + EXPECT_THROW(ctx.lastHadSignature(), TSIGContextError); + } else { + EXPECT_EQ(record != 0, ctx.lastHadSignature()); + } +} + +TEST_F(TSIGTest, initialState) { + // Until signing or verifying, the state should be INIT + EXPECT_EQ(TSIGContext::INIT, tsig_ctx->getState()); + + // And there should be no error code. + EXPECT_EQ(TSIGError(Rcode::NOERROR()), tsig_ctx->getError()); + + // Nothing verified yet + EXPECT_THROW(tsig_ctx->lastHadSignature(), TSIGContextError); +} + +TEST_F(TSIGTest, constructFromKeyRing) { + // Construct a TSIG context with an empty key ring. Key shouldn't be + // found, and the BAD_KEY error should be recorded. + TSIGContext ctx1(test_name, TSIGKey::HMACMD5_NAME(), keyring); + EXPECT_EQ(TSIGContext::INIT, ctx1.getState()); + EXPECT_EQ(TSIGError::BAD_KEY(), ctx1.getError()); + + // Add a matching key (we don't use the secret so leave it empty), and + // construct it again. This time it should be constructed with a valid + // key. + keyring.add(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(), 0, 0)); + TSIGContext ctx2(test_name, TSIGKey::HMACMD5_NAME(), keyring); + EXPECT_EQ(TSIGContext::INIT, ctx2.getState()); + EXPECT_EQ(TSIGError::NOERROR(), ctx2.getError()); + + // Similar to the first case except that the key ring isn't empty but + // it doesn't contain a matching key. + TSIGContext ctx3(test_name, TSIGKey::HMACSHA1_NAME(), keyring); + EXPECT_EQ(TSIGContext::INIT, ctx3.getState()); + EXPECT_EQ(TSIGError::BAD_KEY(), ctx3.getError()); + + TSIGContext ctx4(Name("different-key.example"), TSIGKey::HMACMD5_NAME(), + keyring); + EXPECT_EQ(TSIGContext::INIT, ctx4.getState()); + EXPECT_EQ(TSIGError::BAD_KEY(), ctx4.getError()); + + // "Unknown" algorithm name will result in BADKEY, too. + TSIGContext ctx5(test_name, Name("unknown.algorithm"), keyring); + EXPECT_EQ(TSIGContext::INIT, ctx5.getState()); + EXPECT_EQ(TSIGError::BAD_KEY(), ctx5.getError()); +} + +// Example output generated by +// "dig -y www.example.com:SFuWd/q99SzF8Yzd1QbB9g== www.example.com +// QID: 0x2d65 +// Time Signed: 0x00004da8877a +// MAC: 227026ad297beee721ce6c6fff1e9ef3 +const uint8_t common_expected_mac[] = { + 0x22, 0x70, 0x26, 0xad, 0x29, 0x7b, 0xee, 0xe7, + 0x21, 0xce, 0x6c, 0x6f, 0xff, 0x1e, 0x9e, 0xf3 +}; +TEST_F(TSIGTest, sign) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + { + SCOPED_TRACE("Sign test for query"); + commonSignChecks(createMessageAndSign(qid, test_name, tsig_ctx.get()), + qid, 0x4da8877a, common_expected_mac, + sizeof(common_expected_mac)); + } +} + +// Same test as sign, but specifying the key name with upper-case (i.e. +// non canonical) characters. The digest must be the same. It should actually +// be ensured at the level of TSIGKey, but we confirm that at this level, too. +TEST_F(TSIGTest, signUsingUpperCasedKeyName) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + TSIGContext cap_ctx(TSIGKey(Name("WWW.EXAMPLE.COM"), + TSIGKey::HMACMD5_NAME(), + &secret[0], secret.size())); + + { + SCOPED_TRACE("Sign test for query using non canonical key name"); + commonSignChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid, + 0x4da8877a, common_expected_mac, + sizeof(common_expected_mac)); + } +} + +// Same as the previous test, but for the algorithm name. +TEST_F(TSIGTest, signUsingUpperCasedAlgorithmName) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + TSIGContext cap_ctx(TSIGKey(test_name, + Name("HMAC-md5.SIG-alg.REG.int"), + &secret[0], secret.size())); + + { + SCOPED_TRACE("Sign test for query using non canonical algorithm name"); + commonSignChecks(createMessageAndSign(qid, test_name, &cap_ctx), qid, + 0x4da8877a, common_expected_mac, + sizeof(common_expected_mac)); + } +} + +TEST_F(TSIGTest, signAtActualTime) { + // Sign the message using the actual time, and check the accuracy of it. + // We cannot reasonably predict the expected MAC, so don't bother to + // check it. + const uint64_t now = static_cast<uint64_t>(time(0)); + + { + SCOPED_TRACE("Sign test for query at actual time"); + ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name, + tsig_ctx.get()); + const TSIG& tsig_rdata = tsig->getRdata(); + + // Check the resulted time signed is in the range of [now, now + 5] + // (5 is an arbitrary choice). Note that due to the order of the call + // to time() and sign(), time signed must not be smaller than the + // current time. + EXPECT_LE(now, tsig_rdata.getTimeSigned()); + EXPECT_GE(now + 5, tsig_rdata.getTimeSigned()); + } +} + +TEST_F(TSIGTest, signBadData) { + // some specific bad data should be rejected proactively. + const unsigned char dummy_data = 0; + EXPECT_THROW(tsig_ctx->sign(0, 0, 10), InvalidParameter); + EXPECT_THROW(tsig_ctx->sign(0, &dummy_data, 0), InvalidParameter); +} + +TEST_F(TSIGTest, verifyBadData) { + // the data must at least hold the DNS message header and the specified + // TSIG. + EXPECT_THROW(tsig_ctx->verify(&dummy_record, &dummy_data[0], + 12 + dummy_record.getLength() - 1), + InvalidParameter); + + // Still nothing verified + EXPECT_THROW(tsig_ctx->lastHadSignature(), TSIGContextError); + + // And the data must not be null. + EXPECT_THROW(tsig_ctx->verify(&dummy_record, 0, + 12 + dummy_record.getLength()), + InvalidParameter); + + // Still nothing verified + EXPECT_THROW(tsig_ctx->lastHadSignature(), TSIGContextError); + +} + +#ifdef ENABLE_CUSTOM_OPERATOR_NEW +// We enable this test only when we enable custom new/delete at build time +// We could enable/disable the test runtime using the gtest filter, but +// we'd basically like to minimize the number of disabled tests (they +// should generally be considered tests that temporarily fail and should +// be fixed). +TEST_F(TSIGTest, signExceptionSafety) { + // Check sign() provides the strong exception guarantee for the simpler + // case (with a key error and empty MAC). The general case is more + // complicated and involves more memory allocation, so the test result + // won't be reliable. + + commonVerifyChecks(*tsig_verify_ctx, &dummy_record, &dummy_data[0], + dummy_data.size(), TSIGError::BAD_KEY(), + TSIGContext::RECEIVED_REQUEST); + + try { + int dummydata; + isc::util::unittests::force_throw_on_new = true; + isc::util::unittests::throw_size_on_new = sizeof(TSIGRecord); + tsig_verify_ctx->sign(0, &dummydata, sizeof(dummydata)); + isc::util::unittests::force_throw_on_new = false; + ASSERT_FALSE(true) << "Expected throw on new, but it didn't happen"; + } catch (const std::bad_alloc&) { + isc::util::unittests::force_throw_on_new = false; + + // sign() threw, so the state should still be RECEIVED_REQUEST + EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, tsig_verify_ctx->getState()); + } + isc::util::unittests::force_throw_on_new = false; +} +#endif // ENABLE_CUSTOM_OPERATOR_NEW + +// Same test as "sign" but use a different algorithm just to confirm we don't +// naively hardcode constants specific to a particular algorithm. +// Test data generated by +// "dig -y hmac-sha1:www.example.com:MA+QDhXbyqUak+qnMFyTyEirzng= www.example.com" +// QID: 0x0967, RDflag +// Current Time: 00004da8be86 +// Time Signed: 00004dae7d5f +// HMAC Size: 20 +// HMAC: 415340c7daf824ed684ee586f7b5a67a2febc0d3 +TEST_F(TSIGTest, signUsingHMACSHA1) { + isc::util::detail::getTimeFunction = testGetTime<0x4dae7d5f>; + + secret.clear(); + decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret); + TSIGContext sha1_ctx(TSIGKey(test_name, TSIGKey::HMACSHA1_NAME(), + &secret[0], secret.size())); + + const uint16_t sha1_qid = 0x0967; + const uint8_t expected_mac[] = { + 0x41, 0x53, 0x40, 0xc7, 0xda, 0xf8, 0x24, 0xed, 0x68, 0x4e, + 0xe5, 0x86, 0xf7, 0xb5, 0xa6, 0x7a, 0x2f, 0xeb, 0xc0, 0xd3 + }; + { + SCOPED_TRACE("Sign test using HMAC-SHA1"); + commonSignChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx), + sha1_qid, 0x4dae7d5f, expected_mac, + sizeof(expected_mac), 0, 0, 0, + TSIGKey::HMACSHA1_NAME()); + } +} + +TEST_F(TSIGTest, signUsingHMACSHA224) { + isc::util::detail::getTimeFunction = testGetTime<0x4dae7d5f>; + + secret.clear(); + decodeBase64("MA+QDhXbyqUak+qnMFyTyEirzng=", secret); + TSIGContext sha1_ctx(TSIGKey(test_name, TSIGKey::HMACSHA224_NAME(), + &secret[0], secret.size())); + + const uint16_t sha1_qid = 0x0967; + const uint8_t expected_mac[] = { + 0x3b, 0x93, 0xd3, 0xc5, 0xf9, 0x64, 0xb9, 0xc5, 0x00, 0x35, + 0x02, 0x69, 0x9f, 0xfc, 0x44, 0xd6, 0xe2, 0x66, 0xf4, 0x08, + 0xef, 0x33, 0xa2, 0xda, 0xa1, 0x48, 0x71, 0xd3 + }; + { + SCOPED_TRACE("Sign test using HMAC-SHA224"); + commonSignChecks(createMessageAndSign(sha1_qid, test_name, &sha1_ctx), + sha1_qid, 0x4dae7d5f, expected_mac, + sizeof(expected_mac), 0, 0, 0, + TSIGKey::HMACSHA224_NAME()); + } +} + +// The first part of this test checks verifying the signed query used for +// the "sign" test. +// The second part of this test generates a signed response to the signed +// query as follows: +// Answer: www.example.com. 86400 IN A 192.0.2.1 +// MAC: 8fcda66a7cd1a3b9948eb1869d384a9f +TEST_F(TSIGTest, verifyThenSignResponse) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + // This test data for the message test has the same wire format data + // as the message used in the "sign" test. + createMessageFromFile("message_toWire2.wire"); + { + SCOPED_TRACE("Verify test for request"); + commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST); + } + + // Transform the original message to a response, then sign the response + // with the context of "verified state". + ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name, + tsig_verify_ctx.get(), + QR_FLAG|AA_FLAG|RD_FLAG, + RRType::A(), "192.0.2.1"); + const uint8_t expected_mac[] = { + 0x8f, 0xcd, 0xa6, 0x6a, 0x7c, 0xd1, 0xa3, 0xb9, + 0x94, 0x8e, 0xb1, 0x86, 0x9d, 0x38, 0x4a, 0x9f + }; + { + SCOPED_TRACE("Sign test for response"); + commonSignChecks(tsig, qid, 0x4da8877a, expected_mac, + sizeof(expected_mac)); + } +} + +TEST_F(TSIGTest, verifyUpperCaseNames) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + // This test data for the message test has the same wire format data + // as the message used in the "sign" test. + createMessageFromFile("tsig_verify9.wire"); + { + SCOPED_TRACE("Verify test for request"); + commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST); + } +} + +TEST_F(TSIGTest, verifyForwardedMessage) { + // Similar to the first part of the previous test, but this test emulates + // the "forward" case, where the ID of the Header and the original ID in + // TSIG is different. + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + createMessageFromFile("tsig_verify6.wire"); + { + SCOPED_TRACE("Verify test for forwarded request"); + commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST); + } +} + +// Example of signing multiple messages in a single TCP stream, +// taken from data using BIND 9's "one-answer" transfer-format. +// Request: +// QID: 0x3410, flags (none) +// Question: example.com/IN/AXFR +// Time Signed: 0x4da8e951 +// MAC: 35b2fd08268781634400c7c8a5533b13 +// First message: +// QID: 0x3410, flags QR, AA +// Question: example.com/IN/AXFR +// Answer: example.com. 86400 IN SOA ns.example.com. root.example.com. ( +// 2011041503 7200 3600 2592000 1200) +// MAC: bdd612cd2c7f9e0648bd6dc23713e83c +// Second message: +// Answer: example.com. 86400 IN NS ns.example.com. +// MAC: 102458f7f62ddd7d638d746034130968 +TEST_F(TSIGTest, signContinuation) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8e951>; + + const uint16_t axfr_qid = 0x3410; + const Name zone_name("example.com"); + + // Create and sign the AXFR request + ConstTSIGRecordPtr tsig = createMessageAndSign(axfr_qid, zone_name, + tsig_ctx.get(), 0, + RRType("AXFR")); + // Then verify it (the wire format test data should contain the same + // message data, and verification should succeed). + received_data.clear(); + UnitTestUtil::readWireData("tsig_verify1.wire", received_data); + { + SCOPED_TRACE("Verify AXFR query"); + commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &received_data[0], + received_data.size(), TSIGError::NOERROR(), + TSIGContext::RECEIVED_REQUEST); + } + + // Create and sign the first response message + tsig = createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(), + AA_FLAG|QR_FLAG, RRType("AXFR"), + "ns.example.com. root.example.com. " + "2011041503 7200 3600 2592000 1200", + &RRType::SOA()); + + // Then verify it at the requester side. + received_data.clear(); + UnitTestUtil::readWireData("tsig_verify2.wire", received_data); + { + SCOPED_TRACE("Verify first AXFR response"); + commonVerifyChecks(*tsig_ctx, tsig.get(), &received_data[0], + received_data.size(), TSIGError::NOERROR()); + } + + // Create and sign the second response message + const uint8_t expected_mac[] = { + 0x10, 0x24, 0x58, 0xf7, 0xf6, 0x2d, 0xdd, 0x7d, + 0x63, 0x8d, 0x74, 0x60, 0x34, 0x13, 0x09, 0x68 + }; + { + SCOPED_TRACE("Sign test for continued response in TCP stream"); + tsig = createMessageAndSign(axfr_qid, zone_name, tsig_verify_ctx.get(), + AA_FLAG|QR_FLAG, RRType("AXFR"), + "ns.example.com.", &RRType::NS(), false); + commonSignChecks(tsig, axfr_qid, 0x4da8e951, expected_mac, + sizeof(expected_mac)); + } + + // Then verify it at the requester side. + received_data.clear(); + UnitTestUtil::readWireData("tsig_verify3.wire", received_data); + { + SCOPED_TRACE("Verify second AXFR response"); + commonVerifyChecks(*tsig_ctx, tsig.get(), &received_data[0], + received_data.size(), TSIGError::NOERROR()); + } +} + +// BADTIME example, taken from data using specially hacked BIND 9's nsupdate +// Query: +// QID: 0x1830, RD flag +// Current Time: 00004da8be86 +// Time Signed: 00004da8b9d6 +// Question: www.example.com/IN/SOA +//(mac) 8406 7d50 b8e7 d054 3d50 5bd9 de2a bb68 +// Response: +// QRbit, RCODE=9(NOTAUTH) +// Time Signed: 00004da8b9d6 (the one in the query) +// MAC: d4b043f6f44495ec8a01260e39159d76 +// Error: 0x12 (BADTIME), Other Len: 6 +// Other data: 00004da8be86 +TEST_F(TSIGTest, badtimeResponse) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6>; + + const uint16_t test_qid = 0x7fc4; + ConstTSIGRecordPtr tsig = createMessageAndSign(test_qid, test_name, + tsig_ctx.get(), 0, + RRType::SOA()); + + // "advance the clock" and try validating, which should fail due to BADTIME + isc::util::detail::getTimeFunction = testGetTime<0x4da8be86>; + { + SCOPED_TRACE("Verify resulting in BADTIME due to expired SIG"); + commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &dummy_data[0], + dummy_data.size(), TSIGError::BAD_TIME(), + TSIGContext::RECEIVED_REQUEST); + } + + // make and sign a response in the context of TSIG error. + tsig = createMessageAndSign(test_qid, test_name, tsig_verify_ctx.get(), + QR_FLAG, RRType::SOA(), 0, 0, + true, Rcode::NOTAUTH()); + const uint8_t expected_otherdata[] = { 0, 0, 0x4d, 0xa8, 0xbe, 0x86 }; + const uint8_t expected_mac[] = { + 0xd4, 0xb0, 0x43, 0xf6, 0xf4, 0x44, 0x95, 0xec, + 0x8a, 0x01, 0x26, 0x0e, 0x39, 0x15, 0x9d, 0x76 + }; + { + SCOPED_TRACE("Sign test for response with BADTIME"); + commonSignChecks(tsig, message.getQid(), 0x4da8b9d6, + expected_mac, sizeof(expected_mac), + 18, // error: BADTIME + sizeof(expected_otherdata), + expected_otherdata); + } +} + +TEST_F(TSIGTest, badtimeResponse2) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6>; + + ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name, + tsig_ctx.get(), 0, + RRType::SOA()); + + // "rewind the clock" and try validating, which should fail due to BADTIME + isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 - 600>; + { + SCOPED_TRACE("Verify resulting in BADTIME due to too future SIG"); + commonVerifyChecks(*tsig_verify_ctx, tsig.get(), &dummy_data[0], + dummy_data.size(), TSIGError::BAD_TIME(), + TSIGContext::RECEIVED_REQUEST); + } +} + +TEST_F(TSIGTest, badtimeBoundaries) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6>; + + // Test various boundary conditions. We intentionally use the magic + // number of 300 instead of the constant variable for testing. + // In the okay cases, signature is not correct, but it's sufficient to + // check the error code isn't BADTIME for the purpose of this test. + ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name, + tsig_ctx.get(), 0, + RRType::SOA()); + isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 + 301>; + EXPECT_EQ(TSIGError::BAD_TIME(), + tsig_verify_ctx->verify(tsig.get(), &dummy_data[0], + dummy_data.size())); + isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 + 300>; + EXPECT_NE(TSIGError::BAD_TIME(), + tsig_verify_ctx->verify(tsig.get(), &dummy_data[0], + dummy_data.size())); + isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 - 301>; + EXPECT_EQ(TSIGError::BAD_TIME(), + tsig_verify_ctx->verify(tsig.get(), &dummy_data[0], + dummy_data.size())); + isc::util::detail::getTimeFunction = testGetTime<0x4da8b9d6 - 300>; + EXPECT_NE(TSIGError::BAD_TIME(), + tsig_verify_ctx->verify(tsig.get(), &dummy_data[0], + dummy_data.size())); +} + +TEST_F(TSIGTest, badtimeOverflow) { + isc::util::detail::getTimeFunction = testGetTime<200>; + ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name, + tsig_ctx.get(), 0, + RRType::SOA()); + + // This should be in the okay range, but since "200 - fudge" overflows + // and we compare them as 64-bit unsigned integers, it results in a false + // positive (we intentionally accept that). + isc::util::detail::getTimeFunction = testGetTime<100>; + EXPECT_EQ(TSIGError::BAD_TIME(), + tsig_verify_ctx->verify(tsig.get(), &dummy_data[0], + dummy_data.size())); +} + +TEST_F(TSIGTest, badsigResponse) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + // Try to sign a simple message with bogus secret. It should fail + // with BADSIG. + createMessageFromFile("message_toWire2.wire"); + TSIGContext bad_ctx(TSIGKey(test_name, TSIGKey::HMACMD5_NAME(), + &dummy_data[0], dummy_data.size())); + { + SCOPED_TRACE("Verify resulting in BADSIG"); + commonVerifyChecks(bad_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST); + } + + // Sign the same message (which doesn't matter for this test) with the + // context of "checked state". + { + SCOPED_TRACE("Sign test for response with BADSIG error"); + commonSignChecks(createMessageAndSign(qid, test_name, &bad_ctx), + message.getQid(), 0x4da8877a, 0, 0, + 16); // 16: BADSIG + } +} + +TEST_F(TSIGTest, badkeyResponse) { + // A similar test as badsigResponse but for BADKEY + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + tsig_ctx.reset(new TestTSIGContext(badkey_name, TSIGKey::HMACMD5_NAME(), + keyring)); + { + SCOPED_TRACE("Verify resulting in BADKEY"); + commonVerifyChecks(*tsig_ctx, &dummy_record, &dummy_data[0], + dummy_data.size(), TSIGError::BAD_KEY(), + TSIGContext::RECEIVED_REQUEST); + } + + { + SCOPED_TRACE("Sign test for response with BADKEY error"); + ConstTSIGRecordPtr sig = createMessageAndSign(qid, test_name, + tsig_ctx.get()); + EXPECT_EQ(badkey_name, sig->getName()); + commonSignChecks(sig, qid, 0x4da8877a, 0, 0, 17); // 17: BADKEY + } +} + +TEST_F(TSIGTest, badkeyForResponse) { + // "BADKEY" case for a response to a signed message + createMessageAndSign(qid, test_name, tsig_ctx.get()); + { + SCOPED_TRACE("Verify a response resulting in BADKEY"); + commonVerifyChecks(*tsig_ctx, &dummy_record, &dummy_data[0], + dummy_data.size(), TSIGError::BAD_KEY(), + TSIGContext::SENT_REQUEST); + } + + // A similar case with a different algorithm + const TSIGRecord dummy_record2(test_name, TSIG(TSIGKey::HMACSHA1_NAME(), + 0x4da8877a, + TSIGContext::DEFAULT_FUDGE, + 0, 0, qid, 0, 0, 0)); + { + SCOPED_TRACE("Verify a response resulting in BADKEY due to bad alg"); + commonVerifyChecks(*tsig_ctx, &dummy_record2, &dummy_data[0], + dummy_data.size(), TSIGError::BAD_KEY(), + TSIGContext::SENT_REQUEST); + } +} + +TEST_F(TSIGTest, badsigThenValidate) { + // According to RFC2845 4.6, if TSIG verification fails the client + // should discard that message and wait for another signed response. + // This test emulates that situation. + + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + createMessageAndSign(qid, test_name, tsig_ctx.get()); + + createMessageFromFile("tsig_verify4.wire"); + { + SCOPED_TRACE("Verify a response that should fail due to BADSIG"); + commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::BAD_SIG(), TSIGContext::SENT_REQUEST); + } + + createMessageFromFile("tsig_verify5.wire"); + { + SCOPED_TRACE("Verify a response after a BADSIG failure"); + commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::NOERROR(), + TSIGContext::VERIFIED_RESPONSE); + } +} + +TEST_F(TSIGTest, nosigThenValidate) { + // Similar to the previous test, but the first response doesn't contain + // TSIG. + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + createMessageAndSign(qid, test_name, tsig_ctx.get()); + + { + SCOPED_TRACE("Verify a response without TSIG that should exist"); + commonVerifyChecks(*tsig_ctx, 0, &dummy_data[0], + dummy_data.size(), TSIGError::FORMERR(), + TSIGContext::SENT_REQUEST, true); + } + + createMessageFromFile("tsig_verify5.wire"); + { + SCOPED_TRACE("Verify a response after a FORMERR failure"); + commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::NOERROR(), + TSIGContext::VERIFIED_RESPONSE); + } +} + +TEST_F(TSIGTest, badtimeThenValidate) { + // Similar to the previous test, but the first response results in BADTIME. + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + ConstTSIGRecordPtr tsig = createMessageAndSign(qid, test_name, + tsig_ctx.get()); + + // "advance the clock" and try validating, which should fail due to BADTIME + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a + 600>; + { + SCOPED_TRACE("Verify resulting in BADTIME due to expired SIG"); + commonVerifyChecks(*tsig_ctx, tsig.get(), &dummy_data[0], + dummy_data.size(), TSIGError::BAD_TIME(), + TSIGContext::SENT_REQUEST); + } + + // revert the clock again. + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + createMessageFromFile("tsig_verify5.wire"); + { + SCOPED_TRACE("Verify a response after a BADTIME failure"); + commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::NOERROR(), + TSIGContext::VERIFIED_RESPONSE); + } +} + +TEST_F(TSIGTest, emptyMAC) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + // We don't allow empty MAC unless the TSIG error is BADSIG or BADKEY. + createMessageFromFile("tsig_verify7.wire"); + { + SCOPED_TRACE("Verify test for request"); + commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST); + } + + // If the empty MAC comes with a BADKEY error, the error is passed + // transparently. + createMessageFromFile("tsig_verify8.wire"); + { + SCOPED_TRACE("Verify test for request"); + commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::BAD_KEY(), TSIGContext::RECEIVED_REQUEST); + } +} + +TEST_F(TSIGTest, verifyAfterSendResponse) { + // Once the context is used for sending a signed response, it shouldn't + // be used for further verification. + + // The following are essentially the same as what verifyThenSignResponse + // does with simplification. + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + createMessageFromFile("message_toWire2.wire"); + tsig_verify_ctx->verify(message.getTSIGRecord(), &received_data[0], + received_data.size()); + EXPECT_EQ(TSIGContext::RECEIVED_REQUEST, tsig_verify_ctx->getState()); + createMessageAndSign(qid, test_name, tsig_verify_ctx.get(), + QR_FLAG|AA_FLAG|RD_FLAG, RRType::A(), "192.0.2.1"); + EXPECT_EQ(TSIGContext::SENT_RESPONSE, tsig_verify_ctx->getState()); + + // Now trying further verification. + createMessageFromFile("message_toWire2.wire"); + EXPECT_THROW(tsig_verify_ctx->verify(message.getTSIGRecord(), + &received_data[0], + received_data.size()), + TSIGContextError); +} + +TEST_F(TSIGTest, signAfterVerified) { + // Likewise, once the context verifies a response, it shouldn't for + // signing any more. + + // The following are borrowed from badsigThenValidate (without the + // intermediate failure) + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + createMessageAndSign(qid, test_name, tsig_ctx.get()); + createMessageFromFile("tsig_verify5.wire"); + tsig_ctx->verify(message.getTSIGRecord(), &received_data[0], + received_data.size()); + EXPECT_EQ(TSIGContext::VERIFIED_RESPONSE, tsig_ctx->getState()); + + // Now trying further signing. + EXPECT_THROW(createMessageAndSign(qid, test_name, tsig_ctx.get()), + TSIGContextError); +} + +TEST_F(TSIGTest, tooShortMAC) { + // Too short MAC should be rejected. + + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + createMessageFromFile("tsig_verify10.wire"); + { + SCOPED_TRACE("Verify test for request"); + commonVerifyChecks(*tsig_verify_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::FORMERR(), TSIGContext::RECEIVED_REQUEST); + } +} + +TEST_F(TSIGTest, truncatedMAC) { + // Check truncated MAC support with HMAC-SHA512-256 + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + secret.clear(); + decodeBase64("jI/Pa4qRu96t76Pns5Z/Ndxbn3QCkwcxLOgt9vgvnJw5wqTRvNyk3FtD6yIMd1dWVlqZ+Y4fe6Uasc0ckctEmg==", secret); + TSIGContext sha_ctx(TSIGKey(test_name, TSIGKey::HMACSHA512_NAME(), + &secret[0], secret.size(), 256)); + + createMessageFromFile("tsig_verify11.wire"); + { + SCOPED_TRACE("Verify test for request"); + commonVerifyChecks(sha_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::NOERROR(), TSIGContext::RECEIVED_REQUEST); + } + + // Try with HMAC-SHA512-264 (should fail) + TSIGContext bad_sha_ctx(TSIGKey(test_name, TSIGKey::HMACSHA512_NAME(), + &secret[0], secret.size(), 264)); + { + SCOPED_TRACE("Verify test for request"); + commonVerifyChecks(bad_sha_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::BAD_TRUNC(), TSIGContext::RECEIVED_REQUEST); + } +} + +TEST_F(TSIGTest, getTSIGLength) { + // Check for the most common case with various algorithms + // See the comment in TSIGContext::getTSIGLength() for calculation and + // parameter notation. + // The key name (www.example.com) is the same for most cases, where n1=17 + + // hmac-md5.sig-alg.reg.int.: n2=26, x=16 + EXPECT_EQ(85, tsig_ctx->getTSIGLength()); + + // hmac-md5-80: n2=26, x=10 + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACMD5_NAME(), + &dummy_data[0], 10, 80))); + EXPECT_EQ(79, tsig_ctx->getTSIGLength()); + + // hmac-sha1: n2=11, x=20 + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACSHA1_NAME(), + &dummy_data[0], 20))); + EXPECT_EQ(74, tsig_ctx->getTSIGLength()); + + // hmac-sha256: n2=13, x=32 + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACSHA256_NAME(), + &dummy_data[0], 32))); + EXPECT_EQ(88, tsig_ctx->getTSIGLength()); + + // hmac-sha224: n2=13, x=28 + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACSHA224_NAME(), + &dummy_data[0], 28))); + EXPECT_EQ(84, tsig_ctx->getTSIGLength()); + + // hmac-sha384: n2=13, x=48 + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACSHA384_NAME(), + &dummy_data[0], 48))); + EXPECT_EQ(104, tsig_ctx->getTSIGLength()); + + // hmac-sha512: n2=13, x=64 + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACSHA512_NAME(), + &dummy_data[0], 64))); + EXPECT_EQ(120, tsig_ctx->getTSIGLength()); + + // hmac-sha512-256: n2=13, x=32 + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACSHA512_NAME(), + &dummy_data[0], 32, 256))); + EXPECT_EQ(88, tsig_ctx->getTSIGLength()); + + // bad key case: n1=len(badkey.example.com)=20, n2=26, x=0 + tsig_ctx.reset(new TestTSIGContext(badkey_name, TSIGKey::HMACMD5_NAME(), + keyring)); + EXPECT_EQ(72, tsig_ctx->getTSIGLength()); + + // bad sig case: n1=17, n2=26, x=0 + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + createMessageFromFile("message_toWire2.wire"); + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACMD5_NAME(), + &dummy_data[0], + dummy_data.size()))); + { + SCOPED_TRACE("Verify resulting in BADSIG"); + commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::BAD_SIG(), TSIGContext::RECEIVED_REQUEST); + } + EXPECT_EQ(69, tsig_ctx->getTSIGLength()); + + // bad time case: n1=17, n2=26, x=16, y=6 + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a - 1000>; + tsig_ctx.reset(new TestTSIGContext(TSIGKey(test_name, + TSIGKey::HMACMD5_NAME(), + &dummy_data[0], + dummy_data.size()))); + { + SCOPED_TRACE("Verify resulting in BADTIME"); + commonVerifyChecks(*tsig_ctx, message.getTSIGRecord(), + &received_data[0], received_data.size(), + TSIGError::BAD_TIME(), + TSIGContext::RECEIVED_REQUEST); + } + EXPECT_EQ(91, tsig_ctx->getTSIGLength()); +} + +// Verify a stream of multiple messages. Some of them have a signature omitted. +// +// We have two contexts, one that signs, another that verifies. +TEST_F(TSIGTest, verifyMulti) { + isc::util::detail::getTimeFunction = testGetTime<0x4da8877a>; + + // First, send query from the verify one to the normal one, so + // we initialize something like AXFR + { + SCOPED_TRACE("Query"); + ConstTSIGRecordPtr tsig = createMessageAndSign(1234, test_name, + tsig_verify_ctx.get()); + commonVerifyChecks(*tsig_ctx, tsig.get(), + renderer.getData(), renderer.getLength(), + TSIGError(Rcode::NOERROR()), + TSIGContext::RECEIVED_REQUEST); + } + + { + SCOPED_TRACE("First message"); + ConstTSIGRecordPtr tsig = createMessageAndSign(1234, test_name, + tsig_ctx.get()); + commonVerifyChecks(*tsig_verify_ctx, tsig.get(), + renderer.getData(), renderer.getLength(), + TSIGError(Rcode::NOERROR()), + TSIGContext::VERIFIED_RESPONSE); + EXPECT_TRUE(tsig_verify_ctx->lastHadSignature()); + } + + { + SCOPED_TRACE("Second message"); + ConstTSIGRecordPtr tsig = createMessageAndSign(1234, test_name, + tsig_ctx.get()); + commonVerifyChecks(*tsig_verify_ctx, tsig.get(), + renderer.getData(), renderer.getLength(), + TSIGError(Rcode::NOERROR()), + TSIGContext::VERIFIED_RESPONSE); + EXPECT_TRUE(tsig_verify_ctx->lastHadSignature()); + } + + { + SCOPED_TRACE("Third message. Unsigned."); + // Another message does not carry the TSIG on it. But it should + // be OK, it's in the middle of stream. + message.clear(Message::RENDER); + message.setQid(1234); + message.setOpcode(Opcode::QUERY()); + message.setRcode(Rcode::NOERROR()); + RRsetPtr answer_rrset(new RRset(test_name, test_class, RRType::A(), + test_ttl)); + answer_rrset->addRdata(createRdata(RRType::A(), test_class, + "192.0.2.1")); + message.addRRset(Message::SECTION_ANSWER, answer_rrset); + message.toWire(renderer); + // Update the internal state. We abuse the knowledge of + // internals here a little bit to generate correct test data + tsig_ctx->update(renderer.getData(), renderer.getLength()); + + commonVerifyChecks(*tsig_verify_ctx, 0, + renderer.getData(), renderer.getLength(), + TSIGError(Rcode::NOERROR()), + TSIGContext::VERIFIED_RESPONSE); + + EXPECT_FALSE(tsig_verify_ctx->lastHadSignature()); + } + + { + SCOPED_TRACE("Fourth message. Signed again."); + ConstTSIGRecordPtr tsig = createMessageAndSign(1234, test_name, + tsig_ctx.get()); + commonVerifyChecks(*tsig_verify_ctx, tsig.get(), + renderer.getData(), renderer.getLength(), + TSIGError(Rcode::NOERROR()), + TSIGContext::VERIFIED_RESPONSE); + EXPECT_TRUE(tsig_verify_ctx->lastHadSignature()); + } + + { + SCOPED_TRACE("Filling in bunch of unsigned messages"); + for (size_t i = 0; i < 100; ++i) { + SCOPED_TRACE(i); + // Another message does not carry the TSIG on it. But it should + // be OK, it's in the middle of stream. + message.clear(Message::RENDER); + message.setQid(1234); + message.setOpcode(Opcode::QUERY()); + message.setRcode(Rcode::NOERROR()); + RRsetPtr answer_rrset(new RRset(test_name, test_class, RRType::A(), + test_ttl)); + answer_rrset->addRdata(createRdata(RRType::A(), test_class, + "192.0.2.1")); + message.addRRset(Message::SECTION_ANSWER, answer_rrset); + message.toWire(renderer); + // Update the internal state. We abuse the knowledge of + // internals here a little bit to generate correct test data + tsig_ctx->update(renderer.getData(), renderer.getLength()); + + // 99 unsigned messages is OK. But the 100th must be signed, according + // to the RFC2845, section 4.4 + commonVerifyChecks(*tsig_verify_ctx, 0, + renderer.getData(), renderer.getLength(), + i == 99 ? TSIGError::FORMERR() : + TSIGError(Rcode::NOERROR()), + TSIGContext::VERIFIED_RESPONSE); + + EXPECT_FALSE(tsig_verify_ctx->lastHadSignature()); + } + } +} + +} // end namespace diff --git a/src/lib/dns/tests/tsigerror_unittest.cc b/src/lib/dns/tests/tsigerror_unittest.cc new file mode 100644 index 0000000..7355eb2 --- /dev/null +++ b/src/lib/dns/tests/tsigerror_unittest.cc @@ -0,0 +1,126 @@ +// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <string> +#include <ostream> + +#include <gtest/gtest.h> + +#include <exceptions/exceptions.h> + +#include <dns/rcode.h> +#include <dns/tsigerror.h> + +using namespace std; +using namespace isc; +using namespace isc::dns; + +namespace { +TEST(TSIGErrorTest, constructFromErrorCode) { + // These are pretty trivial, and also test getCode(); + EXPECT_EQ(0, TSIGError(0).getCode()); + EXPECT_EQ(18, TSIGError(18).getCode()); + EXPECT_EQ(65535, TSIGError(65535).getCode()); +} + +TEST(TSIGErrorTest, constructFromRcode) { + // We use RCODE for code values from 0-15. + EXPECT_EQ(0, TSIGError(Rcode::NOERROR()).getCode()); + EXPECT_EQ(15, TSIGError(Rcode(15)).getCode()); + + // From error code 16 TSIG errors define a separate space, so passing + // corresponding RCODE for such code values should be prohibited. + EXPECT_THROW(TSIGError(Rcode(16)).getCode(), OutOfRange); +} + +TEST(TSIGErrorTest, constants) { + // We'll only test arbitrarily chosen subsets of the codes. + // This class is quite simple, so it should be suffice. + + EXPECT_EQ(TSIGError::BAD_SIG_CODE, TSIGError(16).getCode()); + EXPECT_EQ(TSIGError::BAD_KEY_CODE, TSIGError(17).getCode()); + EXPECT_EQ(TSIGError::BAD_TIME_CODE, TSIGError(18).getCode()); + EXPECT_EQ(TSIGError::BAD_MODE_CODE, TSIGError(19).getCode()); + EXPECT_EQ(TSIGError::BAD_NAME_CODE, TSIGError(20).getCode()); + EXPECT_EQ(TSIGError::BAD_ALG_CODE, TSIGError(21).getCode()); + EXPECT_EQ(TSIGError::BAD_TRUNC_CODE, TSIGError(22).getCode()); + + EXPECT_EQ(0, TSIGError::NOERROR().getCode()); + EXPECT_EQ(9, TSIGError::NOTAUTH().getCode()); + EXPECT_EQ(14, TSIGError::RESERVED14().getCode()); + EXPECT_EQ(TSIGError::BAD_SIG_CODE, TSIGError::BAD_SIG().getCode()); + EXPECT_EQ(TSIGError::BAD_KEY_CODE, TSIGError::BAD_KEY().getCode()); + EXPECT_EQ(TSIGError::BAD_TIME_CODE, TSIGError::BAD_TIME().getCode()); + EXPECT_EQ(TSIGError::BAD_MODE_CODE, TSIGError::BAD_MODE().getCode()); + EXPECT_EQ(TSIGError::BAD_NAME_CODE, TSIGError::BAD_NAME().getCode()); + EXPECT_EQ(TSIGError::BAD_ALG_CODE, TSIGError::BAD_ALG().getCode()); + EXPECT_EQ(TSIGError::BAD_TRUNC_CODE, TSIGError::BAD_TRUNC().getCode()); +} + +TEST(TSIGErrorTest, equal) { + EXPECT_TRUE(TSIGError::NOERROR() == TSIGError(Rcode::NOERROR())); + EXPECT_TRUE(TSIGError(Rcode::NOERROR()) == TSIGError::NOERROR()); + EXPECT_TRUE(TSIGError::NOERROR().equals(TSIGError(Rcode::NOERROR()))); + EXPECT_TRUE(TSIGError::NOERROR().equals(TSIGError(Rcode::NOERROR()))); + + EXPECT_TRUE(TSIGError::BAD_SIG() == TSIGError(16)); + EXPECT_TRUE(TSIGError(16) == TSIGError::BAD_SIG()); + EXPECT_TRUE(TSIGError::BAD_SIG().equals(TSIGError(16))); + EXPECT_TRUE(TSIGError(16).equals(TSIGError::BAD_SIG())); +} + +TEST(TSIGErrorTest, nequal) { + EXPECT_TRUE(TSIGError::BAD_KEY() != TSIGError(Rcode::NOERROR())); + EXPECT_TRUE(TSIGError(Rcode::NOERROR()) != TSIGError::BAD_KEY()); + EXPECT_TRUE(TSIGError::BAD_KEY().nequals(TSIGError(Rcode::NOERROR()))); + EXPECT_TRUE(TSIGError(Rcode::NOERROR()).nequals(TSIGError::BAD_KEY())); +} + +TEST(TSIGErrorTest, toText) { + // TSIGError derived from the standard Rcode + EXPECT_EQ("NOERROR", TSIGError(Rcode::NOERROR()).toText()); + + // Well known TSIG errors + EXPECT_EQ("BADSIG", TSIGError::BAD_SIG().toText()); + EXPECT_EQ("BADKEY", TSIGError::BAD_KEY().toText()); + EXPECT_EQ("BADTIME", TSIGError::BAD_TIME().toText()); + EXPECT_EQ("BADMODE", TSIGError::BAD_MODE().toText()); + EXPECT_EQ("BADNAME", TSIGError::BAD_NAME().toText()); + EXPECT_EQ("BADALG", TSIGError::BAD_ALG().toText()); + EXPECT_EQ("BADTRUNC", TSIGError::BAD_TRUNC().toText()); + + // Unknown (or not yet supported) codes. Simply converted as numeric. + EXPECT_EQ("23", TSIGError(23).toText()); + EXPECT_EQ("65535", TSIGError(65535).toText()); +} + +TEST(TSIGErrorTest, toRcode) { + // TSIGError derived from the standard Rcode + EXPECT_EQ(Rcode::NOERROR(), TSIGError(Rcode::NOERROR()).toRcode()); + + // Well known TSIG errors + EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_SIG().toRcode()); + EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_KEY().toRcode()); + EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_TIME().toRcode()); + EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_MODE().toRcode()); + EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_NAME().toRcode()); + EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_ALG().toRcode()); + EXPECT_EQ(Rcode::NOTAUTH(), TSIGError::BAD_TRUNC().toRcode()); + + // Unknown (or not yet supported) codes are treated as SERVFAIL. + EXPECT_EQ(Rcode::SERVFAIL(), TSIGError(23).toRcode()); + EXPECT_EQ(Rcode::SERVFAIL(), TSIGError(65535).toRcode()); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST(TSIGErrorTest, LeftShiftOperator) { + ostringstream oss; + oss << TSIGError::BAD_KEY(); + EXPECT_EQ(TSIGError::BAD_KEY().toText(), oss.str()); +} +} // end namespace diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc new file mode 100644 index 0000000..80b4604 --- /dev/null +++ b/src/lib/dns/tests/tsigkey_unittest.cc @@ -0,0 +1,347 @@ +// Copyright (C) 2019-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <string> + +#include <gtest/gtest.h> + +#include <exceptions/exceptions.h> + +#include <cryptolink/cryptolink.h> + +#include <dns/tsigkey.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc::dns; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { +class TSIGKeyTest : public ::testing::Test { +protected: + TSIGKeyTest() : secret("someRandomData"), key_name("example.com") {} + string secret; + const Name key_name; +}; + +TEST_F(TSIGKeyTest, algorithmNames) { + EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), TSIGKey::HMACMD5_NAME()); + EXPECT_EQ(Name("hmac-md5"), TSIGKey::HMACMD5_SHORT_NAME()); + EXPECT_EQ(Name("hmac-sha1"), TSIGKey::HMACSHA1_NAME()); + EXPECT_EQ(Name("hmac-sha256"), TSIGKey::HMACSHA256_NAME()); + EXPECT_EQ(Name("hmac-sha224"), TSIGKey::HMACSHA224_NAME()); + EXPECT_EQ(Name("hmac-sha384"), TSIGKey::HMACSHA384_NAME()); + EXPECT_EQ(Name("hmac-sha512"), TSIGKey::HMACSHA512_NAME()); + EXPECT_EQ(Name("gss-tsig"), TSIGKey::GSSTSIG_NAME()); + + // Also check conversion to cryptolink definitions + EXPECT_EQ(isc::cryptolink::MD5, TSIGKey(key_name, TSIGKey::HMACMD5_NAME(), + 0, 0).getAlgorithm()); + EXPECT_EQ(isc::cryptolink::MD5, + TSIGKey(key_name, TSIGKey::HMACMD5_SHORT_NAME(), + 0, 0).getAlgorithm()); + EXPECT_EQ(isc::cryptolink::SHA1, TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(), + 0, 0).getAlgorithm()); + EXPECT_EQ(isc::cryptolink::SHA256, TSIGKey(key_name, + TSIGKey::HMACSHA256_NAME(), + 0, 0).getAlgorithm()); + EXPECT_EQ(isc::cryptolink::SHA224, TSIGKey(key_name, + TSIGKey::HMACSHA224_NAME(), + 0, 0).getAlgorithm()); + EXPECT_EQ(isc::cryptolink::SHA384, TSIGKey(key_name, + TSIGKey::HMACSHA384_NAME(), + 0, 0).getAlgorithm()); + EXPECT_EQ(isc::cryptolink::SHA512, TSIGKey(key_name, + TSIGKey::HMACSHA512_NAME(), + 0, 0).getAlgorithm()); +} + +TEST_F(TSIGKeyTest, construct) { + TSIGKey key(key_name, TSIGKey::HMACMD5_NAME(), + secret.c_str(), secret.size()); + EXPECT_EQ(key_name, key.getKeyName()); + EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), key.getAlgorithmName()); + matchWireData(secret.c_str(), secret.size(), + key.getSecret(), key.getSecretLength()); + + TSIGKey key_short_md5(key_name, TSIGKey::HMACMD5_SHORT_NAME(), + secret.c_str(), secret.size()); + EXPECT_EQ(key_name, key_short_md5.getKeyName()); + EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), + key_short_md5.getAlgorithmName()); + matchWireData(secret.c_str(), secret.size(), + key_short_md5.getSecret(), key_short_md5.getSecretLength()); + + // "unknown" algorithm is only accepted with empty secret. + EXPECT_THROW(TSIGKey(key_name, Name("unknown-alg"), + secret.c_str(), secret.size()), + isc::InvalidParameter); + TSIGKey key2(key_name, Name("unknown-alg"), 0, 0); + EXPECT_EQ(key_name, key2.getKeyName()); + EXPECT_EQ(Name("unknown-alg"), key2.getAlgorithmName()); + + // The algorithm name should be converted to the canonical form. + EXPECT_EQ("hmac-sha1.", + TSIGKey(key_name, Name("HMAC-sha1"), + secret.c_str(), + secret.size()).getAlgorithmName().toText()); + + // Same for key name + EXPECT_EQ("example.com.", + TSIGKey(Name("EXAMPLE.CoM."), TSIGKey::HMACSHA256_NAME(), + secret.c_str(), + secret.size()).getKeyName().toText()); + + // Check digestbits + EXPECT_EQ(key.getDigestbits(), 0); + TSIGKey key_trunc(key_name, TSIGKey::HMACMD5_NAME(), + secret.c_str(), secret.size(), 120); + EXPECT_EQ(key_trunc.getDigestbits(), 120); + + // Invalid combinations of secret and secret_len: + EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(), secret.c_str(), 0), + isc::InvalidParameter); + EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), 0, 16), + isc::InvalidParameter); + + // Empty secret + TSIGKey keye = TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), 0, 0); + EXPECT_EQ(keye.getSecretLength(), 0); + EXPECT_EQ(keye.getSecret(), (const void*)0); +} + +void +compareTSIGKeys(const TSIGKey& expect, const TSIGKey& actual) { + EXPECT_EQ(expect.getKeyName(), actual.getKeyName()); + EXPECT_EQ(expect.getAlgorithmName(), actual.getAlgorithmName()); + EXPECT_EQ(expect.getDigestbits(), actual.getDigestbits()); + matchWireData(expect.getSecret(), expect.getSecretLength(), + actual.getSecret(), actual.getSecretLength()); +} + +TEST_F(TSIGKeyTest, copyConstruct) { + const TSIGKey original(key_name, TSIGKey::HMACSHA256_NAME(), + secret.c_str(), secret.size(), 128); + const TSIGKey copy(original); + compareTSIGKeys(original, copy); + + // Check the copied data is valid even after the original is deleted + TSIGKey* copy2 = new TSIGKey(original); + TSIGKey copy3(*copy2); + delete copy2; + compareTSIGKeys(original, copy3); +} + +TEST_F(TSIGKeyTest, assignment) { + const TSIGKey original(key_name, TSIGKey::HMACSHA256_NAME(), + secret.c_str(), secret.size(), 200); + TSIGKey copy = original; + compareTSIGKeys(original, copy); + + // Check if the copied data is valid even after the original is deleted + TSIGKey* copy2 = new TSIGKey(original); + TSIGKey copy3(original); + copy3 = *copy2; + delete copy2; + compareTSIGKeys(original, copy3); + + // Self assignment + copy = *© + compareTSIGKeys(original, copy); +} + +class TSIGKeyRingTest : public ::testing::Test { +protected: + TSIGKeyRingTest() : + key_name("example.com"), + md5_name("hmac-md5.sig-alg.reg.int"), + sha1_name("hmac-sha1"), + sha256_name("hmac-sha256"), + secretstring("anotherRandomData"), + secret(secretstring.c_str()), + secret_len(secretstring.size()) + {} + TSIGKeyRing keyring; + const Name key_name; + const Name md5_name; + const Name sha1_name; + const Name sha256_name; +private: + const string secretstring; +protected: + const char* secret; + size_t secret_len; +}; + +TEST_F(TSIGKeyRingTest, init) { + EXPECT_EQ(0, keyring.size()); +} + +TEST_F(TSIGKeyRingTest, add) { + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add( + TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), + secret, secret_len))); + EXPECT_EQ(1, keyring.size()); + EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add( + TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), + secret, secret_len))); + // keys are identified by their names, the same name of key with a + // different algorithm would be considered a duplicate. + EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add( + TSIGKey(Name("example.com"), TSIGKey::HMACSHA1_NAME(), + secret, secret_len))); + // names are compared in a case insensitive manner. + EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add( + TSIGKey(Name("EXAMPLE.COM"), TSIGKey::HMACSHA1_NAME(), + secret, secret_len))); + EXPECT_EQ(1, keyring.size()); +} + +TEST_F(TSIGKeyRingTest, addMore) { + // essentially the same test, but try adding more than 1 + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add( + TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), + secret, secret_len))); + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add( + TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(), + secret, secret_len))); + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add( + TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(), + secret, secret_len))); + EXPECT_EQ(3, keyring.size()); +} + +TEST_F(TSIGKeyRingTest, remove) { + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add( + TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), + secret, secret_len))); + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.remove(key_name)); + EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.remove(key_name)); +} + +TEST_F(TSIGKeyRingTest, removeFromSome) { + // essentially the same test, but try removing from a larger set + + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add( + TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), + secret, secret_len))); + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add( + TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(), + secret, secret_len))); + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add( + TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(), + secret, secret_len))); + + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.remove(Name("another.example"))); + EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.remove(Name("noexist.example"))); + EXPECT_EQ(2, keyring.size()); +} + +TEST_F(TSIGKeyRingTest, find) { + // If the keyring is empty the search should fail. + EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.find(key_name, md5_name).code); + EXPECT_FALSE(keyring.find(key_name, md5_name).key); + + // Add a key and try to find it. Should succeed. + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(key_name, sha256_name, + secret, secret_len))); + const TSIGKeyRing::FindResult result1(keyring.find(key_name, sha256_name)); + EXPECT_EQ(TSIGKeyRing::SUCCESS, result1.code); + EXPECT_EQ(key_name, result1.key->getKeyName()); + EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result1.key->getAlgorithmName()); + matchWireData(secret, secret_len, + result1.key->getSecret(), result1.key->getSecretLength()); + + // If either key name or algorithm doesn't match, search should fail. + const TSIGKeyRing::FindResult result2 = + keyring.find(Name("different-key.example"), sha256_name); + EXPECT_EQ(TSIGKeyRing::NOTFOUND, result2.code); + EXPECT_FALSE(result2.key); + + const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name); + EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code); + EXPECT_FALSE(result3.key); + + // But with just the name it should work + const TSIGKeyRing::FindResult result4(keyring.find(key_name)); + EXPECT_EQ(TSIGKeyRing::SUCCESS, result4.code); + EXPECT_EQ(key_name, result4.key->getKeyName()); + EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result4.key->getAlgorithmName()); + matchWireData(secret, secret_len, + result4.key->getSecret(), result4.key->getSecretLength()); +} + +TEST_F(TSIGKeyRingTest, findFromSome) { + // essentially the same test, but search a larger set + + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(key_name, sha256_name, + secret, secret_len))); + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(Name("another.example"), + md5_name, + secret, secret_len))); + EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(TSIGKey(Name("more.example"), + sha1_name, + secret, secret_len))); + + const TSIGKeyRing::FindResult result( + keyring.find(Name("another.example"), md5_name)); + EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code); + EXPECT_EQ(Name("another.example"), result.key->getKeyName()); + EXPECT_EQ(TSIGKey::HMACMD5_NAME(), result.key->getAlgorithmName()); + + EXPECT_EQ(TSIGKeyRing::NOTFOUND, + keyring.find(Name("noexist.example"), sha1_name).code); + EXPECT_FALSE(keyring.find(Name("noexist.example"), sha256_name).key); + + EXPECT_EQ(TSIGKeyRing::NOTFOUND, + keyring.find(Name("another.example"), sha1_name).code); + EXPECT_FALSE(keyring.find(Name("another.example"), sha256_name).key); +} + +TEST(TSIGStringTest, TSIGKeyFromToString) { + TSIGKey k1 = TSIGKey("test.example:MSG6Ng==:hmac-md5.sig-alg.reg.int"); + TSIGKey k2 = TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int."); + TSIGKey k3 = TSIGKey("test.example:MSG6Ng=="); + TSIGKey k4 = TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.:120"); + TSIGKey k5 = TSIGKey(Name("test.example."), Name("hmac-sha1."), 0, 0); + // "Unknown" key with empty secret is okay + TSIGKey k6 = TSIGKey("test.example.::unknown"); + + EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.", + k1.toText()); + EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.", + k2.toText()); + EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.", + k3.toText()); + EXPECT_EQ("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.:120", + k4.toText()); + EXPECT_EQ(120, k4.getDigestbits()); + EXPECT_EQ("test.example.::hmac-sha1.", k5.toText()); + EXPECT_EQ(Name("test.example."), k6.getKeyName()); + EXPECT_EQ(Name("unknown"), k6.getAlgorithmName()); + + EXPECT_THROW(TSIGKey(""), isc::InvalidParameter); + EXPECT_THROW(TSIGKey(":"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("::"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("..:aa:"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example:xxxx:"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.::"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.:"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:unknown"), isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.:"), + isc::InvalidParameter); + EXPECT_THROW(TSIGKey("test.example.:MSG6Ng==:hmac-md5.sig-alg.reg.int.:xxx"), + isc::InvalidParameter); +} + + +} // end namespace diff --git a/src/lib/dns/tests/tsigrecord_unittest.cc b/src/lib/dns/tests/tsigrecord_unittest.cc new file mode 100644 index 0000000..e491036 --- /dev/null +++ b/src/lib/dns/tests/tsigrecord_unittest.cc @@ -0,0 +1,158 @@ +// Copyright (C) 2011-2024 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 <vector> +#include <sstream> + +#include <gtest/gtest.h> + +#include <util/buffer.h> + +#include <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataclass.h> +#include <dns/tsig.h> +#include <dns/tsigkey.h> +#include <dns/tsigrecord.h> + +#include <dns/tests/unittest_util.h> +#include <util/unittests/wiredata.h> + +using namespace std; +using namespace isc::util; +using namespace isc::dns; +using namespace isc::dns::rdata; +using namespace isc::dns::rdata::any; +using isc::UnitTestUtil; +using isc::util::unittests::matchWireData; + +namespace { +class TSIGRecordTest : public ::testing::Test { +protected: + TSIGRecordTest() : + test_name("www.example.com"), test_mac(16, 0xda), + test_rdata(TSIG(TSIGKey::HMACMD5_NAME(), 0x4da8877a, TSIGContext::DEFAULT_FUDGE, + test_mac.size(), &test_mac[0], 0x2d65, 0, 0, 0)), + test_record(test_name, test_rdata), + buffer(0) { + } + + const Name test_name; + + vector<unsigned char> test_mac; + + const TSIG test_rdata; + + const TSIGRecord test_record; + + OutputBuffer buffer; + + MessageRenderer renderer; + + vector<unsigned char> data; +}; + +TEST_F(TSIGRecordTest, getName) { + EXPECT_EQ(test_name, test_record.getName()); +} + +TEST_F(TSIGRecordTest, getLength) { + // 85 = 17 + 26 + 16 + 26 + // len(www.example.com) = 17 + // len(hmac-md5.sig-alg.reg.int) = 26 + // len(MAC) = 16 + // the rest are fixed length fields (26 in total) + EXPECT_EQ(85, test_record.getLength()); +} + +TEST_F(TSIGRecordTest, fromParams) { + // Construct the same TSIG RR as test_record from parameters. + // See the getLength test for the magic number of 85 (although it + // actually doesn't matter) + const TSIGRecord record(test_name, TSIGRecord::getClass(), + TSIGRecord::getTTL(), test_rdata, 85); + // Perform straight sanity checks + EXPECT_EQ(test_name, record.getName()); + EXPECT_EQ(85, record.getLength()); + EXPECT_EQ(0, test_rdata.compare(record.getRdata())); + + // The constructor doesn't check the length... + EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(), + TSIGRecord::getTTL(), test_rdata, 82)); + // ...even for impossibly small values... + EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(), + TSIGRecord::getTTL(), test_rdata, 1)); + // ...or too large values. + EXPECT_NO_THROW(TSIGRecord(test_name, TSIGRecord::getClass(), + TSIGRecord::getTTL(), test_rdata, 65536)); + + // RDATA must indeed be TSIG + EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(), + TSIGRecord::getTTL(), in::A("192.0.2.1"), 85), + DNSMessageFORMERR); + + // Unexpected class + EXPECT_THROW(TSIGRecord(test_name, RRClass::IN(), TSIGRecord::getTTL(), + test_rdata, 85), DNSMessageFORMERR); + + // Unexpected TTL + EXPECT_THROW(TSIGRecord(test_name, TSIGRecord::getClass(), + RRTTL(3600), test_rdata, 85), DNSMessageFORMERR); +} + +TEST_F(TSIGRecordTest, recordToWire) { + UnitTestUtil::readWireData("tsigrecord_toWire1.wire", data); + EXPECT_EQ(1, test_record.toWire(renderer)); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); + + // Same test for a dumb buffer + buffer.clear(); + EXPECT_EQ(1, test_record.toWire(buffer)); + matchWireData(&data[0], data.size(), + buffer.getData(), buffer.getLength()); +} + +TEST_F(TSIGRecordTest, recordToOLongToWire) { + // By setting the limit to "record length - 1", it will fail, and the + // renderer will be marked as "truncated". + renderer.setLengthLimit(test_record.getLength() - 1); + EXPECT_FALSE(renderer.isTruncated()); // not marked before render attempt + EXPECT_EQ(0, test_record.toWire(renderer)); + EXPECT_TRUE(renderer.isTruncated()); +} + +TEST_F(TSIGRecordTest, recordToWireAfterNames) { + // A similar test but the TSIG RR follows some domain names that could + // cause name compression inside TSIG. Our implementation shouldn't + // compress either owner (key) name or the algorithm name. This test + // confirms that. + + UnitTestUtil::readWireData("tsigrecord_toWire2.wire", data); + renderer.writeName(TSIGKey::HMACMD5_NAME()); + renderer.writeName(Name("foo.example.com")); + EXPECT_EQ(1, test_record.toWire(renderer)); + matchWireData(&data[0], data.size(), + renderer.getData(), renderer.getLength()); +} + +TEST_F(TSIGRecordTest, toText) { + EXPECT_EQ("www.example.com. 0 ANY TSIG hmac-md5.sig-alg.reg.int. " + "1302890362 300 16 2tra2tra2tra2tra2tra2g== 11621 NOERROR 0\n", + test_record.toText()); +} + +// test operator<<. We simply confirm it appends the result of toText(). +TEST_F(TSIGRecordTest, LeftShiftOperator) { + ostringstream oss; + oss << test_record; + EXPECT_EQ(test_record.toText(), oss.str()); +} +} // end namespace diff --git a/src/lib/dns/tests/unittest_util.cc b/src/lib/dns/tests/unittest_util.cc new file mode 100644 index 0000000..caedeac --- /dev/null +++ b/src/lib/dns/tests/unittest_util.cc @@ -0,0 +1,140 @@ +// Copyright (C) 2009-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <iostream> +#include <fstream> +#include <sstream> +#include <stdexcept> +#include <vector> +#include <string> + +#include <gtest/gtest.h> + +#include <dns/rcode.h> +#include <dns/name.h> +#include <dns/message.h> +#include <dns/tests/unittest_util.h> + +using namespace std; +using namespace isc::dns; + +using isc::UnitTestUtil; + +namespace { +class UnitTestUtilConfig { +private: + // This is a singleton object and cannot be constructed explicitly. + UnitTestUtilConfig() {} + UnitTestUtilConfig(const UnitTestUtilConfig& source); + ~UnitTestUtilConfig() {} +public: + /// Return a singleton unit test configuration object. On first invocation + /// one will be constructed. + static UnitTestUtilConfig& getConfig(); + + /// A list of paths to wire data files. + /// \c UnitTestUtil::readWireData() (first version) + /// will search the directories in this list for the specified data file. + std::vector<string> data_paths_; +}; + +UnitTestUtilConfig& +UnitTestUtilConfig::getConfig() { + static UnitTestUtilConfig config; + return (config); +} +} + +void +UnitTestUtil::readWireData(const char* datafile, vector<unsigned char>& data) { + ifstream ifs; + + const UnitTestUtilConfig& config = UnitTestUtilConfig::getConfig(); + vector<string>::const_iterator it = config.data_paths_.begin(); + for (; it != config.data_paths_.end(); ++it) { + string data_path = *it; + if (data_path.empty() || *data_path.rbegin() != '/') { + data_path.push_back('/'); + } + ifs.open((data_path + datafile).c_str(), ios_base::in); + if ((ifs.rdstate() & istream::failbit) == 0) { + break; + } + } + + if (it == config.data_paths_.end()) { + throw runtime_error("failed to open data file in data paths: " + + string(datafile)); + } + + data.clear(); + + string s; + while (getline(ifs, s), !ifs.eof()) { + if (ifs.bad() || ifs.fail()) { + throw runtime_error("unexpected data line"); + } + if (s.empty() || s[0] == '#') { + continue; + } + + readWireData(s, data); + } +} + +void +UnitTestUtil::addDataPath(const string& directory) { + UnitTestUtilConfig::getConfig().data_paths_.push_back(directory); +} + +void +UnitTestUtil::readWireData(const string& datastr, + vector<unsigned char>& data) { + istringstream iss(datastr); + + do { + string bytes; + iss >> bytes; + if (iss.bad() || iss.fail() || (bytes.size() % 2) != 0) { + ostringstream err_oss; + err_oss << "unexpected input or I/O error in reading " << + datastr; + throw runtime_error(err_oss.str()); + } + + for (string::size_type pos = 0; pos < bytes.size(); pos += 2) { + istringstream iss_byte(bytes.substr(pos, 2)); + unsigned int ch; + + iss_byte >> hex >> ch; + if (iss_byte.rdstate() != istream::eofbit) { + ostringstream err_oss; + err_oss << "invalid byte representation: " << iss_byte.str(); + throw runtime_error(err_oss.str()); + } + data.push_back(static_cast<unsigned char>(ch)); + } + } while (!iss.eof()); +} + +::testing::AssertionResult +UnitTestUtil::matchName(const char*, const char*, + const isc::dns::Name& name1, + const isc::dns::Name& name2) { + ::testing::Message msg; + + NameComparisonResult cmpresult = name1.compare(name2); + if (cmpresult.getOrder() != 0 || + cmpresult.getRelation() != NameComparisonResult::EQUAL) { + msg << "Two names are expected to be equal but not:\n" + << " One: " << name1 << "\n" + << "Other: " << name2 << "\n"; + return (::testing::AssertionFailure(msg)); + } + return (::testing::AssertionSuccess()); +} diff --git a/src/lib/dns/tests/unittest_util.h b/src/lib/dns/tests/unittest_util.h new file mode 100644 index 0000000..34045a4 --- /dev/null +++ b/src/lib/dns/tests/unittest_util.h @@ -0,0 +1,55 @@ +// Copyright (C) 2009-2024 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 UNITTEST_UTIL_H +#define UNITTEST_UTIL_H + +#include <vector> +#include <string> + +#include <dns/name.h> +#include <dns/message.h> + +#include <gtest/gtest.h> + +namespace isc { + +class UnitTestUtil { +public: + /// + /// read text format wire data from a file and put it to the given vector. + /// + static void readWireData(const char* datafile, + std::vector<unsigned char>& data); + + /// + /// add a path that \c readWireData() will search for test data files. + /// + static void addDataPath(const std::string& directory); + + /// + /// convert a sequence of hex strings into the corresponding list of + /// 8-bit integers, and append them to the vector. + /// + static void readWireData(const std::string& datastr, + std::vector<unsigned char>& data); + + /// + /// Compare two names. + /// + /// This check method uses \c Name::compare() for comparison, which performs + /// deeper checks including the equality of offsets, and should be better + /// than EXPECT_EQ, which uses operator==. Like the \c matchWireData() + /// method, the usage is a bit awkward; the caller should use + /// \c EXPECT_PRED_FORMAT2. + /// + static ::testing::AssertionResult + matchName(const char* nameexp1, const char* nameexp2, + const isc::dns::Name& name1, const isc::dns::Name& name2); +}; + +} +#endif // UNITTEST_UTIL_H diff --git a/src/lib/dns/time_utils.cc b/src/lib/dns/time_utils.cc new file mode 100644 index 0000000..2a34afe --- /dev/null +++ b/src/lib/dns/time_utils.cc @@ -0,0 +1,195 @@ +// Copyright (C) 2024 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 <dns/time_utils.h> +#include <exceptions/exceptions.h> + +#include <cstdint> +#include <cstdio> +#include <ctime> +#include <iomanip> +#include <iostream> +#include <sstream> +#include <string> +#include <sys/time.h> + +using namespace std; + +namespace { +int days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +bool +isLeap(const int y) { + return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0); +} + +unsigned int +yearSecs(const int year) { + return ((isLeap(year) ? 366 : 365) * 86400); +} + +unsigned int +monthSecs(const int month, const int year) { + return ((days[month] + ((month == 1 && isLeap(year)) ? 1 : 0)) * 86400); +} +} // anonymous namespace + +namespace isc { +namespace util { +namespace { +constexpr size_t DATE_LEN = 14; // YYYYMMDDHHmmSS + +uint64_t +ull(const int c) { + return (static_cast<uint64_t>(c)); +} + +void +checkRange(const unsigned min, const unsigned max, const unsigned value, const string& valname) { + if ((value >= min) && (value <= max)) { + return; + } + + isc_throw(InvalidTime, "Invalid " << valname << " value: " << value); +} +} // anonymous namespace + +namespace detail { +// timeToText32() below uses the current system time. To test it with +// unusual current time values we introduce the following function pointer; +// when it's non NULL, we call it to get the (normally faked) current time. +// Otherwise we use the standard gettimeofday(2). This hook is specifically +// intended for testing purposes, so, even if it's visible outside of this +// library, it's not even declared in a header file. +int64_t (*getTimeFunction)() = 0; + +int64_t +getTimeWrapper() { + if (getTimeFunction != 0) { + return (getTimeFunction()); + } + + struct timeval now{}; + gettimeofday(&now, 0); + + return (static_cast<int64_t>(now.tv_sec)); +} +} // namespace detail + +string +timeToText64(uint64_t value) { + struct tm tm{}; + unsigned int secs; + + // We cannot rely on gmtime() because time_t may not be of 64 bit + // integer. The following conversion logic is borrowed from BIND 9. + tm.tm_year = 70; + while ((secs = yearSecs(tm.tm_year + 1900)) <= value) { + value -= secs; + ++tm.tm_year; + if (tm.tm_year + 1900 > 9999) { + isc_throw(InvalidTime, "Time value out of range (year > 9999): " << tm.tm_year + 1900); + } + } + + tm.tm_mon = 0; + while ((secs = monthSecs(tm.tm_mon, tm.tm_year + 1900)) <= value) { + value -= secs; + tm.tm_mon++; + } + + tm.tm_mday = 1; + while (86400 <= value) { + value -= 86400; + ++tm.tm_mday; + } + + tm.tm_hour = 0; + while (3600 <= value) { + value -= 3600; + ++tm.tm_hour; + } + + tm.tm_min = 0; + while (60 <= value) { + value -= 60; + ++tm.tm_min; + } + + tm.tm_sec = value; // now t < 60, so this substitution is safe. + + ostringstream oss; + oss << setfill('0') << setw(4) << tm.tm_year + 1900 << setw(2) << tm.tm_mon + 1 << setw(2) + << tm.tm_mday << setw(2) << tm.tm_hour << setw(2) << tm.tm_min << setw(2) << tm.tm_sec; + return (oss.str()); +} + +string +timeToText32(const uint32_t value) { + // We first adjust the time to the closest epoch based on the current time. + // Note that the following variables must be signed in order to handle + // time until year 2038 correctly. + const int64_t start = detail::getTimeWrapper() - 0x7fffffff; + int64_t base = 0; + int64_t t; + while ((t = (base + value)) < start) { + base += 0x100000000LL; + } + + // Then convert it to text. + return (timeToText64(t)); +} + +uint64_t +timeFromText64(const string& time_txt) { + // Confirm the source only consists digits. sscanf() allows some + // minor exceptions. + for (string::size_type i = 0; i < time_txt.length(); ++i) { + if (!isdigit(time_txt.at(i))) { + isc_throw(InvalidTime, "Couldn't convert non-numeric time value: " << time_txt); + } + } + + unsigned year, month, day, hour, minute, second; + if (time_txt.length() != DATE_LEN || sscanf(time_txt.c_str(), "%4u%2u%2u%2u%2u%2u", &year, + &month, &day, &hour, &minute, &second) != 6) { + isc_throw(InvalidTime, "Couldn't convert time value: " << time_txt); + } + + checkRange(1970, 9999, year, "year"); + checkRange(1, 12, month, "month"); + checkRange(1, days[month - 1] + ((month == 2 && isLeap(year)) ? 1 : 0), day, "day"); + checkRange(0, 23, hour, "hour"); + checkRange(0, 59, minute, "minute"); + checkRange(0, 60, second, "second"); // 60 == leap second. + + uint64_t timeval = second + (ull(60) * minute) + (ull(3600) * hour) + ((day - 1) * ull(86400)); + for (unsigned m = 0; m < (month - 1); ++m) { + timeval += days[m] * ull(86400); + } + + if (isLeap(year) && month > 2) { + timeval += ull(86400); + } + + for (unsigned y = 1970; y < year; ++y) { + timeval += ((isLeap(y) ? 366 : 365) * ull(86400)); + } + + return (timeval); +} + +uint32_t +timeFromText32(const string& time_txt) { + // The implicit conversion from uint64_t to uint32_t should just work here, + // because we only need to drop higher 32 bits. + return (timeFromText64(time_txt)); +} + +} // namespace util +} // namespace isc diff --git a/src/lib/dns/time_utils.h b/src/lib/dns/time_utils.h new file mode 100644 index 0000000..7459446 --- /dev/null +++ b/src/lib/dns/time_utils.h @@ -0,0 +1,168 @@ +// Copyright (C) 2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef TIME_UTILS_H +#define TIME_UTILS_H 1 + +#include <exceptions/exceptions.h> + +#include <cstdint> +#include <string> +#include <sys/types.h> + +namespace isc { +namespace util { + +/// @brief A standard DNS (or ISC) module exception that is thrown if +/// a time conversion function encounters bad input. +class InvalidTime : public Exception { +public: + InvalidTime(const char* file, size_t line, const char* what) + : isc::Exception(file, line, what) { + } +}; + +namespace detail { + +/// @brief Return the current time in seconds. +/// +/// This function returns the "current" time in seconds from epoch +/// (00:00:00 January 1, 1970) as a 64-bit signed integer. The return +/// value can represent a point of time before epoch as a negative number. +/// +/// This function is provided to help test time conscious implementations +/// such as DNSSEC and TSIG signatures. It is difficult to test them with +/// an unusual or a specifically chosen "current" via system-provided +/// library functions to get time. This function acts as a straightforward +/// wrapper of such a library function, but provides test code with a hook +/// to return an arbitrary time value: if @c isc::util::detail::getTimeFunction +/// is set to a pointer of function that returns 64-bit signed integer, +/// @c getTimeWrapper() calls that function instead of the system library. +/// +/// This hook variable is specifically intended for testing purposes, so, +/// even if it's visible outside of this library, it's not even declared in a +/// header file. +/// +/// If the implementation doesn't need to be tested with faked current time, +/// it should simply use the system supplied library function instead of +/// this one. +/// +/// @return current time in seconds +int64_t +getTimeWrapper(); + +} // namespace detail + +/// @name DNSSEC time conversion functions. +/// +/// These functions convert between times represented in seconds (in integer) +/// since epoch and those in the textual form used in the RRSIG records. +/// For integers we provide both 32-bit and 64-bit versions. +/// The RRSIG expiration and inception fields are both 32-bit unsigned +/// integers, so 32-bit versions would be more useful for protocol operations. +/// However, with 32-bit integers we need to take into account wrap-around +/// points and compare values using the serial number arithmetic as specified +/// in RFC4034, which would be more error prone. We therefore provide 64-bit +/// versions, too. +/// +/// The timezone is always UTC for these functions. + +/// @{ +/// @brief Convert textual DNSSEC time to integer, 64-bit version. +/// +/// The textual form must only consist of digits and be in the form of +/// YYYYMMDDHHmmSS, where: +/// - YYYY must be between 1970 and 9999 +/// - MM must be between 01 and 12 +/// - DD must be between 01 and 31 and must be a valid day for the month +/// represented in 'MM'. For example, if MM is 04, DD cannot be 31. +/// DD can be 29 when MM is 02 only when YYYY is a leap year. +/// - HH must be between 00 and 23 +/// - mm must be between 00 and 59 +/// - SS must be between 00 and 60 +/// +/// For all fields the range includes the begin and end values. Note that +/// 60 is allowed for 'SS', intending a leap second, although in real operation +/// it's unlikely to be specified. +/// +/// If the given text is valid, this function converts it to an unsigned +/// 64-bit number of seconds since epoch (1 January 1970 00:00:00) and returns +/// the converted value. 64 bits are sufficient to represent all possible +/// values for the valid format uniquely, so there is no overflow. +/// +/// @note RFC4034 also defines the textual form of an unsigned decimal integer +/// for the corresponding time in seconds. This function doesn't support +/// this form, and if given it throws an exception of class @c InvalidTime. +/// +/// @param time_txt Textual time in the form of YYYYMMDDHHmmSS +/// +/// @return Seconds since epoch corresponding to @c time_txt +/// +/// @throw InvalidTime The given textual representation is invalid. +uint64_t +timeFromText64(const std::string& time_txt); + +/// @brief Convert textual DNSSEC time to integer, 32-bit version. +/// +/// This version is the same as @c timeFromText64() except that the return +/// value is wrapped around to an unsigned 32-bit integer, simply dropping +/// the upper 32 bits. +/// +/// @param time_txt Textual time in the form of YYYYMMDDHHmmSS +/// +/// @return Seconds since epoch corresponding to @c time_txt as uint_32 +uint32_t +timeFromText32(const std::string& time_txt); + +/// @brief Convert integral DNSSEC time to textual form, 64-bit version. +/// +/// This function takes an integer that would be seconds since epoch and +/// converts it in the form of YYYYMMDDHHmmSS. For example, if @c value is +/// 0, it returns "19700101000000". If the value corresponds to a point +/// of time on and after year 10,000, which cannot be represented in the +/// YYYY... form, an exception of class @c InvalidTime will be thrown. +/// +/// @param value Seconds since epoch to be converted. +/// +/// @return Textual representation of @c value in the form of YYYYMMDDHHmmSS. +/// +/// @throw InvalidTime The given time specifies on or after year 10,000. +/// @throw Other A standard exception, if resource allocation for the +/// returned text fails. +std::string +timeToText64(uint64_t value); + +/// @brief Convert integral DNSSEC time to textual form, 32-bit version. +/// +/// This version is the same as @c timeToText64(), but the time value +/// is expected to be the lower 32 bits of the full 64-bit value. +/// These two will be different on and after a certain point of time +/// in year 2106, so this function internally resolves the ambiguity +/// using the current system time at the time of function call; +/// it first identifies the range of [N*2^32 - 2^31, N*2^32 + 2^31) +/// that contains the current time, and interprets @c value in the context +/// of that range. It then applies the same process as @c timeToText64(). +/// +/// There is one important exception in this processing, however. +/// Until 19 Jan 2038 03:14:08 (2^31 seconds since epoch), this range +/// would contain time before epoch. In order to ensure the returned +/// value is also a valid input to @c timeFromText, this function uses +/// a special range [0, 2^32) until that time. As a result, all upper +/// half of the 32-bit values are treated as a future time. For example, +/// 2^32-1 (the highest value in 32-bit unsigned integers) will be converted +/// to "21060207062815", instead of "19691231235959". +/// +/// @param value Seconds since epoch to be converted. +/// +/// @return Textual representation of @c value in the form of YYYYMMDDHHmmSS. +std::string +timeToText32(uint32_t value); +///@} + +} // namespace util +} // namespace isc + +#endif // TIME_UTILS_H diff --git a/src/lib/dns/tsig.cc b/src/lib/dns/tsig.cc new file mode 100644 index 0000000..c096ae5 --- /dev/null +++ b/src/lib/dns/tsig.cc @@ -0,0 +1,577 @@ +// Copyright (C) 2011-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <exceptions/isc_assert.h> +#include <cryptolink/cryptolink.h> +#include <cryptolink/crypto_hmac.h> +#include <dns/rdataclass.h> +#include <dns/rrclass.h> +#include <dns/time_utils.h> +#include <dns/tsig.h> +#include <dns/tsigerror.h> +#include <dns/tsigkey.h> +#include <util/buffer.h> + +#include <cassert> +#include <sys/time.h> +#include <stdint.h> +#include <vector> +#include <boost/shared_ptr.hpp> + +using namespace isc::util; +using namespace isc::cryptolink; +using namespace isc::dns::rdata; + +using namespace std; + + +namespace isc { +namespace dns { +namespace { +typedef boost::shared_ptr<HMAC> HMACPtr; + +// TSIG uses 48-bit unsigned integer to represent time signed. +// Since getTimeWrapper() returns a 64-bit *signed* integer, we +// make sure it's stored in an unsigned 64-bit integer variable and +// represents a value in the expected range. (In reality, however, +// getTimeWrapper() will return a positive integer that will fit +// in 48 bits) +uint64_t +getTSIGTime() { + return (detail::getTimeWrapper() & 0x0000ffffffffffffULL); +} +} + +struct TSIGContext::TSIGContextImpl { + TSIGContextImpl(const TSIGKey& key, + TSIGError error = TSIGError::NOERROR()) : + state_(INIT), key_(key), error_(error), + previous_timesigned_(0), digest_len_(0), + last_sig_dist_(-1) { + if (error == TSIGError::NOERROR()) { + // In normal (NOERROR) case, the key should be valid, and we + // should be able to pre-create a corresponding HMAC object, + // which will be likely to be used for sign or verify later. + // We do this in the constructor so that we can know the expected + // digest length in advance. The creation should normally succeed, + // but the key information could be still broken, which could + // trigger an exception inside the cryptolink module. We ignore + // it at this moment; a subsequent sign/verify operation will try + // to create the HMAC, which would also fail. + try { + hmac_.reset(CryptoLink::getCryptoLink().createHMAC( + key_.getSecret(), key_.getSecretLength(), + key_.getAlgorithm()), + deleteHMAC); + } catch (const isc::Exception&) { + return; + } + size_t digestbits = key_.getDigestbits(); + size_t default_digest_len = hmac_->getOutputLength(); + if (digestbits > 0) { + digest_len_ = (digestbits + 7) / 8; + // sanity (cf. RFC 4635) + if ((digest_len_ < 10) || + (digest_len_ < (default_digest_len / 2)) || + (digest_len_ > default_digest_len)) { + // should emit a warning? + digest_len_ = default_digest_len; + } + } else { + digest_len_ = default_digest_len; + } + } + } + + // This helper method is used from verify(). It's expected to be called + // just before verify() returns. It updates internal state based on + // the verification result and return the TSIGError to be returned to + // the caller of verify(), so that verify() can call this method within + // its 'return' statement. + TSIGError postVerifyUpdate(TSIGError error, const void* digest, + uint16_t digest_len) { + if (state_ == INIT) { + state_ = RECEIVED_REQUEST; + } else if (state_ == SENT_REQUEST && error == TSIGError::NOERROR()) { + state_ = VERIFIED_RESPONSE; + } + if (digest) { + previous_digest_.assign(static_cast<const uint8_t*>(digest), + static_cast<const uint8_t*>(digest) + + digest_len); + } + error_ = error; + return (error); + } + + // A shortcut method to create an HMAC object for sign/verify. If one + // has been successfully created in the constructor, return it; otherwise + // create a new one and return it. In the former case, the ownership is + // transferred to the caller; the stored HMAC will be reset after the + // call. + HMACPtr createHMAC() { + if (hmac_) { + HMACPtr ret = HMACPtr(); + ret.swap(hmac_); + return (ret); + } + return (HMACPtr(CryptoLink::getCryptoLink().createHMAC( + key_.getSecret(), key_.getSecretLength(), + key_.getAlgorithm()), + deleteHMAC)); + } + + // The following three are helper methods to compute the digest for + // TSIG sign/verify in order to unify the common code logic for sign() + // and verify() and to keep these callers concise. + // These methods take an HMAC object, which will be updated with the + // calculated digest. + // Note: All methods construct a local OutputBuffer as a work space with a + // fixed initial buffer size to avoid intermediate buffer extension. + // This should be efficient enough, especially for fundamentally expensive + // operation like cryptographic sign/verify, but if the creation of the + // buffer in each helper method is still identified to be a severe + // performance bottleneck, we could have this class a buffer as a member + // variable and reuse it throughout the object's lifetime. Right now, + // we prefer keeping the scope for local things as small as possible. + void digestPreviousMAC(HMACPtr hmac); + void digestTSIGVariables(HMACPtr hmac, uint16_t rrclass, uint32_t rrttl, + uint64_t time_signed, uint16_t fudge, + uint16_t error, uint16_t otherlen, + const void* otherdata, + bool time_variables_only) const; + void digestDNSMessage(HMACPtr hmac, uint16_t qid, const void* data, + size_t data_len) const; + State state_; + const TSIGKey key_; + vector<uint8_t> previous_digest_; + TSIGError error_; + uint64_t previous_timesigned_; // only meaningful for response with BADTIME + size_t digest_len_; + HMACPtr hmac_; + // This is the distance from the last verified signed message. Value of 0 + // means the last message was signed. Special value -1 means there was no + // signed message yet. + int last_sig_dist_; +}; + +void +TSIGContext::TSIGContextImpl::digestPreviousMAC(HMACPtr hmac) { + // We should have ensured the digest size fits 16 bits within this class + // implementation. + isc_throw_assert(previous_digest_.size() <= 0xffff); + + if (previous_digest_.empty()) { + // The previous digest was already used. We're in the middle of + // TCP stream somewhere and we already pushed some unsigned message + // into the HMAC state. + return; + } + + OutputBuffer buffer(sizeof(uint16_t) + previous_digest_.size()); + const uint16_t previous_digest_len(previous_digest_.size()); + buffer.writeUint16(previous_digest_len); + if (previous_digest_len != 0) { + buffer.writeData(&previous_digest_[0], previous_digest_len); + } + hmac->update(buffer.getData(), buffer.getLength()); +} + +void +TSIGContext::TSIGContextImpl::digestTSIGVariables(HMACPtr hmac, uint16_t rrclass, + uint32_t rrttl, uint64_t time_signed, + uint16_t fudge, uint16_t error, + uint16_t otherlen, const void* otherdata, + bool time_variables_only) const { + // It's bit complicated, but we can still predict the necessary size of + // the data to be digested. So we precompute it to avoid possible + // reallocation inside OutputBuffer (not absolutely necessary, but this + // is a bit more efficient) + size_t data_size = 8; + if (!time_variables_only) { + data_size += 10 + key_.getKeyName().getLength() + + key_.getAlgorithmName().getLength(); + } + OutputBuffer buffer(data_size); + + if (!time_variables_only) { + key_.getKeyName().toWire(buffer); + buffer.writeUint16(rrclass); + buffer.writeUint32(rrttl); + key_.getAlgorithmName().toWire(buffer); + } + buffer.writeUint16(time_signed >> 32); + buffer.writeUint32(time_signed & 0xffffffff); + buffer.writeUint16(fudge); + + if (!time_variables_only) { + buffer.writeUint16(error); + buffer.writeUint16(otherlen); + } + + hmac->update(buffer.getData(), buffer.getLength()); + if (!time_variables_only && otherlen > 0) { + hmac->update(otherdata, otherlen); + } +} + +// In digestDNSMessage, we exploit some minimum knowledge of DNS message +// format: +// - the header section has a fixed length of 12 octets (MESSAGE_HEADER_LEN) +// - the offset in the header section to the ID field is 0 +// - the offset in the header section to the ARCOUNT field is 10 (and the field +// length is 2 octets) +// We could construct a separate Message object from the given data, adjust +// fields via the Message interfaces and then render it back to a separate +// buffer, but that would be overkilling. The DNS message header has a +// fixed length and necessary modifications are quite straightforward, so +// we do the job using lower level interfaces. +namespace { +const size_t MESSAGE_HEADER_LEN = 12; +} + +void +TSIGContext::TSIGContextImpl::digestDNSMessage(HMACPtr hmac, + uint16_t qid, const void* data, + size_t data_len) const { + OutputBuffer buffer(MESSAGE_HEADER_LEN); + const uint8_t* msgptr = static_cast<const uint8_t*>(data); + + // Install the original ID + buffer.writeUint16(qid); + msgptr += sizeof(uint16_t); + + // Copy the rest of the header except the ARCOUNT field. + buffer.writeData(msgptr, 8); + msgptr += 8; + + // Install the adjusted ARCOUNT (we don't care even if the value is bogus + // and it underflows; it would simply result in verification failure) + buffer.writeUint16(InputBuffer(msgptr, sizeof(uint16_t)).readUint16() - 1); + msgptr += 2; + + // Digest the header and the rest of the DNS message + hmac->update(buffer.getData(), buffer.getLength()); + hmac->update(msgptr, data_len - MESSAGE_HEADER_LEN); +} + +TSIGContext::TSIGContext(const TSIGKey& key) : impl_(new TSIGContextImpl(key)) { +} + +TSIGContext::TSIGContext(const Name& key_name, const Name& algorithm_name, + const TSIGKeyRing& keyring) : impl_(0) { + const TSIGKeyRing::FindResult result(keyring.find(key_name, + algorithm_name)); + if (result.code == TSIGKeyRing::NOTFOUND) { + // If not key is found, create a dummy key with the specified key + // parameters and empty secret. In the common scenario this will + // be used in subsequent response with a TSIG indicating a BADKEY + // error. + impl_.reset(new TSIGContextImpl(TSIGKey(key_name, algorithm_name, 0, 0), + TSIGError::BAD_KEY())); + } else { + impl_.reset(new TSIGContextImpl(*result.key)); + } +} + +TSIGContext::~TSIGContext() { +} + +size_t +TSIGContext::getTSIGLength() const { + // + // The space required for an TSIG record is: + // + // n1 bytes for the (key) name + // 2 bytes for the type + // 2 bytes for the class + // 4 bytes for the ttl + // 2 bytes for the rdlength + // n2 bytes for the algorithm name + // 6 bytes for the time signed + // 2 bytes for the fudge + // 2 bytes for the MAC size + // x bytes for the MAC + // 2 bytes for the original id + // 2 bytes for the error + // 2 bytes for the other data length + // y bytes for the other data (at most) + // --------------------------------- + // 26 + n1 + n2 + x + y bytes + // + + // Normally the digest length ("x") is the length of the underlying + // hash output. If a key related error occurred, however, the + // corresponding TSIG will be "unsigned", and the digest length will be 0. + const size_t digest_len = + (impl_->error_ == TSIGError::BAD_KEY() || + impl_->error_ == TSIGError::BAD_SIG()) ? 0 : impl_->digest_len_; + + // Other Len ("y") is normally 0; if BAD_TIME error occurred, the + // subsequent TSIG will contain 48 bits of the server current time. + const size_t other_len = (impl_->error_ == TSIGError::BAD_TIME()) ? 6 : 0; + + return (26 + impl_->key_.getKeyName().getLength() + + impl_->key_.getAlgorithmName().getLength() + + digest_len + other_len); +} + +TSIGContext::State +TSIGContext::getState() const { + return (impl_->state_); +} + +TSIGError +TSIGContext::getError() const { + return (impl_->error_); +} + +ConstTSIGRecordPtr +TSIGContext::sign(const uint16_t qid, const void* const data, + const size_t data_len) { + if (impl_->state_ == VERIFIED_RESPONSE) { + isc_throw(TSIGContextError, + "TSIG sign attempt after verifying a response"); + } + + if (!data || data_len == 0) { + isc_throw(InvalidParameter, "TSIG sign error: empty data is given"); + } + + TSIGError error(TSIGError::NOERROR()); + const uint64_t now = getTSIGTime(); + + // For responses adjust the error code. + if (impl_->state_ == RECEIVED_REQUEST) { + error = impl_->error_; + } + + // For errors related to key or MAC, return an unsigned response as + // specified in Section 4.3 of RFC2845. + if (error == TSIGError::BAD_SIG() || error == TSIGError::BAD_KEY()) { + ConstTSIGRecordPtr tsig(new TSIGRecord( + impl_->key_.getKeyName(), + any::TSIG(impl_->key_.getAlgorithmName(), + now, DEFAULT_FUDGE, 0, 0, + qid, error.getCode(), 0, 0))); + impl_->previous_digest_.clear(); + impl_->state_ = SENT_RESPONSE; + return (tsig); + } + + HMACPtr hmac(impl_->createHMAC()); + + // If the context has previous MAC (either the Request MAC or its own + // previous MAC), digest it. + if (impl_->state_ != INIT) { + impl_->digestPreviousMAC(hmac); + } + + // Digest the message (without TSIG) + hmac->update(data, data_len); + + // Digest TSIG variables. + // First, prepare some non constant variables. + const uint64_t time_signed = (error == TSIGError::BAD_TIME()) ? + impl_->previous_timesigned_ : now; + // For BADTIME error, we include 6 bytes of other data. + // (6 bytes = size of time signed value) + const uint16_t otherlen = (error == TSIGError::BAD_TIME()) ? 6 : 0; + OutputBuffer otherdatabuf(otherlen); + if (error == TSIGError::BAD_TIME()) { + otherdatabuf.writeUint16(now >> 32); + otherdatabuf.writeUint32(now & 0xffffffff); + } + const void* const otherdata = + (otherlen == 0) ? 0 : otherdatabuf.getData(); + // Then calculate the digest. If state_ is SENT_RESPONSE we are sending + // a continued message in the same TCP stream so skip digesting + // variables except for time related variables (RFC2845 4.4). + impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(), + TSIGRecord::TSIG_TTL, time_signed, + DEFAULT_FUDGE, error.getCode(), + otherlen, otherdata, + impl_->state_ == SENT_RESPONSE); + + // Get the final digest, update internal state, then finish. + vector<uint8_t> digest = hmac->sign(impl_->digest_len_); + isc_throw_assert(digest.size() <= 0xffff); // cryptolink API should have ensured it. + ConstTSIGRecordPtr tsig(new TSIGRecord( + impl_->key_.getKeyName(), + any::TSIG(impl_->key_.getAlgorithmName(), + time_signed, DEFAULT_FUDGE, + digest.size(), &digest[0], + qid, error.getCode(), otherlen, + otherdata))); + // Exception free from now on. + impl_->previous_digest_.swap(digest); + impl_->state_ = (impl_->state_ == INIT) ? SENT_REQUEST : SENT_RESPONSE; + return (tsig); +} + +TSIGError +TSIGContext::verify(const TSIGRecord* const record, const void* const data, + const size_t data_len) { + if (impl_->state_ == SENT_RESPONSE) { + isc_throw(TSIGContextError, + "TSIG verify attempt after sending a response"); + } + + if (!record) { + if (impl_->last_sig_dist_ >= 0 && impl_->last_sig_dist_ < 99) { + // It is not signed, but in the middle of TCP stream. We just + // update the HMAC state and consider this message OK. + update(data, data_len); + // This one is not signed, the last signed is one message further + // now. + impl_->last_sig_dist_++; + // No digest to return now. Just say it's OK. + return (impl_->postVerifyUpdate(TSIGError::NOERROR(), 0, 0)); + } + // This case happens when we sent a signed request and have received an + // unsigned response. According to RFC2845 Section 4.6 this case should be + // considered a "format error" (although the specific error code + // wouldn't matter much for the caller). + return (impl_->postVerifyUpdate(TSIGError::FORMERR(), 0, 0)); + } + + const any::TSIG& tsig_rdata = record->getRdata(); + + // Reject some obviously invalid data + if (data_len < MESSAGE_HEADER_LEN + record->getLength()) { + isc_throw(InvalidParameter, + "TSIG verify: data length is invalid: " << data_len); + } + if (!data) { + isc_throw(InvalidParameter, "TSIG verify: empty data is invalid"); + } + + // This message is signed and we won't throw any more. + impl_->last_sig_dist_ = 0; + + // Check key: whether we first verify it with a known key or we verify + // it using the consistent key in the context. If the check fails we are + // done with BADKEY. + if (impl_->state_ == INIT && impl_->error_ == TSIGError::BAD_KEY()) { + return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), 0, 0)); + } + if (impl_->key_.getKeyName() != record->getName() || + impl_->key_.getAlgorithmName() != tsig_rdata.getAlgorithm()) { + return (impl_->postVerifyUpdate(TSIGError::BAD_KEY(), 0, 0)); + } + + // Check time: the current time must be in the range of + // [time signed - fudge, time signed + fudge]. Otherwise verification + // fails with BADTIME. (RFC2845 Section 4.6.2) + // Note: for simplicity we don't explicitly catch the case of too small + // current time causing underflow. With the fact that fudge is quite + // small and (for now) non configurable, it shouldn't be a real concern + // in practice. + const uint64_t now = getTSIGTime(); + if (tsig_rdata.getTimeSigned() + DEFAULT_FUDGE < now || + tsig_rdata.getTimeSigned() - DEFAULT_FUDGE > now) { + const void* digest = 0; + size_t digest_len = 0; + if (impl_->state_ == INIT) { + digest = tsig_rdata.getMAC(); + digest_len = tsig_rdata.getMACSize(); + impl_->previous_timesigned_ = tsig_rdata.getTimeSigned(); + } + return (impl_->postVerifyUpdate(TSIGError::BAD_TIME(), digest, + digest_len)); + } + + // Handling empty MAC. While RFC2845 doesn't explicitly prohibit other + // cases, it can only reasonably happen in a response with BADSIG or + // BADKEY. We reject other cases as if it were BADSIG to avoid unexpected + // acceptance of a bogus signature. This behavior follows the BIND 9 + // implementation. + if (tsig_rdata.getMACSize() == 0) { + TSIGError error = TSIGError(tsig_rdata.getError()); + if (error != TSIGError::BAD_SIG() && error != TSIGError::BAD_KEY()) { + error = TSIGError::BAD_SIG(); + } + return (impl_->postVerifyUpdate(error, 0, 0)); + } + + HMACPtr hmac(impl_->createHMAC()); + + // If the context has previous MAC (either the Request MAC or its own + // previous MAC), digest it. + if (impl_->state_ != INIT) { + impl_->digestPreviousMAC(hmac); + } + + // Signature length check based on RFC 4635 3.1 + if (tsig_rdata.getMACSize() > hmac->getOutputLength()) { + // signature length too big + return (impl_->postVerifyUpdate(TSIGError::FORMERR(), 0, 0)); + } + if ((tsig_rdata.getMACSize() < 10) || + (tsig_rdata.getMACSize() < (hmac->getOutputLength() / 2))) { + // signature length below minimum + return (impl_->postVerifyUpdate(TSIGError::FORMERR(), 0, 0)); + } + if (tsig_rdata.getMACSize() < impl_->digest_len_) { + // (truncated) signature length too small + return (impl_->postVerifyUpdate(TSIGError::BAD_TRUNC(), 0, 0)); + } + + // + // Digest DNS message (excluding the trailing TSIG RR and adjusting the + // QID and ARCOUNT header fields) + // + impl_->digestDNSMessage(hmac, tsig_rdata.getOriginalID(), + data, data_len - record->getLength()); + + // Digest TSIG variables. If state_ is VERIFIED_RESPONSE, it's a + // continuation of the same TCP stream and skip digesting them except + // for time related variables (RFC2845 4.4). + // Note: we use the constant values for RR class and TTL specified + // in RFC2845, not received values (we reject other values in constructing + // the TSIGRecord). + impl_->digestTSIGVariables(hmac, TSIGRecord::getClass().getCode(), + TSIGRecord::TSIG_TTL, + tsig_rdata.getTimeSigned(), + tsig_rdata.getFudge(), tsig_rdata.getError(), + tsig_rdata.getOtherLen(), + tsig_rdata.getOtherData(), + impl_->state_ == VERIFIED_RESPONSE); + + // Verify the digest with the received signature. + if (hmac->verify(tsig_rdata.getMAC(), tsig_rdata.getMACSize())) { + return (impl_->postVerifyUpdate(TSIGError::NOERROR(), + tsig_rdata.getMAC(), + tsig_rdata.getMACSize())); + } + + return (impl_->postVerifyUpdate(TSIGError::BAD_SIG(), 0, 0)); +} + +bool +TSIGContext::lastHadSignature() const { + if (impl_->last_sig_dist_ == -1) { + isc_throw(TSIGContextError, "No message was verified yet"); + } + return (impl_->last_sig_dist_ == 0); +} + +void +TSIGContext::update(const void* const data, size_t len) { + HMACPtr hmac(impl_->createHMAC()); + // Use the previous digest and never use it again + impl_->digestPreviousMAC(hmac); + impl_->previous_digest_.clear(); + // Push the message there + hmac->update(data, len); + impl_->hmac_ = hmac; +} + +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/tsig.h b/src/lib/dns/tsig.h new file mode 100644 index 0000000..10e59b3 --- /dev/null +++ b/src/lib/dns/tsig.h @@ -0,0 +1,441 @@ +// Copyright (C) 2011-2024 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/. + +// IMPORTANT: the server side of this code MUST NOT be used until +// it was fixed, cf RFC 8945. Note that Kea uses only the client side. + +#ifndef TSIG_H +#define TSIG_H + +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> + +#include <exceptions/exceptions.h> + +#include <dns/tsigerror.h> +#include <dns/tsigkey.h> +#include <dns/tsigrecord.h> + +namespace isc { +namespace dns { + +/// An exception that is thrown for logic errors identified in TSIG +/// sign/verify operations. +/// +/// Note that this exception is not thrown for TSIG protocol errors such as +/// verification failures. In general, this exception indicates an internal +/// program bug. +class TSIGContextError : public isc::Exception { +public: + TSIGContextError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) {} +}; + +/// TSIG session context. +/// +/// The \c TSIGContext class maintains a context of a signed session of +/// DNS transactions by TSIG. In many cases a TSIG signed session consists +/// of a single set of request (e.g. normal query) and reply (e.g. normal +/// response), where the request is initially signed by the client, and the +/// reply is signed by the server using the initial signature. As mentioned +/// in RFC2845, a session can consist of multiple exchanges in a TCP +/// connection. As also mentioned in the RFC, an AXFR response often contains +/// multiple DNS messages, which can belong to the same TSIG session. +/// This class supports all these cases. +/// +/// A \c TSIGContext object is generally constructed with a TSIG key to be +/// used for the session, and keeps track of various kinds of session specific +/// information, such as the original digest while waiting for a response or +/// verification error information that is to be used for a subsequent +/// response. +/// +/// This class has two main methods, \c sign() and \c verify(). +/// The \c sign() method signs given data (which is supposed to be a complete +/// DNS message without the TSIG itself) using the TSIG key and other +/// related information associated with the \c TSIGContext object. +/// The \c verify() method verifies a given DNS message that contains a TSIG +/// RR using the key and other internal information. +/// +/// In general, a DNS client that wants to send a signed query will construct +/// a \c TSIGContext object with the TSIG key that the client is intending to +/// use, and sign the query with the context. The client will keeps the +/// context, and verify the response with it. +/// +/// On the other hand, a DNS server will construct a \c TSIGContext object +/// with the information of the TSIG RR included in a query with a set of +/// possible keys (in the form of a \c TSIGKeyRing object). The constructor +/// in this mode will identify the appropriate TSIG key (or internally record +/// an error if it doesn't find a key). The server will then verify the +/// query with the context, and generate a signed response using the same +/// same context. +/// +/// When multiple messages belong to the same TSIG session, either side +/// (signer or verifier) will keep using the same context. It records +/// the latest session state (such as the previous digest) so that repeated +/// calls to \c sign() or \c verify() work correctly in terms of the TSIG +/// protocol. +/// +/// \b Examples +/// +/// This is a typical client application that sends a TSIG signed query +/// and verifies the response. +/// +/// \code +/// // "renderer" is of MessageRenderer to render the message. +/// // (TSIGKey would be configured from config or command line in real app) +/// TSIGContext ctx(TSIGKey("key.example:MSG6Ng==")); +/// Message message(Message::RENDER); +/// message.addQuestion(Question(Name("www.example.com"), RRClass::IN(), +/// RRType::A())); +/// message.toWire(renderer, ctx); +/// +/// // sendto, then recvfrom. received result in (data, data_len) +/// +/// message.clear(Message::PARSE); +/// InputBuffer buffer(data, data_len); +/// message.fromWire(buffer); +/// TSIGError tsig_error = ctx.verify(message.getTSIGRecord(), +/// data, data_len); +/// if (tsig_error == TSIGError::NOERROR()) { +/// // okay. ctx can be continuously used if it's receiving subsequent +/// // signed responses from a TCP stream. +/// } else if (message.getRcode() == Rcode::NOTAUTH()) { +/// // hard error. give up this transaction per RFC2845 4.6. +/// } else { +/// // Other error: discard response keep waiting with the same ctx +/// // for another (again, RFC2845 4.6). +/// } \endcode +/// +/// And this is a typical server application that authenticates a signed +/// query and returns a response according to the result. +/// +/// \code +/// // Assume "message" is of type Message for query handling and +/// // "renderer" is of MessageRenderer to render responses. +/// Message message(Message::RENDER); +/// +/// TSIGKeyRing keyring; // this must be configured with keys somewhere +/// +/// // Receive a query and store it in (data, data_len) +/// InputBuffer buffer(data, data_len); +/// message.clear(Message::PARSE); +/// message.fromWire(buffer); +/// +/// const TSIGRecord* tsig = message.getTSIGRecord(); +/// if (tsig) { +/// TSIGContext ctx(tsig->getName(), tsig->getRdata().getAlgorithm(), +/// keyring); +/// ctx.verify(tsig, data, data_len); +/// +/// // prepare response +/// message.makeResponse(); +/// //... +/// message.toWire(renderer, ctx); +/// +/// // send the response data back to the client. +/// // If this is a beginning of a signed session over a TCP and +/// // server has more data to send to the client, this ctx +/// // will be used to sign subsequent messages. +/// } \endcode +/// +/// <b>TCP Consideration</b> +/// +/// RFC2845 describes the case where a single TSIG session is used for +/// multiple DNS messages (Section 4.4). This class supports signing and +/// verifying the messages in this scenario, but does not care if the messages +/// were delivered over a TCP connection or not. If, for example, the +/// same \c TSIGContext object is used to sign two independent DNS queries +/// sent over UDP, they will be considered to belong to the same TSIG +/// session, and, as a result, verification will be likely to fail. +/// +/// \b Copyability +/// +/// This class is currently non copyable based on the observation of the +/// typical usage as described above. But there is no strong technical +/// reason why this class cannot be copyable. If we see the need for it +/// in future we may change the implementation on this point. +/// +/// <b>Note to developers:</b> +/// One basic design choice is to make the \c TSIGContext class is as +/// independent from the \c Message class. This is because the latter is +/// much more complicated, depending on many other classes, while TSIG is +/// a very specific part of the entire DNS protocol set. If the \c TSIGContext +/// class depends on \c \c Message, it will be more vulnerable to changes +/// to other classes, and will be more difficult to test due to the +/// direct or indirect dependencies. The interface of \c sign() that takes +/// opaque data (instead of, e.g., a \c Message or \c MessageRenderer object) +/// is therefore a deliberate design decision. +class TSIGContext : boost::noncopyable { +public: + /// Internal state of context + /// + /// The constants of this enum type define a specific state of + /// \c TSIGContext to adjust the behavior. The definition is public + /// and the state can be seen via the \c getState() method, but this is + /// mostly private information. It's publicly visible mainly for testing + /// purposes; there is no API for the application to change the state + /// directly. + enum State { + INIT, ///< Initial state + SENT_REQUEST, ///< Client sent a signed request, waiting response + RECEIVED_REQUEST, ///< Server received a signed request + SENT_RESPONSE, ///< Server sent a signed response + VERIFIED_RESPONSE ///< Client successfully verified a response + }; + + /// \name Constructors and destructor + /// + //@{ + /// Constructor from a TSIG key. + /// + /// \exception std::bad_alloc Resource allocation for internal data fails + /// + /// \param key The TSIG key to be used for TSIG sessions with this context. + explicit TSIGContext(const TSIGKey& key); + + /// Constructor from key parameters and key ring. + TSIGContext(const Name& key_name, const Name& algorithm_name, + const TSIGKeyRing& keyring); + + /// The destructor. + virtual ~TSIGContext(); + //@} + + /// Sign a DNS message. + /// + /// This method computes the TSIG MAC for the given data, which is + /// generally expected to be a complete, wire-format DNS message + /// that doesn't contain a TSIG RR, based on the TSIG key and + /// other context information of \c TSIGContext, and returns a + /// result in the form of a (pointer object pointing to) + /// \c TSIGRecord object. + /// + /// The caller of this method will use the returned value to render a + /// complete TSIG RR into the message that has been signed so that it + /// will become a complete TSIG-signed message. + /// + /// In general, this method is called once by a client to send a + /// signed request or one more times by a server to sign + /// response(s) to a signed request. To avoid allowing accidental + /// misuse, if this method is called after a "client" validates a + /// response, an exception of class \c TSIGContextError will be + /// thrown. + /// + /// \note Normal applications are not expected to call this method + /// directly; they will usually use the \c Message::toWire() method + /// with a \c TSIGContext object being a parameter and have the + /// \c Message class create a complete signed message. + /// + /// This method treats the given data as opaque, even though it's generally + /// expected to represent a wire-format DNS message (see also the class + /// description), and doesn't inspect it in any way. For example, it + /// doesn't check whether the data length is sane for a valid DNS message. + /// This is also the reason why this method takes the \c qid parameter, + /// which will be used as the original ID of the resulting + /// \c TSIGRecordx object, even though this value should be stored in the + /// first two octets (in wire format) of the given data. + /// + /// \note This method still checks and rejects empty data (null pointer + /// data or the specified data length is 0) in order to avoid catastrophic + /// effect such as program crash. Empty data is not necessarily invalid + /// for HMAC computation, but obviously it doesn't make sense for a DNS + /// message. + /// + /// This method provides the strong exception guarantee; unless the method + /// returns (without an exception being thrown), the internal state of + /// the \c TSIGContext won't be modified. + /// + /// \exception TSIGContextError Context already verified a response. + /// \exception InvalidParameter \c data is 0 or \c data_len is 0 + /// \exception cryptolink::LibraryError Some unexpected error in the + /// underlying crypto operation + /// \exception std::bad_alloc Temporary resource allocation failure + /// + /// \param qid The QID to be as the value of the original ID field of + /// the resulting TSIG record + /// \param data Points to the wire-format data to be signed + /// \param data_len The length of \c data in bytes + /// + /// \return A TSIG record for the given data along with the context. + virtual ConstTSIGRecordPtr + sign(const uint16_t qid, const void* const data, const size_t data_len); + + /// Verify a DNS message. + /// + /// This method verifies given data along with the context and a given + /// TSIG in the form of a \c TSIGRecord object. The data to be verified + /// is generally expected to be a complete, wire-format DNS message, + /// exactly as received by the host, and ending with a TSIG RR. + /// After verification process this method updates its internal state, + /// and returns the result in the form of a \c TSIGError object. + /// Possible return values are (see the \c TSIGError class description + /// for the mnemonics): + /// + /// - \c NOERROR: The data has been verified correctly. + /// - \c FORMERR: \c TSIGRecord is not given (see below). + /// - \c BAD_KEY: Appropriate key is not found or specified key doesn't + /// match for the data. + /// - \c BAD_TIME: The current time doesn't fall in the range specified + /// in the TSIG. + /// - \c BAD_SIG: The signature given in the TSIG doesn't match against + /// the locally computed digest or is the signature is + /// invalid in other way. + /// - \c BAD_MODE: Not yet implemented TKEY error + /// - \c BAD_NAME: Not yet implemented TKEY error + /// - \c BAD_ALG: Not yet implemented TKEY error + /// - \c BAD_TRUNC: The signature or truncated signature length is too + /// small. + /// + /// If this method is called by a DNS client waiting for a signed + /// response and the result is not \c NOERROR, the context can be used + /// to try validating another signed message as described in RFC2845 + /// Section 4.6. + /// + /// If this method is called by a DNS server that tries to authenticate + /// a signed request, and if the result is not \c NOERROR, the + /// corresponding error condition is recorded in the context so that + /// the server can return a response indicating what was wrong by calling + /// \c sign() with the updated context. + /// + /// In general, this method is called once by a server for + /// authenticating a signed request or one more times by a client to + /// validate signed response(s) to a signed request. To avoid allowing + /// accidental misuse, if this method is called after a "server" signs + /// a response, an exception of class \c TSIGContextError will be thrown. + /// + /// The \c record parameter can be 0; in that case this method simply + /// returns \c FORMERR as the case described in Section 4.6 of RFC2845, + /// i.e., receiving an unsigned response to a signed request. This way + /// a client can transparently pass the result of + /// \c Message::getTSIGRecord() without checking whether it isn't 0 + /// and take an appropriate action based on the result of this method. + /// + /// This method handles the given data mostly as opaque. It digests + /// the data assuming it begins with a DNS header and ends with a TSIG + /// RR whose length is given by calling \c TSIGRecord::getLength() on + /// \c record, but otherwise it doesn't parse the data to confirm the + /// assumption. It's caller's responsibility to ensure the data is + /// valid and consistent with \c record. To avoid disruption, this + /// method performs minimal validation on the given \c data and \c record: + /// \c data must not be 0; \c data_len must not be smaller than the + /// sum of the DNS header length (fixed, 12 octets) and the length of + /// the TSIG RR. If this check fails it throws an \c InvalidParameter + /// exception. + /// + /// One unexpected case that is not covered by this method is that a + /// client receives a signed response to an unsigned request. RFC2845 is + /// silent about such cases; BIND 9 explicitly identifies the case and + /// rejects it. With this implementation, the client can know that the + /// response contains a TSIG via the result of + /// \c Message::getTSIGRecord() and that it is an unexpected TSIG due to + /// the fact that it doesn't have a corresponding \c TSIGContext. + /// It's up to the client implementation whether to react to such a case + /// explicitly (for example, it could either ignore the TSIG and accept + /// the response or drop it). + /// + /// This method provides the strong exception guarantee; unless the method + /// returns (without an exception being thrown), the internal state of + /// the \c TSIGContext won't be modified. + /// + /// \todo Signature truncation support based on RFC4635 + /// + /// \exception TSIGContextError Context already signed a response. + /// \exception InvalidParameter \c data is 0 or \c data_len is too small. + /// + /// \param record The \c TSIGRecord to be verified with \c data + /// \param data Points to the wire-format data (exactly as received) to + /// be verified + /// \param data_len The length of \c data in bytes + /// \return The \c TSIGError that indicates verification result + virtual TSIGError + verify(const TSIGRecord* const record, const void* const data, const size_t data_len); + + /// \brief Check whether the last verified message was signed. + /// + /// RFC2845 allows for some of the messages not to be signed. However, + /// the last message must be signed and the class has no knowledge if a + /// given message is the last one, therefore it can't check directly. + /// + /// It is up to the caller to check if the last verified message was signed + /// after all are verified by calling this function. + /// + /// \return If the last message was signed or not. + /// \exception TSIGContextError if no message was verified yet. + virtual bool lastHadSignature() const; + + /// Return the expected length of TSIG RR after \c sign() + /// + /// This method returns the length of the TSIG RR that would be + /// produced as a result of \c sign() with the state of the context + /// at the time of the call. The expected length can be decided + /// from the key and the algorithm (which determines the MAC size if + /// included) and the recorded TSIG error. Specifically, if a key + /// related error has been identified, the MAC will be excluded; if + /// a time error has occurred, the TSIG will include "other data". + /// + /// This method is provided mainly for the convenience of the Message + /// class, which needs to know the expected TSIG length in rendering a + /// signed DNS message so that it can handle truncated messages with TSIG + /// correctly. Normal applications wouldn't need this method. The Python + /// binding for this method won't be provided for the same reason. + /// + /// \exception None + /// + /// \return The expected TSIG RR length in bytes + virtual size_t getTSIGLength() const; + + /// Return the current state of the context + /// + /// \note + /// The states are visible in public mainly for testing purposes. + /// Normal applications won't have to deal with them. + /// + /// \exception None + virtual State getState() const; + + /// Return the TSIG error as a result of the latest verification + /// + /// This method can be called even before verifying anything, but the + /// returned value is meaningless in that case. + /// + /// \exception None + virtual TSIGError getError() const; + + /// \name Protocol constants and defaults + /// + //@{ + /// The recommended fudge value (in seconds) by RFC2845. + /// + /// Right now fudge is not tunable, and all TSIGs generated by this API + /// will have this value of fudge. + static const uint16_t DEFAULT_FUDGE = 300; + //@} + +protected: + /// \brief Update internal HMAC state by more data. + /// + /// This is used mostly internally, when we need to verify a message without + /// TSIG signature in the middle of signed TCP stream. However, it is also + /// used in tests, so it's protected instead of private, to allow tests + /// in. + /// + /// It doesn't contain sanity checks, and it is not tested directly. But + /// we may want to add these one day to allow generating the skipped TSIG + /// messages too. Until then, do not use this method. + void update(const void* const data, size_t len); + +private: + struct TSIGContextImpl; + boost::shared_ptr<TSIGContextImpl> impl_; +}; + +typedef boost::shared_ptr<TSIGContext> TSIGContextPtr; +typedef boost::shared_ptr<TSIGKey> TSIGKeyPtr; + +} +} + +#endif // TSIG_H diff --git a/src/lib/dns/tsigerror.cc b/src/lib/dns/tsigerror.cc new file mode 100644 index 0000000..d51094a --- /dev/null +++ b/src/lib/dns/tsigerror.cc @@ -0,0 +1,66 @@ +// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <ostream> +#include <string> + +#include <boost/lexical_cast.hpp> + +#include <exceptions/exceptions.h> + +#include <dns/rcode.h> +#include <dns/tsigerror.h> + +namespace isc { +namespace dns { +namespace { +const char* const tsigerror_text[] = { + "BADSIG", + "BADKEY", + "BADTIME", + "BADMODE", + "BADNAME", + "BADALG", + "BADTRUNC" +}; +} + +TSIGError::TSIGError(Rcode rcode) : code_(rcode.getCode()) { + if (code_ > MAX_RCODE_FOR_TSIGERROR) { + isc_throw(OutOfRange, "Invalid RCODE for TSIG Error: " << rcode); + } +} + +std::string +TSIGError::toText() const { + if (code_ <= MAX_RCODE_FOR_TSIGERROR) { + return (Rcode(code_).toText()); + } else if (code_ <= BAD_TRUNC_CODE) { + return (tsigerror_text[code_ - (MAX_RCODE_FOR_TSIGERROR + 1)]); + } else { + return (boost::lexical_cast<std::string>(code_)); + } +} + +Rcode +TSIGError::toRcode() const { + if (code_ <= MAX_RCODE_FOR_TSIGERROR) { + return (Rcode(code_)); + } + if (code_ > BAD_TRUNC_CODE) { + return (Rcode::SERVFAIL()); + } + return (Rcode::NOTAUTH()); +} + +std::ostream& +operator<<(std::ostream& os, const TSIGError& error) { + return (os << error.toText()); +} +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/tsigerror.h b/src/lib/dns/tsigerror.h new file mode 100644 index 0000000..da9bc54 --- /dev/null +++ b/src/lib/dns/tsigerror.h @@ -0,0 +1,378 @@ +// Copyright (C) 2011-2024 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 TSIGERROR_H +#define TSIGERROR_H + +#include <ostream> +#include <string> + +#include <dns/rcode.h> + +namespace isc { +namespace dns { +/// TSIG errors +/// +/// The \c TSIGError class objects represent standard errors related to +/// TSIG protocol operations as defined in related specifications, mainly +/// in RFC2845, RFC2930 and RFC4635. +class TSIGError { +public: + /// Constants for pre-defined TSIG error values. + /// + /// Code values from 0 through 15 (inclusive) are derived from those of + /// RCODE and are not defined here. See the \c Rcode class. + /// + /// \note Unfortunately some systems define "BADSIG" as a macro in a public + /// header file. To avoid conflict with it we add an underscore to our + /// definitions. + enum CodeValue { + BAD_SIG_CODE = 16, ///< 16: TSIG verification failure + BAD_KEY_CODE = 17, ///< 17: TSIG key is not recognized + BAD_TIME_CODE = 18, ///< 18: Current time and time signed are too different + BAD_MODE_CODE = 19, ///< 19: Bad TKEY mode + BAD_NAME_CODE = 20, ///< 20: Duplicate TKEY name + BAD_ALG_CODE = 21, ///< 21: TKEY algorithm not supported + BAD_TRUNC_CODE = 22 ///< 22: Bad truncation + }; + + /// \name Constructors + /// + /// We use the default versions of destructor, copy constructor, + /// and assignment operator. + //@{ + /// Constructor from the code value. + /// + /// \exception None + /// + /// \param error_code The underlying 16-bit error code value of the \c TSIGError. + explicit TSIGError(uint16_t error_code) : code_(error_code) {} + + /// Constructor from \c Rcode. + /// + /// As defined in RFC2845, error code values from 0 to 15 (inclusive) are + /// derived from the DNS RCODEs, which are represented via the \c Rcode + /// class in this library. This constructor works as a converter from + /// these RCODEs to corresponding TSIGError objects. + /// + /// \exception isc::OutOfRange Given rcode is not convertible to + /// TSIGErrors. + /// + /// \param rcode the \c Rcode from which the TSIGError should be derived. + explicit TSIGError(Rcode rcode); + //@} + + /// \brief Returns the \c TSIGCode error code value. + /// + /// \exception None + /// + /// \return The underlying code value corresponding to the \c TSIGError. + uint16_t getCode() const { + return (code_); + } + + /// \brief Return true iff two \c TSIGError objects are equal. + /// + /// Two TSIGError objects are equal iff their error codes are equal. + /// + /// \exception None + /// + /// \param other the \c TSIGError object to compare against. + /// \return true if the two TSIGError are equal; otherwise false. + bool equals(const TSIGError& other) const { + return (code_ == other.code_); + } + + /// \brief Same as \c equals(). + bool operator==(const TSIGError& other) const { + return (equals(other)); + } + + /// \brief Return true iff two \c TSIGError objects are not equal. + /// + /// \exception None + /// + /// \param other the \c TSIGError object to compare against. + /// \return true if the two TSIGError objects are not equal; + /// otherwise false. + bool nequals(const TSIGError& other) const { + return (code_ != other.code_); + } + + /// \brief Same as \c nequals(). + bool operator!=(const TSIGError& other) const { + return (nequals(other)); + } + + /// \brief Convert the \c TSIGError to a string. + /// + /// For codes derived from RCODEs up to 15, this method returns the + /// same string as \c Rcode::toText() for the corresponding code. + /// For other pre-defined code values (see TSIGError::CodeValue), + /// this method returns a string representation of the "mnemonic' used + /// for the enum and constant objects as defined in RFC2845. + /// For example, the string for code value 16 is "BADSIG", etc. + /// For other code values it returns a string representation of the decimal + /// number of the value, e.g. "32", "100", etc. + /// + /// \exception std::bad_alloc Resource allocation for the string fails + /// + /// \return A string representation of the \c TSIGError. + std::string toText() const; + + /// \brief Convert the \c TSIGError to a \c Rcode + /// + /// This method returns an \c Rcode object that is corresponding to + /// the TSIG error. The returned \c Rcode is expected to be used + /// by a verifying server to specify the RCODE of a response when + /// TSIG verification fails. + /// + /// Specifically, this method returns \c Rcode::NOTAUTH() for the + /// TSIG specific errors, BADSIG, BADKEY, BADTIME, as described in + /// RFC2845. For errors derived from the standard Rcode (code 0-15), + /// it returns the corresponding \c Rcode. For others, this method + /// returns \c Rcode::SERVFAIL() as a last resort. + /// + /// \exception None + Rcode toRcode() const; + + /// A constant TSIG error object derived from \c Rcode::NOERROR() + static const TSIGError& NOERROR(); + + /// A constant TSIG error object derived from \c Rcode::FORMERR() + static const TSIGError& FORMERR(); + + /// A constant TSIG error object derived from \c Rcode::SERVFAIL() + static const TSIGError& SERVFAIL(); + + /// A constant TSIG error object derived from \c Rcode::NXDOMAIN() + static const TSIGError& NXDOMAIN(); + + /// A constant TSIG error object derived from \c Rcode::NOTIMP() + static const TSIGError& NOTIMP(); + + /// A constant TSIG error object derived from \c Rcode::REFUSED() + static const TSIGError& REFUSED(); + + /// A constant TSIG error object derived from \c Rcode::YXDOMAIN() + static const TSIGError& YXDOMAIN(); + + /// A constant TSIG error object derived from \c Rcode::YXRRSET() + static const TSIGError& YXRRSET(); + + /// A constant TSIG error object derived from \c Rcode::NXRRSET() + static const TSIGError& NXRRSET(); + + /// A constant TSIG error object derived from \c Rcode::NOTAUTH() + static const TSIGError& NOTAUTH(); + + /// A constant TSIG error object derived from \c Rcode::NOTZONE() + static const TSIGError& NOTZONE(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED11() + static const TSIGError& RESERVED11(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED12() + static const TSIGError& RESERVED12(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED13() + static const TSIGError& RESERVED13(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED14() + static const TSIGError& RESERVED14(); + + /// A constant TSIG error object derived from \c Rcode::RESERVED15() + static const TSIGError& RESERVED15(); + + /// A constant TSIG error object for the BADSIG code + /// (see \c TSIGError::BAD_SIG_CODE). + static const TSIGError& BAD_SIG(); + + /// A constant TSIG error object for the BADKEY code + /// (see \c TSIGError::BAD_KEY_CODE). + static const TSIGError& BAD_KEY(); + + /// A constant TSIG error object for the BADTIME code + /// (see \c TSIGError::BAD_TIME_CODE). + static const TSIGError& BAD_TIME(); + + /// A constant TSIG error object for the BADMODE code + /// (see \c TSIGError::BAD_MODE_CODE). + static const TSIGError& BAD_MODE(); + + /// A constant TSIG error object for the BADNAME code + /// (see \c TSIGError::BAD_NAME_CODE). + static const TSIGError& BAD_NAME(); + + /// A constant TSIG error object for the BADALG code + /// (see \c TSIGError::BAD_ALG_CODE). + static const TSIGError& BAD_ALG(); + + /// A constant TSIG error object for the BADTRUNC code + /// (see \c TSIGError::BAD_TRUNC_CODE). + static const TSIGError& BAD_TRUNC(); + +private: + // This is internally used to specify the maximum possible RCODE value + // that can be convertible to TSIGErrors. + static const int MAX_RCODE_FOR_TSIGERROR = 15; + + uint16_t code_; +}; + +inline const TSIGError& +TSIGError::NOERROR() { + static TSIGError e(Rcode::NOERROR()); + return (e); +} + +inline const TSIGError& +TSIGError::FORMERR() { + static TSIGError e(Rcode::FORMERR()); + return (e); +} + +inline const TSIGError& +TSIGError::SERVFAIL() { + static TSIGError e(Rcode::SERVFAIL()); + return (e); +} + +inline const TSIGError& +TSIGError::NXDOMAIN() { + static TSIGError e(Rcode::NXDOMAIN()); + return (e); +} + +inline const TSIGError& +TSIGError::NOTIMP() { + static TSIGError e(Rcode::NOTIMP()); + return (e); +} + +inline const TSIGError& +TSIGError::REFUSED() { + static TSIGError e(Rcode::REFUSED()); + return (e); +} + +inline const TSIGError& +TSIGError::YXDOMAIN() { + static TSIGError e(Rcode::YXDOMAIN()); + return (e); +} + +inline const TSIGError& +TSIGError::YXRRSET() { + static TSIGError e(Rcode::YXRRSET()); + return (e); +} + +inline const TSIGError& +TSIGError::NXRRSET() { + static TSIGError e(Rcode::NXRRSET()); + return (e); +} + +inline const TSIGError& +TSIGError::NOTAUTH() { + static TSIGError e(Rcode::NOTAUTH()); + return (e); +} + +inline const TSIGError& +TSIGError::NOTZONE() { + static TSIGError e(Rcode::NOTZONE()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED11() { + static TSIGError e(Rcode::RESERVED11()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED12() { + static TSIGError e(Rcode::RESERVED12()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED13() { + static TSIGError e(Rcode::RESERVED13()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED14() { + static TSIGError e(Rcode::RESERVED14()); + return (e); +} + +inline const TSIGError& +TSIGError::RESERVED15() { + static TSIGError e(Rcode::RESERVED15()); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_SIG() { + static TSIGError e(BAD_SIG_CODE); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_KEY() { + static TSIGError e(BAD_KEY_CODE); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_TIME() { + static TSIGError e(BAD_TIME_CODE); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_MODE() { + static TSIGError e(BAD_MODE_CODE); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_NAME() { + static TSIGError e(BAD_NAME_CODE); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_ALG() { + static TSIGError e(BAD_ALG_CODE); + return (e); +} + +inline const TSIGError& +TSIGError::BAD_TRUNC() { + static TSIGError e(BAD_TRUNC_CODE); + return (e); +} + +/// Insert the \c TSIGError as a string into stream. +/// +/// This method convert \c tsig_error into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param tsig_error An \c TSIGError object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const TSIGError& tsig_error); +} +} + +#endif // TSIGERROR_H diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc new file mode 100644 index 0000000..92a12bd --- /dev/null +++ b/src/lib/dns/tsigkey.cc @@ -0,0 +1,354 @@ +// Copyright (C) 2010-2024 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 <map> +#include <utility> +#include <vector> +#include <sstream> + +#include <exceptions/exceptions.h> + +#include <cryptolink/cryptolink.h> + +#include <dns/name.h> +#include <util/encode/encode.h> +#include <dns/tsigkey.h> + +#include <boost/lexical_cast.hpp> + +using namespace std; +using namespace isc::cryptolink; + +namespace isc { +namespace dns { +namespace { + HashAlgorithm + convertAlgorithmName(const isc::dns::Name& name) { + if (name == TSIGKey::HMACMD5_NAME()) { + return (isc::cryptolink::MD5); + } + if (name == TSIGKey::HMACMD5_SHORT_NAME()) { + return (isc::cryptolink::MD5); + } + if (name == TSIGKey::HMACSHA1_NAME()) { + return (isc::cryptolink::SHA1); + } + if (name == TSIGKey::HMACSHA256_NAME()) { + return (isc::cryptolink::SHA256); + } + if (name == TSIGKey::HMACSHA224_NAME()) { + return (isc::cryptolink::SHA224); + } + if (name == TSIGKey::HMACSHA384_NAME()) { + return (isc::cryptolink::SHA384); + } + if (name == TSIGKey::HMACSHA512_NAME()) { + return (isc::cryptolink::SHA512); + } + + return (isc::cryptolink::UNKNOWN_HASH); + } +} + +struct +TSIGKey::TSIGKeyImpl { + TSIGKeyImpl(const Name& key_name, const Name& algorithm_name, + isc::cryptolink::HashAlgorithm algorithm, + size_t digestbits) : + key_name_(key_name), algorithm_name_(algorithm_name), + algorithm_(algorithm), digestbits_(digestbits), + secret_() { + // Convert the key and algorithm names to the canonical form. + key_name_.downcase(); + if (algorithm == isc::cryptolink::MD5) { + algorithm_name_ = TSIGKey::HMACMD5_NAME(); + } + algorithm_name_.downcase(); + } + TSIGKeyImpl(const Name& key_name, const Name& algorithm_name, + isc::cryptolink::HashAlgorithm algorithm, + size_t digestbits, + const void* secret, size_t secret_len) : + key_name_(key_name), algorithm_name_(algorithm_name), + algorithm_(algorithm), digestbits_(digestbits), + secret_(static_cast<const uint8_t*>(secret), + static_cast<const uint8_t*>(secret) + secret_len) { + // Convert the key and algorithm names to the canonical form. + key_name_.downcase(); + if (algorithm == isc::cryptolink::MD5) { + algorithm_name_ = TSIGKey::HMACMD5_NAME(); + } + algorithm_name_.downcase(); + } + Name key_name_; + Name algorithm_name_; + const isc::cryptolink::HashAlgorithm algorithm_; + size_t digestbits_; + const vector<uint8_t> secret_; +}; + +TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name, + const void* secret, size_t secret_len, + size_t digestbits /*= 0*/) : impl_(0) { + const HashAlgorithm algorithm = convertAlgorithmName(algorithm_name); + if ((secret && secret_len == 0) || + (!secret && secret_len != 0)) { + isc_throw(InvalidParameter, + "TSIGKey secret and its length are inconsistent: " << + key_name << ":" << algorithm_name); + } + if (algorithm == isc::cryptolink::UNKNOWN_HASH && secret_len != 0) { + isc_throw(InvalidParameter, + "TSIGKey with unknown algorithm has non empty secret: " << + key_name << ":" << algorithm_name); + } + if (!secret) { + impl_.reset(new TSIGKey::TSIGKeyImpl(key_name, algorithm_name, algorithm, + digestbits)); + } else { + impl_.reset(new TSIGKey::TSIGKeyImpl(key_name, algorithm_name, algorithm, + digestbits, secret, secret_len)); + } +} + +TSIGKey::TSIGKey(const std::string& str) : impl_(0) { + try { + istringstream iss(str); + + string keyname_str; + getline(iss, keyname_str, ':'); + if (iss.fail() || iss.bad() || iss.eof()) { + isc_throw(InvalidParameter, "Invalid TSIG key string: " << str); + } + + string secret_str; + getline(iss, secret_str, ':'); + if (iss.fail() || iss.bad()) { + isc_throw(InvalidParameter, "Invalid TSIG key string: " << str); + } + + string algo_str; + if (!iss.eof()) { + getline(iss, algo_str, ':'); + } + if (iss.fail() || iss.bad()) { + isc_throw(InvalidParameter, "Invalid TSIG key string: " << str); + } + + string dgstbt_str; + if (!iss.eof()) { + getline(iss, dgstbt_str); + } + if (iss.fail() || iss.bad()) { + isc_throw(InvalidParameter, "Invalid TSIG key string: " << str); + } + + const Name algo_name(algo_str.empty() ? "hmac-md5.sig-alg.reg.int" : + algo_str); + const HashAlgorithm algorithm = convertAlgorithmName(algo_name); + size_t digestbits = 0; + try { + if (!dgstbt_str.empty()) { + digestbits = boost::lexical_cast<size_t>(dgstbt_str); + } + } catch (const boost::bad_lexical_cast&) { + isc_throw(InvalidParameter, + "TSIG key with non-numeric digestbits: " << dgstbt_str); + } + + vector<uint8_t> secret; + isc::util::encode::decodeBase64(secret_str, secret); + + if (algorithm == isc::cryptolink::UNKNOWN_HASH && !secret.empty()) { + isc_throw(InvalidParameter, + "TSIG key with unknown algorithm has non empty secret: " + << str); + } + + if (secret.empty()) { + impl_.reset(new TSIGKey::TSIGKeyImpl(Name(keyname_str), algo_name, algorithm, + digestbits)); + } else { + impl_.reset(new TSIGKey::TSIGKeyImpl(Name(keyname_str), algo_name, algorithm, + digestbits, &secret[0], secret.size())); + } + } catch (const isc::Exception& e) { + // 'reduce' the several types of exceptions name parsing and + // Base64 decoding can throw to just the InvalidParameter + isc_throw(InvalidParameter, e.what()); + } +} + +TSIGKey::TSIGKey(const TSIGKey& source) : impl_(new TSIGKeyImpl(*source.impl_)) { +} + +TSIGKey& +TSIGKey::operator=(const TSIGKey& source) { + if (this == &source) { + return (*this); + } + + impl_.reset(new TSIGKey::TSIGKeyImpl(*source.impl_)); + return (*this); +} + +TSIGKey::~TSIGKey() { +} + +const Name& +TSIGKey::getKeyName() const { + return (impl_->key_name_); +} + +const Name& +TSIGKey::getAlgorithmName() const { + return (impl_->algorithm_name_); +} + +isc::cryptolink::HashAlgorithm +TSIGKey::getAlgorithm() const { + return (impl_->algorithm_); +} + +size_t +TSIGKey::getDigestbits() const { + return (impl_->digestbits_); +} + +const void* +TSIGKey::getSecret() const { + return ((impl_->secret_.size() > 0) ? &impl_->secret_[0] : 0); +} + +size_t +TSIGKey::getSecretLength() const { + return (impl_->secret_.size()); +} + +std::string +TSIGKey::toText() const { + size_t digestbits = getDigestbits(); + const vector<uint8_t> secret_v(static_cast<const uint8_t*>(getSecret()), + static_cast<const uint8_t*>(getSecret()) + + getSecretLength()); + std::string secret_str = isc::util::encode::encodeBase64(secret_v); + + if (digestbits) { + std::string dgstbt_str = boost::lexical_cast<std::string>(static_cast<int>(digestbits)); + return (getKeyName().toText() + ":" + secret_str + ":" + + getAlgorithmName().toText() + ":" + dgstbt_str); + } else { + return (getKeyName().toText() + ":" + secret_str + ":" + + getAlgorithmName().toText()); + } +} + +const +Name& TSIGKey::HMACMD5_NAME() { + static Name alg_name("hmac-md5.sig-alg.reg.int"); + return (alg_name); +} + +const +Name& TSIGKey::HMACMD5_SHORT_NAME() { + static Name alg_name("hmac-md5"); + return (alg_name); +} + +const +Name& TSIGKey::HMACSHA1_NAME() { + static Name alg_name("hmac-sha1"); + return (alg_name); +} + +const +Name& TSIGKey::HMACSHA256_NAME() { + static Name alg_name("hmac-sha256"); + return (alg_name); +} + +const +Name& TSIGKey::HMACSHA224_NAME() { + static Name alg_name("hmac-sha224"); + return (alg_name); +} + +const +Name& TSIGKey::HMACSHA384_NAME() { + static Name alg_name("hmac-sha384"); + return (alg_name); +} + +const +Name& TSIGKey::HMACSHA512_NAME() { + static Name alg_name("hmac-sha512"); + return (alg_name); +} + +const +Name& TSIGKey::GSSTSIG_NAME() { + static Name alg_name("gss-tsig"); + return (alg_name); +} + +struct TSIGKeyRing::TSIGKeyRingImpl { + typedef map<Name, TSIGKey> TSIGKeyMap; + typedef pair<Name, TSIGKey> NameAndKey; + TSIGKeyMap keys; +}; + +TSIGKeyRing::TSIGKeyRing() : impl_(new TSIGKeyRingImpl()) { +} + +TSIGKeyRing::~TSIGKeyRing() { +} + +unsigned int +TSIGKeyRing::size() const { + return (impl_->keys.size()); +} + +TSIGKeyRing::Result +TSIGKeyRing::add(const TSIGKey& key) { + if (impl_->keys.insert( + TSIGKeyRingImpl::NameAndKey(key.getKeyName(), key)).second + == true) { + return (SUCCESS); + } else { + return (EXIST); + } +} + +TSIGKeyRing::Result +TSIGKeyRing::remove(const Name& key_name) { + return (impl_->keys.erase(key_name) == 1 ? SUCCESS : NOTFOUND); +} + +TSIGKeyRing::FindResult +TSIGKeyRing::find(const Name& key_name) const { + TSIGKeyRingImpl::TSIGKeyMap::const_iterator found = + impl_->keys.find(key_name); + if (found == impl_->keys.end()) { + return (FindResult(NOTFOUND, 0)); + } + return (FindResult(SUCCESS, &((*found).second))); +} + +TSIGKeyRing::FindResult +TSIGKeyRing::find(const Name& key_name, const Name& algorithm_name) const { + TSIGKeyRingImpl::TSIGKeyMap::const_iterator found = + impl_->keys.find(key_name); + if (found == impl_->keys.end() || + (*found).second.getAlgorithmName() != algorithm_name) { + return (FindResult(NOTFOUND, 0)); + } + return (FindResult(SUCCESS, &((*found).second))); +} + +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h new file mode 100644 index 0000000..9381074 --- /dev/null +++ b/src/lib/dns/tsigkey.h @@ -0,0 +1,387 @@ +// Copyright (C) 2010-2024 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 TSIGKEY_H +#define TSIGKEY_H + +#include <cryptolink/cryptolink.h> + +namespace isc { +namespace dns { + +class Name; + +/// \brief TSIG key. +/// +/// This class holds a TSIG key along with some related attributes as +/// defined in RFC2845. +/// +/// A TSIG key consists of the following attributes: +/// - Key name +/// - Hash algorithm +/// - Digest bits +/// - Shared secret +/// +/// <b>Implementation Notes</b> +/// +/// We may add more attributes in future versions. For example, if and when +/// we support the TKEY protocol (RFC2930), we may need to introduce the +/// notion of inception and expiration times. +/// At that point we may also have to introduce a class hierarchy to handle +/// different types of keys in a polymorphic way. +/// At the moment we use the straightforward value-type class with minimal +/// attributes. +/// +/// In the TSIG protocol, hash algorithms are represented in the form of +/// domain name. +/// Our interfaces provide direct translation of this concept; for example, +/// the constructor from parameters take a \c Name object to specify the +/// algorithm. +/// On one hand, this may be counter intuitive. +/// An API user would rather specify "hmac-md5" instead of +/// <code>Name("hmac-md5.sig-alg.reg.int")</code>. +/// On the other hand, it may be more convenient for some kind of applications +/// if we maintain the algorithm as the expected representation for +/// protocol operations (such as sign and very a message). +/// Considering these points, we adopt the interface closer to the protocol +/// specification for now. +/// To minimize the burden for API users, we also define a set of constants +/// for commonly used algorithm names so that the users don't have to +/// remember the actual domain names defined in the protocol specification. +/// We may also have to add conversion routines between domain names +/// and more intuitive representations (e.g. strings) for algorithms. +class TSIGKey { +public: + /// + /// \name Constructors, Assignment Operator and Destructor. + /// + //@{ + /// \brief Constructor from key parameters + /// + /// \c algorithm_name should generally be a known algorithm to this + /// implementation, which are defined via the + /// <code>static const</code> member functions. + /// + /// Other names are still accepted as long as the secret is empty + /// (\c secret is null and \c secret_len is 0), however; in some cases + /// we might want to treat just the pair of key name and algorithm name + /// opaquely, e.g., when generating a response TSIG with a BADKEY error + /// because the algorithm is unknown as specified in Section 3.2 of + /// RFC2845 (in which case the algorithm name would be copied from the + /// request to the response, and for that purpose it would be convenient + /// if a \c TSIGKey object can hold a name for an "unknown" algorithm). + /// + /// \note RFC2845 does not specify which algorithm name should be used + /// in such a BADKEY response. The behavior of using the same algorithm + /// is derived from the BIND 9 implementation. + /// + /// It is unlikely that a TSIG key with an unknown algorithm is of any + /// use with actual crypto operation, so care must be taken when dealing + /// with such keys. (The restriction for the secret will prevent + /// accidental creation of such a dangerous key, e.g., due to misspelling + /// in a configuration file). + /// If the given algorithm name is unknown and non empty secret is + /// specified, an exception of type \c InvalidParameter will be thrown. + /// + /// \c secret and \c secret_len must be consistent in that the latter + /// is 0 if and only if the former is null; + /// otherwise an exception of type \c InvalidParameter will be thrown. + /// + /// \c digestbits is the truncated length in bits or 0 which means no + /// truncation and is the default. Constraints for non-zero value + /// are in RFC 4635 section 3.1: minimum 80 or the half of the + /// full (i.e., not truncated) length, integral number of octets + /// (i.e., multiple of 8), and maximum the full length. + /// + /// This constructor internally involves resource allocation, and if + /// it fails, a corresponding standard exception will be thrown. + /// + /// \param key_name The name of the key as a domain name. + /// \param algorithm_name The hash algorithm used for this key in the + /// form of domain name. For example, it can be + /// \c TSIGKey::HMACSHA256_NAME() for HMAC-SHA256. + /// \param secret Point to a binary sequence of the shared secret to be + /// used for this key, or null if the secret is empty. + /// \param secret_len The size of the binary %data (\c secret) in bytes. + /// \param digestbits The number of bits to include in the digest + /// (0 means to include all) + TSIGKey(const Name& key_name, const Name& algorithm_name, + const void* secret, size_t secret_len, size_t digestbits = 0); + + /// \brief Constructor from an input string + /// + /// The string must be of the form: + /// name:secret[:algorithm][:digestbits] + /// Where "name" is a domain name for the key, "secret" is a + /// base64 representation of the key secret, and the optional + /// "algorithm" is an algorithm identifier as specified in RFC 4635. + /// The default algorithm is hmac-md5.sig-alg.reg.int. + /// "digestbits" is the minimum truncated length in bits. + /// The default digestbits value is 0 and means truncation is forbidden. + /// + /// The same restriction about the algorithm name (and secret) as that + /// for the other constructor applies. + /// + /// Since ':' is used as a separator here, it is not possible to + /// use this constructor to create keys with a ':' character in + /// their name. + /// + /// \exception InvalidParameter exception if the input string is + /// invalid. + /// + /// \param str The string to make a TSIGKey from + explicit TSIGKey(const std::string& str); + + /// \brief The copy constructor. + /// + /// It internally allocates a resource, and if it fails a corresponding + /// standard exception will be thrown. + /// This constructor never throws an exception otherwise. + TSIGKey(const TSIGKey& source); + + /// \brief Assignment operator. + /// + /// It internally allocates a resource, and if it fails a corresponding + /// standard exception will be thrown. + /// This operator never throws an exception otherwise. + /// + /// This operator provides the strong exception guarantee: When an + /// exception is thrown the content of the assignment target will be + /// intact. + TSIGKey& operator=(const TSIGKey& source); + + /// The destructor. + virtual ~TSIGKey(); + //@} + + /// + /// \name Getter Methods + /// + /// These methods never throw an exception. + //@{ + /// Return the key name. + const Name& getKeyName() const; + + /// Return the algorithm name. + const Name& getAlgorithmName() const; + + /// Return the hash algorithm name in the form of cryptolink::HashAlgorithm + isc::cryptolink::HashAlgorithm getAlgorithm() const; + + /// Return the minimum truncated length. + size_t getDigestbits() const; + + /// Return the length of the TSIG secret in bytes. + size_t getSecretLength() const; + + /// Return the value of the TSIG secret. + /// + /// If it returns a non null pointer, the memory region beginning at the + /// address returned by this method is valid up to the bytes specified + /// by the return value of \c getSecretLength(). + /// + /// The memory region is only valid while the corresponding \c TSIGKey + /// object is valid. The caller must hold the \c TSIGKey object while + /// it needs to refer to the region or it must make a local copy of the + /// region. + const void* getSecret() const; + //@} + + /// \brief Converts the TSIGKey to a string value + /// + /// The resulting string will be of the form + /// name:secret:algorithm[:digestbits] + /// Where "name" is a domain name for the key, "secret" is a + /// base64 representation of the key secret, and "algorithm" is + /// an algorithm identifier as specified in RFC 4635. + /// When not zero, digestbits is appended. + /// + /// \return The string representation of the given TSIGKey. + std::string toText() const; + + /// + /// \name Well known algorithm names as defined in RFC2845 and RFC4635. + /// + /// Note: we begin with the "mandatory" algorithms defined in RFC4635 + /// as a minimal initial set. + /// We'll add others as we see the need for them. + //@{ + static const Name& HMACMD5_NAME(); ///< HMAC-MD5 (RFC2845) + static const Name& HMACMD5_SHORT_NAME(); + static const Name& HMACSHA1_NAME(); ///< HMAC-SHA1 (RFC4635) + static const Name& HMACSHA256_NAME(); ///< HMAC-SHA256 (RFC4635) + static const Name& HMACSHA224_NAME(); ///< HMAC-SHA256 (RFC4635) + static const Name& HMACSHA384_NAME(); ///< HMAC-SHA256 (RFC4635) + static const Name& HMACSHA512_NAME(); ///< HMAC-SHA256 (RFC4635) + static const Name& GSSTSIG_NAME(); ///< GSS-TSIG (RFC3645) + //@} + +private: + struct TSIGKeyImpl; + boost::shared_ptr<TSIGKeyImpl> impl_; +}; + +/// \brief A simple repository of a set of \c TSIGKey objects. +/// +/// This is a "key ring" to maintain TSIG keys (\c TSIGKey objects) and +/// provides trivial operations such as add, remove, and find. +/// +/// The keys are identified by their key names. +/// So, for example, two or more keys of the same key name but of different +/// algorithms are considered to be the same, and cannot be stored in the +/// key ring at the same time. +/// +/// <b>Implementation Note:</b> +/// For simplicity the initial implementation requests the application make +/// a copy of keys stored in the key ring if it needs to use the keys for +/// a long period (during which some of the keys may be removed). +/// This is based on the observations that a single server will not hold +/// a huge number of keys nor use keys in many different contexts (such as +/// in different DNS transactions). +/// If this assumption does not hold and memory consumption becomes an issue +/// we may have to revisit the design. +class TSIGKeyRing { +public: + /// Result codes of various public methods of \c TSIGKeyRing + enum Result { + SUCCESS = 0, ///< The operation is successful. + EXIST = 1, ///< A key is already stored in \c TSIGKeyRing. + NOTFOUND = 2 ///< The specified key is not found in \c TSIGKeyRing. + }; + + /// \brief A helper structure to represent the search result of + /// <code>TSIGKeyRing::find()</code>. + /// + /// This is a straightforward pair of the result code and a pointer + /// to the found key to represent the result of \c find(). + /// We use this in order to avoid overloading the return value for both + /// the result code ("success" or "not found") and the found object, + /// i.e., avoid using null to mean "not found", etc. + /// + /// This is a simple value class with no internal state, so for + /// convenience we allow the applications to refer to the members + /// directly. + /// + /// See the description of \c find() for the semantics of the member + /// variables. + struct FindResult { + FindResult(Result param_code, const TSIGKey* param_key) : + code(param_code), key(param_key) { + } + const Result code; + const TSIGKey* const key; + }; + + /// + /// \name Constructors and Destructor. + /// + /// \b Note: + /// The copy constructor and the assignment operator are + /// intentionally defined as private, making this class non copyable. + /// There is no technical reason why this class cannot be copied, + /// but since the key ring can potentially have a large number of keys, + /// a naive copy operation may cause unexpected overhead. + /// It's generally expected for an application to share the same + /// instance of key ring and share it throughout the program via + /// references, so we prevent the copy operation explicitly to avoid + /// unexpected copy operations. + //@{ +private: + TSIGKeyRing(const TSIGKeyRing& source); + TSIGKeyRing& operator=(const TSIGKeyRing& source); +public: + /// \brief The default constructor. + /// + /// This constructor never throws an exception. + TSIGKeyRing(); + + /// The destructor. + ~TSIGKeyRing(); + //@} + + /// Return the number of keys stored in the \c TSIGKeyRing. + /// + /// This method never throws an exception. + unsigned int size() const; + + /// Add a \c TSIGKey to the \c TSIGKeyRing. + /// + /// This method will create a local copy of the given key, so the caller + /// does not have to keep owning it. + /// + /// If internal resource allocation fails, a corresponding standard + /// exception will be thrown. + /// This method never throws an exception otherwise. + /// + /// \param key A \c TSIGKey to be added. + /// \return \c SUCCESS If the key is successfully added to the key ring. + /// \return \c EXIST The key ring already stores a key whose name is + /// identical to that of \c key. + Result add(const TSIGKey& key); + + /// Remove a \c TSIGKey for the given name from the \c TSIGKeyRing. + /// + /// This method never throws an exception. + /// + /// \param key_name The name of the key to be removed. + /// \return \c SUCCESS If the key is successfully removed from the key + /// ring. + /// \return \c NOTFOUND The key ring does not store the key that matches + /// \c key_name. + Result remove(const Name& key_name); + + /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing. + /// + /// It searches the internal storage for a \c TSIGKey whose name is + /// \c key_name. + /// It returns the result in the form of a \c FindResult + /// object as follows: + /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND. + /// - \c key: A pointer to the found \c TSIGKey object if one is found; + /// otherwise null. + /// + /// The pointer returned in the \c FindResult object is only valid until + /// the corresponding key is removed from the key ring. + /// The caller must ensure that the key is held in the key ring while + /// it needs to refer to it, or it must make a local copy of the key. + /// + /// This method never throws an exception. + /// + /// \param key_name The name of the key to be found. + /// \return A \c FindResult object enclosing the search result (see above). + FindResult find(const Name& key_name) const; + + /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing. + /// + /// It searches the internal storage for a \c TSIGKey whose name is + /// \c key_name and that uses the hash algorithm identified by + /// \c algorithm_name. + /// It returns the result in the form of a \c FindResult + /// object as follows: + /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND. + /// - \c key: A pointer to the found \c TSIGKey object if one is found; + /// otherwise null. + /// + /// The pointer returned in the \c FindResult object is only valid until + /// the corresponding key is removed from the key ring. + /// The caller must ensure that the key is held in the key ring while + /// it needs to refer to it, or it must make a local copy of the key. + /// + /// This method never throws an exception. + /// + /// \param key_name The name of the key to be found. + /// \param algorithm_name The name of the algorithm of the found key. + /// \return A \c FindResult object enclosing the search result (see above). + FindResult find(const Name& key_name, const Name& algorithm_name) const; + +private: + struct TSIGKeyRingImpl; + boost::shared_ptr<TSIGKeyRingImpl> impl_; +}; +} +} + +#endif // TSIGKEY_H diff --git a/src/lib/dns/tsigrecord.cc b/src/lib/dns/tsigrecord.cc new file mode 100644 index 0000000..28ff079 --- /dev/null +++ b/src/lib/dns/tsigrecord.cc @@ -0,0 +1,140 @@ +// Copyright (C) 2011-2024 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 <dns/exceptions.h> +#include <dns/messagerenderer.h> +#include <dns/rrclass.h> +#include <dns/rrttl.h> +#include <dns/tsigrecord.h> +#include <util/buffer.h> + +#include <ostream> +#include <string> + +using namespace isc::util; +using namespace isc::dns::rdata; + +namespace { +// Internally used constants: + +// Size in octets for the RR type, class TTL, RDLEN fields. +const size_t RR_COMMON_LEN = 10; + +// Size in octets for the fixed part of TSIG RDATAs. +// - Time Signed (6) +// - Fudge (2) +// - MAC Size (2) +// - Original ID (2) +// - Error (2) +// - Other Len (2) +const size_t RDATA_COMMON_LEN = 16; +} + +namespace isc { +namespace dns { +TSIGRecord::TSIGRecord(const Name& key_name, + const rdata::any::TSIG& tsig_rdata) : + key_name_(key_name), rdata_(tsig_rdata), + length_(RR_COMMON_LEN + RDATA_COMMON_LEN + key_name_.getLength() + + rdata_.getAlgorithm().getLength() + + rdata_.getMACSize() + rdata_.getOtherLen()) { +} + +namespace { +// This is a straightforward wrapper of dynamic_cast<const any::TSIG&>. +// We use this so that we can throw the DNSMessageFORMERR exception when +// unexpected type of RDATA is detected in the member initialization list +// of the constructor below. +const any::TSIG& +castToTSIGRdata(const rdata::Rdata& rdata) { + const any::TSIG* tsig_rdata = + dynamic_cast<const any::TSIG*>(&rdata); + if (!tsig_rdata) { + isc_throw(DNSMessageFORMERR, + "TSIG record is being constructed from " + "incompatible RDATA: " << rdata.toText()); + } + return (*tsig_rdata); +} +} + +TSIGRecord::TSIGRecord(const Name& name, const RRClass& rrclass, + const RRTTL& ttl, const rdata::Rdata& rdata, + size_t length) : + key_name_(name), rdata_(castToTSIGRdata(rdata)), length_(length) { + if (rrclass != getClass()) { + isc_throw(DNSMessageFORMERR, "Unexpected TSIG RR class: " << rrclass); + } + if (ttl != RRTTL(TSIG_TTL)) { + isc_throw(DNSMessageFORMERR, "Unexpected TSIG TTL: " << ttl); + } +} + +const RRClass& +TSIGRecord::getClass() { + return (RRClass::ANY()); +} + +const RRTTL& +TSIGRecord::getTTL() { + static RRTTL ttl(TSIG_TTL); + return (ttl); +} + +namespace { +template <typename OUTPUT> +void +toWireCommon(OUTPUT& output, const rdata::any::TSIG& rdata) { + // RR type, class, TTL are fixed constants. + RRType::TSIG().toWire(output); + TSIGRecord::getClass().toWire(output); + output.writeUint32(TSIGRecord::TSIG_TTL); + + // RDLEN + output.writeUint16(RDATA_COMMON_LEN + rdata.getAlgorithm().getLength() + + rdata.getMACSize() + rdata.getOtherLen()); + + // TSIG RDATA + rdata.toWire(output); +} +} + +uint32_t +TSIGRecord::toWire(AbstractMessageRenderer& renderer) const { + // If adding the TSIG would exceed the size limit, don't do it. + if (renderer.getLength() + length_ > renderer.getLengthLimit()) { + renderer.setTruncated(); + return (0); + } + + // key name = owner. note that we disable compression. + renderer.writeName(key_name_, false); + toWireCommon(renderer, rdata_); + return (1); +} + +uint32_t +TSIGRecord::toWire(OutputBuffer& buffer) const { + key_name_.toWire(buffer); + toWireCommon(buffer, rdata_); + return (1); +} + +std::string +TSIGRecord::toText() const { + return (key_name_.toText() + " " + RRTTL(TSIG_TTL).toText() + " " + + getClass().toText() + " " + RRType::TSIG().toText() + " " + + rdata_.toText() + "\n"); +} + +std::ostream& +operator<<(std::ostream& os, const TSIGRecord& record) { + return (os << record.toText()); +} +} // namespace dns +} // namespace isc diff --git a/src/lib/dns/tsigrecord.h b/src/lib/dns/tsigrecord.h new file mode 100644 index 0000000..1ab71b4 --- /dev/null +++ b/src/lib/dns/tsigrecord.h @@ -0,0 +1,299 @@ +// Copyright (C) 2011-2024 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 TSIGRECORD_H +#define TSIGRECORD_H + +#include <ostream> +#include <string> + +#include <boost/shared_ptr.hpp> + +#include <util/buffer.h> + +#include <dns/name.h> +#include <dns/rdataclass.h> + +namespace isc { +namespace dns { +class AbstractMessageRenderer; + +/// TSIG resource record. +/// +/// A \c TSIGRecord class object represents a TSIG resource record and is +/// responsible for conversion to and from wire format TSIG record based on +/// the protocol specification (RFC2845). +/// This class is provided so that other classes and applications can handle +/// TSIG without knowing protocol details of TSIG, such as that it uses a +/// fixed constant of TTL. +/// +/// \todo So the plan is to eventually provide the "from wire" constructor. +/// It's not yet provided in the current phase of development. +/// +/// \note +/// This class could be a derived class of \c AbstractRRset. That way +/// it would be able to be used in a polymorphic way; for example, +/// an application can construct a TSIG RR by itself and insert it to a +/// \c Message object as a generic RRset. On the other hand, it would mean +/// this class would have to implement an \c RdataIterator (even though it +/// can be done via straightforward forwarding) while the iterator is mostly +/// redundant since there should be one and only one RDATA for a valid TSIG +/// RR. Likewise, some methods such as \c setTTL() method wouldn't be well +/// defined due to such special rules for TSIG as using a fixed TTL. +/// Overall, TSIG is a very special RR type that simply uses the compatible +/// resource record format, and it will be unlikely that a user wants to +/// handle it through a generic interface in a polymorphic way. +/// We therefore chose to define it as a separate class. This is also +/// similar to why \c EDNS is a separate class. +class TSIGRecord { +public: + /// + /// \name Constructors + /// + /// We use the default copy constructor, default copy assignment operator, + /// (and default destructor) intentionally. + //@{ + /// Constructor from TSIG key name and RDATA + /// + /// \exception std::bad_alloc Resource allocation for copying the name or + /// RDATA fails + TSIGRecord(const Name& key_name, const rdata::any::TSIG& tsig_rdata); + + /// Constructor from resource record (RR) parameters. + /// + /// This constructor is intended to be used in the context of parsing + /// an incoming DNS message that contains a TSIG. The parser would + /// first extract the owner name, RR type (which is TSIG) class, TTL and + /// the TSIG RDATA from the message. This constructor is expected to + /// be given these RR parameters (except the RR type, because it must be + /// TSIG). + /// + /// According to RFC2845, a TSIG RR uses fixed RR class (ANY) and TTL (0). + /// If the RR class or TTL is different from the expected one, this + /// implementation considers it an invalid record and throws an exception + /// of class \c DNSMessageFORMERR. + /// + /// \note This behavior is not specified in the protocol specification, + /// but this implementation rejects unexpected values for the following + /// reasons (but in any case, this won't matter much in practice as + /// RFC2848 clearly states these fields always have the fixed values and + /// any sane implementation of TSIG signer will follow that): + /// According to the RFC editor (in a private communication), the intended + /// use of the TSIG TTL field is to signal protocol extensions (currently + /// no such extension is defined), so this field may actually be + /// validly non 0 in future. However, until the implementation supports + /// that extension it may not always be able to handle the extended + /// TSIG as intended; the extension may even affect digest computation. + /// There's a related issue on this point. Different implementations + /// interpret the RFC in different ways on the received TTL when + /// digesting the message: BIND 9 uses the received value (even if + /// it's not 0) as part of the TSIG variables; NLnet Labs' LDNS and NSD + /// always use a fixed constant of 0 regardless of the received TTL value. + /// This means if and when an extension with non 0 TTL is introduced + /// there will be interoperability problems in the form of verification + /// failure. By explicitly rejecting it (and subsequently returning + /// a response with a format error) we can indicate the source of the + /// problem more clearly than a "bad signature" TSIG error, which can + /// happen for various reasons. On the other hand, rejecting unexpected + /// RR classes is mostly for consistency; the RFC lists these two fields + /// in the same way, so it makes more sense to handle them equally. + /// (BIND 9 rejects unexpected RR classes for TSIG, but that is part of + /// general check on RR classes on received RRs; it generally requests + /// all classes are the same, and if the protocol specifies the use of + /// a particular class for a particular type of RR, it requests that + /// class be used). + /// + /// Likewise, if \c rdata is not of type \c any::TSIG, an exception of + /// class DNSMessageFORMERR will be thrown. When the caller is a + /// DNS message parser and builds \c rdata from incoming wire format + /// data as described above, this case happens when the RR class is + /// different from ANY (in the implementation, the type check takes place + /// before the explicit check against the RR class explained in the + /// previous paragraph). + /// + /// The \c length parameter is intended to be the length of the TSIG RR + /// (from the beginning of the owner name to the end of the RDATA) when + /// the caller is a DNS message parser. Note that it is the actual length + /// for the RR in the format; if the owner name or the algorithm name + /// (in the RDATA) is compressed (although the latter should not be + /// compressed according to RFC3597), the length must be the size of the + /// compressed data. The length is recorded inside the class and will + /// be returned via subsequent calls to \c getLength(). It's intended to + /// be used in the context TSIG verification; in the verify process + /// the MAC computation must be performed for the original data without + /// TSIG, so, to avoid parsing the entire data in the verify process + /// again, it's necessary to record information that can identify the + /// length to be digested for the MAC. This parameter serves for that + /// purpose. + /// + /// \note Since the constructor doesn't take the wire format data per se, + /// it doesn't (and cannot) check the validity of \c length, and simply + /// accepts any given value. It even accepts obviously invalid values + /// such as 0. It's caller's responsibility to provide a valid value of + /// length, and, the verifier's responsibility to use the length safely. + /// + /// <b>DISCUSSION:</b> this design is fragile in that it introduces + /// a tight coupling between message parsing and TSIG verification via + /// the \c TSIGRecord class. In terms of responsibility decoupling, + /// the ideal way to have \c TSIGRecord remember the entire wire data + /// along with the length of the TSIG. Then in the TSIG verification + /// we could refer to the necessary potion of data solely from a + /// \c TSIGRecord object. However, this approach would require expensive + /// heavy copy of the original data or introduce another kind of coupling + /// between the data holder and this class (if the original data is freed + /// while a \c TSIGRecord object referencing the data still exists, the + /// result will be catastrophic). As a "best current compromise", we + /// use the current design. We may reconsider it if it turns out to + /// cause a big problem or we come up with a better idea. + /// + /// \exception DNSMessageFORMERR Given RR parameters are invalid for TSIG. + /// \exception std::bad_alloc Internal resource allocation fails. + /// + /// \param name The owner name of the TSIG RR + /// \param rrclass The RR class of the RR. Must be \c RRClass::ANY() + /// (see above) + /// \param ttl The TTL of the RR. Must be 0 (see above) + /// \param rdata The RDATA of the RR. Must be of type \c any::TSIG. + /// \param length The size of the RR (see above) + TSIGRecord(const Name& name, const RRClass& rrclass, const RRTTL& ttl, + const rdata::Rdata& rdata, size_t length); + //@} + + /// Return the owner name of the TSIG RR, which is the TSIG key name + /// + /// \exception None + const Name& getName() const { + return (key_name_); + } + + /// Return the RDATA of the TSIG RR + /// + /// \exception None + const rdata::any::TSIG& getRdata() const { + return (rdata_); + } + + /// \name Protocol constants and defaults + /// + //@{ + /// Return the RR class of TSIG + /// + /// TSIG always uses the ANY RR class. This static method returns it, + /// when, though unlikely, an application wants to know which class TSIG + /// is supposed to use. + /// + /// \exception None + static const RRClass& getClass(); + + /// Return the TTL value of TSIG + /// + /// TSIG always uses 0 TTL. This static method returns it, + /// when, though unlikely, an application wants to know the TTL TSIG + /// is supposed to use. + /// + /// \exception None + static const RRTTL& getTTL(); + //@} + + /// Return the length of the TSIG record + /// + /// When constructed from the key name and RDATA, it is the length of + /// the record when it is rendered by the \c toWire() method. + /// + /// \note When constructed "from wire", that will mean the length of + /// the wire format data for the TSIG RR. The length will be necessary + /// to verify the message once parse is completed. + /// + /// \exception None + size_t getLength() const { + return (length_); + } + + /// \brief Render the \c TSIG RR in the wire format. + /// + /// This method renders the TSIG record as a form of a DNS TSIG RR + /// via \c renderer, which encapsulates output buffer and other rendering + /// contexts. + /// + /// Normally this version of \c toWire() method tries to compress the + /// owner name of the RR whenever possible, but this method intentionally + /// skips owner name compression. This is due to a report that some + /// Windows clients refuse a TSIG if its owner name is compressed + /// (See http://marc.info/?l=bind-workers&m=126637138430819&w=2). + /// Reportedly this seemed to be specific to GSS-TSIG, but this + /// implementation skip compression regardless of the algorithm. + /// + /// If by adding the TSIG RR the message size would exceed the limit + /// maintained in \c renderer, this method skips rendering the RR + /// and returns 0 and mark \c renderer as "truncated" (so that a + /// subsequent call to \c isTruncated() on \c renderer will result in + /// \c true); otherwise it returns 1, which is the number of RR + /// rendered. + /// + /// \note If the caller follows the specification of adding TSIG + /// as described in RFC2845, this should not happen; the caller is + /// generally expected to leave a sufficient room in the message for + /// the TSIG. But this method checks the unexpected case nevertheless. + /// + /// \exception std::bad_alloc Internal resource allocation fails (this + /// should be rare). + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// \return 1 if the TSIG RR fits in the message size limit; otherwise 0. + uint32_t toWire(AbstractMessageRenderer& renderer) const; + + /// \brief Render the \c TSIG RR in the wire format. + /// + /// This method is same as \c toWire(AbstractMessageRenderer&)const + /// except it renders the RR in an \c OutputBuffer and therefore + /// does not care about message size limit. + /// As a consequence it always returns 1. + uint32_t toWire(isc::util::OutputBuffer& buffer) const; + + /// Convert the TSIG record to a string. + /// + /// The output format is the same as the result of \c toText() for + /// other normal types of RRsets (with always using the same RR class + /// and TTL). It also ends with a newline. + /// + /// \exception std::bad_alloc Internal resource allocation fails (this + /// should be rare). + /// + /// \return A string representation of \c TSIG record + std::string toText() const; + + /// The TTL value to be used in TSIG RRs. + static const uint32_t TSIG_TTL = 0; + //@} + +private: + const Name key_name_; + const rdata::any::TSIG rdata_; + const size_t length_; +}; + +/// A pointer-like type pointing to a \c TSIGRecord object. +typedef boost::shared_ptr<TSIGRecord> TSIGRecordPtr; + +/// A pointer-like type pointing to an immutable \c TSIGRecord object. +typedef boost::shared_ptr<const TSIGRecord> ConstTSIGRecordPtr; + +/// Insert the \c TSIGRecord as a string into stream. +/// +/// This method convert \c record into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param record A \c TSIGRecord object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const TSIGRecord& record); +} +} + +#endif // TSIGRECORD_H diff --git a/src/lib/dns/txt_like.h b/src/lib/dns/txt_like.h new file mode 100644 index 0000000..f88b1a6 --- /dev/null +++ b/src/lib/dns/txt_like.h @@ -0,0 +1,233 @@ +// Copyright (C) 2011-2024 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 TXT_LIKE_H +#define TXT_LIKE_H + +#include <dns/master_lexer.h> +#include <dns/char_string.h> + +#include <stdint.h> +#include <algorithm> +#include <string> +#include <sstream> +#include <vector> + +namespace isc { +namespace dns { +namespace rdata { +namespace generic { +namespace detail { + +/// \brief \c rdata::TXTLikeImpl class represents the TXT-like RDATA for TXT +/// and SPF types. +/// +/// This class implements the basic interfaces inherited by the TXT and SPF +/// classes from the abstract \c rdata::Rdata class, and provides trivial +/// accessors to TXT-like RDATA. +template<class Type, uint16_t typeCode>class TXTLikeImpl { +public: + /// \brief Constructor from wire-format data. + /// + /// \param buffer A buffer storing the wire format data. + /// \param rdata_len The length of the RDATA in bytes, normally expected + /// to be the value of the RDLENGTH field of the corresponding RR. + /// + /// <b>Exceptions</b> + /// + /// \c InvalidRdataLength is thrown if rdata_len exceeds the maximum. + /// \c DNSMessageFORMERR is thrown if the RR is malformed. + TXTLikeImpl(util::InputBuffer& buffer, size_t rdata_len) { + if (rdata_len > MAX_RDLENGTH) { + isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len); + } + + if (rdata_len == 0) { // note that this couldn't happen in the loop. + isc_throw(DNSMessageFORMERR, "Error in parsing " << + RRType(typeCode) << " RDATA: 0-length character string"); + } + + do { + const uint8_t len = buffer.readUint8(); + if (rdata_len < len + 1) { + isc_throw(DNSMessageFORMERR, "Error in parsing " << + RRType(typeCode) << + " RDATA: character string length is too large: " << + static_cast<int>(len)); + } + std::vector<uint8_t> data(len + 1); + data[0] = len; + buffer.readData(&data[0] + 1, len); + string_list_.push_back(data); + + rdata_len -= (len + 1); + } while (rdata_len > 0); + } + + /// \brief Constructor from string. + /// + /// \throw CharStringTooLong the parameter string length exceeds maximum. + /// \throw InvalidRdataText the method cannot process the parameter data + explicit TXTLikeImpl(const std::string& txtstr) { + std::istringstream ss(txtstr); + MasterLexer lexer; + lexer.pushSource(ss); + + try { + buildFromTextHelper(lexer); + if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) { + isc_throw(InvalidRdataText, "Failed to construct " << + RRType(typeCode) << " RDATA from '" << txtstr << + "': extra new line"); + } + } catch (const MasterLexer::LexerError& ex) { + isc_throw(InvalidRdataText, "Failed to construct " << + RRType(typeCode) << " RDATA from '" << txtstr << "': " + << ex.what()); + } + } + + /// \brief Constructor using the master lexer. + /// + /// \throw CharStringTooLong the parameter string length exceeds maximum. + /// \throw InvalidRdataText the method cannot process the parameter data + /// + /// \param lexer A \c MasterLexer object parsing a master file for this + /// RDATA. + TXTLikeImpl(MasterLexer& lexer) { + buildFromTextHelper(lexer); + } + +private: + void buildFromTextHelper(MasterLexer& lexer) { + while (true) { + const MasterToken& token = lexer.getNextToken( + MasterToken::QSTRING, true); + if (token.getType() != MasterToken::STRING && + token.getType() != MasterToken::QSTRING) { + break; + } + string_list_.push_back(std::vector<uint8_t>()); + stringToCharString(token.getStringRegion(), string_list_.back()); + } + + // Let upper layer handle eol/eof. + lexer.ungetToken(); + + if (string_list_.empty()) { + isc_throw(InvalidRdataText, "Failed to construct " << + RRType(typeCode) << " RDATA: empty input"); + } + } + +public: + /// \brief The copy constructor. + /// + /// Trivial for now, we could've used the default one. + TXTLikeImpl(const TXTLikeImpl& other) : + string_list_(other.string_list_) + {} + + /// \brief Render the TXT-like data in the wire format to an OutputBuffer + /// object. + /// + /// \param buffer An output buffer to store the wire data. + void + toWire(util::OutputBuffer& buffer) const { + for (std::vector<std::vector<uint8_t> >::const_iterator it = + string_list_.begin(); + it != string_list_.end(); + ++it) + { + buffer.writeData(&(*it)[0], (*it).size()); + } + } + + /// \brief Render the TXT-like data in the wire format to an + /// AbstractMessageRenderer object. + /// + /// \param renderer An output AbstractMessageRenderer to send the wire data + /// to. + void + toWire(AbstractMessageRenderer& renderer) const { + for (std::vector<std::vector<uint8_t> >::const_iterator it = + string_list_.begin(); + it != string_list_.end(); + ++it) + { + renderer.writeData(&(*it)[0], (*it).size()); + } + } + + /// \brief Convert the TXT-like data to a string. + /// + /// \return A \c string object that represents the TXT-like data. + std::string + toText() const { + std::string s; + + for (std::vector<std::vector<uint8_t> >::const_iterator it = + string_list_.begin(); it != string_list_.end(); ++it) + { + if (!s.empty()) { + s.push_back(' '); + } + s.push_back('"'); + s.append(charStringToString(*it)); + s.push_back('"'); + } + + return (s); + } + + /// \brief Compare two instances of TXT-like RDATA. + /// + /// It is up to the caller to make sure that \c other is an object of the + /// same \c TXTLikeImpl class. + /// + /// \param other the right-hand operand to compare against. + /// \return < 0 if \c this would be sorted before \c other. + /// \return 0 if \c this is identical to \c other in terms of sorting + /// order. + /// \return > 0 if \c this would be sorted after \c other. + int + compare(const TXTLikeImpl& other) const { + // This implementation is not efficient. Revisit this (TBD). + util::OutputBuffer this_buffer(0); + toWire(this_buffer); + uint8_t const* const this_data = (uint8_t const*)this_buffer.getData(); + const size_t this_len = this_buffer.getLength(); + + util::OutputBuffer other_buffer(0); + other.toWire(other_buffer); + uint8_t const* const other_data + = (uint8_t const*)other_buffer.getData(); + const size_t other_len = other_buffer.getLength(); + + const size_t cmplen = std::min(this_len, other_len); + const int cmp = memcmp(this_data, other_data, cmplen); + + if (cmp != 0) { + return (cmp); + } else { + return ((this_len == other_len) ? 0 : + (this_len < other_len) ? -1 : 1); + } + } + +private: + /// Note: this is a prototype version; we may reconsider + /// this representation later. + std::vector<std::vector<uint8_t> > string_list_; +}; + +} // namespace detail +} // namespace generic +} // namespace rdata +} // namespace dns +} // namespace isc + +#endif // TXT_LIKE_H |