Parcourir la source

[2414] Merge branch 'master' into trac2414

Conflicts:
	src/bin/dhcp6/dhcp6_messages.mes
	src/bin/dhcp6/dhcp6_srv.cc
	src/bin/dhcp6/dhcp6_srv.h
	src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
	src/lib/dhcp/addr_utilities.cc
	src/lib/dhcp/tests/cfgmgr_unittest.cc

Files automerged successfully:
    src/lib/dhcp/alloc_engine.cc
    src/lib/dhcp/subnet.h
    src/lib/dhcp/tests/alloc_engine_unittest.cc
Stephen Morris il y a 12 ans
Parent
commit
aa1643faad
81 fichiers modifiés avec 5342 ajouts et 1802 suppressions
  1. 23 0
      ChangeLog
  2. 1 1
      Makefile.am
  3. 9 23
      configure.ac
  4. 1 1
      doc/Doxyfile
  5. 15 0
      examples/README
  6. 7 0
      examples/configure.ac
  7. 2 2
      examples/m4/ax_boost_include.m4
  8. 27 13
      examples/m4/ax_isc_bind10.m4
  9. 46 0
      examples/m4/ax_isc_rpath.m4
  10. 1 2
      src/bin/auth/Makefile.am
  11. 38 102
      src/bin/auth/auth.spec.pre.in
  12. 14 5
      src/bin/auth/auth_messages.mes
  13. 49 55
      src/bin/auth/auth_srv.cc
  14. 1 45
      src/bin/auth/auth_srv.h
  15. 1 2
      src/bin/auth/benchmarks/Makefile.am
  16. 1 45
      src/bin/auth/command.cc
  17. 201 5
      src/bin/auth/datasrc_clients_mgr.h
  18. 185 111
      src/bin/auth/statistics.cc
  19. 134 89
      src/bin/auth/statistics.h
  20. 609 0
      src/bin/auth/statistics_items.h
  21. 1 2
      src/bin/auth/tests/Makefile.am
  22. 188 44
      src/bin/auth/tests/auth_srv_unittest.cc
  23. 0 275
      src/bin/auth/tests/command_unittest.cc
  24. 334 13
      src/bin/auth/tests/datasrc_clients_builder_unittest.cc
  25. 49 0
      src/bin/auth/tests/datasrc_clients_mgr_unittest.cc
  26. 62 310
      src/bin/auth/tests/statistics_unittest.cc
  27. 2 0
      src/bin/auth/tests/test_datasrc_clients_mgr.cc
  28. 2 1
      src/bin/auth/tests/test_datasrc_clients_mgr.h
  29. 1 4
      src/bin/bindctl/tests/bindctl_test.py
  30. 1 0
      src/bin/dhcp6/Makefile.am
  31. 441 31
      src/bin/dhcp6/config_parser.cc
  32. 2 0
      src/bin/dhcp6/dhcp6.dox
  33. 65 4
      src/bin/dhcp6/dhcp6.spec
  34. 15 0
      src/bin/dhcp6/dhcp6_messages.mes
  35. 71 12
      src/bin/dhcp6/dhcp6_srv.cc
  36. 19 7
      src/bin/dhcp6/dhcp6_srv.h
  37. 1 0
      src/bin/dhcp6/tests/Makefile.am
  38. 504 6
      src/bin/dhcp6/tests/config_parser_unittest.cc
  39. 185 21
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  40. 2 1
      src/bin/sysinfo/.gitignore
  41. 1 0
      src/bin/sysinfo/Makefile.am
  42. 38 0
      src/bin/sysinfo/run_sysinfo.sh.in
  43. 2 4
      src/bin/sysinfo/sysinfo.py.in
  44. 7 0
      src/bin/tests/process_rename_test.py.in
  45. 0 1
      src/lib/bench/example/search_bench.cc
  46. 0 1
      src/lib/datasrc/memory/benchmarks/rdata_reader_bench.cc
  47. 0 1
      src/lib/datasrc/memory/benchmarks/rrset_render_bench.cc
  48. 6 2
      src/lib/datasrc/tests/client_list_unittest.cc
  49. 6 7
      src/lib/dhcp/addr_utilities.cc
  50. 4 2
      src/lib/dhcp/alloc_engine.cc
  51. 102 0
      src/lib/dhcp/libdhcp++.cc
  52. 53 4
      src/lib/dhcp/libdhcp++.h
  53. 6 1
      src/lib/dhcp/option_definition.cc
  54. 85 4
      src/lib/dhcp/option_definition.h
  55. 6 1
      src/lib/dhcp/subnet.h
  56. 11 25
      src/lib/dhcp/tests/alloc_engine_unittest.cc
  57. 1 0
      src/lib/dhcp/tests/cfgmgr_unittest.cc
  58. 82 0
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  59. 50 0
      src/lib/dhcp/tests/option_definition_unittest.cc
  60. 4 0
      src/lib/dns/Makefile.am
  61. 0 1
      src/lib/dns/benchmarks/message_renderer_bench.cc
  62. 47 0
      src/lib/dns/master_lexer.cc
  63. 241 0
      src/lib/dns/master_lexer.h
  64. 154 0
      src/lib/dns/master_lexer_inputsource.cc
  65. 164 0
      src/lib/dns/master_lexer_inputsource.h
  66. 109 26
      src/lib/dns/name.cc
  67. 42 0
      src/lib/dns/name.h
  68. 2 0
      src/lib/dns/tests/Makefile.am
  69. 325 0
      src/lib/dns/tests/master_lexer_inputsource_unittest.cc
  70. 156 0
      src/lib/dns/tests/master_lexer_token_unittest.cc
  71. 110 14
      src/lib/dns/tests/name_unittest.cc
  72. 30 7
      src/lib/python/isc/sysinfo/sysinfo.py
  73. 53 12
      src/lib/python/isc/sysinfo/tests/sysinfo_test.py
  74. 6 5
      src/lib/statistics/Makefile.am
  75. 0 82
      src/lib/statistics/counter.cc
  76. 33 11
      src/lib/statistics/counter.h
  77. 0 265
      src/lib/statistics/counter_dict.cc
  78. 91 89
      src/lib/statistics/counter_dict.h
  79. 0 1
      src/lib/statistics/tests/Makefile.am
  80. 4 3
      tests/lettuce/features/ddns_system.feature
  81. 1 1
      tests/lettuce/features/inmemory_over_sqlite3.feature

+ 23 - 0
ChangeLog

@@ -1,3 +1,26 @@
+499.	[func]		team
+	The b10-auth 'loadzone' command now uses the internal thread
+	introduced in 495 to (re)load a zone in the background, so that
+	query processing isn't blocked while loading a zone.
+	(Trac #2213, git 686594e391c645279cc4a95e0e0020d1c01fba7e)
+
+498.	[func]		marcin
+	Implemented DHCPv6 option values configuration using configuration
+	manager. In order to set values for data fields carried by the
+	particular option, user specifies the string of hexadecimal digits
+	that is in turn converted to binary data and stored into option buffer.
+	More user friendly way of option content specification is planned.
+	(Trac #2318, git e75c686cd9c14f4d6c2a242a0a0853314704fee9)
+
+497.	[bug]		jinmei
+	Fixed several issues in isc-sysinfo:
+	- make sure it doesn't report a negative value for free memory
+	  size (this happened on FreeBSD, but can possibly occur on other
+	  BSD variants)
+	- correctly identifies the SMP support in kernel on FreeBSD
+	- print more human readable uptime as well as the time in seconds
+	(Trac #2297, git 59a449f506948e2371ffa87dcd19059388bd1657)
+
 496.	[func]		tomek
 	DHCPv6 Allocation Engine implemented. It allows address allocation
 	from the configured subnets/pools. It currently features a single

+ 1 - 1
Makefile.am

@@ -1,4 +1,4 @@
-ACLOCAL_AMFLAGS = -I m4macros ${ACLOCAL_FLAGS}
+ACLOCAL_AMFLAGS = -I m4macros -I examples/m4 ${ACLOCAL_FLAGS}
 # ^^^^^^^^ This has to be the first line and cannot come later in this
 # Makefile.am due to some bork in some versions of autotools.
 

+ 9 - 23
configure.ac

@@ -64,25 +64,9 @@ AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
 
 # Linker options
 
-# check -R and -Wl,-R rather than gcc specific -rpath to be as portable
-# as possible.
-AC_MSG_CHECKING([whether -R flag is available in linker])
-LDFLAGS_SAVED="$LDFLAGS"
-LDFLAGS="$LDFLAGS -R/usr/lib"
-AC_TRY_LINK([],[],
-    [ AC_MSG_RESULT(yes)
-        rpath_flag=-R
-    ],[ AC_MSG_RESULT(no)
-        AC_MSG_CHECKING([whether -Wl,-R flag is available in linker])
-        LDFLAGS="$LDFLAGS_SAVED -Wl,-R"
-        AC_TRY_LINK([], [],
-            [ AC_MSG_RESULT(yes)
-                rpath_flag=-Wl,-R
-            ],[ AC_MSG_RESULT(no)
-                 rpath_flag=no
-            ])
-    ])
-LDFLAGS=$LDFLAGS_SAVED
+# check -R, "-Wl,-R" or -rpath (we share the AX function defined in
+#  examples/m4)
+AX_ISC_RPATH
 
 # Compiler dependent settings: define some mandatory CXXFLAGS here.
 # We also use a separate variable B10_CXXFLAGS.  This will (and should) be
@@ -332,10 +316,10 @@ fi
 # modules, we embed the path to the modules when possible.  We do this even
 # when the path is known in the common operational environment (e.g. when
 # it's stored in a common "hint" file) for simplicity.
-if test $rpath_flag != no; then
+if test "x$ISC_RPATH_FLAG" != "x"; then
 	python_rpath=
 	for flag in ${PYTHON_LDFLAGS}; do
-		python_rpath="${python_rpath} `echo $flag | sed -ne "s/^\(\-L\)/${rpath_flag}/p"`"
+		python_rpath="${python_rpath} `echo $flag | sed -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
 	done
 	PYTHON_LDFLAGS="${PYTHON_LDFLAGS} ${python_rpath}"
 fi
@@ -701,10 +685,10 @@ for flag in ${BOTAN_LIBS}; do
 done
 
 # See python_rpath for some info on why we do this
-if test $rpath_flag != no; then
+if test "x$ISC_RPATH_FLAG" != "x"; then
     BOTAN_RPATH=
     for flag in ${BOTAN_LIBS}; do
-            BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne "s/^\(\-L\)/${rpath_flag}/p"`"
+            BOTAN_RPATH="${BOTAN_RPATH} `echo $flag | sed -ne "s/^\(\-L\)/${ISC_RPATH_FLAG}/p"`"
     done
 AC_SUBST(BOTAN_RPATH)
 
@@ -1299,6 +1283,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/zonemgr/tests/zonemgr_test
            src/bin/zonemgr/run_b10-zonemgr.sh
            src/bin/sysinfo/sysinfo.py
+           src/bin/sysinfo/run_sysinfo.sh
            src/bin/stats/stats.py
            src/bin/stats/stats_httpd.py
            src/bin/bind10/bind10_src.py
@@ -1377,6 +1362,7 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/bin/loadzone/run_loadzone.sh
            chmod +x src/bin/loadzone/tests/correct/correct_test.sh
            chmod +x src/bin/loadzone/tests/error/error_test.sh
+           chmod +x src/bin/sysinfo/run_sysinfo.sh
            chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh
            chmod +x src/bin/msgq/run_msgq.sh
            chmod +x src/bin/msgq/tests/msgq_test

+ 1 - 1
doc/Doxyfile

@@ -580,7 +580,7 @@ INPUT                  = ../src/lib/exceptions ../src/lib/cc \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
     ../src/lib/util/threads/ ../src/lib/resolve ../src/lib/acl \
-    ../src/bin/dhcp6 ../src/lib/dhcp ../src/bin/dhcp4 \
+    ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/bin/dhcp4 \
     ../tests/tools/perfdhcp devel
 
 # This tag can be used to specify the character encoding of the source files

+ 15 - 0
examples/README

@@ -30,3 +30,18 @@ to the configure.ac file:
 sinclude(m4/ax_boost_include.m4)
 sinclude(m4/ax_isc_bind10.m4)
 (and same for other m4 files as they are added under m4/)
+
+On some systems, espeically if you have installed the BIND 10
+libraries in an uncommon path, programs linked with the BIND 10
+library may not work at run time due to the "missing" shared library.
+Normally, you should be able to avoid this problem by making sure
+to invoking the program explicitly specifying the path to the library,
+e.g., "LD_LIBRARY_PATH=/usr/local/lib/bind10 ./my_bind10_app", or
+you may not even notice the issue if you have installed BIND 10
+library in a common library path on your system (sometimes you may
+still need to run ldconfig(8) beforehand).  Another option is to embed
+the path to the library in your program.  While this approach is
+controversial, and some people rather choose the alternatives, we
+provide a helper tool in case you want to use this option: see the
+lines using BIND10_RPATH in the sample configure.ac file of this
+directory.

+ 7 - 0
examples/configure.ac

@@ -14,6 +14,13 @@ AC_LANG([C++])
 # Checks for BIND 10 headers and libraries
 AX_ISC_BIND10
 
+# We use -R, -rpath etc so the resulting program will be more likekly to
+# "just work" by default.  Embedding a specific library path is a controversial
+# practice, though; if you don't like it you can remove the following setting.
+if test "x$BIND10_RPATH" != "x"; then
+   LDFLAGS="$LDFLAGS $BIND10_RPATH"
+fi
+
 # For the example host program, we require the BIND 10 DNS library
 if test "x$BIND10_DNS_LIB" = "x"; then
    AC_MSG_ERROR([unable to find BIND 10 DNS library needed to build 'host'])

+ 2 - 2
examples/m4/ax_boost_include.m4

@@ -34,7 +34,7 @@ if test -z "$with_boost_include"; then
 		fi
 	done
 fi
-CPPFLAGS_SAVES="$CPPFLAGS"
+CPPFLAGS_SAVED="$CPPFLAGS"
 if test "${boost_include_path}" ; then
 	BOOST_CPPFLAGS="-I${boost_include_path}"
 	CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
@@ -57,7 +57,7 @@ AC_TRY_COMPILE([
  CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
 [AC_MSG_RESULT(yes)])
 
-CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
+CPPFLAGS="$CPPFLAGS_SAVED $CPPFLAGS_BOOST_THREADCONF"
 AC_SUBST(BOOST_CPPFLAGS)
 
 AC_LANG_RESTORE

+ 27 - 13
examples/m4/ax_isc_bind10.m4

@@ -1,4 +1,4 @@
-dnl @synopsis AX_BIND10
+dnl @synopsis AX_ISC_BIND10
 dnl
 dnl @summary figure out how to build C++ programs using ISC BIND 10 libraries
 dnl
@@ -20,9 +20,18 @@ dnl Checks for other BIND 10 module libraries are option, as not all
 dnl applications need all libraries.  The main configure.ac can handle any
 dnl missing library as fatal by checking whether the corresponding
 dnl BIND10_xxx_LIB is defined.
+dnl
+dnl In addition, it sets the BIND10_RPATH variable to a usable linker option
+dnl to embed the path to the BIND 10 library to the programs that are to be
+dnl linked with the library.  If the developer wants to use the option,
+dnl it can be used as follows:
+dnl if test "x$BIND10_RPATH" != "x"; then
+dnl     LDFLAGS="$LDFLAGS $BIND10_RPATH"
+dnl fi
 
 AC_DEFUN([AX_ISC_BIND10], [
 AC_REQUIRE([AX_BOOST_INCLUDE])
+AC_REQUIRE([AX_ISC_RPATH])
 AC_LANG_SAVE
 AC_LANG([C++])
 
@@ -42,19 +51,20 @@ if test "$bind10_inc_path" = "no"; then
 	fi
    done
 fi
-CPPFLAGS_SAVES="$CPPFLAGS"
+CPPFLAGS_SAVED="$CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS" # boost headers will be used in buffer.h
 if test "${bind10_inc_path}" != "no"; then
    BIND10_CPPFLAGS="-I${bind10_inc_path}"
    CPPFLAGS="$CPPFLAGS $BIND10_CPPFLAGS"
 fi
 AC_CHECK_HEADERS([util/buffer.h],,
-  AC_MSG_ERROR([Missing a commonly used BIND 10 header files]))
-CPPFLAGS="$CPPFLAGS_SAVES"
+  AC_MSG_ERROR([Missing a commonly used BIND 10 header file]))
+CPPFLAGS="$CPPFLAGS_SAVED"
 AC_SUBST(BIND10_CPPFLAGS)
 
 # Check for BIND10 libraries
 CPPFLAGS_SAVED="$CPPFLAGS"
-CPPFLAGS="$CPPFLAGS $BIND10_CPPFLAGS"
+CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS $BIND10_CPPFLAGS"
 
 AC_ARG_WITH(bind10-lib,
   AS_HELP_STRING([--with-bind10-lib=PATH],
@@ -70,21 +80,25 @@ fi
 # make sure we have buildable libraries
 AC_MSG_CHECKING([for BIND 10 common library])
 BIND10_COMMON_LIB="-lb10-util -lb10-exceptions"
-LDFLAGS="$LDFLAGS $BIND10_LDFLAGS"
+LDFLAGS_SAVED="$LDFLAGS"
+LDFLAGS_CHECK_COMMON="$LDFLAGS $BIND10_LDFLAGS"
+LIBS_SAVED="$LIBS"
 LIBS="$LIBS $BIND10_COMMON_LIB"
 for d in $bind10_lib_dirs
 do
-  LDFLAGS_SAVED="$LDFLAGS"
-  LDFLAGS="$LDFLAGS -L$d"
+  LDFLAGS="$LDFLAGS_CHECK_COMMON -L$d"
   AC_TRY_LINK([
 #include <util/buffer.h>
 ],[
 isc::util::OutputBuffer buffer(0);
-], [BIND10_LDFLAGS="-L${d}"])
+], [BIND10_LDFLAGS="-L${d}"
+    if test "x$ISC_RPATH_FLAG" != "x"; then
+       BIND10_RPATH="${ISC_RPATH_FLAG}${d}"
+    fi
+    ])
   if test "x$BIND10_LDFLAGS" != "x"; then
      break
   fi
-  LDFLAGS="$LDFLAGS_SAVED"
 done
 if test "x$BIND10_LDFLAGS" != "x"; then
   AC_MSG_RESULT(yes)
@@ -94,7 +108,7 @@ else
 fi
 
 # restore LIBS once at this point
-LIBS="$LIBS_SAVES"
+LIBS="$LIBS_SAVED"
 
 AC_SUBST(BIND10_LDFLAGS)
 AC_SUBST(BIND10_COMMON_LIB)
@@ -111,12 +125,12 @@ isc::dns::RRType rrtype(1);
 ], [BIND10_DNS_LIB="-lb10-dns++"
     AC_MSG_RESULT(yes)],
    [AC_MSG_RESULT(no)])
-LIBS="$LIBS_SAVES"
+LIBS="$LIBS_SAVED"
 AC_SUBST(BIND10_DNS_LIB)
 
 # Restore other flags
 CPPFLAGS="$CPPFLAGS_SAVED"
-LDFLAGS="$LDFLAGS_SAVES"
+LDFLAGS="$LDFLAGS_SAVED"
 
 AC_LANG_RESTORE
 ])dnl AX_ISC_BIND10

+ 46 - 0
examples/m4/ax_isc_rpath.m4

@@ -0,0 +1,46 @@
+dnl @synopsis AX_ISC_RPATH
+dnl
+dnl @summary figure out whether and which "rpath" linker option is available
+dnl
+dnl This macro checks if the linker supports an option to embed a path
+dnl to a runtime library (often installed in an uncommon place), such as
+dnl gcc's -rpath option.  If found, it sets the ISC_RPATH_FLAG variable to
+dnl the found option flag.  The main configure.ac can use it as follows:
+dnl if test "x$ISC_RPATH_FLAG" != "x"; then
+dnl     LDFLAGS="$LDFLAGS ${ISC_RPATH_FLAG}/usr/local/lib/some_library"
+dnl fi
+
+AC_DEFUN([AX_ISC_RPATH], [
+
+# We'll tweak both CXXFLAGS and CCFLAGS so this function will work whichever
+# language is used in the main script.  Note also that it's not LDFLAGS;
+# technically this is a linker flag, but we've noticed $LDFLAGS can be placed
+# where the compiler could interpret it as a compiler option, leading to
+# subtle failure mode.  So, in the check below using the compiler flag is
+# safer (in the actual Makefiles the flag should be set in LDFLAGS).
+CXXFLAGS_SAVED="$CXXFLAGS"
+CXXFLAGS="$CXXFLAGS -Wl,-R/usr/lib"
+CCFLAGS_SAVED="$CCFLAGS"
+CCFLAGS="$CCFLAGS -Wl,-R/usr/lib"
+
+# check -Wl,-R and -R rather than gcc specific -rpath to be as portable
+# as possible.  -Wl,-R seems to be safer, so we try it first.  In some cases
+# -R is not actually recognized but AC_TRY_LINK doesn't fail due to that.
+AC_MSG_CHECKING([whether -Wl,-R flag is available in linker])
+AC_TRY_LINK([],[],
+    [ AC_MSG_RESULT(yes)
+        ISC_RPATH_FLAG=-Wl,-R
+    ],[ AC_MSG_RESULT(no)
+        AC_MSG_CHECKING([whether -R flag is available in linker])
+	CXXFLAGS="$CXXFLAGS_SAVED -R"
+	CCFLAGS="$CCFLAGS_SAVED -R"
+        AC_TRY_LINK([], [],
+            [ AC_MSG_RESULT([yes; note that -R is more sensitive about the position in option arguments])
+                ISC_RPATH_FLAG=-R
+            ],[ AC_MSG_RESULT(no) ])
+    ])
+
+CXXFLAGS=$CXXFLAGS_SAVED
+CCFLAGS=$CCFLAGS_SAVED
+
+])dnl AX_ISC_RPATH

+ 1 - 2
src/bin/auth/Makefile.am

@@ -54,7 +54,7 @@ b10_auth_SOURCES += auth_log.cc auth_log.h
 b10_auth_SOURCES += auth_config.cc auth_config.h
 b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += common.h common.cc
-b10_auth_SOURCES += statistics.cc statistics.h
+b10_auth_SOURCES += statistics.cc statistics.h statistics_items.h
 b10_auth_SOURCES += datasrc_clients_mgr.h
 b10_auth_SOURCES += datasrc_config.h datasrc_config.cc
 b10_auth_SOURCES += main.cc
@@ -74,7 +74,6 @@ b10_auth_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_auth_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_auth_LDADD += $(top_builddir)/src/lib/xfr/libb10-xfr.la
 b10_auth_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la
-b10_auth_LDADD += $(top_builddir)/src/lib/statistics/libb10-statistics.la
 b10_auth_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 b10_auth_LDADD += $(SQLITE_LIBS)
 

+ 38 - 102
src/bin/auth/auth.spec.pre.in

@@ -145,7 +145,7 @@
         "item_type": "integer",
         "item_optional": false,
         "item_default": 0,
-        "item_title": "Queries TCP ",
+        "item_title": "Queries TCP",
         "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
       },
       {
@@ -181,14 +181,6 @@
         "item_description": "The number of total request counts whose opcode is status"
       },
       {
-        "item_name": "opcode.reserved3",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 3",
-        "item_description": "The number of total request counts whose opcode is 3 (reserved)"
-      },
-      {
         "item_name": "opcode.notify",
         "item_type": "integer",
         "item_optional": true,
@@ -205,84 +197,12 @@
         "item_description": "The number of total request counts whose opcode is update"
       },
       {
-        "item_name": "opcode.reserved6",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 6",
-        "item_description": "The number of total request counts whose opcode is 6 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved7",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 7",
-        "item_description": "The number of total request counts whose opcode is 7 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved8",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 8",
-        "item_description": "The number of total request counts whose opcode is 8 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved9",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 9",
-        "item_description": "The number of total request counts whose opcode is 9 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved10",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 10",
-        "item_description": "The number of total request counts whose opcode is 10 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved11",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 11",
-        "item_description": "The number of total request counts whose opcode is 11 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved12",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 12",
-        "item_description": "The number of total request counts whose opcode is 12 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved13",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 13",
-        "item_description": "The number of total request counts whose opcode is 13 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved14",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode 14",
-        "item_description": "The number of total request counts whose opcode is 14 (reserved)"
-      },
-      {
-        "item_name": "opcode.reserved15",
+        "item_name": "opcode.other",
         "item_type": "integer",
         "item_optional": true,
         "item_default": 0,
-        "item_title": "Received requests opcode 15",
-        "item_description": "The number of total request counts whose opcode is 15 (reserved)"
+        "item_title": "Received requests opcode other",
+        "item_description": "The number of total request counts whose opcode is other (not well-known)"
       },
       {
         "item_name": "rcode.noerror",
@@ -373,52 +293,68 @@
         "item_description": "The number of total responses with rcode 10 (NOTZONE)"
       },
       {
-        "item_name": "rcode.reserved11",
+        "item_name": "rcode.badsigvers",
         "item_type": "integer",
         "item_optional": true,
         "item_default": 0,
-        "item_title": "Sent response with rcode 11",
-        "item_description": "The number of total responses with rcode 11 (reserved)"
+        "item_title": "Sent 'EDNS version not implemented' response",
+        "item_description": "The number of total responses with rcode 16 (BADVERS)"
       },
       {
-        "item_name": "rcode.reserved12",
+        "item_name": "rcode.badkey",
         "item_type": "integer",
         "item_optional": true,
         "item_default": 0,
-        "item_title": "Sent response with rcode 12",
-        "item_description": "The number of total responses with rcode 12 (reserved)"
+        "item_title": "Sent 'Key not recognized' response",
+        "item_description": "The number of total responses with rcode 17 (BADKEY)"
       },
       {
-        "item_name": "rcode.reserved13",
+        "item_name": "rcode.badtime",
         "item_type": "integer",
         "item_optional": true,
         "item_default": 0,
-        "item_title": "Sent response with rcode 13",
-        "item_description": "The number of total responses with rcode 13 (reserved)"
+        "item_title": "Sent 'Signature out of time window' response",
+        "item_description": "The number of total responses with rcode 18 (BADTIME)"
       },
       {
-        "item_name": "rcode.reserved14",
+        "item_name": "rcode.badmode",
         "item_type": "integer",
         "item_optional": true,
         "item_default": 0,
-        "item_title": "Sent response with rcode 14",
-        "item_description": "The number of total responses with rcode 14 (reserved)"
+        "item_title": "Sent 'Bad TKEY Mode' response",
+        "item_description": "The number of total responses with rcode 19 (BADMODE)"
       },
       {
-        "item_name": "rcode.reserved15",
+        "item_name": "rcode.badname",
         "item_type": "integer",
         "item_optional": true,
         "item_default": 0,
-        "item_title": "Sent response with rcode 15",
-        "item_description": "The number of total responses with rcode 15 (reserved)"
+        "item_title": "Sent 'Duplicate key name' response",
+        "item_description": "The number of total responses with rcode 20 (BADNAME)"
       },
       {
-        "item_name": "rcode.badvers",
+        "item_name": "rcode.badalg",
         "item_type": "integer",
         "item_optional": true,
         "item_default": 0,
-        "item_title": "Sent 'EDNS version not implemented' response",
-        "item_description": "The number of total responses with rcode 16 (BADVERS)"
+        "item_title": "Sent 'Algorithm not supported' response",
+        "item_description": "The number of total responses with rcode 21 (BADALG)"
+      },
+      {
+        "item_name": "rcode.badtrunc",
+        "item_type": "integer",
+        "item_optional": true,
+        "item_default": 0,
+        "item_title": "Sent 'Bad Truncation' response",
+        "item_description": "The number of total responses with rcode 22 (BADTRUNC)"
+      },
+      {
+        "item_name": "rcode.other",
+        "item_type": "integer",
+        "item_optional": true,
+        "item_default": 0,
+        "item_title": "Sent responses with rcode other",
+        "item_description": "The number of total responses with rcode other (not well-known)"
       }
     ]
   }

+ 14 - 5
src/bin/auth/auth_messages.mes

@@ -61,6 +61,15 @@ the message.
 A debug message, showing when the separate thread for maintaining data
 source clients receives a command from the manager.
 
+% AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR command execution failure: %1
+The separate thread for maintaining data source clients failed to complete a
+command given by the main thread.  In most cases this is some kind of
+configuration or temporary error such as an attempt to load a non-existent
+zone or a temporary DB connection failure.  So the event is just logged and
+the thread keeps running.  In some rare cases, however, this may indicate an
+internal bug and it may be better to restart the entire program, so the log
+message should be carefully examined.
+
 % AUTH_DATASRC_CLIENTS_BUILDER_FAILED data source builder thread stopped due to an exception: %1
 The separate thread for maintaining data source clients has been
 terminated due to some uncaught exception.  When this happens, the
@@ -79,6 +88,11 @@ indicate some run time failure than program errors.  As in the other
 failure case, the thread terminates the entire process immediately
 after logging this message.
 
+% AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE loaded zone %1/%2
+This debug message is issued when the separate thread for maintaining data
+source clients successfully loaded the named zone of the named class as a
+result of the 'loadzone' command.
+
 % AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_CONFIG_ERROR Error in data source configuration: %1
 The thread for maintaining data source clients has received a command to
 reconfigure, but the parameter data (the new configuration) contains an
@@ -164,11 +178,6 @@ has requested the keyring holding TSIG keys from the configuration
 database. It is issued during server startup is an indication that the
 initialization is proceeding normally.
 
-% AUTH_LOAD_ZONE loaded zone %1/%2
-This debug message is issued during the processing of the 'loadzone' command
-when the authoritative server has successfully loaded the named zone of the
-named class.
-
 % AUTH_MEM_DATASRC_DISABLED memory data source is disabled for class %1
 This is a debug message reporting that the authoritative server has
 discovered that the memory data source is disabled for the given class.

+ 49 - 55
src/bin/auth/auth_srv.cc

@@ -85,6 +85,7 @@ using namespace isc::xfr;
 using namespace isc::asiolink;
 using namespace isc::asiodns;
 using namespace isc::server_common::portconfig;
+using isc::auth::statistics::Counters;
 
 namespace {
 // A helper class for cleaning up message renderer.
@@ -260,7 +261,7 @@ public:
     AbstractSession* xfrin_session_;
 
     /// Query counters for statistics
-    AuthCounters counters_;
+    Counters counters_;
 
     /// Addresses we listen on
     AddressList listen_addresses_;
@@ -285,27 +286,29 @@ public:
 
     /// \brief Resume the server
     ///
-    /// This is a wrapper call for DNSServer::resume(done), if 'done' is true,
-    /// the Rcode set in the given Message is counted in the statistics
-    /// counter.
+    /// This is a wrapper call for DNSServer::resume(done). Query/Response
+    /// statistics counters are incremented in this method.
     ///
     /// This method is expected to be called by processMessage()
     ///
     /// \param server The DNSServer as passed to processMessage()
     /// \param message The response as constructed by processMessage()
-    /// \param done If true, the Rcode from the given message is counted,
-    ///             this value is then passed to server->resume(bool)
+    /// \param stats_attrs Query/response attributes for statistics which is
+    ///                    not in \p messsage.
+    ///                    Note: This parameter is modified inside this method
+    ///                          to store whether the answer has been sent and
+    ///                          the response is truncated.
+    /// \param done If true, it indicates there is a response.
+    ///             this value will be passed to server->resume(bool)
     void resumeServer(isc::asiodns::DNSServer* server,
                       isc::dns::Message& message,
-                      bool done);
+                      statistics::QRAttributes& stats_attrs,
+                      const bool done);
 
 private:
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
 
-    /// Increment query counter
-    void incCounter(const int protocol);
-
     // validateStatistics
     bool validateStatistics(isc::data::ConstElementPtr data) const;
 
@@ -494,6 +497,12 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                         OutputBuffer& buffer, DNSServer* server)
 {
     InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
+    statistics::QRAttributes stats_attrs;
+
+    // statistics: check transport carrying the message (IP, transport)
+    stats_attrs.setQueryIPVersion(io_message.getRemoteEndpoint().getFamily());
+    stats_attrs.setQueryTransportProtocol(
+        io_message.getRemoteEndpoint().getProtocol());
 
     // First, check the header part.  If we fail even for the base header,
     // just drop the message.
@@ -503,13 +512,13 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         // Ignore all responses.
         if (message.getHeaderFlag(Message::HEADERFLAG_QR)) {
             LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_RECEIVED);
-            impl_->resumeServer(server, message, false);
+            impl_->resumeServer(server, message, stats_attrs, false);
             return;
         }
     } catch (const Exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_HEADER_PARSE_FAIL)
                   .arg(ex.what());
-        impl_->resumeServer(server, message, false);
+        impl_->resumeServer(server, message, stats_attrs, false);
         return;
     }
 
@@ -520,13 +529,13 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PROTOCOL_ERROR)
                   .arg(error.getRcode().toText()).arg(error.what());
         makeErrorMessage(impl_->renderer_, message, buffer, error.getRcode());
-        impl_->resumeServer(server, message, true);
+        impl_->resumeServer(server, message, stats_attrs, true);
         return;
     } catch (const Exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_ERROR)
                   .arg(ex.what());
         makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
-        impl_->resumeServer(server, message, true);
+        impl_->resumeServer(server, message, stats_attrs, true);
         return;
     } // other exceptions will be handled at a higher layer.
 
@@ -549,21 +558,35 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                                            **impl_->keyring_));
         tsig_error = tsig_context->verify(tsig_record, io_message.getData(),
                                           io_message.getDataSize());
+        // statistics: check TSIG attributes
+        // SIG(0) is currently not implemented in Auth, but it is implemented
+        // in BIND 9. At the point we support it, the code to check if the
+        // signature is valid would be around here.
+        stats_attrs.setQuerySig(true, false,
+                                tsig_error == TSIGError::NOERROR());
     }
 
     if (tsig_error != TSIGError::NOERROR()) {
         makeErrorMessage(impl_->renderer_, message, buffer,
                          tsig_error.toRcode(), tsig_context);
-        impl_->resumeServer(server, message, true);
+        impl_->resumeServer(server, message, stats_attrs, true);
         return;
     }
 
     const Opcode opcode = message.getOpcode();
     bool send_answer = true;
     try {
-        // update per opcode statistics counter.  This can only be reliable
-        // after TSIG check succeeds.
-        impl_->counters_.inc(message.getOpcode());
+        // statistics: check EDNS
+        //     note: This can only be reliable after TSIG check succeeds.
+        ConstEDNSPtr edns = message.getEDNS();
+        if (edns != NULL) {
+            stats_attrs.setQueryEDNS(true, edns->getVersion() == 0);
+            stats_attrs.setQueryDO(edns->getDNSSECAwareness());
+        }
+
+        // statistics: check OpCode
+        //     note: This can only be reliable after TSIG check succeeds.
+        stats_attrs.setQueryOpCode(opcode.getCode());
 
         if (opcode == Opcode::NOTIFY()) {
             send_answer = impl_->processNotify(io_message, message, buffer,
@@ -605,7 +628,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE_UNKNOWN);
         makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
     }
-    impl_->resumeServer(server, message, send_answer);
+    impl_->resumeServer(server, message, stats_attrs, send_answer);
 }
 
 bool
@@ -622,9 +645,6 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
     message.setHeaderFlag(Message::HEADERFLAG_AA);
     message.setRcode(Rcode::NOERROR());
 
-    // Increment query counter.
-    incCounter(io_message.getSocket().getProtocol());
-
     if (remote_edns) {
         EDNSPtr local_edns = EDNSPtr(new EDNS());
         local_edns->setDNSSECAwareness(dnssec_ok);
@@ -675,9 +695,6 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
                              OutputBuffer& buffer,
                              auto_ptr<TSIGContext> tsig_context)
 {
-    // Increment query counter.
-    incCounter(io_message.getSocket().getProtocol());
-
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_UDP);
         makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
@@ -810,19 +827,6 @@ AuthSrvImpl::processUpdate(const IOMessage& io_message) {
 }
 
 void
-AuthSrvImpl::incCounter(const int protocol) {
-    // Increment query counter.
-    if (protocol == IPPROTO_UDP) {
-        counters_.inc(AuthCounters::SERVER_UDP_QUERY);
-    } else if (protocol == IPPROTO_TCP) {
-        counters_.inc(AuthCounters::SERVER_TCP_QUERY);
-    } else {
-        // unknown protocol
-        isc_throw(Unexpected, "Unknown protocol: " << protocol);
-    }
-}
-
-void
 AuthSrvImpl::registerStatisticsValidator() {
     counters_.registerStatisticsValidator(
         boost::bind(&AuthSrvImpl::validateStatistics, this, _1));
@@ -839,10 +843,15 @@ AuthSrvImpl::validateStatistics(isc::data::ConstElementPtr data) const {
 }
 
 void
-AuthSrvImpl::resumeServer(DNSServer* server, Message& message, bool done) {
+AuthSrvImpl::resumeServer(DNSServer* server, Message& message,
+                          statistics::QRAttributes& stats_attrs,
+                          const bool done) {
     if (done) {
-        counters_.inc(message.getRcode());
+        stats_attrs.answerWasSent();
+        // isTruncated from MessageRenderer
+        stats_attrs.setResponseTruncated(renderer_.isTruncated());
     }
+    counters_.inc(stats_attrs, message);
     server->resume(done);
 }
 
@@ -865,21 +874,6 @@ ConstElementPtr AuthSrv::getStatistics() const {
     return (impl_->counters_.getStatistics());
 }
 
-uint64_t
-AuthSrv::getCounter(const AuthCounters::ServerCounterType type) const {
-    return (impl_->counters_.getCounter(type));
-}
-
-uint64_t
-AuthSrv::getCounter(const Opcode opcode) const {
-    return (impl_->counters_.getCounter(opcode));
-}
-
-uint64_t
-AuthSrv::getCounter(const Rcode rcode) const {
-    return (impl_->counters_.getCounter(rcode));
-}
-
 const AddressList&
 AuthSrv::getListenAddresses() const {
     return (impl_->listen_addresses_);

+ 1 - 45
src/bin/auth/auth_srv.h

@@ -222,55 +222,11 @@ public:
     /// \brief Returns statistics data
     ///
     /// This function can throw an exception from
-    /// AuthCounters::getStatistics().
+    /// Counters::getStatistics().
     ///
     /// \return JSON format statistics data.
     isc::data::ConstElementPtr getStatistics() const;
 
-    /// \brief Get the value of counter in the AuthCounters.
-    ///
-    /// This function calls AuthCounters::getStatistics() and
-    /// returns its return value.
-    ///
-    /// This function never throws an exception as far as
-    /// AuthCounters::getStatistics() doesn't throw.
-    ///
-    /// Note: Currently this function is for testing purpose only.
-    ///
-    /// \param type Type of a counter to get the value of
-    ///
-    /// \return the value of the counter.
-
-    uint64_t getCounter(const AuthCounters::ServerCounterType type) const;
-
-    /// \brief Get the value of per Opcode counter in the Auth Counters.
-    ///
-    /// This function calls AuthCounters::getCounter(isc::dns::Opcode) and
-    /// returns its return value.
-    ///
-    /// \note This is a tentative interface as an attempt of experimentally
-    /// supporting more statistics counters.  This should eventually be more
-    /// generalized.  In any case, this method is mainly for testing.
-    ///
-    /// \throw None
-    /// \param opcode The opcode of the counter to get the value of
-    /// \return the value of the counter.
-    uint64_t getCounter(const isc::dns::Opcode opcode) const;
-
-    /// \brief Get the value of per Rcode counter in the Auth Counters.
-    ///
-    /// This function calls AuthCounters::getCounter(isc::dns::Rcode) and
-    /// returns its return value.
-    ///
-    /// \note This is a tentative interface as an attempt of experimentally
-    /// supporting more statistics counters.  This should eventually be more
-    /// generalized.  In any case, this method is mainly for testing.
-    ///
-    /// \throw None
-    /// \param rcode The rcode of the counter to get the value of
-    /// \return the value of the counter.
-    uint64_t getCounter(const isc::dns::Rcode rcode) const;
-
     /**
      * \brief Set and get the addresses we listen on.
      */

+ 1 - 2
src/bin/auth/benchmarks/Makefile.am

@@ -15,7 +15,7 @@ query_bench_SOURCES = query_bench.cc
 query_bench_SOURCES += ../query.h  ../query.cc
 query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
 query_bench_SOURCES += ../auth_config.h ../auth_config.cc
-query_bench_SOURCES += ../statistics.h ../statistics.cc
+query_bench_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h
 query_bench_SOURCES += ../auth_log.h ../auth_log.cc
 query_bench_SOURCES += ../datasrc_config.h ../datasrc_config.cc
 
@@ -34,7 +34,6 @@ query_bench_LDADD += $(top_builddir)/src/lib/nsas/libb10-nsas.la
 query_bench_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 query_bench_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la
 query_bench_LDADD += $(top_builddir)/src/lib/asiodns/libb10-asiodns.la
-query_bench_LDADD += $(top_builddir)/src/lib/statistics/libb10-statistics.la
 query_bench_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 query_bench_LDADD += $(SQLITE_LIBS)
 

+ 1 - 45
src/bin/auth/command.cc

@@ -176,51 +176,7 @@ public:
     virtual ConstElementPtr exec(AuthSrv& server,
                                  isc::data::ConstElementPtr args)
     {
-        if (args == NULL) {
-            isc_throw(AuthCommandError, "Null argument");
-        }
-
-        ConstElementPtr class_elem = args->get("class");
-        RRClass zone_class(class_elem ? RRClass(class_elem->stringValue()) :
-            RRClass::IN());
-
-        ConstElementPtr origin_elem = args->get("origin");
-        if (!origin_elem) {
-            isc_throw(AuthCommandError, "Zone origin is missing");
-        }
-        const Name origin(origin_elem->stringValue());
-
-        DataSrcClientsMgr::Holder holder(server.getDataSrcClientsMgr());
-        boost::shared_ptr<ConfigurableClientList> list =
-            holder.findClientList(zone_class);
-
-        if (!list) {
-            isc_throw(AuthCommandError, "There's no client list for "
-                      "class " << zone_class);
-        }
-
-        switch (list->reload(origin)) {
-            case ConfigurableClientList::ZONE_SUCCESS:
-                // Everything worked fine.
-                LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
-                    .arg(zone_class).arg(origin);
-                return (createAnswer());
-            case ConfigurableClientList::ZONE_NOT_FOUND:
-                isc_throw(AuthCommandError, "Zone " << origin << "/" <<
-                          zone_class << " was not found in any configured "
-                          "data source. Configure it first.");
-            case ConfigurableClientList::ZONE_NOT_CACHED:
-                isc_throw(AuthCommandError, "Zone " << origin << "/" <<
-                          zone_class << " is not served from memory, but "
-                          "directly from the data source. It is not possible "
-                          "to reload it into memory. Configure it to be cached "
-                          "first.");
-            case ConfigurableClientList::CACHE_DISABLED:
-                // This is an internal error. Auth server must have the cache
-                // enabled.
-                isc_throw(isc::Unexpected, "Cache disabled in client list of "
-                          "class " << zone_class);
-        }
+        server.getDataSrcClientsMgr().loadZone(args);
         return (createAnswer());
     }
 };

+ 201 - 5
src/bin/auth/datasrc_clients_mgr.h

@@ -27,6 +27,7 @@
 
 #include <datasrc/data_source.h>
 #include <datasrc/client_list.h>
+#include <datasrc/memory/zone_writer.h>
 
 #include <auth/auth_log.h>
 #include <auth/datasrc_config.h>
@@ -37,12 +38,26 @@
 #include <boost/noncopyable.hpp>
 
 #include <exception>
+#include <cassert>
 #include <list>
 #include <utility>
 
 namespace isc {
 namespace auth {
 
+/// \brief An exception that is thrown if initial checks for a command fail
+///
+/// This is raised *before* the command to the thread is constructed and
+/// sent, so the application can still handle them (and therefore it is
+/// public, as opposed to InternalCommandError).
+///
+/// And example of its use is currently in loadZone().
+class CommandError : public isc::Exception {
+public:
+    CommandError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 namespace datasrc_clientmgr_internal {
 // This namespace is essentially private for DataSrcClientsMgr(Base) and
 // DataSrcClientsBuilder(Base).  This is exposed in the public header
@@ -55,6 +70,9 @@ enum CommandID {
     RECONFIGURE,  ///< Reconfigure the datasource client lists,
                   ///  the argument to the command is the full new
                   ///  datasources configuration.
+    LOADZONE,     ///< Load a new version of zone into a memory,
+                  ///  the argument to the command is a map containing 'class'
+                  ///  and 'origin' elements, both should have been validated.
     SHUTDOWN,     ///< Shutdown the builder; no argument
     NUM_COMMANDS
 };
@@ -234,6 +252,60 @@ public:
         clients_map_ = new_lists;
     }
 
+    /// \brief Instruct internal thread to (re)load a zone
+    ///
+    /// \param args Element argument that should be a map of the form
+    /// { "class": "IN", "origin": "example.com" }
+    /// (but class is optional and will default to IN)
+    ///
+    /// \exception CommandError if the args value is null, or not in
+    ///                                 the expected format, or contains
+    ///                                 a bad origin or class string
+    void
+    loadZone(data::ConstElementPtr args) {
+        if (!args) {
+            isc_throw(CommandError, "loadZone argument empty");
+        }
+        if (args->getType() != isc::data::Element::map) {
+            isc_throw(CommandError, "loadZone argument not a map");
+        }
+        if (!args->contains("origin")) {
+            isc_throw(CommandError,
+                      "loadZone argument has no 'origin' value");
+        }
+        // Also check if it really is a valid name
+        try {
+            dns::Name(args->get("origin")->stringValue());
+        } catch (const isc::Exception& exc) {
+            isc_throw(CommandError, "bad origin: " << exc.what());
+        }
+
+        if (args->get("origin")->getType() != data::Element::string) {
+            isc_throw(CommandError,
+                      "loadZone argument 'origin' value not a string");
+        }
+        if (args->contains("class")) {
+            if (args->get("class")->getType() != data::Element::string) {
+                isc_throw(CommandError,
+                          "loadZone argument 'class' value not a string");
+            }
+            // Also check if it is a valid class
+            try {
+                dns::RRClass(args->get("class")->stringValue());
+            } catch (const isc::Exception& exc) {
+                isc_throw(CommandError, "bad class: " << exc.what());
+            }
+        }
+
+        // Note: we could do some more advanced checks here,
+        // e.g. check if the zone is known at all in the configuration.
+        // For now these are skipped, but one obvious way to
+        // implement it would be to factor out the code from
+        // the start of doLoadZone(), and call it here too
+
+        sendCommand(datasrc_clientmgr_internal::LOADZONE, args);
+    }
+
 private:
     // This is expected to be called at the end of the destructor.  It
     // actually does nothing, but provides a customization point for
@@ -290,14 +362,27 @@ namespace datasrc_clientmgr_internal {
 /// threads or locks.
 template <typename MutexType, typename CondVarType>
 class DataSrcClientsBuilderBase : boost::noncopyable {
+private:
+    typedef std::map<dns::RRClass,
+                     boost::shared_ptr<datasrc::ConfigurableClientList> >
+    ClientListsMap;
+
 public:
+    /// \brief Internal errors in handling commands.
+    ///
+    /// This exception is expected to be caught within the
+    /// \c DataSrcClientsBuilder implementation, but is defined as public
+    /// so tests can be checked it.
+    class InternalCommandError : public isc::Exception {
+    public:
+        InternalCommandError(const char* file, size_t line, const char* what) :
+            isc::Exception(file, line, what) {}
+    };
+
     /// \brief Constructor.
     ///
     /// It simply sets up a local copy of shared data with the manager.
     ///
-    /// Note: this will take actual set (map) of data source clients and
-    /// a mutex object for it in #2210 or #2212.
-    ///
     /// \throw None
     DataSrcClientsBuilderBase(std::list<Command>* command_queue,
                               CondVarType* cond, MutexType* queue_mutex,
@@ -365,6 +450,11 @@ private:
         }
     }
 
+    void doLoadZone(const isc::data::ConstElementPtr& arg);
+    boost::shared_ptr<datasrc::memory::ZoneWriter> getZoneWriter(
+        datasrc::ConfigurableClientList& client_list,
+        const dns::RRClass& rrclass, const dns::Name& origin);
+
     // The following are shared with the manager
     std::list<Command>* command_queue_;
     CondVarType* cond_;
@@ -397,7 +487,13 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::run() {
             } // the lock is released here.
 
             while (keep_running && !current_commands.empty()) {
-                keep_running = handleCommand(current_commands.front());
+                try {
+                    keep_running = handleCommand(current_commands.front());;
+                } catch (const InternalCommandError& e) {
+                    LOG_ERROR(auth_logger,
+                              AUTH_DATASRC_CLIENTS_BUILDER_COMMAND_ERROR).
+                        arg(e.what());
+                }
                 current_commands.pop_front();
             }
         }
@@ -426,7 +522,7 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     }
 
     const boost::array<const char*, NUM_COMMANDS> command_desc = {
-        {"NOOP", "RECONFIGURE", "SHUTDOWN"}
+        {"NOOP", "RECONFIGURE", "LOADZONE", "SHUTDOWN"}
     };
     LOG_DEBUG(auth_logger, DBGLVL_TRACE_BASIC,
               AUTH_DATASRC_CLIENTS_BUILDER_COMMAND).arg(command_desc.at(cid));
@@ -434,6 +530,9 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     case RECONFIGURE:
         doReconfigure(command.second);
         break;
+    case LOADZONE:
+        doLoadZone(command.second);
+        break;
     case SHUTDOWN:
         return (false);
     case NOOP:
@@ -444,6 +543,103 @@ DataSrcClientsBuilderBase<MutexType, CondVarType>::handleCommand(
     }
     return (true);
 }
+
+template <typename MutexType, typename CondVarType>
+void
+DataSrcClientsBuilderBase<MutexType, CondVarType>::doLoadZone(
+    const isc::data::ConstElementPtr& arg)
+{
+    // We assume some basic level validation as this method can only be
+    // called via the manager in practice.  manager is expected to do the
+    // minimal validation.
+    assert(arg);
+    assert(arg->get("origin"));
+
+    // TODO: currently, we hardcode IN as the default for the optional
+    // 'class' argument. We should really derive this from the specification,
+    // but at the moment the config/command API does not allow that to be
+    // done easily. Once that is in place (tickets have yet to be created,
+    // as we need to do a tiny bit of design work for that), this
+    // code can be replaced with the original part:
+    // assert(arg->get("class"));
+    // const dns::RRClass(arg->get("class")->stringValue());
+    isc::data::ConstElementPtr class_elem = arg->get("class");
+    const dns::RRClass rrclass(class_elem ?
+                                dns::RRClass(class_elem->stringValue()) :
+                                dns::RRClass::IN());
+    const dns::Name origin(arg->get("origin")->stringValue());
+    ClientListsMap::iterator found = (*clients_map_)->find(rrclass);
+    if (found == (*clients_map_)->end()) {
+        isc_throw(InternalCommandError, "failed to load a zone " << origin <<
+                  "/" << rrclass << ": not configured for the class");
+    }
+
+    boost::shared_ptr<datasrc::ConfigurableClientList> client_list =
+        found->second;
+    assert(client_list);
+
+    try {
+        boost::shared_ptr<datasrc::memory::ZoneWriter> zwriter =
+            getZoneWriter(*client_list, rrclass, origin);
+
+        zwriter->load(); // this can take time but doesn't cause a race
+        {   // install() can cause a race and must be in a critical section
+            typename MutexType::Locker locker(*map_mutex_);
+            zwriter->install();
+        }
+        LOG_DEBUG(auth_logger, DBG_AUTH_OPS,
+                  AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE)
+            .arg(origin).arg(rrclass);
+
+        // same as load(). We could let the destructor do it, but do it
+        // ourselves explicitly just in case.
+        zwriter->cleanup();
+    } catch (const InternalCommandError& ex) {
+        throw;     // this comes from getZoneWriter.  just let it go through.
+    } catch (const isc::Exception& ex) {
+        // We catch our internal exceptions (which will be just ignored) and
+        // propagated others (which should generally be considered fatal and
+        // will make the thread terminate)
+        isc_throw(InternalCommandError, "failed to load a zone " << origin <<
+                  "/" << rrclass << ": error occurred in reload: " <<
+                  ex.what());
+    }
+}
+
+// A dedicated subroutine of doLoadZone().  Separated just for keeping the
+// main method concise.
+template <typename MutexType, typename CondVarType>
+boost::shared_ptr<datasrc::memory::ZoneWriter>
+DataSrcClientsBuilderBase<MutexType, CondVarType>::getZoneWriter(
+    datasrc::ConfigurableClientList& client_list,
+    const dns::RRClass& rrclass, const dns::Name& origin)
+{
+    const datasrc::ConfigurableClientList::ZoneWriterPair writerpair =
+        client_list.getCachedZoneWriter(origin);
+
+    switch (writerpair.first) {
+    case datasrc::ConfigurableClientList::ZONE_SUCCESS:
+        assert(writerpair.second);
+        return (writerpair.second);
+    case datasrc::ConfigurableClientList::ZONE_NOT_FOUND:
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": not found in any configured "
+                  "data source.");
+    case datasrc::ConfigurableClientList::ZONE_NOT_CACHED:
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": not served from memory");
+    case datasrc::ConfigurableClientList::CACHE_DISABLED:
+        // This is an internal error. Auth server must have the cache
+        // enabled.
+        isc_throw(InternalCommandError, "failed to load zone " << origin
+                  << "/" << rrclass << ": internal failure, in-memory cache "
+                  "is somehow disabled");
+    }
+
+    // all cases above should either return or throw, but some compilers
+    // still need a return statement
+    return (boost::shared_ptr<datasrc::memory::ZoneWriter>());
+}
 } // namespace datasrc_clientmgr_internal
 
 /// \brief Shortcut type for normal data source clients manager.

+ 185 - 111
src/bin/auth/statistics.cc

@@ -13,9 +13,11 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <auth/statistics.h>
+#include <auth/statistics_items.h>
 #include <auth/auth_log.h>
 
 #include <dns/opcode.h>
+#include <dns/rcode.h>
 
 #include <cc/data.h>
 #include <cc/session.h>
@@ -32,107 +34,206 @@
 
 #include <boost/noncopyable.hpp>
 
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
 using namespace isc::dns;
 using namespace isc::auth;
 using namespace isc::statistics;
 
-// TODO: We need a namespace ("auth_server"?) to hold
-// AuthSrv and AuthCounters.
+namespace isc {
+namespace auth {
+namespace statistics {
 
 // TODO: Make use of wrappers like isc::dns::Opcode
 // for counter item type.
 
-class AuthCountersImpl : boost::noncopyable {
+class CountersImpl : boost::noncopyable {
 public:
-    AuthCountersImpl();
-    ~AuthCountersImpl();
-    void inc(const AuthCounters::ServerCounterType type);
-    void inc(const Opcode opcode) {
-        opcode_counter_.inc(opcode.getCode());
-    }
-    void inc(const Rcode rcode) {
-        rcode_counter_.inc(rcode.getCode());
-    }
-    void inc(const std::string& zone,
-             const AuthCounters::PerZoneCounterType type);
+    CountersImpl();
+    ~CountersImpl();
+    void inc(const QRAttributes& qrattrs, const Message& response);
     isc::data::ConstElementPtr getStatistics() const;
-    void registerStatisticsValidator
-    (AuthCounters::validator_type validator);
-    // Currently for testing purpose only
-    uint64_t getCounter(const AuthCounters::ServerCounterType type) const;
-    uint64_t getCounter(const Opcode opcode) const {
-        return (opcode_counter_.get(opcode.getCode()));
-    }
-    uint64_t getCounter(const Rcode rcode) const {
-        return (rcode_counter_.get(rcode.getCode()));
-    }
+    void registerStatisticsValidator(Counters::validator_type validator);
 private:
-    Counter server_counter_;
-    Counter opcode_counter_;
-    static const size_t NUM_OPCODES = 16;
-    Counter rcode_counter_;
-    static const size_t NUM_RCODES = 17;
-    CounterDictionary per_zone_counter_;
-    AuthCounters::validator_type validator_;
+    // counter for query/response
+    Counter server_qr_counter_;
+    // set of counters for zones
+    CounterDictionary zone_qr_counters_;
+    // validator
+    Counters::validator_type validator_;
 };
 
-AuthCountersImpl::AuthCountersImpl() :
-    // initialize counter
-    // size of server_counter_: AuthCounters::SERVER_COUNTER_TYPES
-    // size of per_zone_counter_: AuthCounters::PER_ZONE_COUNTER_TYPES
-    server_counter_(AuthCounters::SERVER_COUNTER_TYPES),
-    opcode_counter_(NUM_OPCODES), rcode_counter_(NUM_RCODES),
-    per_zone_counter_(AuthCounters::PER_ZONE_COUNTER_TYPES)
-{
-    per_zone_counter_.addElement("_SERVER_");
-}
+CountersImpl::CountersImpl() :
+    // size of server_qr_counter_, zone_qr_counters_: QR_COUNTER_TYPES
+    server_qr_counter_(QR_COUNTER_TYPES),
+    zone_qr_counters_(QR_COUNTER_TYPES),
+    validator_()
+{}
 
-AuthCountersImpl::~AuthCountersImpl()
+CountersImpl::~CountersImpl()
 {}
 
 void
-AuthCountersImpl::inc(const AuthCounters::ServerCounterType type) {
-    server_counter_.inc(type);
-}
+CountersImpl::inc(const QRAttributes& qrattrs, const Message& response) {
+    // protocols carrying request
+    if (qrattrs.req_ip_version_ == AF_INET) {
+        server_qr_counter_.inc(QR_REQUEST_IPV4);
+    } else if (qrattrs.req_ip_version_ == AF_INET6) {
+        server_qr_counter_.inc(QR_REQUEST_IPV6);
+    }
+    if (qrattrs.req_transport_protocol_ == IPPROTO_UDP) {
+        server_qr_counter_.inc(QR_REQUEST_UDP);
+    } else if (qrattrs.req_transport_protocol_ == IPPROTO_TCP) {
+        server_qr_counter_.inc(QR_REQUEST_TCP);
+    }
 
-void
-AuthCountersImpl::inc(const std::string& zone,
-                      const AuthCounters::PerZoneCounterType type)
-{
-    per_zone_counter_[zone].inc(type);
+    // query TSIG
+    if (qrattrs.req_is_tsig_) {
+        server_qr_counter_.inc(QR_REQUEST_TSIG);
+    }
+    if (qrattrs.req_is_sig0_) {
+        server_qr_counter_.inc(QR_REQUEST_SIG0);
+    }
+    if (qrattrs.req_is_badsig_) {
+        server_qr_counter_.inc(QR_REQUEST_BADSIG);
+        // If signature validation is failed, no other attributes are reliable
+        return;
+    }
+
+    // query EDNS
+    if (qrattrs.req_is_edns_0_) {
+        server_qr_counter_.inc(QR_REQUEST_EDNS0);
+    }
+    if (qrattrs.req_is_edns_badver_) {
+        server_qr_counter_.inc(QR_REQUEST_BADEDNSVER);
+    }
+
+    // query DNSSEC
+    if (qrattrs.req_is_dnssec_ok_) {
+        server_qr_counter_.inc(QR_REQUEST_DNSSEC_OK);
+    }
+
+    // QTYPE
+    unsigned int qtype_type = QR_QTYPE_OTHER;
+    const QuestionIterator qiter = response.beginQuestion();
+    if (qiter != response.endQuestion()) {
+        // get the first and only question section and
+        // get the qtype code
+        const unsigned int qtype = (*qiter)->getType().getCode();
+        if (qtype < 258) {
+            // qtype 0..257: lookup qtype-countertype table
+            qtype_type = QRQTypeToQRCounterType[qtype];
+        } else if (qtype < 32768) {
+            // qtype 258..32767: (Unassigned)
+            qtype_type = QR_QTYPE_OTHER;
+        } else if (qtype < 32770) {
+            // qtype 32768..32769: TA and DLV
+            qtype_type = QR_QTYPE_TA + (qtype - 32768);
+        } else {
+            // qtype 32770..65535: (Unassigned, Private use, Reserved)
+            qtype_type = QR_QTYPE_OTHER;
+        }
+    }
+    server_qr_counter_.inc(qtype_type);
+    // OPCODE
+    server_qr_counter_.inc(QROpCodeToQRCounterType[qrattrs.req_opcode_]);
+
+    // response
+    if (qrattrs.answer_sent_) {
+        // responded
+        server_qr_counter_.inc(QR_RESPONSE);
+
+        // response truncated
+        if (qrattrs.res_is_truncated_) {
+            server_qr_counter_.inc(QR_RESPONSE_TRUNCATED);
+        }
+
+        // response EDNS
+        ConstEDNSPtr response_edns = response.getEDNS();
+        if (response_edns != NULL && response_edns->getVersion() == 0) {
+            server_qr_counter_.inc(QR_RESPONSE_EDNS0);
+        }
+
+        // response TSIG
+        if (qrattrs.req_is_tsig_) {
+            // assume response is TSIG signed if request is TSIG signed
+            server_qr_counter_.inc(QR_RESPONSE_TSIG);
+        }
+
+        // response SIG(0) is currently not implemented
+
+        // RCODE
+        const unsigned int rcode = response.getRcode().getCode();
+        unsigned int rcode_type = QR_RCODE_OTHER;
+        if (rcode < 23) {
+            // rcode 0..22: lookup rcode-countertype table
+            rcode_type = QRRCodeToQRCounterType[rcode];
+        } else {
+            // opcode larger than 22 is reserved or unassigned
+            rcode_type = QR_RCODE_OTHER;
+        }
+        server_qr_counter_.inc(rcode_type);
+
+        // compound attributes
+        const unsigned int answer_rrs =
+            response.getRRCount(Message::SECTION_ANSWER);
+        const bool is_aa_set = response.getHeaderFlag(Message::HEADERFLAG_AA);
+
+        if (is_aa_set) {
+            // QryAuthAns
+            server_qr_counter_.inc(QR_QRYAUTHANS);
+        } else {
+            // QryNoAuthAns
+            server_qr_counter_.inc(QR_QRYNOAUTHANS);
+        }
+
+        if (rcode == Rcode::NOERROR_CODE) {
+            if (answer_rrs > 0) {
+                // QrySuccess
+                server_qr_counter_.inc(QR_QRYSUCCESS);
+            } else {
+                if (is_aa_set) {
+                    // QryNxrrset
+                    server_qr_counter_.inc(QR_QRYNXRRSET);
+                } else {
+                    // QryReferral
+                    server_qr_counter_.inc(QR_QRYREFERRAL);
+                }
+            }
+        } else if (rcode == Rcode::REFUSED_CODE) {
+            // AuthRej
+            server_qr_counter_.inc(QR_QRYREJECT);
+        }
+    }
 }
 
 isc::data::ConstElementPtr
-AuthCountersImpl::getStatistics() const {
+CountersImpl::getStatistics() const {
     std::stringstream statistics_string;
     statistics_string << "{ \"queries.udp\": "
-                      << server_counter_.get(AuthCounters::SERVER_UDP_QUERY)
+                      << server_qr_counter_.get(QR_REQUEST_UDP)
                       << ", \"queries.tcp\": "
-                      << server_counter_.get(AuthCounters::SERVER_TCP_QUERY);
+                      << server_qr_counter_.get(QR_REQUEST_TCP);
     // Insert non 0 Opcode counters.
-    for (int i = 0; i < NUM_OPCODES; ++i) {
-        const Counter::Type counter = opcode_counter_.get(i);
+    for (int i = QR_OPCODE_QUERY; i <= QR_OPCODE_OTHER; ++i) {
+        const Counter::Type counter = server_qr_counter_.get(i);
         if (counter != 0) {
-            // The counter item name should be derived lower-cased textual
-            // representation of the code.
-            std::string opcode_txt = Opcode(i).toText();
-            std::transform(opcode_txt.begin(), opcode_txt.end(),
-                           opcode_txt.begin(), ::tolower);
-            statistics_string << ", \"opcode." << opcode_txt << "\": "
-                              << counter;
+            statistics_string << ", \"" << "opcode." <<
+                                 QRCounterOpcode[i - QR_OPCODE_QUERY].name <<
+                                 "\": " << counter;
         }
     }
     // Insert non 0 Rcode counters.
-    for (int i = 0; i < NUM_RCODES; ++i) {
-        const Counter::Type counter = rcode_counter_.get(i);
+    for (int i = QR_RCODE_NOERROR; i <= QR_RCODE_OTHER; ++i) {
+        const Counter::Type counter = server_qr_counter_.get(i);
         if (counter != 0) {
-            // The counter item name should be derived lower-cased textual
-            // representation of the code.
-            std::string rcode_txt = Rcode(i).toText();
-            std::transform(rcode_txt.begin(), rcode_txt.end(),
-                           rcode_txt.begin(), ::tolower);
-            statistics_string << ", \"rcode." << rcode_txt << "\": "
-                              << counter;
+            statistics_string << ", \"" << "rcode." <<
+                                 QRCounterRcode[i - QR_RCODE_NOERROR].name <<
+                                 "\": " << counter;
         }
     }
     statistics_string << "}";
@@ -150,61 +251,34 @@ AuthCountersImpl::getStatistics() const {
 }
 
 void
-AuthCountersImpl::registerStatisticsValidator
-    (AuthCounters::validator_type validator)
+CountersImpl::registerStatisticsValidator
+    (Counters::validator_type validator)
 {
     validator_ = validator;
 }
 
-// Currently for testing purpose only
-uint64_t
-AuthCountersImpl::getCounter(const AuthCounters::ServerCounterType type) const {
-    return (server_counter_.get(type));
-}
-
-AuthCounters::AuthCounters() : impl_(new AuthCountersImpl())
+Counters::Counters() : impl_(new CountersImpl())
 {}
 
-AuthCounters::~AuthCounters() {}
+Counters::~Counters() {}
 
 void
-AuthCounters::inc(const AuthCounters::ServerCounterType type) {
-    impl_->inc(type);
-}
-
-void
-AuthCounters::inc(const Opcode opcode) {
-    impl_->inc(opcode);
-}
-
-void
-AuthCounters::inc(const Rcode rcode) {
-    impl_->inc(rcode);
+Counters::inc(const QRAttributes& qrattrs, const Message& response) {
+    impl_->inc(qrattrs, response);
 }
 
 isc::data::ConstElementPtr
-AuthCounters::getStatistics() const {
+Counters::getStatistics() const {
     return (impl_->getStatistics());
 }
 
-uint64_t
-AuthCounters::getCounter(const AuthCounters::ServerCounterType type) const {
-    return (impl_->getCounter(type));
-}
-
-uint64_t
-AuthCounters::getCounter(const Opcode opcode) const {
-    return (impl_->getCounter(opcode));
-}
-
-uint64_t
-AuthCounters::getCounter(const Rcode rcode) const {
-    return (impl_->getCounter(rcode));
-}
-
 void
-AuthCounters::registerStatisticsValidator
-    (AuthCounters::validator_type validator) const
+Counters::registerStatisticsValidator
+    (Counters::validator_type validator) const
 {
     return (impl_->registerStatisticsValidator(validator));
 }
+
+} // namespace statistics
+} // namespace auth
+} // namespace isc

+ 134 - 89
src/bin/auth/statistics.h

@@ -15,19 +15,133 @@
 #ifndef STATISTICS_H
 #define STATISTICS_H 1
 
-#include <dns/opcode.h>
-#include <dns/rcode.h>
 #include <cc/session.h>
 #include <cc/data.h>
 
+#include <dns/message.h>
+
+#include <string>
+
 #include <stdint.h>
 #include <boost/scoped_ptr.hpp>
 
-class AuthCountersImpl;
+namespace isc {
+namespace auth {
+namespace statistics {
+
+class CountersImpl;
+
+class QRAttributes {
+/// \brief Query/Response attributes for statistics.
+///
+/// This class holds some attributes related to a query/response
+/// for statistics data collection.
+///
+/// This class does not have getter methods since it exposes private members
+/// to \c CountersImpl directly.
+friend class CountersImpl;
+private:
+    // request attributes
+    int req_ip_version_;            // IP version
+    int req_transport_protocol_;    // Transport layer protocol
+    int req_opcode_;                // OpCode
+    bool req_is_edns_0_;            // EDNS ver.0
+    bool req_is_edns_badver_;       // other EDNS version
+    bool req_is_dnssec_ok_;         // DO bit
+    bool req_is_tsig_;              // signed with valid TSIG
+    bool req_is_sig0_;              // signed with valid SIG(0)
+    bool req_is_badsig_;            // signed but bad signature
+    // zone origin
+    std::string zone_origin_;       // zone origin
+    // response attributes
+    bool answer_sent_;              // DNS message has sent
+    bool res_is_truncated_;         // DNS message is truncated
+public:
+    /// The constructor.
+    ///
+    /// This constructor is mostly exception free. But it may still throw
+    /// a standard exception if memory allocation fails inside the method.
+    ///
+    QRAttributes() {
+        reset();
+    };
+
+    /// The destructor.
+    ///
+    /// This method never throws an exception.
+    ///
+    ~QRAttributes() {};
+    /// \brief Set query opcode.
+    /// \throw None
+    void setQueryOpCode(const int opcode) {
+        req_opcode_ = opcode;
+    };
+    /// \brief Set IP version carrying a query.
+    /// \throw None
+    void setQueryIPVersion(const int ip_version) {
+        req_ip_version_ = ip_version;
+    };
+    /// \brief Set transport protocol carrying a query.
+    /// \throw None
+    void setQueryTransportProtocol(const int transport_protocol) {
+        req_transport_protocol_ = transport_protocol;
+    };
+    /// \brief Set query EDNS attributes.
+    /// \throw None
+    void setQueryEDNS(const bool is_edns_0, const bool is_edns_badver) {
+        req_is_edns_0_ = is_edns_0;
+        req_is_edns_badver_ = is_edns_badver;
+    };
+    /// \brief Set query DO bit.
+    /// \throw None
+    void setQueryDO(const bool is_dnssec_ok) {
+        req_is_dnssec_ok_ = is_dnssec_ok;
+    };
+    /// \brief Set query TSIG attributes.
+    /// \throw None
+    void setQuerySig(const bool is_tsig, const bool is_sig0,
+                            const bool is_badsig)
+    {
+        req_is_tsig_ = is_tsig;
+        req_is_sig0_ = is_sig0;
+        req_is_badsig_ = is_badsig;
+    };
+    /// \brief Set zone origin.
+    /// \throw None
+    void setOrigin(const std::string& origin) {
+        zone_origin_ = origin;
+    };
+    /// \brief Set if the answer was sent.
+    /// \throw None
+    void answerWasSent() {
+        answer_sent_ = true;
+    };
+    /// \brief Set if the response is truncated.
+    /// \throw None
+    void setResponseTruncated(const bool is_truncated) {
+        res_is_truncated_ = is_truncated;
+    };
+    /// \brief Reset attributes.
+    /// \throw None
+    void reset() {
+        req_ip_version_ = 0;
+        req_transport_protocol_ = 0;
+        req_opcode_ = 0;
+        req_is_edns_0_ = false;
+        req_is_edns_badver_ = false;
+        req_is_dnssec_ok_ = false;
+        req_is_tsig_ = false;
+        req_is_sig0_ = false;
+        req_is_badsig_ = false;
+        zone_origin_.clear();
+        answer_sent_ = false;
+        res_is_truncated_ = false;
+    };
+};
 
 /// \brief Set of query counters.
 ///
-/// \c AuthCounters is set of query counters class. It holds query counters
+/// \c Counters is set of query counters class. It holds query counters
 /// and provides an interface to increment the counter of specified type
 /// (e.g. UDP query, TCP query).
 ///
@@ -35,9 +149,7 @@ class AuthCountersImpl;
 /// statistics module.
 ///
 /// This class is designed to be a part of \c AuthSrv.
-/// Call \c inc() to increment a counter for specific type of query in
-/// the query processing function. use \c enum \c CounterType to specify
-/// the type of query.
+/// Call \c inc() to increment a counter for the query.
 /// Call \c getStatistics() to answer statistics information to statistics
 /// module with statistics_session, when the command \c getstats is received.
 ///
@@ -50,61 +162,31 @@ class AuthCountersImpl;
 /// construction overhead of this approach should be acceptable.
 ///
 /// \todo Hold counters for each query types (Notify, Axfr, Ixfr, Normal)
-/// \todo Consider overhead of \c AuthCounters::inc()
-class AuthCounters {
+/// \todo Consider overhead of \c Counters::inc()
+class Counters {
 private:
-    boost::scoped_ptr<AuthCountersImpl> impl_;
+    boost::scoped_ptr<CountersImpl> impl_;
 public:
-    // Enum for the type of counter
-    enum ServerCounterType {
-        SERVER_UDP_QUERY,       ///< SERVER_UDP_QUERY: counter for UDP queries
-        SERVER_TCP_QUERY,       ///< SERVER_TCP_QUERY: counter for TCP queries
-        SERVER_COUNTER_TYPES    ///< The number of defined counters
-    };
-    enum PerZoneCounterType {
-        ZONE_UDP_QUERY,         ///< ZONE_UDP_QUERY: counter for UDP queries
-        ZONE_TCP_QUERY,         ///< ZONE_TCP_QUERY: counter for TCP queries
-        PER_ZONE_COUNTER_TYPES  ///< The number of defined counters
-    };
     /// The constructor.
     ///
     /// This constructor is mostly exception free. But it may still throw
     /// a standard exception if memory allocation fails inside the method.
     ///
-    AuthCounters();
+    Counters();
     /// The destructor.
     ///
     /// This method never throws an exception.
     ///
-    ~AuthCounters();
+    ~Counters();
 
-    /// \brief Increment the counter specified by the parameter.
-    ///
-    /// \param type Type of a counter to increment.
+    /// \brief Increment counters according to the parameters.
     ///
-    /// \throw std::out_of_range \a type is unknown.
-    ///
-    /// usage: counter.inc(AuthCounters::SERVER_UDP_QUERY);
-    /// 
-    void inc(const ServerCounterType type);
-
-    /// \brief Increment the counter of a per opcode counter.
-    ///
-    /// \note This is a tentative interface.  See \c getCounter().
-    ///
-    /// \param opcode The opcode of the counter to increment.
+    /// \param qrattrs Query/Response attributes.
+    /// \param response DNS response message.
     ///
     /// \throw None
-    void inc(const isc::dns::Opcode opcode);
-
-    /// \brief Increment the counter of a per rcode counter.
-    ///
-    /// \note This is a tentative interface.  See \c getCounter().
     ///
-    /// \param rcode The rcode of the counter to increment.
-    ///
-    /// \throw None
-    void inc(const isc::dns::Rcode rcode);
+    void inc(const QRAttributes& qrattrs, const isc::dns::Message& response);
 
     /// \brief Answers statistics counters to statistics module.
     ///
@@ -116,47 +198,6 @@ public:
     ///
     isc::data::ConstElementPtr getStatistics() const;
 
-    /// \brief Get the value of a counter in the AuthCounters.
-    ///
-    /// This function returns a value of the counter specified by \a type.
-    /// This method never throws an exception.
-    ///
-    /// Note: Currently this function is for testing purpose only.
-    ///
-    /// \param type Type of a counter to get the value of
-    ///
-    /// \return the value of the counter specified by \a type.
-    uint64_t getCounter(const AuthCounters::ServerCounterType type) const;
-
-    /// \brief Get the value of a per opcode counter.
-    ///
-    /// This method returns the value of the per opcode counter for the
-    /// specified \c opcode.
-    ///
-    /// \note This is a tentative interface as an attempt of experimentally
-    /// supporting more statistics counters.  This should eventually be more
-    /// generalized.  In any case, this method is mainly for testing.
-    ///
-    /// \throw None
-    /// \param opcode The opcode of the counter to get the value of
-    /// \return the value of the counter.
-    uint64_t getCounter(const isc::dns::Opcode opcode) const;
-
-    /// \brief Get the value of a per rcode counter.
-    ///
-    /// This method returns the value of the per rcode counter for the
-    /// specified \c rcode.
-    ///
-    /// \note As mentioned in getCounter(const isc::dns::Opcode opcode),
-    /// This is a tentative interface as an attempt of experimentally
-    /// supporting more statistics counters.  This should eventually be more
-    /// generalized.  In any case, this method is mainly for testing.
-    ///
-    /// \throw None
-    /// \param rcode The rcode of the counter to get the value of
-    /// \return the value of the counter.
-    uint64_t getCounter(const isc::dns::Rcode rcode) const;
-
     /// \brief A type of validation function for the specification in
     /// isc::config::ModuleSpec.
     ///
@@ -168,16 +209,20 @@ public:
     validator_type;
 
     /// \brief Register a function type of the statistics validation
-    /// function for AuthCounters.
+    /// function for Counters.
     ///
     /// This method never throws an exception.
     ///
     /// \param validator A function type of the validation of
     /// statistics specification.
     ///
-    void registerStatisticsValidator(AuthCounters::validator_type validator) const;
+    void registerStatisticsValidator(Counters::validator_type validator) const;
 };
 
+} // namespace statistics
+} // namespace auth
+} // namespace isc
+
 #endif // STATISTICS_H
 
 // Local Variables:

+ 609 - 0
src/bin/auth/statistics_items.h

@@ -0,0 +1,609 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __STATISTICS_ITEMS_H
+#define __STATISTICS_ITEMS_H 1
+
+/// This file defines a set of statistics items in Auth module for internal
+/// use. This file is intended to be included in statistics.cc.
+
+namespace {
+
+struct CounterTypeTree {
+    const char* const name;
+    const struct CounterTypeTree* const sub_tree;
+    const int counter_id;
+};
+
+// enum for query/response counters
+enum QRCounterType {
+    // Request Attributes
+    QR_REQUEST_IPV4,        ///< Number of IPv4 requests received
+    QR_REQUEST_IPV6,        ///< Number of IPv6 requests received
+    QR_REQUEST_EDNS0,       ///< Number of requests with EDNS(0) received
+    QR_REQUEST_BADEDNSVER,  ///< Number of requests with unsupported EDNS version received
+    QR_REQUEST_TSIG,        ///< Number of requests with TSIG received
+    QR_REQUEST_SIG0,        ///< Number of requests with SIG(0) received; not implemented in BIND 10
+    QR_REQUEST_BADSIG,      ///< Number of requests with invalid TSIG or SIG(0) signature received
+    QR_REQUEST_UDP,         ///< Number of UDP requests received
+    QR_REQUEST_TCP,         ///< Number of TCP requests received
+    QR_REQUEST_DNSSEC_OK,   ///< Number of requests with DO bit
+    // Request Opcodes
+    QR_OPCODE_QUERY,        ///< Number of Opcode=QUERY requests received
+    QR_OPCODE_IQUERY,       ///< Number of Opcode=IQUERY requests received
+    QR_OPCODE_STATUS,       ///< Number of Opcode=STATUS requests received
+    QR_OPCODE_NOTIFY,       ///< Number of Opcode=NOTIFY requests received
+    QR_OPCODE_UPDATE,       ///< Number of Opcode=UPDATE requests received
+    QR_OPCODE_OTHER,        ///< Number of requests in other OpCode received
+    // Query Types
+    QR_QTYPE_A,             ///< Number of QTYPE = A queries received
+    QR_QTYPE_NS,            ///< Number of QTYPE = NS queries received
+    QR_QTYPE_MD,            ///< Number of QTYPE = MD queries received
+    QR_QTYPE_MF,            ///< Number of QTYPE = MF queries received
+    QR_QTYPE_CNAME,         ///< Number of QTYPE = CNAME queries received
+    QR_QTYPE_SOA,           ///< Number of QTYPE = SOA queries received
+    QR_QTYPE_MB,            ///< Number of QTYPE = MB queries received
+    QR_QTYPE_MG,            ///< Number of QTYPE = MG queries received
+    QR_QTYPE_MR,            ///< Number of QTYPE = MR queries received
+    QR_QTYPE_NULL,          ///< Number of QTYPE = NULL queries received
+    QR_QTYPE_WKS,           ///< Number of QTYPE = WKS queries received
+    QR_QTYPE_PTR,           ///< Number of QTYPE = PTR queries received
+    QR_QTYPE_HINFO,         ///< Number of QTYPE = HINFO queries received
+    QR_QTYPE_MINFO,         ///< Number of QTYPE = MINFO queries received
+    QR_QTYPE_MX,            ///< Number of QTYPE = MX queries received
+    QR_QTYPE_TXT,           ///< Number of QTYPE = TXT queries received
+    QR_QTYPE_RP,            ///< Number of QTYPE = RP queries received
+    QR_QTYPE_AFSDB,         ///< Number of QTYPE = AFSDB queries received
+    QR_QTYPE_X25,           ///< Number of QTYPE = X25 queries received
+    QR_QTYPE_ISDN,          ///< Number of QTYPE = ISDN queries received
+    QR_QTYPE_RT,            ///< Number of QTYPE = RT queries received
+    QR_QTYPE_NSAP,          ///< Number of QTYPE = NSAP queries received
+    QR_QTYPE_NSAP_PTR,      ///< Number of QTYPE = NSAP-PTR queries received
+    QR_QTYPE_SIG,           ///< Number of QTYPE = SIG queries received
+    QR_QTYPE_KEY,           ///< Number of QTYPE = KEY queries received
+    QR_QTYPE_PX,            ///< Number of QTYPE = PX queries received
+    QR_QTYPE_GPOS,          ///< Number of QTYPE = GPOS queries received
+    QR_QTYPE_AAAA,          ///< Number of QTYPE = AAAA queries received
+    QR_QTYPE_LOC,           ///< Number of QTYPE = LOC queries received
+    QR_QTYPE_NXT,           ///< Number of QTYPE = NXT queries received
+    QR_QTYPE_EID,           ///< Number of QTYPE = EID queries received
+    QR_QTYPE_NIMLOC,        ///< Number of QTYPE = NIMLOC queries received
+    QR_QTYPE_SRV,           ///< Number of QTYPE = SRV queries received
+    QR_QTYPE_ATMA,          ///< Number of QTYPE = ATMA queries received
+    QR_QTYPE_NAPTR,         ///< Number of QTYPE = NAPTR queries received
+    QR_QTYPE_KX,            ///< Number of QTYPE = KX queries received
+    QR_QTYPE_CERT,          ///< Number of QTYPE = CERT queries received
+    QR_QTYPE_A6,            ///< Number of QTYPE = A6 queries received
+    QR_QTYPE_DNAME,         ///< Number of QTYPE = DNAME queries received
+    QR_QTYPE_SINK,          ///< Number of QTYPE = SINK queries received
+    QR_QTYPE_OPT,           ///< Number of QTYPE = OPT queries received
+    QR_QTYPE_APL,           ///< Number of QTYPE = APL queries received
+    QR_QTYPE_DS,            ///< Number of QTYPE = DS queries received
+    QR_QTYPE_SSHFP,         ///< Number of QTYPE = SSHFP queries received
+    QR_QTYPE_IPSECKEY,      ///< Number of QTYPE = IPSECKEY queries received
+    QR_QTYPE_RRSIG,         ///< Number of QTYPE = RRSIG queries received
+    QR_QTYPE_NSEC,          ///< Number of QTYPE = NSEC queries received
+    QR_QTYPE_DNSKEY,        ///< Number of QTYPE = DNSKEY queries received
+    QR_QTYPE_DHCID,         ///< Number of QTYPE = DHCID queries received
+    QR_QTYPE_NSEC3,         ///< Number of QTYPE = NSEC3 queries received
+    QR_QTYPE_NSEC3PARAM,    ///< Number of QTYPE = NSEC3PARAM queries received
+    QR_QTYPE_HIP,           ///< Number of QTYPE = HIP queries received
+    QR_QTYPE_NINFO,         ///< Number of QTYPE = NINFO queries received
+    QR_QTYPE_RKEY,          ///< Number of QTYPE = RKEY queries received
+    QR_QTYPE_TALINK,        ///< Number of QTYPE = TALINK queries received
+    QR_QTYPE_SPF,           ///< Number of QTYPE = SPF queries received
+    QR_QTYPE_UINFO,         ///< Number of QTYPE = UINFO queries received
+    QR_QTYPE_UID,           ///< Number of QTYPE = UID queries received
+    QR_QTYPE_GID,           ///< Number of QTYPE = GID queries received
+    QR_QTYPE_UNSPEC,        ///< Number of QTYPE = UNSPEC queries received
+    QR_QTYPE_TKEY,          ///< Number of QTYPE = TKEY queries received
+    QR_QTYPE_TSIG,          ///< Number of QTYPE = TSIG queries received
+    QR_QTYPE_IXFR,          ///< Number of QTYPE = IXFR queries received
+    QR_QTYPE_AXFR,          ///< Number of QTYPE = AXFR queries received
+    QR_QTYPE_MAILB,         ///< Number of QTYPE = MAILB queries received
+    QR_QTYPE_MAILA,         ///< Number of QTYPE = MAILA queries received
+    QR_QTYPE_URI,           ///< Number of QTYPE = URI queries received
+    QR_QTYPE_CAA,           ///< Number of QTYPE = CAA queries received
+    QR_QTYPE_TA,            ///< Number of QTYPE = TA queries received
+    QR_QTYPE_DLV,           ///< Number of QTYPE = DLV queries received
+    QR_QTYPE_OTHER,         ///< Number of queries in other QTYPE received
+    // Respose Attributes
+    QR_RESPONSE,            ///< Number of responses sent
+    QR_RESPONSE_TRUNCATED,  ///< Number of truncated responses sent
+    QR_RESPONSE_EDNS0,      ///< Number of responses with EDNS0; not implemented in BIND 10
+    QR_RESPONSE_TSIG,       ///< Number of responses with TSIG
+    QR_RESPONSE_SIG0,       ///< Number of responses with SIG(0); not implemented in BIND 10
+    QR_QRYSUCCESS,          ///< Number of queries resulted in rcode = NOERROR and answer RR >= 1
+    QR_QRYAUTHANS,          ///< Number of queries resulted in authoritative answer
+    QR_QRYNOAUTHANS,        ///< Number of queries resulted in non-authoritative answer
+    QR_QRYREFERRAL,         ///< Number of queries resulted in referral answer
+    QR_QRYNXRRSET,          ///< Number of queries resulted in NOERROR but answer RR == 0
+    QR_QRYREJECT,           ///< Number of queries rejected
+    // Response Rcodes
+    QR_RCODE_NOERROR,       ///< Number of queries resulted in RCODE = 0 (NoError)
+    QR_RCODE_FORMERR,       ///< Number of queries resulted in RCODE = 1 (FormErr)
+    QR_RCODE_SERVFAIL,      ///< Number of queries resulted in RCODE = 2 (ServFail)
+    QR_RCODE_NXDOMAIN,      ///< Number of queries resulted in RCODE = 3 (NXDomain)
+    QR_RCODE_NOTIMP,        ///< Number of queries resulted in RCODE = 4 (NotImp)
+    QR_RCODE_REFUSED,       ///< Number of queries resulted in RCODE = 5 (Refused)
+    QR_RCODE_YXDOMAIN,      ///< Number of queries resulted in RCODE = 6 (YXDomain)
+    QR_RCODE_YXRRSET,       ///< Number of queries resulted in RCODE = 7 (YXRRSet)
+    QR_RCODE_NXRRSET,       ///< Number of queries resulted in RCODE = 8 (NXRRSet)
+    QR_RCODE_NOTAUTH,       ///< Number of queries resulted in RCODE = 9 (NotAuth)
+    QR_RCODE_NOTZONE,       ///< Number of queries resulted in RCODE = 10 (NotZone)
+    QR_RCODE_BADSIGVERS,    ///< Number of queries resulted in RCODE = 16 (BADVERS, BADSIG)
+    QR_RCODE_BADKEY,        ///< Number of queries resulted in RCODE = 17 (BADKEY)
+    QR_RCODE_BADTIME,       ///< Number of queries resulted in RCODE = 18 (BADTIME)
+    QR_RCODE_BADMODE,       ///< Number of queries resulted in RCODE = 19 (BADMODE)
+    QR_RCODE_BADNAME,       ///< Number of queries resulted in RCODE = 20 (BADNAME)
+    QR_RCODE_BADALG,        ///< Number of queries resulted in RCODE = 21 (BADALG)
+    QR_RCODE_BADTRUNC,      ///< Number of queries resulted in RCODE = 22 (BADTRUNC)
+    QR_RCODE_OTHER,         ///< Number of queries resulted in other RCODEs
+    // End of counter types
+    QR_COUNTER_TYPES  ///< The number of defined counters
+};
+
+// item names for query/response counters
+const struct CounterTypeTree QRCounterRequest[] = {
+    { "v4",         NULL,   QR_REQUEST_IPV4       },
+    { "v6",         NULL,   QR_REQUEST_IPV6       },
+    { "edns0",      NULL,   QR_REQUEST_EDNS0      },
+    { "badednsver", NULL,   QR_REQUEST_BADEDNSVER },
+    { "tsig",       NULL,   QR_REQUEST_TSIG       },
+    { "sig0",       NULL,   QR_REQUEST_SIG0       },
+    { "badsig",     NULL,   QR_REQUEST_BADSIG     },
+    { "udp",        NULL,   QR_REQUEST_UDP        },
+    { "tcp",        NULL,   QR_REQUEST_TCP        },
+    { "dnssec_ok",  NULL,   QR_REQUEST_DNSSEC_OK  },
+    { NULL,         NULL,   -1                    }
+};
+const struct CounterTypeTree QRCounterOpcode[] = {
+    { "query",  NULL,   QR_OPCODE_QUERY  },
+    { "iquery", NULL,   QR_OPCODE_IQUERY },
+    { "status", NULL,   QR_OPCODE_STATUS },
+    { "notify", NULL,   QR_OPCODE_NOTIFY },
+    { "update", NULL,   QR_OPCODE_UPDATE },
+    { "other",  NULL,   QR_OPCODE_OTHER  },
+    { NULL,     NULL,   -1               }
+};
+const struct CounterTypeTree QRCounterQtype[] = {
+    { "a",          NULL,   QR_QTYPE_A,         },
+    { "ns",         NULL,   QR_QTYPE_NS         },
+    { "md",         NULL,   QR_QTYPE_MD         },
+    { "mf",         NULL,   QR_QTYPE_MF         },
+    { "cname",      NULL,   QR_QTYPE_CNAME      },
+    { "soa",        NULL,   QR_QTYPE_SOA        },
+    { "mb",         NULL,   QR_QTYPE_MB         },
+    { "mg",         NULL,   QR_QTYPE_MG         },
+    { "mr",         NULL,   QR_QTYPE_MR         },
+    { "null",       NULL,   QR_QTYPE_NULL       },
+    { "wks",        NULL,   QR_QTYPE_WKS        },
+    { "ptr",        NULL,   QR_QTYPE_PTR        },
+    { "hinfo",      NULL,   QR_QTYPE_HINFO      },
+    { "minfo",      NULL,   QR_QTYPE_MINFO      },
+    { "mx",         NULL,   QR_QTYPE_MX         },
+    { "txt",        NULL,   QR_QTYPE_TXT        },
+    { "rp",         NULL,   QR_QTYPE_RP         },
+    { "afsdb",      NULL,   QR_QTYPE_AFSDB      },
+    { "x25",        NULL,   QR_QTYPE_X25        },
+    { "isdn",       NULL,   QR_QTYPE_ISDN       },
+    { "rt",         NULL,   QR_QTYPE_RT         },
+    { "nsap",       NULL,   QR_QTYPE_NSAP       },
+    { "nsap-ptr",   NULL,   QR_QTYPE_NSAP_PTR   },
+    { "sig",        NULL,   QR_QTYPE_SIG        },
+    { "key",        NULL,   QR_QTYPE_KEY        },
+    { "px",         NULL,   QR_QTYPE_PX         },
+    { "gpos",       NULL,   QR_QTYPE_GPOS       },
+    { "aaaa",       NULL,   QR_QTYPE_AAAA       },
+    { "loc",        NULL,   QR_QTYPE_LOC        },
+    { "nxt",        NULL,   QR_QTYPE_NXT        },
+    { "eid",        NULL,   QR_QTYPE_EID        },
+    { "nimloc",     NULL,   QR_QTYPE_NIMLOC     },
+    { "srv",        NULL,   QR_QTYPE_SRV        },
+    { "atma",       NULL,   QR_QTYPE_ATMA       },
+    { "naptr",      NULL,   QR_QTYPE_NAPTR      },
+    { "kx",         NULL,   QR_QTYPE_KX         },
+    { "cert",       NULL,   QR_QTYPE_CERT       },
+    { "a6",         NULL,   QR_QTYPE_A6         },
+    { "dname",      NULL,   QR_QTYPE_DNAME      },
+    { "sink",       NULL,   QR_QTYPE_SINK       },
+    { "opt",        NULL,   QR_QTYPE_OPT        },
+    { "apl",        NULL,   QR_QTYPE_APL        },
+    { "ds",         NULL,   QR_QTYPE_DS         },
+    { "sshfp",      NULL,   QR_QTYPE_SSHFP      },
+    { "ipseckey",   NULL,   QR_QTYPE_IPSECKEY   },
+    { "rrsig",      NULL,   QR_QTYPE_RRSIG      },
+    { "nsec",       NULL,   QR_QTYPE_NSEC       },
+    { "dnskey",     NULL,   QR_QTYPE_DNSKEY     },
+    { "dhcid",      NULL,   QR_QTYPE_DHCID      },
+    { "nsec3",      NULL,   QR_QTYPE_NSEC3      },
+    { "nsec3param", NULL,   QR_QTYPE_NSEC3PARAM },
+    { "hip",        NULL,   QR_QTYPE_HIP        },
+    { "ninfo",      NULL,   QR_QTYPE_NINFO      },
+    { "rkey",       NULL,   QR_QTYPE_RKEY       },
+    { "talink",     NULL,   QR_QTYPE_TALINK     },
+    { "spf",        NULL,   QR_QTYPE_SPF        },
+    { "uinfo",      NULL,   QR_QTYPE_UINFO      },
+    { "uid",        NULL,   QR_QTYPE_UID        },
+    { "gid",        NULL,   QR_QTYPE_GID        },
+    { "unspec",     NULL,   QR_QTYPE_UNSPEC     },
+    { "tkey",       NULL,   QR_QTYPE_TKEY       },
+    { "tsig",       NULL,   QR_QTYPE_TSIG       },
+    { "ixfr",       NULL,   QR_QTYPE_IXFR       },
+    { "axfr",       NULL,   QR_QTYPE_AXFR       },
+    { "mailb",      NULL,   QR_QTYPE_MAILB      },
+    { "maila",      NULL,   QR_QTYPE_MAILA      },
+    { "uri",        NULL,   QR_QTYPE_URI        },
+    { "caa",        NULL,   QR_QTYPE_CAA        },
+    { "ta",         NULL,   QR_QTYPE_TA         },
+    { "dlv",        NULL,   QR_QTYPE_DLV        },
+    { "other",      NULL,   QR_QTYPE_OTHER      },
+    { NULL,         NULL,   -1                  }
+};
+const struct CounterTypeTree QRCounterResponse[] = {
+    { "truncated",  NULL,   QR_RESPONSE_TRUNCATED },
+    { "edns0",      NULL,   QR_RESPONSE_EDNS0     },
+    { "tsig",       NULL,   QR_RESPONSE_TSIG      },
+    { "sig0",       NULL,   QR_RESPONSE_SIG0      },
+    { NULL,         NULL,   -1                    }
+};
+const struct CounterTypeTree QRCounterRcode[] = {
+    { "noerror",    NULL,   QR_RCODE_NOERROR    },
+    { "formerr",    NULL,   QR_RCODE_FORMERR    },
+    { "servfail",   NULL,   QR_RCODE_SERVFAIL   },
+    { "nxdomain",   NULL,   QR_RCODE_NXDOMAIN   },
+    { "notimp",     NULL,   QR_RCODE_NOTIMP     },
+    { "refused",    NULL,   QR_RCODE_REFUSED    },
+    { "yxdomain",   NULL,   QR_RCODE_YXDOMAIN   },
+    { "yxrrset",    NULL,   QR_RCODE_YXRRSET    },
+    { "nxrrset",    NULL,   QR_RCODE_NXRRSET    },
+    { "notauth",    NULL,   QR_RCODE_NOTAUTH    },
+    { "notzone",    NULL,   QR_RCODE_NOTZONE    },
+    { "badsigvers", NULL,   QR_RCODE_BADSIGVERS },
+    { "badkey",     NULL,   QR_RCODE_BADKEY     },
+    { "badtime",    NULL,   QR_RCODE_BADTIME    },
+    { "badmode",    NULL,   QR_RCODE_BADMODE    },
+    { "badname",    NULL,   QR_RCODE_BADNAME    },
+    { "badalg",     NULL,   QR_RCODE_BADALG     },
+    { "badtrunc",   NULL,   QR_RCODE_BADTRUNC   },
+    { "other",      NULL,   QR_RCODE_OTHER      },
+    { NULL,         NULL,   -1 }
+};
+const struct CounterTypeTree QRCounterTree[] = {
+    { "request",        QRCounterRequest,   -1              },
+    { "opcode",         QRCounterOpcode,    -1              },
+    { "qtype",          QRCounterQtype,     -1              },
+    { "responses",      NULL,               QR_RESPONSE     },
+    { "response",       QRCounterResponse,  -1              },
+    { "qrysuccess",     NULL,               QR_QRYSUCCESS   },
+    { "qryauthans",     NULL,               QR_QRYAUTHANS   },
+    { "qrynoauthans",   NULL,               QR_QRYNOAUTHANS },
+    { "qryreferral",    NULL,               QR_QRYREFERRAL  },
+    { "qrynxrrset",     NULL,               QR_QRYNXRRSET   },
+    { "authqryrej",     NULL,               QR_QRYREJECT    },
+    { "rcode",          QRCounterRcode,     -1              },
+    { NULL,             NULL,               -1              }
+};
+
+const int QROpCodeToQRCounterType[16] = {
+    QR_OPCODE_QUERY,    // Opcode =  0: Query
+    QR_OPCODE_IQUERY,   // Opcode =  1: Iquery
+    QR_OPCODE_STATUS,   // Opcode =  2: STATUS
+    QR_OPCODE_OTHER,    // Opcode =  3: (Unassigned)
+    QR_OPCODE_NOTIFY,   // Opcode =  4: Notify
+    QR_OPCODE_UPDATE,   // Opcode =  5: Update
+    QR_OPCODE_OTHER,    // Opcode =  6: (Unassigned)
+    QR_OPCODE_OTHER,    // Opcode =  7: (Unassigned)
+    QR_OPCODE_OTHER,    // Opcode =  8: (Unassigned)
+    QR_OPCODE_OTHER,    // Opcode =  9: (Unassigned)
+    QR_OPCODE_OTHER,    // Opcode = 10: (Unassigned)
+    QR_OPCODE_OTHER,    // Opcode = 11: (Unassigned)
+    QR_OPCODE_OTHER,    // Opcode = 12: (Unassigned)
+    QR_OPCODE_OTHER,    // Opcode = 13: (Unassigned)
+    QR_OPCODE_OTHER,    // Opcode = 14: (Unassigned)
+    QR_OPCODE_OTHER     // Opcode = 15: (Unassigned)
+};
+const int QRQTypeToQRCounterType[258] = {
+    QR_QTYPE_OTHER,         // RRtype =   0: special use
+    QR_QTYPE_A,             // RRtype =   1: A
+    QR_QTYPE_NS,            // RRtype =   2: NS
+    QR_QTYPE_MD,            // RRtype =   3: MD
+    QR_QTYPE_MF,            // RRtype =   4: MF
+    QR_QTYPE_CNAME,         // RRtype =   5: CNAME
+    QR_QTYPE_SOA,           // RRtype =   6: SOA
+    QR_QTYPE_MB,            // RRtype =   7: MB
+    QR_QTYPE_MG,            // RRtype =   8: MG
+    QR_QTYPE_MR,            // RRtype =   9: MR
+    QR_QTYPE_NULL,          // RRtype =  10: NULL
+    QR_QTYPE_WKS,           // RRtype =  11: WKS
+    QR_QTYPE_PTR,           // RRtype =  12: PTR
+    QR_QTYPE_HINFO,         // RRtype =  13: HINFO
+    QR_QTYPE_MINFO,         // RRtype =  14: MINFO
+    QR_QTYPE_MX,            // RRtype =  15: MX
+    QR_QTYPE_TXT,           // RRtype =  16: TXT
+    QR_QTYPE_RP,            // RRtype =  17: RP
+    QR_QTYPE_AFSDB,         // RRtype =  18: AFSDB
+    QR_QTYPE_X25,           // RRtype =  19: X25
+    QR_QTYPE_ISDN,          // RRtype =  20: ISDN
+    QR_QTYPE_RT,            // RRtype =  21: RT
+    QR_QTYPE_NSAP,          // RRtype =  22: NSAP
+    QR_QTYPE_NSAP_PTR,      // RRtype =  23: NSAP-PTR
+    QR_QTYPE_SIG,           // RRtype =  24: SIG
+    QR_QTYPE_KEY,           // RRtype =  25: KEY
+    QR_QTYPE_PX,            // RRtype =  26: PX
+    QR_QTYPE_GPOS,          // RRtype =  27: GPOS
+    QR_QTYPE_AAAA,          // RRtype =  28: AAAA
+    QR_QTYPE_LOC,           // RRtype =  29: LOC
+    QR_QTYPE_NXT,           // RRtype =  30: NXT
+    QR_QTYPE_EID,           // RRtype =  31: EID        
+    QR_QTYPE_NIMLOC,        // RRtype =  32: NIMLOC     
+    QR_QTYPE_SRV,           // RRtype =  33: SRV        
+    QR_QTYPE_ATMA,          // RRtype =  34: ATMA       
+    QR_QTYPE_NAPTR,         // RRtype =  35: NAPTR      
+    QR_QTYPE_KX,            // RRtype =  36: KX         
+    QR_QTYPE_CERT,          // RRtype =  37: CERT       
+    QR_QTYPE_A6,            // RRtype =  38: A6         
+    QR_QTYPE_DNAME,         // RRtype =  39: DNAME      
+    QR_QTYPE_SINK,          // RRtype =  40: SINK       
+    QR_QTYPE_OPT,           // RRtype =  41: OPT        
+    QR_QTYPE_APL,           // RRtype =  42: APL        
+    QR_QTYPE_DS,            // RRtype =  43: DS         
+    QR_QTYPE_SSHFP,         // RRtype =  44: SSHFP      
+    QR_QTYPE_IPSECKEY,      // RRtype =  45: IPSECKEY   
+    QR_QTYPE_RRSIG,         // RRtype =  46: RRSIG      
+    QR_QTYPE_NSEC,          // RRtype =  47: NSEC       
+    QR_QTYPE_DNSKEY,        // RRtype =  48: DNSKEY     
+    QR_QTYPE_DHCID,         // RRtype =  49: DHCID      
+    QR_QTYPE_NSEC3,         // RRtype =  50: NSEC3      
+    QR_QTYPE_NSEC3PARAM,    // RRtype =  51: NSEC3PARAM 
+    QR_QTYPE_OTHER,         // RRtype =  52: TLSA
+    QR_QTYPE_OTHER,         // RRtype =  53: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  54: (Unassigned)
+    QR_QTYPE_HIP,           // RRtype =  55: HIP
+    QR_QTYPE_NINFO,         // RRtype =  56: NINFO
+    QR_QTYPE_RKEY,          // RRtype =  57: RKEY
+    QR_QTYPE_TALINK,        // RRtype =  58: TALINK
+    QR_QTYPE_OTHER,         // RRtype =  59: CDS
+    QR_QTYPE_OTHER,         // RRtype =  60: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  61: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  62: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  63: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  64: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  65: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  66: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  67: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  68: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  69: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  70: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  71: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  72: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  73: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  74: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  75: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  76: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  77: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  78: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  79: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  80: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  81: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  82: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  83: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  84: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  85: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  86: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  87: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  88: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  89: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  90: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  91: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  92: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  93: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  94: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  95: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  96: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  97: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype =  98: (Unassigned)
+    QR_QTYPE_SPF,           // RRtype =  99: SPF
+    QR_QTYPE_UINFO,         // RRtype = 100: UINFO
+    QR_QTYPE_UID,           // RRtype = 101: UID
+    QR_QTYPE_GID,           // RRtype = 102: GID
+    QR_QTYPE_UNSPEC,        // RRtype = 103: UNSPEC
+    QR_QTYPE_OTHER,         // RRtype = 104: NID
+    QR_QTYPE_OTHER,         // RRtype = 105: L32
+    QR_QTYPE_OTHER,         // RRtype = 106: L64
+    QR_QTYPE_OTHER,         // RRtype = 107: LP 
+    QR_QTYPE_OTHER,         // RRtype = 108: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 109: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 110: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 111: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 112: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 113: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 114: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 115: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 116: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 117: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 118: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 119: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 120: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 121: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 122: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 123: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 124: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 125: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 126: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 127: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 128: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 129: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 130: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 131: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 132: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 133: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 134: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 135: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 136: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 137: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 138: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 139: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 140: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 141: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 142: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 143: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 144: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 145: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 146: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 147: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 148: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 149: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 150: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 151: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 152: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 153: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 154: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 155: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 156: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 157: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 158: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 159: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 160: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 161: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 162: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 163: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 164: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 165: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 166: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 167: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 168: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 169: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 170: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 171: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 172: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 173: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 174: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 175: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 176: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 177: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 178: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 179: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 180: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 181: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 182: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 183: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 184: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 185: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 186: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 187: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 188: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 189: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 190: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 191: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 192: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 193: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 194: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 195: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 196: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 197: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 198: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 199: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 200: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 201: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 202: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 203: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 204: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 205: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 206: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 207: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 208: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 209: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 210: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 211: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 212: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 213: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 214: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 215: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 216: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 217: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 218: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 219: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 220: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 221: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 222: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 223: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 224: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 225: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 226: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 227: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 228: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 229: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 230: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 231: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 232: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 233: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 234: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 235: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 236: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 237: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 238: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 239: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 240: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 241: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 242: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 243: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 244: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 245: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 246: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 247: (Unassigned)
+    QR_QTYPE_OTHER,         // RRtype = 248: (Unassigned)
+    QR_QTYPE_TKEY,          // RRtype = 249: TKEY
+    QR_QTYPE_TSIG,          // RRtype = 250: TSIG
+    QR_QTYPE_IXFR,          // RRtype = 251: IXFR
+    QR_QTYPE_AXFR,          // RRtype = 252: AXFR
+    QR_QTYPE_MAILB,         // RRtype = 253: MAILB
+    QR_QTYPE_MAILA,         // RRtype = 254: MAILA
+    QR_QTYPE_OTHER,         // RRtype = 255: for All records
+    QR_QTYPE_URI,           // RRtype = 256: URI
+    QR_QTYPE_CAA            // RRtype = 257: CAA
+};
+const int QRRCodeToQRCounterType[23] = {
+    QR_RCODE_NOERROR,       // Rcode =  0: NoError
+    QR_RCODE_FORMERR,       // Rcode =  1: FormErr
+    QR_RCODE_SERVFAIL,      // Rcode =  2: ServFail
+    QR_RCODE_NXDOMAIN,      // Rcode =  3: NXDomain
+    QR_RCODE_NOTIMP,        // Rcode =  4: NotImp
+    QR_RCODE_REFUSED,       // Rcode =  5: Refused
+    QR_RCODE_YXDOMAIN,      // Rcode =  6: YXDomain
+    QR_RCODE_YXRRSET,       // Rcode =  7: YXRRSet
+    QR_RCODE_NXRRSET,       // Rcode =  8: NXRRSet
+    QR_RCODE_NOTAUTH,       // Rcode =  9: NotAuth
+    QR_RCODE_NOTZONE,       // Rcode = 10: NotZone
+    QR_RCODE_OTHER,         // Rcode = 11: (Unassigned)
+    QR_RCODE_OTHER,         // Rcode = 12: (Unassigned)
+    QR_RCODE_OTHER,         // Rcode = 13: (Unassigned)
+    QR_RCODE_OTHER,         // Rcode = 14: (Unassigned)
+    QR_RCODE_OTHER,         // Rcode = 15: (Unassigned)
+    QR_RCODE_BADSIGVERS,    // Rcode = 16: BADVERS, BADSIG
+    QR_RCODE_BADKEY,        // Rcode = 17: BADKEY
+    QR_RCODE_BADTIME,       // Rcode = 18: BADTIME
+    QR_RCODE_BADMODE,       // Rcode = 19: BADMODE
+    QR_RCODE_BADNAME,       // Rcode = 20: BADNAME
+    QR_RCODE_BADALG,        // Rcode = 21: BADALG
+    QR_RCODE_BADTRUNC       // Rcode = 22: BADTRUNC
+};
+
+} // anonymous namespace
+
+#endif // __STATISTICS_ITEMS_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 2
src/bin/auth/tests/Makefile.am

@@ -41,7 +41,7 @@ run_unittests_SOURCES += ../query.h ../query.cc
 run_unittests_SOURCES += ../auth_config.h ../auth_config.cc
 run_unittests_SOURCES += ../command.h ../command.cc
 run_unittests_SOURCES += ../common.h ../common.cc
-run_unittests_SOURCES += ../statistics.h ../statistics.cc
+run_unittests_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h
 run_unittests_SOURCES += ../datasrc_config.h ../datasrc_config.cc
 run_unittests_SOURCES += datasrc_util.h datasrc_util.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
@@ -75,7 +75,6 @@ run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libb10-server-common.la
 run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libb10-nsas.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
-run_unittests_LDADD += $(top_builddir)/src/lib/statistics/libb10-statistics.la
 run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 run_unittests_LDADD += $(GTEST_LDADD)

+ 188 - 44
src/bin/auth/tests/auth_srv_unittest.cc

@@ -35,6 +35,7 @@
 #include <auth/command.h>
 #include <auth/common.h>
 #include <auth/statistics.h>
+#include <auth/statistics_items.h>
 #include <auth/datasrc_config.h>
 
 #include <util/unittests/mock_socketsession.h>
@@ -76,6 +77,7 @@ using namespace isc::server_common::portconfig;
 using isc::datasrc::memory::ZoneTableSegment;
 using isc::UnitTestUtil;
 using boost::scoped_ptr;
+using isc::auth::statistics::Counters;
 
 namespace {
 const char* const CONFIG_TESTDB =
@@ -123,29 +125,47 @@ protected:
 
     // Helper for checking Rcode statistic counters;
     // Checks for one specific Rcode statistics counter value
-    void checkRcodeCounter(const Rcode& rcode, int expected_value) const {
-        EXPECT_EQ(expected_value, server.getCounter(rcode)) <<
-                  "Expected Rcode count for " << rcode.toText() <<
-                  " " << expected_value << ", was: " <<
-                  server.getCounter(rcode);
+    void checkRcodeCounter(const std::string& rcode_name, const int rcode_value,
+                           const int expected_value) const
+    {
+            EXPECT_EQ(expected_value, rcode_value) <<
+                      "Expected Rcode count for " << rcode_name <<
+                      " " << expected_value << ", was: " <<
+                      rcode_value;
     }
 
     // Checks whether all Rcode counters are set to zero
     void checkAllRcodeCountersZero() const {
-        for (int i = 0; i < 17; i++) {
-            checkRcodeCounter(Rcode(i), 0);
-        }
+        // with checking NOERROR == 0 and the others are 0
+        checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 0);
     }
 
     // Checks whether all Rcode counters are set to zero except the given
     // rcode (it is checked to be set to 'value')
     void checkAllRcodeCountersZeroExcept(const Rcode& rcode, int value) const {
-        for (int i = 0; i < 17; i++) {
-            const Rcode rc(i);
-            if (rc == rcode) {
-                checkRcodeCounter(Rcode(i), value);
-            } else {
-                checkRcodeCounter(Rcode(i), 0);
+        std::string target_rcode_name = rcode.toText();
+        std::transform(target_rcode_name.begin(), target_rcode_name.end(),
+                       target_rcode_name.begin(), ::tolower);
+        // rcode 16 is registered as both BADVERS and BADSIG
+        if (target_rcode_name == "badvers") {
+            target_rcode_name = "badsigvers";
+        }
+
+        const std::map<std::string, ConstElementPtr>
+            stats_map(server.getStatistics()->mapValue());
+
+        const std::string rcode_prefix("rcode.");
+        for (std::map<std::string, ConstElementPtr>::const_iterator
+                 i = stats_map.begin(), e = stats_map.end();
+             i != e;
+             ++i)
+        {
+            if (i->first.compare(0, rcode_prefix.size(), rcode_prefix) == 0) {
+                if (i->first.compare(rcode_prefix + target_rcode_name) == 0) {
+                    checkRcodeCounter(i->first, i->second->intValue(), value);
+                } else {
+                    checkRcodeCounter(i->first, i->second->intValue(), 0);
+                }
             }
         }
     }
@@ -222,6 +242,29 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
                 renderer.getLength());
 }
 
+// Check if the item has expected value.
+// Before reading the item, check the item exists.
+void
+expectCounterItem(ConstElementPtr stats,
+                  const std::string& item, const int expected) {
+    ConstElementPtr value(Element::create(0));
+    if (item == "queries.udp" || item == "queries.tcp" || expected != 0) {
+        // if the value of the item is not zero, the item exists and has
+        // expected value
+        // item "queries.udp" and "queries.tcp" exists whether the value
+        // is zero or nonzero
+        ASSERT_TRUE(stats->find(item, value)) << "    Item: " << item;
+        // Get the value of the item with another method because of API bug
+        // (ticket #2302)
+        value = stats->find(item);
+        EXPECT_EQ(expected, value->intValue()) << "    Item: " << item;
+    } else {
+        // otherwise the item does not exist
+        ASSERT_FALSE(stats->find(item, value)) << "    Item: " << item <<
+            std::endl << "   Value: " << value->intValue();
+    }
+}
+
 // We did not configure any client lists. Therefore it should be REFUSED
 TEST_F(AuthSrvTest, noClientList) {
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
@@ -405,7 +448,9 @@ TEST_F(AuthSrvTest, TSIGCheckFirst) {
         "It should be unsigned with this error";
     // TSIG should have failed, and so the per opcode counter shouldn't be
     // incremented.
-    EXPECT_EQ(0, server.getCounter(Opcode::RESERVED14()));
+    ConstElementPtr stats = server.getStatistics();
+    expectCounterItem(stats, "opcode.normal", 0);
+    expectCounterItem(stats, "opcode.other", 0);
 
     checkAllRcodeCountersZeroExcept(Rcode::NOTAUTH(), 1);
 }
@@ -1041,8 +1086,12 @@ TEST_F(AuthSrvTest,
 
 // Submit UDP normal query and check query counter
 TEST_F(AuthSrvTest, queryCounterUDPNormal) {
-    // The counter should be initialized to 0.
-    EXPECT_EQ(0, server.getCounter(AuthCounters::SERVER_UDP_QUERY));
+    // The counters should be initialized to 0.
+    ConstElementPtr stats_init = server.getStatistics();
+    expectCounterItem(stats_init, "queries.udp", 0);
+    expectCounterItem(stats_init, "queries.tcp", 0);
+    expectCounterItem(stats_init, "opcode.query", 0);
+    expectCounterItem(stats_init, "rcode.refused", 0);
     // Create UDP message and process.
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("example.com"),
@@ -1050,18 +1099,25 @@ TEST_F(AuthSrvTest, queryCounterUDPNormal) {
     createRequestPacket(request_message, IPPROTO_UDP);
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
-    // After processing UDP query, the counter should be 1.
-    EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_UDP_QUERY));
-    // The counter for opcode Query should also be one
-    EXPECT_EQ(1, server.getCounter(Opcode::QUERY()));
-    // The counter for REFUSED responses should also be one, the rest zero
-    checkAllRcodeCountersZeroExcept(Rcode::REFUSED(), 1);
+    // After processing the UDP query, these counters should be incremented:
+    //   queries.udp, opcode.query, rcode.refused
+    // and these counters should not be incremented:
+    //   queries.tcp
+    ConstElementPtr stats_after = server.getStatistics();
+    expectCounterItem(stats_after, "queries.udp", 1);
+    expectCounterItem(stats_after, "queries.tcp", 0);
+    expectCounterItem(stats_after, "opcode.query", 1);
+    expectCounterItem(stats_after, "rcode.refused", 1);
 }
 
 // Submit TCP normal query and check query counter
 TEST_F(AuthSrvTest, queryCounterTCPNormal) {
-    // The counter should be initialized to 0.
-    EXPECT_EQ(0, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
+    // The counters should be initialized to 0.
+    ConstElementPtr stats_init = server.getStatistics();
+    expectCounterItem(stats_init, "queries.udp", 0);
+    expectCounterItem(stats_init, "queries.tcp", 0);
+    expectCounterItem(stats_init, "opcode.query", 0);
+    expectCounterItem(stats_init, "rcode.refused", 0);
     // Create TCP message and process.
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("example.com"),
@@ -1069,18 +1125,24 @@ TEST_F(AuthSrvTest, queryCounterTCPNormal) {
     createRequestPacket(request_message, IPPROTO_TCP);
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
-    // After processing TCP query, the counter should be 1.
-    EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
-    // The counter for SUCCESS responses should also be one
-    EXPECT_EQ(1, server.getCounter(Opcode::QUERY()));
-    // The counter for REFUSED responses should also be one, the rest zero
-    checkAllRcodeCountersZeroExcept(Rcode::REFUSED(), 1);
+    // After processing the TCP query, these counters should be incremented:
+    //   queries.tcp, opcode.query, rcode.refused
+    // and these counters should not be incremented:
+    //   queries.udp
+    ConstElementPtr stats_after = server.getStatistics();
+    expectCounterItem(stats_after, "queries.udp", 0);
+    expectCounterItem(stats_after, "queries.tcp", 1);
+    expectCounterItem(stats_after, "opcode.query", 1);
+    expectCounterItem(stats_after, "rcode.refused", 1);
 }
 
 // Submit TCP AXFR query and check query counter
 TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
-    // The counter should be initialized to 0.
-    EXPECT_EQ(0, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
+    // The counters should be initialized to 0.
+    ConstElementPtr stats_init = server.getStatistics();
+    expectCounterItem(stats_init, "queries.udp", 0);
+    expectCounterItem(stats_init, "queries.tcp", 0);
+    expectCounterItem(stats_init, "opcode.query", 0);
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                          Name("example.com"), RRClass::IN(), RRType::AXFR());
     createRequestPacket(request_message, IPPROTO_TCP);
@@ -1089,16 +1151,24 @@ TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
     EXPECT_FALSE(dnsserv.hasAnswer());
-    // After processing TCP AXFR query, the counter should be 1.
-    EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
-    // No rcodes should be incremented
-    checkAllRcodeCountersZero();
+    // After processing the TCP AXFR query, these counters should be
+    // incremented:
+    //   queries.tcp, opcode.query
+    // and these counters should not be incremented:
+    //   queries.udp
+    ConstElementPtr stats_after = server.getStatistics();
+    expectCounterItem(stats_after, "queries.udp", 0);
+    expectCounterItem(stats_after, "queries.tcp", 1);
+    expectCounterItem(stats_after, "opcode.query", 1);
 }
 
 // Submit TCP IXFR query and check query counter
 TEST_F(AuthSrvTest, queryCounterTCPIXFR) {
-    // The counter should be initialized to 0.
-    EXPECT_EQ(0, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
+    // The counters should be initialized to 0.
+    ConstElementPtr stats_init = server.getStatistics();
+    expectCounterItem(stats_init, "queries.udp", 0);
+    expectCounterItem(stats_init, "queries.tcp", 0);
+    expectCounterItem(stats_init, "opcode.query", 0);
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                          Name("example.com"), RRClass::IN(), RRType::IXFR());
     createRequestPacket(request_message, IPPROTO_TCP);
@@ -1107,14 +1177,27 @@ TEST_F(AuthSrvTest, queryCounterTCPIXFR) {
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
     EXPECT_FALSE(dnsserv.hasAnswer());
-    // After processing TCP IXFR query, the counter should be 1.
-    EXPECT_EQ(1, server.getCounter(AuthCounters::SERVER_TCP_QUERY));
+    // After processing the TCP IXFR query, these counters should be
+    // incremented:
+    //   queries.tcp, opcode.query
+    // and these counters should not be incremented:
+    //   queries.udp
+    ConstElementPtr stats_after = server.getStatistics();
+    expectCounterItem(stats_after, "queries.udp", 0);
+    expectCounterItem(stats_after, "queries.tcp", 1);
+    expectCounterItem(stats_after, "opcode.query", 1);
 }
 
 TEST_F(AuthSrvTest, queryCounterOpcodes) {
-    for (int i = 0; i < 16; ++i) {
+    // Check for 0..2, 3(=other), 4..5
+    // The counter should be initialized to 0.
+    for (int i = 0; i < 6; ++i) {
         // The counter should be initialized to 0.
-        EXPECT_EQ(0, server.getCounter(Opcode(i)));
+        expectCounterItem(server.getStatistics(),
+                          std::string("opcode.") +
+                              QRCounterOpcode[QROpCodeToQRCounterType[i] -
+                                                  QR_OPCODE_QUERY].name,
+                          0);
 
         // For each possible opcode, create a request message and send it
         UnitTestUtil::createRequestMessage(request_message, Opcode(i),
@@ -1132,7 +1215,45 @@ TEST_F(AuthSrvTest, queryCounterOpcodes) {
         }
 
         // Confirm the counter.
-        EXPECT_EQ(i + 1, server.getCounter(Opcode(i)));
+        expectCounterItem(server.getStatistics(),
+                          std::string("opcode.") +
+                              QRCounterOpcode[QROpCodeToQRCounterType[i] -
+                                                  QR_OPCODE_QUERY].name,
+                          i + 1);
+    }
+    // Check for 6..15
+    // they are treated as the 'other' opcode
+    // the 'other' opcode counter is 4 at this point
+    int expected = 4;
+    for (int i = 6; i < 16; ++i) {
+        // The counter should be initialized to 0.
+        expectCounterItem(server.getStatistics(),
+                          std::string("opcode.") +
+                              QRCounterOpcode[QROpCodeToQRCounterType[i] -
+                                              QR_OPCODE_QUERY].name,
+                          expected);
+
+        // For each possible opcode, create a request message and send it
+        UnitTestUtil::createRequestMessage(request_message, Opcode(i),
+                                           default_qid, Name("example.com"),
+                                           RRClass::IN(), RRType::NS());
+        createRequestPacket(request_message, IPPROTO_UDP);
+
+        // "send" the request once
+        parse_message->clear(Message::PARSE);
+        server.processMessage(*io_message, *parse_message,
+                              *response_obuffer,
+                              &dnsserv);
+
+        // the 'other' opcode counter should be incremented
+        ++expected;
+
+        // Confirm the counter.
+        expectCounterItem(server.getStatistics(),
+                          std::string("opcode.") +
+                              QRCounterOpcode[QROpCodeToQRCounterType[i] -
+                                              QR_OPCODE_QUERY].name,
+                          expected);
     }
 }
 
@@ -1718,6 +1839,15 @@ namespace {
         isc::config::parseAnswer(command_result, response);
         EXPECT_EQ(0, command_result);
     }
+
+    void sendCommand(AuthSrv& server, const std::string& command,
+                     ConstElementPtr args, int expected_result) {
+        ConstElementPtr response = execAuthServerCommand(server, command,
+                                                         args);
+        int command_result = -1;
+        isc::config::parseAnswer(command_result, response);
+        EXPECT_EQ(expected_result, command_result);
+    }
 } // end anonymous namespace
 
 TEST_F(AuthSrvTest, DDNSForwardCreateDestroy) {
@@ -1789,4 +1919,18 @@ TEST_F(AuthSrvTest, DDNSForwardCreateDestroy) {
                 Opcode::UPDATE().getCode(), QR_FLAG, 0, 0, 0, 0);
 }
 
+TEST_F(AuthSrvTest, loadZoneCommand) {
+    // Just some very basic tests, to check the command is accepted, and that
+    // it raises on bad arguments, but not on correct ones (full testing
+    // is handled in the unit tests for the corresponding classes)
+
+    // Empty map should fail
+    ElementPtr args(Element::createMap());
+    sendCommand(server, "loadzone", args, 1);
+    // Setting an origin should be enough (even if it isn't actually loaded,
+    // it should be initially accepted)
+    args->set("origin", Element::create("example.com"));
+    sendCommand(server, "loadzone", args, 0);
+}
+
 }

+ 0 - 275
src/bin/auth/tests/command_unittest.cc

@@ -169,281 +169,6 @@ TEST_F(AuthCommandTest, shutdownIncorrectPID) {
     EXPECT_EQ(0, rcode_);
 }
 
-// A helper function commonly used for the "loadzone" command tests.
-// It configures the server with a memory data source containing two
-// zones, and checks the zones are correctly loaded.
-void
-zoneChecks(AuthSrv& server) {
-    const RRClass rrclass(RRClass::IN());
-
-    DataSrcClientsMgr::Holder holder(server.getDataSrcClientsMgr());
-    EXPECT_EQ(ZoneFinder::SUCCESS,
-              holder.findClientList(rrclass)->find(Name("ns.test1.example"))
-              .finder_->find(Name("ns.test1.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET,
-              holder.findClientList(rrclass)->find(Name("ns.test1.example")).
-              finder_->find(Name("ns.test1.example"), RRType::AAAA())->code);
-    EXPECT_EQ(ZoneFinder::SUCCESS,
-              holder.findClientList(rrclass)->find(Name("ns.test2.example")).
-              finder_->find(Name("ns.test2.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET,
-              holder.findClientList(rrclass)->find(Name("ns.test2.example")).
-              finder_->find(Name("ns.test2.example"), RRType::AAAA())->code);
-}
-
-void
-installDataSrcClientLists(AuthSrv& server, ClientListMapPtr lists) {
-    server.getDataSrcClientsMgr().setDataSrcClientLists(lists);
-}
-
-void
-configureZones(AuthSrv& server) {
-    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
-                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
-    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in "
-                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
-
-    const ConstElementPtr config(Element::fromJSON("{"
-        "\"IN\": [{"
-        "   \"type\": \"MasterFiles\","
-        "   \"params\": {"
-        "       \"test1.example\": \"" +
-                string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\","
-        "       \"test2.example\": \"" +
-                string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\""
-        "   },"
-        "   \"cache-enable\": true"
-        "}]}"));
-
-    installDataSrcClientLists(server, configureDataSource(config));
-
-    zoneChecks(server);
-}
-
-void
-newZoneChecks(AuthSrv& server) {
-    const RRClass rrclass(RRClass::IN());
-
-    DataSrcClientsMgr::Holder holder(server.getDataSrcClientsMgr());
-    EXPECT_EQ(ZoneFinder::SUCCESS, holder.findClientList(rrclass)->
-              find(Name("ns.test1.example")).finder_->
-              find(Name("ns.test1.example"), RRType::A())->code);
-
-    // now test1.example should have ns/AAAA
-    EXPECT_EQ(ZoneFinder::SUCCESS, holder.findClientList(rrclass)->
-              find(Name("ns.test1.example")).finder_->
-              find(Name("ns.test1.example"), RRType::AAAA())->code);
-
-    // test2.example shouldn't change
-    EXPECT_EQ(ZoneFinder::SUCCESS, holder.findClientList(rrclass)->
-              find(Name("ns.test2.example")).finder_->
-              find(Name("ns.test2.example"), RRType::A())->code);
-    EXPECT_EQ(ZoneFinder::NXRRSET,
-              holder.findClientList(rrclass)->
-              find(Name("ns.test2.example")).finder_->
-              find(Name("ns.test2.example"), RRType::AAAA())->code);
-}
-
-TEST_F(AuthCommandTest, loadZone) {
-    configureZones(server_);
-
-    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
-                        "/test1-new.zone.in "
-                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
-    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
-                        "/test2-new.zone.in "
-                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
-
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\"}"));
-    checkAnswer(0);
-    newZoneChecks(server_);
-}
-
-TEST_F(AuthCommandTest,
-#ifdef USE_STATIC_LINK
-       DISABLED_loadZoneSQLite3
-#else
-       loadZoneSQLite3
-#endif
-    )
-{
-    // Prepare the database first
-    const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
-    const string bad_db = TEST_DATA_BUILDDIR "/does-not-exist.sqlite3";
-    stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
-    createSQLite3DB(RRClass::IN(), Name("example.org"), test_db.c_str(), ss);
-    // This describes the data source in the configuration
-    const ConstElementPtr config(Element::fromJSON("{"
-        "\"IN\": [{"
-        "    \"type\": \"sqlite3\","
-        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
-        "    \"cache-enable\": true,"
-        "    \"cache-zones\": [\"example.org\"]"
-        "}]}"));
-    installDataSrcClientLists(server_, configureDataSource(config));
-
-    {
-        DataSrcClientsMgr::Holder holder(server_.getDataSrcClientsMgr());
-
-        // Check that the A record at www.example.org does not exist
-        EXPECT_EQ(ZoneFinder::NXDOMAIN,
-                  holder.findClientList(RRClass::IN())->
-                  find(Name("example.org")).finder_->
-                  find(Name("www.example.org"), RRType::A())->code);
-
-        // Add the record to the underlying sqlite database, by loading
-        // it as a separate datasource, and updating it
-        ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
-                                                    "\"database_file\": \""
-                                                    + test_db + "\"}");
-        DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
-        ZoneUpdaterPtr sql_updater =
-            sql_ds.getInstance().getUpdater(Name("example.org"), false);
-        RRsetPtr rrset(new RRset(Name("www.example.org."), RRClass::IN(),
-                                 RRType::A(), RRTTL(60)));
-        rrset->addRdata(rdata::createRdata(rrset->getType(),
-                                           rrset->getClass(),
-                                           "192.0.2.1"));
-        sql_updater->addRRset(*rrset);
-        sql_updater->commit();
-
-        EXPECT_EQ(ZoneFinder::NXDOMAIN,
-                  holder.findClientList(RRClass::IN())->
-                  find(Name("example.org")).finder_->
-                  find(Name("www.example.org"), RRType::A())->code);
-    }
-
-    // Now send the command to reload it
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"example.org\"}"));
-    checkAnswer(0, "Successful load");
-
-    {
-        DataSrcClientsMgr::Holder holder(server_.getDataSrcClientsMgr());
-        // And now it should be present too.
-        EXPECT_EQ(ZoneFinder::SUCCESS,
-                  holder.findClientList(RRClass::IN())->
-                  find(Name("example.org")).finder_->
-                  find(Name("www.example.org"), RRType::A())->code);
-    }
-
-    // Some error cases. First, the zone has no configuration. (note .com here)
-    result_ = execAuthServerCommand(server_, "loadzone",
-        Element::fromJSON("{\"origin\": \"example.com\"}"));
-    checkAnswer(1, "example.com");
-
-    {
-        DataSrcClientsMgr::Holder holder(server_.getDataSrcClientsMgr());
-        // The previous zone is not hurt in any way
-        EXPECT_EQ(ZoneFinder::SUCCESS,
-                  holder.findClientList(RRClass::IN())->
-                  find(Name("example.org")).finder_->
-                  find(Name("example.org"), RRType::SOA())->code);
-    }
-
-    const ConstElementPtr config2(Element::fromJSON("{"
-        "\"IN\": [{"
-        "    \"type\": \"sqlite3\","
-        "    \"params\": {\"database_file\": \"" + bad_db + "\"},"
-        "    \"cache-enable\": true,"
-        "    \"cache-zones\": [\"example.com\"]"
-        "}]}"));
-    EXPECT_THROW(configureDataSource(config2),
-                 ConfigurableClientList::ConfigurationError);
-
-    result_ = execAuthServerCommand(server_, "loadzone",
-        Element::fromJSON("{\"origin\": \"example.com\"}"));
-    checkAnswer(1, "Unreadable");
-
-    DataSrcClientsMgr::Holder holder(server_.getDataSrcClientsMgr());
-    // The previous zone is not hurt in any way
-    EXPECT_EQ(ZoneFinder::SUCCESS,
-              holder.findClientList(RRClass::IN())->
-              find(Name("example.org")).finder_->
-              find(Name("example.org"), RRType::SOA())->code);
-}
-
-TEST_F(AuthCommandTest, loadBrokenZone) {
-    configureZones(server_);
-
-    ASSERT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
-                        "/test1-broken.zone.in "
-                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\"}"));
-    checkAnswer(1);
-    zoneChecks(server_);     // zone shouldn't be replaced
-}
-
-TEST_F(AuthCommandTest, loadUnreadableZone) {
-    configureZones(server_);
-
-    // install the zone file as unreadable
-    ASSERT_EQ(0, system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR
-                        "/test1.zone.in "
-                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\"}"));
-    checkAnswer(1);
-    zoneChecks(server_);     // zone shouldn't be replaced
-}
-
-TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) {
-    // try to execute load command without configuring the zone beforehand.
-    // it should fail.
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\"}"));
-    checkAnswer(1);
-}
-
-TEST_F(AuthCommandTest, loadZoneInvalidParams) {
-    configureZones(server_);
-
-    // null arg
-    result_ = execAuthServerCommand(server_, "loadzone", ElementPtr());
-    checkAnswer(1, "Null arg");
-
-    // zone class is bogus
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\","
-                                        " \"class\": \"no_such_class\"}"));
-    checkAnswer(1, "No such class");
-
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"test1.example\","
-                                        " \"class\": 1}"));
-    checkAnswer(1, "Integral class");
-
-
-    // origin is missing
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON("{}"));
-    checkAnswer(1, "Missing origin");
-
-    // zone doesn't exist in the data source
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON("{\"origin\": \"xx\"}"));
-    checkAnswer(1, "No such zone");
-
-    // origin is bogus
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON(
-                                        "{\"origin\": \"...\"}"));
-    checkAnswer(1, "Wrong name");
-
-    result_ = execAuthServerCommand(server_, "loadzone",
-                                    Element::fromJSON("{\"origin\": 10}"));
-    checkAnswer(1, "Integral name");
-}
-
 TEST_F(AuthCommandTest, getStats) {
     result_ = execAuthServerCommand(server_, "getstats", ConstElementPtr());
     parseAnswer(rcode_, result_);

+ 334 - 13
src/bin/auth/tests/datasrc_clients_builder_unittest.cc

@@ -12,36 +12,62 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <util/unittests/check_valgrind.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
 #include <cc/data.h>
 
+#include <datasrc/client.h>
+#include <datasrc/factory.h>
+
 #include <auth/datasrc_clients_mgr.h>
+#include <auth/datasrc_config.h>
+
+#include <testutils/dnsmessage_test.h>
+
 #include "test_datasrc_clients_mgr.h"
+#include "datasrc_util.h"
 
 #include <gtest/gtest.h>
 
 #include <boost/function.hpp>
 
+#include <cstdlib>
+#include <string>
+#include <sstream>
+
 using isc::data::ConstElementPtr;
+using namespace isc::dns;
+using namespace isc::data;
 using namespace isc::datasrc;
 using namespace isc::auth::datasrc_clientmgr_internal;
+using namespace isc::auth::unittest;
+using namespace isc::testutils;
 
 namespace {
 class DataSrcClientsBuilderTest : public ::testing::Test {
 protected:
     DataSrcClientsBuilderTest() :
+        clients_map(new std::map<RRClass,
+                    boost::shared_ptr<ConfigurableClientList> >),
         builder(&command_queue, &cond, &queue_mutex, &clients_map, &map_mutex),
-        cond(command_queue, delayed_command_queue),
+        cond(command_queue, delayed_command_queue), rrclass(RRClass::IN()),
         shutdown_cmd(SHUTDOWN, ConstElementPtr()),
         noop_cmd(NOOP, ConstElementPtr())
     {}
 
-    TestDataSrcClientsBuilder builder;
+    void configureZones();      // used for loadzone related tests
+
+    ClientListMapPtr clients_map; // configured clients
     std::list<Command> command_queue; // test command queue
     std::list<Command> delayed_command_queue; // commands available after wait
-    ClientListMapPtr clients_map; // configured clients
+    TestDataSrcClientsBuilder builder;
     TestCondVar cond;
     TestMutex queue_mutex;
     TestMutex map_mutex;
+    const RRClass rrclass;
     const Command shutdown_cmd;
     const Command noop_cmd;
 };
@@ -74,13 +100,20 @@ TEST_F(DataSrcClientsBuilderTest, exception) {
     // them.  Right now, we simply abort to prevent the system from running
     // with half-broken state.  Eventually we should introduce a better
     // error handling.
-    command_queue.push_back(noop_cmd);
-    queue_mutex.throw_from_noop = TestMutex::EXCLASS;
-    EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+    if (!isc::util::unittests::runningOnValgrind()) {
+        command_queue.push_back(noop_cmd);
+        queue_mutex.throw_from_noop = TestMutex::EXCLASS;
+        EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+
+        command_queue.push_back(noop_cmd);
+        queue_mutex.throw_from_noop = TestMutex::INTEGER;
+        EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+    }
 
     command_queue.push_back(noop_cmd);
-    queue_mutex.throw_from_noop = TestMutex::INTEGER;
-    EXPECT_DEATH_IF_SUPPORTED({builder.run();}, "");
+    command_queue.push_back(shutdown_cmd); // we need to stop the loop
+    queue_mutex.throw_from_noop = TestMutex::INTERNAL;
+    builder.run();
 }
 
 TEST_F(DataSrcClientsBuilderTest, condWait) {
@@ -106,10 +139,10 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
     Command reconfig_cmd(RECONFIGURE, ConstElementPtr());
 
     // Initially, no clients should be there
-    EXPECT_EQ(ClientListMapPtr(), clients_map);
+    EXPECT_TRUE(clients_map->empty());
 
     // A config that doesn't do much except be accepted
-    ConstElementPtr good_config = isc::data::Element::fromJSON(
+    ConstElementPtr good_config = Element::fromJSON(
         "{"
         "\"IN\": [{"
         "   \"type\": \"MasterFiles\","
@@ -121,7 +154,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
 
     // A configuration that is 'correct' in the top-level, but contains
     // bad data for the type it specifies
-    ConstElementPtr bad_config = isc::data::Element::fromJSON(
+    ConstElementPtr bad_config = Element::fromJSON(
         "{"
         "\"IN\": [{"
         "   \"type\": \"MasterFiles\","
@@ -142,7 +175,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
     // If a 'bad' command argument got here, the config validation should
     // have failed already, but still, the handler should return true,
     // and the clients_map should not be updated.
-    reconfig_cmd.second = isc::data::Element::create("{ \"foo\": \"bar\" }");
+    reconfig_cmd.second = Element::create("{ \"foo\": \"bar\" }");
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_EQ(working_config_clients, clients_map);
     // Building failed, so map mutex should not have been locked again
@@ -173,7 +206,7 @@ TEST_F(DataSrcClientsBuilderTest, reconfigure) {
     EXPECT_EQ(2, map_mutex.lock_count);
 
     // And finally, try an empty config to disable all datasource clients
-    reconfig_cmd.second = isc::data::Element::createMap();
+    reconfig_cmd.second = Element::createMap();
     EXPECT_TRUE(builder.handleCommand(reconfig_cmd));
     EXPECT_EQ(0, clients_map->size());
     EXPECT_EQ(3, map_mutex.lock_count);
@@ -193,4 +226,292 @@ TEST_F(DataSrcClientsBuilderTest, badCommand) {
                  isc::Unexpected);
 }
 
+// A helper function commonly used for the "loadzone" command tests.
+// It configures the given data source client lists with a memory data source
+// containing two zones, and checks the zones are correctly loaded.
+void
+zoneChecks(ClientListMapPtr clients_map, RRClass rrclass) {
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::AAAA())->code);
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::AAAA())->code);
+}
+
+// Another helper that checks after completing loadzone command.
+void
+newZoneChecks(ClientListMapPtr clients_map, RRClass rrclass) {
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::A())->code);
+    // now test1.example should have ns/AAAA
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test1.example")).finder_->
+              find(Name("ns.test1.example"), RRType::AAAA())->code);
+
+    // test2.example shouldn't change
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::A())->code);
+    EXPECT_EQ(ZoneFinder::NXRRSET,
+              clients_map->find(rrclass)->second->
+              find(Name("ns.test2.example")).finder_->
+              find(Name("ns.test2.example"), RRType::AAAA())->code);
+}
+
+void
+DataSrcClientsBuilderTest::configureZones() {
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR "/test1.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR "/test2.zone.in "
+                             TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+    const ConstElementPtr config(
+        Element::fromJSON(
+            "{"
+            "\"IN\": [{"
+            "   \"type\": \"MasterFiles\","
+            "   \"params\": {"
+            "       \"test1.example\": \"" +
+            std::string(TEST_DATA_BUILDDIR "/test1.zone.copied") + "\","
+            "       \"test2.example\": \"" +
+            std::string(TEST_DATA_BUILDDIR "/test2.zone.copied") + "\""
+            "   },"
+            "   \"cache-enable\": true"
+            "}]}"));
+    clients_map = configureDataSource(config);
+    zoneChecks(clients_map, rrclass);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZone) {
+    // pre test condition checks
+    EXPECT_EQ(0, map_mutex.lock_count);
+    EXPECT_EQ(0, map_mutex.unlock_count);
+
+    configureZones();
+
+    EXPECT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
+                        "/test1-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    EXPECT_EQ(0, system(INSTALL_PROG " -c " TEST_DATA_DIR
+                        "/test2-new.zone.in "
+                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
+
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
+    EXPECT_EQ(1, map_mutex.lock_count); // we should have acquired the lock
+    EXPECT_EQ(1, map_mutex.unlock_count); // and released it.
+
+    newZoneChecks(clients_map, rrclass);
+}
+
+TEST_F(DataSrcClientsBuilderTest,
+#ifdef USE_STATIC_LINK
+       DISABLED_loadZoneSQLite3
+#else
+       loadZoneSQLite3
+#endif
+    )
+{
+    // Prepare the database first
+    const std::string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
+    std::stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+    createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss);
+    // This describes the data source in the configuration
+    const ConstElementPtr config(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
+        "    \"cache-enable\": true,"
+        "    \"cache-zones\": [\"example.org\"]"
+        "}]}"));
+    clients_map = configureDataSource(config);
+
+    // Check that the A record at www.example.org does not exist
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Add the record to the underlying sqlite database, by loading
+    // it as a separate datasource, and updating it
+    ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
+                                                "\"database_file\": \""
+                                                + test_db + "\"}");
+    DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
+    ZoneUpdaterPtr sql_updater =
+        sql_ds.getInstance().getUpdater(Name("example.org"), false);
+    sql_updater->addRRset(
+        *textToRRset("www.example.org. 60 IN A 192.0.2.1"));
+    sql_updater->commit();
+
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // Now send the command to reload it
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"example.org\"}"));
+    EXPECT_TRUE(builder.handleCommand(loadzone_cmd));
+    // And now it should be present too.
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("www.example.org"), RRType::A())->code);
+
+    // An error case: the zone has no configuration. (note .com here)
+    const Command nozone_cmd(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.com\"}"));
+    EXPECT_THROW(builder.handleCommand(nozone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    // The previous zone is not hurt in any way
+    EXPECT_EQ(ZoneFinder::SUCCESS, clients_map->find(rrclass)->second->
+              find(Name("example.org")).finder_->
+              find(Name("example.org"), RRType::SOA())->code);
+
+    // attempt of reloading a zone but in-memory cache is disabled.
+    const ConstElementPtr config2(Element::fromJSON("{"
+        "\"IN\": [{"
+        "    \"type\": \"sqlite3\","
+        "    \"params\": {\"database_file\": \"" + test_db + "\"},"
+        "    \"cache-enable\": false,"
+        "    \"cache-zones\": [\"example.org\"]"
+        "}]}"));
+    clients_map = configureDataSource(config2);
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.org\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+
+    // basically impossible case: in-memory cache is completely disabled.
+    // In this implementation of manager-builder, this should never happen,
+    // but it catches it like other configuration error and keeps going.
+    clients_map->clear();
+    boost::shared_ptr<ConfigurableClientList> nocache_list(
+        new ConfigurableClientList(rrclass));
+    nocache_list->configure(
+        Element::fromJSON(
+            "[{\"type\": \"sqlite3\","
+            "  \"params\": {\"database_file\": \"" + test_db + "\"},"
+            "  \"cache-enable\": true,"
+            "  \"cache-zones\": [\"example.org\"]"
+            "}]"), false);           // false = disable cache
+    (*clients_map)[rrclass] = nocache_list;
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE, Element::fromJSON(
+                                 "{\"class\": \"IN\","
+                                 " \"origin\": \"example.org\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadBrokenZone) {
+    configureZones();
+
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_DATA_DIR
+                             "/test1-broken.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    // there's an error in the new zone file.  reload will be rejected.
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_THROW(builder.handleCommand(loadzone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    zoneChecks(clients_map, rrclass);     // zone shouldn't be replaced
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadUnreadableZone) {
+    configureZones();
+
+    // install the zone file as unreadable
+    ASSERT_EQ(0, std::system(INSTALL_PROG " -c -m 000 " TEST_DATA_DIR
+                             "/test1.zone.in "
+                             TEST_DATA_BUILDDIR "/test1.zone.copied"));
+    const Command loadzone_cmd(LOADZONE, Element::fromJSON(
+                                   "{\"class\": \"IN\","
+                                   " \"origin\": \"test1.example\"}"));
+    EXPECT_THROW(builder.handleCommand(loadzone_cmd),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+    zoneChecks(clients_map, rrclass);     // zone shouldn't be replaced
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZoneWithoutDataSrc) {
+    // try to execute load command without configuring the zone beforehand.
+    // it should fail.
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"class\": \"IN\", "
+                                 " \"origin\": \"test1.example\"}"))),
+                 TestDataSrcClientsBuilder::InternalCommandError);
+}
+
+TEST_F(DataSrcClientsBuilderTest, loadZoneInvalidParams) {
+    configureZones();
+
+    if (!isc::util::unittests::runningOnValgrind()) {
+        // null arg: this causes assertion failure
+        EXPECT_DEATH_IF_SUPPORTED({
+                builder.handleCommand(Command(LOADZONE, ElementPtr()));
+            }, "");
+    }
+
+    // zone class is bogus (note that this shouldn't happen except in tests)
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": \"test1.example\","
+                                 " \"class\": \"no_such_class\"}"))),
+                 InvalidRRClass);
+
+    // not a string
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": \"test1.example\","
+                                 " \"class\": 1}"))),
+                 isc::data::TypeError);
+
+    // class or origin is missing: result in assertion failure
+    if (!isc::util::unittests::runningOnValgrind()) {
+        EXPECT_DEATH_IF_SUPPORTED({
+                builder.handleCommand(Command(LOADZONE,
+                                              Element::fromJSON(
+                                                  "{\"class\": \"IN\"}")));
+            }, "");
+    }
+
+    // zone doesn't exist in the data source
+    EXPECT_THROW(
+        builder.handleCommand(
+            Command(LOADZONE,
+                    Element::fromJSON(
+                        "{\"class\": \"IN\", \"origin\": \"xx\"}"))),
+        TestDataSrcClientsBuilder::InternalCommandError);
+
+    // origin is bogus
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"class\": \"IN\", \"origin\": \"...\"}"))),
+                 EmptyLabel);
+    EXPECT_THROW(builder.handleCommand(
+                     Command(LOADZONE,
+                             Element::fromJSON(
+                                 "{\"origin\": 10, \"class\": 1}"))),
+                 isc::data::TypeError);
+}
+
 } // unnamed namespace

+ 49 - 0
src/bin/auth/tests/datasrc_clients_mgr_unittest.cc

@@ -196,6 +196,55 @@ TEST(DataSrcClientsMgrTest, holder) {
     EXPECT_THROW(TestDataSrcClientsMgr::Holder holder2(mgr), isc::Unexpected);
 }
 
+TEST(DataSrcClientsMgrTest, reload) {
+    TestDataSrcClientsMgr mgr;
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::started);
+    EXPECT_TRUE(FakeDataSrcClientsBuilder::command_queue->empty());
+
+    isc::data::ElementPtr args =
+        isc::data::Element::fromJSON("{ \"class\": \"IN\","
+                                     "  \"origin\": \"example.com\" }");
+    mgr.loadZone(args);
+    EXPECT_EQ(1, FakeDataSrcClientsBuilder::command_queue->size());
+    mgr.loadZone(args);
+    EXPECT_EQ(2, FakeDataSrcClientsBuilder::command_queue->size());
+
+    // Should fail with non-string 'class' value
+    args->set("class", Element::create(1));
+    EXPECT_THROW(mgr.loadZone(args), CommandError);
+    EXPECT_EQ(2, FakeDataSrcClientsBuilder::command_queue->size());
+
+    // And with badclass
+    args->set("class", Element::create("BADCLASS"));
+    EXPECT_THROW(mgr.loadZone(args), CommandError);
+    EXPECT_EQ(2, FakeDataSrcClientsBuilder::command_queue->size());
+
+    // Should succeed without 'class'
+    args->remove("class");
+    mgr.loadZone(args);
+    EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
+
+    // but fail without origin, without sending new commands
+    args->remove("origin");
+    EXPECT_THROW(mgr.loadZone(args), CommandError);
+    EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
+
+    // And for 'origin' that is not a string
+    args->set("origin", Element::create(1));
+    EXPECT_THROW(mgr.loadZone(args), CommandError);
+    EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
+
+    // And origin that is not a correct name
+    args->set("origin", Element::create(".."));
+    EXPECT_THROW(mgr.loadZone(args), CommandError);
+    EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
+
+    // same for empty data and data that is not a map
+    EXPECT_THROW(mgr.loadZone(isc::data::ConstElementPtr()), CommandError);
+    EXPECT_THROW(mgr.loadZone(isc::data::Element::createList()), CommandError);
+    EXPECT_EQ(3, FakeDataSrcClientsBuilder::command_queue->size());
+}
+
 TEST(DataSrcClientsMgrTest, realThread) {
     // Using the non-test definition with a real thread.  Just checking
     // no disruption happens.

+ 62 - 310
src/bin/auth/tests/statistics_unittest.cc

@@ -27,345 +27,97 @@
 #include <cc/session.h>
 
 #include <auth/statistics.h>
+#include <auth/statistics_items.h>
 
 #include <dns/tests/unittest_util.h>
 
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
 using namespace std;
 using namespace isc::cc;
 using namespace isc::dns;
 using namespace isc::data;
+using isc::auth::statistics::Counters;
+using isc::auth::statistics::QRAttributes;
 
 namespace {
 
-class AuthCountersTest : public ::testing::Test {
-private:
-    class MockSession : public AbstractSession {
-    public:
-        MockSession() :
-            // by default we return a simple "success" message.
-            msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
-            throw_session_error_(false), throw_session_timeout_(false)
-        {}
-        virtual void establish(const char* socket_file);
-        virtual void disconnect();
-        virtual int group_sendmsg(ConstElementPtr msg, string group,
-                                  string instance, string to);
-        virtual bool group_recvmsg(ConstElementPtr& envelope,
-                                   ConstElementPtr& msg,
-                                   bool nonblock, int seq);
-        virtual void subscribe(string group, string instance);
-        virtual void unsubscribe(string group, string instance);
-        virtual void startRead(boost::function<void()> read_callback);
-        virtual int reply(ConstElementPtr envelope, ConstElementPtr newmsg);
-        virtual bool hasQueuedMsgs() const;
-        virtual void setTimeout(size_t) {}
-        virtual size_t getTimeout() const { return (0); };
-
-        void setThrowSessionError(bool flag);
-        void setThrowSessionTimeout(bool flag);
-
-        void setMessage(ConstElementPtr msg) { msg_ = msg; }
-
-        ConstElementPtr sent_msg;
-        string msg_destination;
-    private:
-        ConstElementPtr msg_;
-        bool throw_session_error_;
-        bool throw_session_timeout_;
-    };
-
+class CountersTest : public ::testing::Test {
 protected:
-    AuthCountersTest() : counters() {
-    }
-    ~AuthCountersTest() {
-    }
-    AuthCounters counters;
-    // no need to be inherited from the original class here.
-    class MockModuleSpec {
-    public:
-        bool validateStatistics(ConstElementPtr, const bool valid) const
-            { return (valid); }
-    };
-    MockModuleSpec module_spec_;
+    CountersTest() : counters() {}
+    ~CountersTest() {}
+    Counters counters;
 };
 
+// Test if the values of the counters are all zero except for the items
+// specified in except_for.
 void
-AuthCountersTest::MockSession::establish(const char*) {}
-
-void
-AuthCountersTest::MockSession::disconnect() {}
-
-void
-AuthCountersTest::MockSession::subscribe(string, string)
-{}
-
-void
-AuthCountersTest::MockSession::unsubscribe(string, string)
-{}
-
-void
-AuthCountersTest::MockSession::startRead(boost::function<void()>)
-{}
-
-int
-AuthCountersTest::MockSession::reply(ConstElementPtr, ConstElementPtr) {
-    return (-1);
-}
-
-bool
-AuthCountersTest::MockSession::hasQueuedMsgs() const {
-    return (false);
-}
-
-int
-AuthCountersTest::MockSession::group_sendmsg(ConstElementPtr msg,
-                                              string group, string, string)
-{
-    if (throw_session_error_) {
-        isc_throw(SessionError, "Session Error");
-    }
-    sent_msg = msg;
-    msg_destination = group;
-    return (0);
-}
-
-bool
-AuthCountersTest::MockSession::group_recvmsg(ConstElementPtr&,
-                                              ConstElementPtr& msg, bool, int)
-{
-    if (throw_session_timeout_) {
-        isc_throw(SessionTimeout, "Session Timeout");
-    }
-    msg = msg_;
-    return (true);
-}
-
-void
-AuthCountersTest::MockSession::setThrowSessionError(bool flag) {
-    throw_session_error_ = flag;
-}
-
-void
-AuthCountersTest::MockSession::setThrowSessionTimeout(bool flag) {
-    throw_session_timeout_ = flag;
-}
-
-TEST_F(AuthCountersTest, incrementUDPCounter) {
-    // The counter should be initialized to 0.
-    EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_UDP_QUERY));
-    EXPECT_NO_THROW(counters.inc(AuthCounters::SERVER_UDP_QUERY));
-    // After increment, the counter should be 1.
-    EXPECT_EQ(1, counters.getCounter(AuthCounters::SERVER_UDP_QUERY));
-}
-
-TEST_F(AuthCountersTest, incrementTCPCounter) {
-    // The counter should be initialized to 0.
-    EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_TCP_QUERY));
-    EXPECT_NO_THROW(counters.inc(AuthCounters::SERVER_TCP_QUERY));
-    // After increment, the counter should be 1.
-    EXPECT_EQ(1, counters.getCounter(AuthCounters::SERVER_TCP_QUERY));
-}
-
-TEST_F(AuthCountersTest, incrementInvalidCounter) {
-    // Expect to throw an isc::OutOfRange
-    EXPECT_THROW(counters.inc(AuthCounters::SERVER_COUNTER_TYPES),
-                 isc::OutOfRange);
-}
-
-TEST_F(AuthCountersTest, incrementOpcodeCounter) {
-    // The counter should be initialized to 0.  If we increment it by 1
-    // the counter should be 1.
-    for (int i = 0; i < 16; ++i) {
-        EXPECT_EQ(0, counters.getCounter(Opcode(i)));
-        counters.inc(Opcode(i));
-        EXPECT_EQ(1, counters.getCounter(Opcode(i)));
-    }
-}
-
-TEST_F(AuthCountersTest, incrementRcodeCounter) {
-    // The counter should be initialized to 0.  If we increment it by 1
-    // the counter should be 1.
-    for (int i = 0; i < 17; ++i) {
-        EXPECT_EQ(0, counters.getCounter(Rcode(i)));
-        counters.inc(Rcode(i));
-        EXPECT_EQ(1, counters.getCounter(Rcode(i)));
-    }
-}
-
-void
-opcodeDataCheck(ConstElementPtr data, const int expected[16]) {
-    const char* item_names[] = {
-        "query", "iquery", "status", "reserved3", "notify", "update",
-        "reserved6", "reserved7", "reserved8", "reserved9", "reserved10",
-        "reserved11", "reserved12", "reserved13", "reserved14", "reserved15",
-        NULL
-    };
-    int i;
-    for (i = 0; i < 16; ++i) {
-        ASSERT_NE(static_cast<const char*>(NULL), item_names[i]);
-        const string item_name = "opcode." + string(item_names[i]);
-        if (expected[i] == 0) {
-            EXPECT_FALSE(data->get(item_name));
-        } else {
-            EXPECT_EQ(expected[i], data->get(item_name)->intValue());
-        }
-    }
-    // We should have examined all names
-    ASSERT_EQ(static_cast<const char*>(NULL), item_names[i]);
-}
-
-void
-rcodeDataCheck(ConstElementPtr data, const int expected[17]) {
-    const char* item_names[] = {
-        "noerror", "formerr", "servfail", "nxdomain", "notimp", "refused",
-        "yxdomain", "yxrrset", "nxrrset", "notauth", "notzone", "reserved11",
-        "reserved12", "reserved13", "reserved14", "reserved15", "badvers",
-        NULL
-    };
-    int i;
-    for (i = 0; i < 17; ++i) {
-        ASSERT_NE(static_cast<const char*>(NULL), item_names[i]);
-        const string item_name = "rcode." + string(item_names[i]);
-        if (expected[i] == 0) {
-            EXPECT_FALSE(data->get(item_name));
-        } else {
-            EXPECT_EQ(expected[i], data->get(item_name)->intValue());
+checkCountersAllZeroExcept(const isc::data::ConstElementPtr counters,
+                           const std::set<std::string>& except_for) {
+    std::map<std::string, ConstElementPtr> stats_map = counters->mapValue();
+
+    for (std::map<std::string, ConstElementPtr>::const_iterator
+            i = stats_map.begin(), e = stats_map.end();
+            i != e;
+            ++i)
+    {
+        int expect = 0;
+        if (except_for.count(i->first) != 0) {
+            expect = 1;
         }
+        EXPECT_EQ(expect, i->second->intValue()) << "Expected counter "
+            << i->first << " = " << expect << ", actual: "
+            << i->second->intValue();
     }
-    // We should have examined all names
-    ASSERT_EQ(static_cast<const char*>(NULL), item_names[i]);
 }
 
-TEST_F(AuthCountersTest, getStatisticsWithoutValidator) {
-    // Get statistics data.
-    // Validate if it answers correct data.
+TEST_F(CountersTest, incrementNormalQuery) {
+    Message response(Message::RENDER);
+    QRAttributes qrattrs;
+    std::set<std::string> expect_nonzero;
 
-    // Counters should be initialized to 0.
-    EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_UDP_QUERY));
-    EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_TCP_QUERY));
+    expect_nonzero.clear();
+    checkCountersAllZeroExcept(counters.getStatistics(), expect_nonzero);
 
-    // UDP query counter is set to 2.
-    counters.inc(AuthCounters::SERVER_UDP_QUERY);
-    counters.inc(AuthCounters::SERVER_UDP_QUERY);
-    // TCP query counter is set to 1.
-    counters.inc(AuthCounters::SERVER_TCP_QUERY);
-    ConstElementPtr statistics_data = counters.getStatistics();
+    qrattrs.setQueryIPVersion(AF_INET6);
+    qrattrs.setQueryTransportProtocol(IPPROTO_UDP);
+    qrattrs.setQueryOpCode(Opcode::QUERY_CODE);
+    qrattrs.setQueryEDNS(true, false);
+    qrattrs.setQueryDO(true);
+    qrattrs.answerWasSent();
 
-    // UDP query counter is 2 and TCP query counter is 1.
-    EXPECT_EQ(2, statistics_data->get("queries.udp")->intValue());
-    EXPECT_EQ(1, statistics_data->get("queries.tcp")->intValue());
+    response.setRcode(Rcode::REFUSED());
+    response.addQuestion(Question(Name("example.com"),
+                                  RRClass::IN(), RRType::AAAA()));
 
-    // By default opcode counters are all 0 and omitted
-    const int opcode_results[16] = { 0, 0, 0, 0, 0, 0, 0, 0,
-                                     0, 0, 0, 0, 0, 0, 0, 0 };
-    opcodeDataCheck(statistics_data, opcode_results);
-    // By default rcode counters are all 0 and omitted
-    const int rcode_results[17] = { 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                                    0, 0, 0, 0, 0, 0, 0, 0 };
-    rcodeDataCheck(statistics_data, rcode_results);
-}
+    counters.inc(qrattrs, response);
 
-void
-updateOpcodeCounters(AuthCounters &counters, const int expected[16]) {
-    for (int i = 0; i < 16; ++i) {
-        for (int j = 0; j < expected[i]; ++j) {
-            counters.inc(Opcode(i));
-        }
-    }
+    expect_nonzero.clear();
+    expect_nonzero.insert("opcode.query");
+    expect_nonzero.insert("queries.udp");
+    expect_nonzero.insert("rcode.refused");
+    checkCountersAllZeroExcept(counters.getStatistics(), expect_nonzero);
 }
 
-void
-updateRcodeCounters(AuthCounters &counters, const int expected[17]) {
-    for (int i = 0; i < 17; ++i) {
-        for (int j = 0; j < expected[i]; ++j) {
-            counters.inc(Rcode(i));
+int
+countTreeElements(const struct CounterTypeTree* tree) {
+    int count = 0;
+    for (int i = 0; tree[i].name != NULL; ++i) {
+        if (tree[i].sub_tree == NULL) {
+            ++count;
+        } else {
+            count += countTreeElements(tree[i].sub_tree);
         }
     }
+    return count;
 }
 
-TEST_F(AuthCountersTest, getStatisticsWithOpcodeCounters) {
-    // Increment some of the opcode counters.  Then they should appear in the
-    // submitted data; others shouldn't
-    const int opcode_results[16] = { 1, 2, 3, 0, 4, 5, 0, 0,
-                                     0, 0, 0, 0, 0, 0, 0, 0 };
-    updateOpcodeCounters(counters, opcode_results);
-    ConstElementPtr statistics_data = counters.getStatistics();
-    opcodeDataCheck(statistics_data, opcode_results);
-}
-
-TEST_F(AuthCountersTest, getStatisticsWithAllOpcodeCounters) {
-    // Increment all opcode counters.  Then they should appear in the
-    // submitted data.
-    const int opcode_results[16] = { 1, 1, 1, 1, 1, 1, 1, 1,
-                                     1, 1, 1, 1, 1, 1, 1, 1 };
-    updateOpcodeCounters(counters, opcode_results);
-    ConstElementPtr statistics_data = counters.getStatistics();
-    opcodeDataCheck(statistics_data, opcode_results);
-}
-
-TEST_F(AuthCountersTest, getStatisticsWithRcodeCounters) {
-    // Increment some of the rcode counters.  Then they should appear in the
-    // submitted data; others shouldn't
-    const int rcode_results[17] = { 1, 2, 3, 4, 5, 6, 7, 8, 9,
-                                    10, 0, 0, 0, 0, 0, 0, 11 };
-    updateRcodeCounters(counters, rcode_results);
-    ConstElementPtr statistics_data = counters.getStatistics();
-    rcodeDataCheck(statistics_data, rcode_results);
+TEST(StatisticsItemsTest, QRItemNamesCheck) {
+    EXPECT_EQ(QR_COUNTER_TYPES, countTreeElements(QRCounterTree));
 }
 
-TEST_F(AuthCountersTest, getStatisticsWithAllRcodeCounters) {
-    // Increment all rcode counters.  Then they should appear in the
-    // submitted data.
-    const int rcode_results[17] = { 1, 1, 1, 1, 1, 1, 1, 1, 1,
-                                     1, 1, 1, 1, 1, 1, 1, 1 };
-    updateOpcodeCounters(counters, rcode_results);
-    ConstElementPtr statistics_data = counters.getStatistics();
-    opcodeDataCheck(statistics_data, rcode_results);
-}
-
-TEST_F(AuthCountersTest, getStatisticsWithValidator) {
-
-    //a validator for the unittest
-    AuthCounters::validator_type validator;
-    ConstElementPtr el;
-
-    // Get statistics data with correct statistics validator.
-    validator = boost::bind(
-        &AuthCountersTest::MockModuleSpec::validateStatistics,
-        &module_spec_, _1, true);
-
-    EXPECT_TRUE(validator(el));
-
-    // register validator to AuthCounters
-    counters.registerStatisticsValidator(validator);
-
-    // Counters should be initialized to 0.
-    EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_UDP_QUERY));
-    EXPECT_EQ(0, counters.getCounter(AuthCounters::SERVER_TCP_QUERY));
-
-    // UDP query counter is set to 2.
-    counters.inc(AuthCounters::SERVER_UDP_QUERY);
-    counters.inc(AuthCounters::SERVER_UDP_QUERY);
-    // TCP query counter is set to 1.
-    counters.inc(AuthCounters::SERVER_TCP_QUERY);
-
-    // checks the value returned by getStatistics
-    ConstElementPtr statistics_data = counters.getStatistics();
-
-    // UDP query counter is 2 and TCP query counter is 1.
-    EXPECT_EQ(2, statistics_data->get("queries.udp")->intValue());
-    EXPECT_EQ(1, statistics_data->get("queries.tcp")->intValue());
-
-    // Get statistics data with incorrect statistics validator.
-    validator = boost::bind(
-        &AuthCountersTest::MockModuleSpec::validateStatistics,
-        &module_spec_, _1, false);
-
-    EXPECT_FALSE(validator(el));
-
-    counters.registerStatisticsValidator(validator);
-
-    // checks the value returned by getStatistics
-    EXPECT_FALSE(counters.getStatistics());
-}
 }

+ 2 - 0
src/bin/auth/tests/test_datasrc_clients_mgr.cc

@@ -50,6 +50,8 @@ TestDataSrcClientsBuilder::doNoop() {
         isc_throw(Exception, "test exception");
     case TestMutex::INTEGER:
         throw 42;
+    case TestMutex::INTERNAL:
+        isc_throw(InternalCommandError, "internal error, should be ignored");
     }
 }
 } // namespace datasrc_clientmgr_internal

+ 2 - 1
src/bin/auth/tests/test_datasrc_clients_mgr.h

@@ -43,7 +43,8 @@ public:
     // None: no throw from specialized doNoop()
     // EXCLASS: throw some exception class object
     // INTEGER: throw an integer
-    enum ExceptionFromNoop { NONE, EXCLASS, INTEGER };
+    // INTERNAL: internal error (shouldn't terminate the thread)
+    enum ExceptionFromNoop { NONE, EXCLASS, INTEGER, INTERNAL };
 
     TestMutex() : lock_count(0), unlock_count(0), noop_count(0),
                   throw_from_noop(NONE)

+ 1 - 4
src/bin/bindctl/tests/bindctl_test.py

@@ -511,10 +511,7 @@ class TestBindCmdInterpreter(unittest.TestCase):
 
     def test_csv_file_dir(self):
         # Checking default value
-        if "HOME" in os.environ:
-            home_dir = os.environ["HOME"]
-        else:
-            home_dir = pwd.getpwnam(getpass.getuser()).pw_dir
+        home_dir = pwd.getpwnam(getpass.getuser()).pw_dir
         self.assertEqual(home_dir + os.sep + '.bind10' + os.sep,
                          bindcmd.BindCmdInterpreter().csv_file_dir)
 

+ 1 - 0
src/bin/dhcp6/Makefile.am

@@ -60,6 +60,7 @@ b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
 endif
 
 b10_dhcp6_LDADD  = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la

+ 441 - 31
src/bin/dhcp6/config_parser.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,10 +21,12 @@
 #include <boost/scoped_ptr.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/algorithm/string.hpp>
+#include <util/encode/hex.h>
 #include <asiolink/io_address.h>
 #include <cc/data.h>
 #include <config/ccsession.h>
 #include <log/logger_support.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/triplet.h>
 #include <dhcp/pool.h>
 #include <dhcp/subnet.h>
@@ -60,12 +62,18 @@ typedef std::map<string, string> StringStorage;
 /// no subnet object created yet to store them.
 typedef std::vector<Pool6Ptr> PoolStorage;
 
+/// @brief Collection of options.
+typedef std::vector<OptionPtr> OptionStorage;
+
 /// @brief Global uint32 parameters that will be used as defaults.
 Uint32Storage uint32_defaults;
 
 /// @brief global string parameters that will be used as defaults.
 StringStorage string_defaults;
 
+/// @brief Global storage for options that will be used as defaults.
+OptionStorage option_defaults;
+
 /// @brief a dummy configuration parser
 ///
 /// It is a debugging parser. It does not configure anything,
@@ -135,6 +143,9 @@ protected:
 ///
 /// For overview of usability of this generic purpose parser, see
 /// \ref dhcpv6-config-inherit page.
+///
+/// @todo this class should be turned into the template class which
+/// will handle all uintX_types of data (see ticket #2415).
 class Uint32Parser : public DhcpConfigParser {
 public:
 
@@ -151,12 +162,37 @@ public:
     ///
     /// @param value pointer to the content of parsed values
     virtual void build(ConstElementPtr value) {
+        bool parse_error = false;
+        // Cast the provided value to int64 value to check.
+        int64_t int64value = 0;
         try {
-            value_ = boost::lexical_cast<uint32_t>(value->str());
-        } catch (const boost::bad_lexical_cast &) {
+            // Parsing the value as a int64 value allows to
+            // check if the provided value is within the range
+            // of uint32_t (is not negative or greater than
+            // maximal uint32_t value.
+            int64value = boost::lexical_cast<int64_t>(value->str());
+        } catch (const boost::bad_lexical_cast&) {
+            parse_error = true;
+        }
+        if (!parse_error) {
+            if ((int64value < 0) ||
+                (int64value > std::numeric_limits<uint32_t>::max())) {
+                parse_error = true;
+            } else {
+                try {
+                    value_ = boost::lexical_cast<uint32_t>(value->str());
+                } catch (const boost::bad_lexical_cast &) {
+                    parse_error = true;
+                }
+            }
+
+        }
+
+        if (parse_error) {
             isc_throw(BadValue, "Failed to parse value " << value->str()
                       << " as unsigned 32-bit integer.");
         }
+
         storage_->insert(pair<string, uint32_t>(param_name_, value_));
     }
 
@@ -445,6 +481,296 @@ protected:
     PoolStorage* pools_;
 };
 
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. If parsing is successful than
+/// instance of an option is created and added to the storage provided
+/// by the calling class.
+///
+/// @todo This class parses and validates option name. However it is
+/// not used anywhere util support for option spaces is implemented
+/// (see tickets #2319, #2314). When option spaces are implemented
+/// there will be a way to reference the particular option using
+/// its type (code) or option name.
+class OptionDataParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Class constructor.
+    OptionDataParser(const std::string&)
+        : options_(NULL) { }
+
+    /// @brief Parses the single option data.
+    ///
+    /// This method parses the data of a single option from the configuration.
+    /// The option data includes option name, option code and data being
+    /// carried by this option. Eventually it creates the instance of the
+    /// option.
+    ///
+    /// @warning setStorage must be called with valid storage pointer prior
+    /// to calling this method.
+    ///
+    /// @param option_data_entries collection of entries that define value
+    /// for a particular option.
+    /// @throw Dhcp6ConfigError if invalid parameter specified in
+    /// the configuration.
+    /// @throw isc::InvalidOperation if failed to set storage prior to
+    /// calling build.
+    /// @throw isc::BadValue if option data storage is invalid.
+    virtual void build(ConstElementPtr option_data_entries) {
+        if (options_ == NULL) {
+            isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+                      "parsing option data.");
+        }
+        BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
+            ParserPtr parser;
+            if (param.first == "name") {
+                boost::shared_ptr<StringParser>
+                    name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+                if (name_parser) {
+                    name_parser->setStorage(&string_values_);
+                    parser = name_parser;
+                }
+            } else if (param.first == "code") {
+                boost::shared_ptr<Uint32Parser>
+                    code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+                if (code_parser) {
+                    code_parser->setStorage(&uint32_values_);
+                    parser = code_parser;
+                }
+            } else if (param.first == "data") {
+                boost::shared_ptr<StringParser>
+                    value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+                if (value_parser) {
+                    value_parser->setStorage(&string_values_);
+                    parser = value_parser;
+                }
+            } else {
+                isc_throw(Dhcp6ConfigError,
+                          "Parser error: option-data parameter not supported: "
+                          << param.first);
+            }
+            parser->build(param.second);
+        }
+        // Try to create the option instance.
+        createOption();
+    }
+
+    /// @brief Does nothing.
+    ///
+    /// This function does noting because option data is committed
+    /// by a higher level parser.
+    virtual void commit() { }
+
+    /// @brief Set storage for the parser.
+    ///
+    /// Sets storage for the parser. This storage points to the
+    /// vector of options and is used by multiple instances of
+    /// OptionDataParser. Each instance creates exactly one object
+    /// of dhcp::Option or derived type and appends it to this
+    /// storage.
+    ///
+    /// @param storage pointer to the options storage
+    void setStorage(OptionStorage* storage) {
+        options_ = storage;
+    }
+
+private:
+
+    /// @brief Create option instance.
+    ///
+    /// Creates an instance of an option and adds it to the provided
+    /// options storage. If the option data parsed by \ref build function
+    /// are invalid or insufficient it emits exception.
+    ///
+    /// @warning this function does not check if options_ storage pointer
+    /// is intitialized but this is not needed here because it is checked in
+    /// \ref build function.
+    ///
+    /// @throw Dhcp6ConfigError if parameters provided in the configuration
+    /// are invalid.
+    void createOption() {
+        // Option code is held in the uint32_t storage but is supposed to
+        // be uint16_t value. We need to check that value in the configuration
+        // does not exceed range of uint16_t and is not zero.
+        uint32_t option_code = getUint32Param("code");
+        if (option_code == 0) {
+            isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+                      << " be equal to zero. Option code '0' is reserved in"
+                      << " DHCPv6.");
+        } else if (option_code > std::numeric_limits<uint16_t>::max()) {
+            isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+                      << " exceed " << std::numeric_limits<uint16_t>::max());
+        }
+        // Check the option name has been specified, is non-empty and does not
+        // contain spaces.
+        // @todo possibly some more restrictions apply here?
+        std::string option_name = getStringParam("name");
+        if (option_name.empty()) {
+            isc_throw(Dhcp6ConfigError, "Parser error: option name must not be"
+                      << " empty");
+        } else if (option_name.find(" ") != std::string::npos) {
+            isc_throw(Dhcp6ConfigError, "Parser error: option name must not contain"
+                      << " spaces");
+        }
+
+        // Get option data from the configuration database ('data' field).
+        // Option data is specified by the user as case insensitive string
+        // of hexadecimal digits for each option.
+        std::string option_data = getStringParam("data");
+        // Transform string of hexadecimal digits into binary format.
+        std::vector<uint8_t> binary;
+        try {
+            util::encode::decodeHex(option_data, binary);
+        } catch (...) {
+            isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
+                      << " string of hexadecimal digits: " << option_data);
+        }
+        // Get all existing DHCPv6 option definitions. The one that matches
+        // our option will be picked and used to create it.
+        OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
+        // Get search index #1. It allows searching for options definitions
+        // using option type value.
+        const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+        // Get all option definitions matching option code we want to create.
+        const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+        size_t num_defs = std::distance(range.first, range.second);
+        OptionPtr option;
+        // Currently we do not allow duplicated definitions and if there are
+        // any duplicates we issue internal server error.
+        if (num_defs > 1) {
+            isc_throw(Dhcp6ConfigError, "Internal error: currently it is not"
+                      << " supported to initialize multiple option definitions"
+                      << " for the same option code. This will be supported once"
+                      << " there option spaces are implemented.");
+        } else if (num_defs == 0) {
+            // @todo We have a limited set of option definitions intiialized at the moment.
+            // In the future we want to initialize option definitions for all options.
+            // Consequently error will be issued if option definition does not exist
+            // for a particular option code. For now it is ok to create generic option
+            // if definition does not exist.
+            OptionPtr option(new Option(Option::V6, static_cast<uint16_t>(option_code),
+                                        binary));
+            // If option is created succesfully, add it to the storage.
+            options_->push_back(option);
+        } else {
+            // We have exactly one option definition for the particular option code.
+            // use it to create option instance.
+            const OptionDefinitionPtr& def = *(range.first);
+            // getFactory should never return NULL pointer.
+            Option::Factory* factory = def->getFactory();
+            assert(factory != NULL);
+            try {
+                OptionPtr option = factory(Option::V6, option_code, binary);
+                options_->push_back(option);
+            } catch (const isc::Exception& ex) {
+                isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
+                          << " option definition (code " << option_code << "): "
+                          << ex.what());
+            }
+        }
+    }
+
+    /// @brief Get a parameter from the strings storage.
+    ///
+    /// @param param_id parameter identifier.
+    /// @throw Dhcp6ConfigError if parameter has not been found.
+    std::string getStringParam(const std::string& param_id) const {
+        StringStorage::const_iterator param = string_values_.find(param_id);
+        if (param == string_values_.end()) {
+            isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
+                      << " '" << param_id << "' not specified");
+        }
+        return (param->second);
+    }
+
+    /// @brief Get a parameter from the uint32 values storage.
+    ///
+    /// @param param_id parameter identifier.
+    /// @throw Dhcp6ConfigError if parameter has not been found.
+    uint32_t getUint32Param(const std::string& param_id) const {
+        Uint32Storage::const_iterator param = uint32_values_.find(param_id);
+        if (param == uint32_values_.end()) {
+            isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
+                      << " '" << param_id << "' not specified");
+        }
+        return (param->second);
+    }
+
+    /// Storage for uint32 values (e.g. option code).
+    Uint32Storage uint32_values_;
+    /// Storage for string values (e.g. option name or data).
+    StringStorage string_values_;
+    /// Pointer to options storage. This storage is provided by
+    /// the calling class and is shared by all OptionDataParser objects.
+    OptionStorage* options_;
+};
+
+/// @brief Parser for option data values with ina subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// Unless otherwise specified, parsed options will be stored in
+    /// a global option containers (option_default). That storage location
+    /// is overriden on a subnet basis.
+    OptionDataListParser(const std::string&)
+        : options_(&option_defaults) { }
+
+    /// @brief Parses entries that define options' data for a subnet.
+    ///
+    /// This method iterates over all entries that define option data
+    /// for options within a single subnet and creates options' instances.
+    ///
+    /// @param option_data_list pointer to a list of options' data sets.
+    /// @throw Dhcp6ConfigError if option parsing failed.
+    void build(ConstElementPtr option_data_list) {
+        BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
+            boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
+            // options_ member will hold instances of all options thus
+            // each OptionDataParser takes it as a storage.
+            parser->setStorage(options_);
+            // Build the instance of a singkle option.
+            parser->build(option_value);
+        }
+    }
+
+    /// @brief Set storage for option instances.
+    ///
+    /// @param storage pointer to options storage.
+    void setStorage(OptionStorage* storage) {
+        options_ = storage;
+    }
+
+
+    /// @brief Does nothing.
+    ///
+    /// @todo Currently this function does nothing but in the future
+    /// we may need to extend it to commit at this level.
+    void commit() { }
+
+    /// @brief Create DhcpDataListParser object
+    ///
+    /// @param param_name param name.
+    ///
+    /// @return DhcpConfigParser object.
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new OptionDataListParser(param_name));
+    }
+
+    /// Pointer to options instances storage.
+    OptionStorage* options_;
+};
+
 /// @brief this class parses a single subnet
 ///
 /// This class parses the whole subnet definition. It creates parsers
@@ -464,35 +790,36 @@ public:
     void build(ConstElementPtr subnet) {
 
         BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
-
             ParserPtr parser(createSubnet6ConfigParser(param.first));
-
-            // if this is an Uint32 parser, tell it to store the values
-            // in values_, rather than in global storage
-            boost::shared_ptr<Uint32Parser> uintParser =
-                boost::dynamic_pointer_cast<Uint32Parser>(parser);
-            if (uintParser) {
-                uintParser->setStorage(&uint32_values_);
-            } else {
-
-                boost::shared_ptr<StringParser> stringParser =
-                    boost::dynamic_pointer_cast<StringParser>(parser);
-                if (stringParser) {
-                    stringParser->setStorage(&string_values_);
-                } else {
-
-                    boost::shared_ptr<PoolParser> poolParser =
-                        boost::dynamic_pointer_cast<PoolParser>(parser);
-                    if (poolParser) {
-                        poolParser->setStorage(&pools_);
-                    }
-                }
+            // The actual type of the parser is unknown here. We have to discover
+            // parser type here to invoke corresponding setStorage function on it.
+            // We discover parser type by trying to cast the parser to various
+            // parser types and checking which one was successful. For this one
+            // a setStorage and build methods are invoked.
+
+            // Try uint32 type parser.
+            if (buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
+                                                          param.second)) {
+                // Storage set, build invoked on the parser, proceed with
+                // next configuration element.
+                continue;
+            }
+            // Try string type parser.
+            if (buildParser<StringParser, StringStorage >(parser, string_values_,
+                                                          param.second)) {
+                continue;
+            }
+            // Try pools parser.
+            if (buildParser<PoolParser, PoolStorage >(parser, pools_,
+                                                      param.second)) {
+                continue;
+            }
+            // Try option data parser.
+            if (buildParser<OptionDataListParser, OptionStorage >(parser, options_,
+                                                                  param.second)) {
+                continue;
             }
-
-            parser->build(param.second);
-            parsers_.push_back(parser);
         }
-
         // Ok, we now have subnet parsed
     }
 
@@ -540,10 +867,78 @@ public:
             subnet->addPool6(*it);
         }
 
+        const Subnet::OptionContainer& options = subnet->getOptions();
+        const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+
+        // Add subnet specific options.
+        BOOST_FOREACH(OptionPtr option, options_) {
+            Subnet::OptionContainerTypeRange range = idx.equal_range(option->getType());
+            if (std::distance(range.first, range.second) > 0) {
+                LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
+                    .arg(option->getType()).arg(addr.toText());
+            }
+            subnet->addOption(option);
+        }
+
+        // Check all global options and add them to the subnet object if
+        // they have been configured in the global scope. If they have been
+        // configured in the subnet scope we don't add global option because
+        // the one configured in the subnet scope always takes precedense.
+        BOOST_FOREACH(OptionPtr option, option_defaults) {
+            // Get all options specified locally in the subnet and having
+            // code equal to global option's code.
+            Subnet::OptionContainerTypeRange range = idx.equal_range(option->getType());
+            // @todo: In the future we will be searching for options using either
+            // option code or namespace. Currently we have only the option
+            // code available so if there is at least one option found with the
+            // specific code we don't add globally configured option.
+            // @todo with this code the first globally configured option
+            // with the given code will be added to a subnet. We may
+            // want to issue warning about dropping configuration of
+            // global option if one already exsist.
+            if (std::distance(range.first, range.second) == 0) {
+                subnet->addOption(option);
+            }
+        }
+
         CfgMgr::instance().addSubnet6(subnet);
     }
 
-protected:
+private:
+
+    /// @brief Set storage for a parser and invoke build.
+    ///
+    /// This helper method casts the provided parser pointer to specified
+    /// type. If cast is successful it sets the corresponding storage for
+    /// this parser, invokes build on it and save the parser.
+    ///
+    /// @tparam T parser type to which parser argument should be cast.
+    /// @tparam Y storage type for the specified parser type.
+    /// @param parser parser on which build must be invoked.
+    /// @param storage reference to a storage that will be set for a parser.
+    /// @param subnet subnet element read from the configuration and being parsed.
+    /// @return true if parser pointer was successfully cast to specialized
+    /// parser type provided as Y.
+    template<typename T, typename Y>
+    bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+        // We need to cast to T in order to set storage for the parser.
+        boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+        // It is common that this cast is not successful because we try to cast to all
+        // supported parser types as we don't know the type of a parser in advance.
+        if (cast_parser) {
+            // Cast, successful so we go ahead with setting storage and actual parse.
+            cast_parser->setStorage(&storage);
+            parser->build(subnet);
+            parsers_.push_back(parser);
+            // We indicate that cast was successful so as the calling function
+            // may skip attempts to cast to other parser types and proceed to
+            // next element.
+            return (true);
+        }
+        // It was not successful. Indicate that another parser type
+        // should be tried.
+        return (false);
+    }
 
     /// @brief creates parsers for entries in subnet definition
     ///
@@ -569,6 +964,10 @@ protected:
         factories.insert(pair<string, ParserFactory*>(
                              "pool", PoolParser::Factory));
 
+        factories.insert(pair<string, ParserFactory*>(
+                             "option-data", OptionDataListParser::Factory));
+
+
         FactoryMap::iterator f = factories.find(config_id);
         if (f == factories.end()) {
             // Used for debugging only.
@@ -622,6 +1021,9 @@ protected:
     /// storage for pools belonging to this subnet
     PoolStorage pools_;
 
+    /// storage for options belonging to this subnet
+    OptionStorage options_;
+
     /// parsers are stored here
     ParserCollection parsers_;
 };
@@ -698,7 +1100,6 @@ public:
 DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
     FactoryMap factories;
 
-    //
     factories.insert(pair<string, ParserFactory*>(
                          "preferred-lifetime", Uint32Parser::Factory));
     factories.insert(pair<string, ParserFactory*>(
@@ -714,6 +1115,9 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
                          "subnet6", Subnets6ListConfigParser::Factory));
 
     factories.insert(pair<string, ParserFactory*>(
+                         "option-data", OptionDataListParser::Factory));
+
+    factories.insert(pair<string, ParserFactory*>(
                          "version", StringParser::Factory));
 
     FactoryMap::iterator f = factories.find(config_id);
@@ -749,6 +1153,12 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
                   "Null pointer is passed to configuration parser");
     }
 
+    /// Reset global storage. Containers being reset below may contain
+    /// data from the previous configuration attempts.
+    option_defaults.clear();
+    uint32_defaults.clear();
+    string_defaults.clear();
+
     /// @todo: append most essential info here (like "2 new subnets configured")
     string config_details;
 

+ 2 - 0
src/bin/dhcp6/dhcp6.dox

@@ -76,4 +76,6 @@
  simple as possible. In fact, currently the code has to call Subnet6->getT1() and
  do not implement any fancy inheritance logic.
 
+ @todo Add section about setting up options and their definitions with bindctl.
+
  */

+ 65 - 4
src/bin/dhcp6/dhcp6.spec

@@ -40,6 +40,37 @@
         "item_default": 4000
       },
 
+      { "item_name": "option-data",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [],
+        "list_item_spec":
+        {
+          "item_name": "single-option-data",
+          "item_type": "map",
+          "item_optional": false,
+          "item_default": {},
+          "map_item_spec": [
+          {
+            "item_name": "name",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          },
+
+          { "item_name": "code",
+            "item_type": "integer",
+            "item_optional": false,
+            "item_default": 0
+          },
+          { "item_name": "data",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          } ]
+        }
+      },
+
       { "item_name": "subnet6",
         "item_type": "list",
         "item_optional": false,
@@ -92,10 +123,40 @@
                         "item_optional": false,
                         "item_default": ""
                     }
-                }
-            ]
-        }
-      }
+                },
+                { "item_name": "option-data",
+                  "item_type": "list",
+                  "item_optional": false,
+                  "item_default": [],
+                  "list_item_spec":
+                  {
+                    "item_name": "single-option-data",
+                    "item_type": "map",
+                    "item_optional": false,
+                    "item_default": {},
+                    "map_item_spec": [
+                    {
+                      "item_name": "name",
+                      "item_type": "string",
+                      "item_optional": false,
+                      "item_default": ""
+                    },
+                    {
+                      "item_name": "code",
+                      "item_type": "integer",
+                      "item_optional": false,
+                      "item_default": 0
+                    },
+                    {
+                      "item_name": "data",
+                      "item_type": "string",
+                      "item_optional": false,
+                      "item_default": ""
+                    } ]
+                  }
+                } ]
+            }
+       }
     ],
     "commands": [
         {

+ 15 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -153,6 +153,16 @@ to a misconfiguration of the server. The packet processing will continue, but
 the response will only contain generic configuration parameters and no
 addresses or prefixes.
 
+% DHCP6_NO_SUBNET_DEF_OPT failed to find subnet for address %1 when adding default options
+This warning message indicates that when attempting to add default options to a response,
+the server found that it was not configured to support the subnet from which the DHCPv6
+request was received.  The packet has been ignored.
+
+% DHCP6_NO_SUBNET_REQ_OPT failed to find subnet for address %1 when adding requested options
+This warning message indicates that when attempting to add requested options to a response,
+the server found that it was not configured to support the subnet from which the DHCPv6
+request was received.  The packet has been ignored.
+
 % DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
 This critical error message indicates that the initial DHCPv6
 configuration has failed. The server will start, but nothing will be
@@ -172,3 +182,8 @@ This is an informational message announcing the successful processing of a
 new configuration. it is output during server startup, and when an updated
 configuration is committed by the administrator.  Additional information
 may be provided.
+
+% DHCP6_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
+This warning message is issued on attempt to configure multiple options with the
+same option code for the particular subnet. Adding multiple options is uncommon
+for DHCPv6, yet it is not prohibited.

+ 71 - 12
src/bin/dhcp6/dhcp6_srv.cc

@@ -20,10 +20,14 @@
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_ia.h>
+#include <dhcp/option6_int_array.h>
 #include <dhcp/pkt6.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
 #include <exceptions/exceptions.h>
 #include <util/io_utilities.h>
 #include <util/range_utilities.h>
@@ -36,6 +40,8 @@
 // once it is merged
 #include <dhcp/memfile_lease_mgr.h>
 
+#include <boost/foreach.hpp>
+
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
@@ -49,9 +55,13 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
 
-    // First call to instance() will create IfaceMgr (it's a singleton)
-    // it may throw something if things go wrong
+    // Initialize objects required for DHCP server operation.
     try {
+        // Initialize standard DHCPv6 option definitions. This function
+        // may throw bad_alloc if system goes out of memory during the
+        // creation if option definitions. It may also throw isc::Unexpected
+        // if definitions are wrong. This would mean error in implementation.
+        initStdOptionDefs();
 
         // Port 0 is used for testing purposes. It means that the server should
         // not open any sockets at all. Some tests, e.g. configuration parser,
@@ -63,7 +73,6 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
                 shutdown_ = true;
                 return;
             }
-
             IfaceMgr::instance().openSockets6(port);
         }
 
@@ -300,23 +309,68 @@ void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
     // TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
 }
 
-void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) {
-    // TODO: question is currently unused, but we need it at least to know
-    // message type we are answering
-
-    // Add server-id.
+void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+    // add server-id
     answer->addOption(getServerID());
-}
 
+    // Get the subnet object. It holds options to be sent to the client
+    // that belongs to the particular subnet.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+    // Warn if subnet is not supported and quit.
+    if (!subnet) {
+        LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_DEF_OPT)
+            .arg(question->getRemoteAddr().toText());
+        return;
+    }
+    // Add DNS_SERVERS option. It should have been configured.
+    const Subnet::OptionContainer& options = subnet->getOptions();
+    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    const Subnet::OptionContainerTypeRange range =
+        idx.equal_range(D6O_NAME_SERVERS);
+    // In theory we may have multiple options with the same
+    // option code. They are not differentiated right now
+    // until support for option spaces is implemented.
+    // Until that's the case, simply add the first found option.
+    if (std::distance(range.first, range.second) > 0) {
+        answer->addOption(range.first->option);
+    }
+}
 
-void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) {
-    // TODO: question is currently unused, but we need to extract ORO from it
-    // and act on its content. Now we just send DNS-SERVERS option.
+void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+    // Get the subnet for a particular address.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+    if (!subnet) {
+        LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_REQ_OPT)
+            .arg(question->getRemoteAddr().toText());
+        return;
+    }
 
     // Add dns-servers option.
     OptionPtr dnsservers(new Option6AddrLst(D6O_NAME_SERVERS,
                                             IOAddress(HARDCODED_DNS_SERVER)));
     answer->addOption(dnsservers);
+
+    // Client requests some options using ORO option. Try to
+    // get this option from client's message.
+    boost::shared_ptr<Option6IntArray<uint16_t> > option_oro =
+        boost::dynamic_pointer_cast<Option6IntArray<uint16_t> >(question->getOption(D6O_ORO));
+    // Option ORO not found. Don't do anything then.
+    if (!option_oro) {
+        return;
+    }
+    // Get the list of options that client requested.
+    const std::vector<uint16_t>& requested_opts = option_oro->getValues();
+    // Get the list of options configured for a subnet.
+    const Subnet::OptionContainer& options = subnet->getOptions();
+    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+    // Try to match requested options with those configured for a subnet.
+    // If match is found, append configured option to the answer message.
+    BOOST_FOREACH(uint16_t opt, requested_opts) {
+        const Subnet::OptionContainerTypeRange& range = idx.equal_range(opt);
+        BOOST_FOREACH(Subnet::OptionDescriptor desc, range) {
+            answer->addOption(desc.option);
+        }
+    }
 }
 
 OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
@@ -587,3 +641,8 @@ Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
     }
     return (UNKNOWN);
 }
+
+void
+Dhcpv6Srv::initStdOptionDefs() {
+    LibDHCP::initStdOptionDefs(Option::V6);
+}

+ 19 - 7
src/bin/dhcp6/dhcp6_srv.h

@@ -15,15 +15,17 @@
 #ifndef DHCPV6_SRV_H
 #define DHCPV6_SRV_H
 
+#include <iostream>
+
 #include <boost/noncopyable.hpp>
+#include <dhcp/alloc_engine.h>
 #include <dhcp/dhcp6.h>
-#include <dhcp/pkt6.h>
-#include <dhcp/option.h>
-#include <dhcp/subnet.h>
 #include <dhcp/duid.h>
+#include <dhcp/option.h>
 #include <dhcp/option6_ia.h>
-#include <dhcp/alloc_engine.h>
-#include <iostream>
+#include <dhcp/option_definition.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/subnet.h>
 
 namespace isc {
 
@@ -205,8 +207,6 @@ protected:
     /// @brief Appends requested options to server's answer.
     ///
     /// Appends options requested by client to the server's answer.
-    /// TODO: This method is currently a stub. It just appends DNS-SERVERS
-    /// option.
     ///
     /// @param question client's message
     /// @param answer server's message (options will be added here)
@@ -234,6 +234,18 @@ protected:
     ///         interfaces for new DUID generation are detected.
     void setServerID();
 
+    /// @brief Initializes option definitions for standard options.
+    ///
+    /// Each standard option's format is described by the
+    /// dhcp::OptionDefinition object. This function creates such objects
+    /// for each standard DHCPv6 option.
+    ///
+    /// @todo list thrown exceptions.
+    /// @todo extend this function to cover all standard options. Currently
+    /// it is limited to critical options only.
+    void initStdOptionDefs();
+
+private:
     /// server DUID (to be sent in server-identifier option)
     boost::shared_ptr<isc::dhcp::Option> serverid_;
 

+ 1 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -63,6 +63,7 @@ dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la

+ 504 - 6
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -17,14 +17,18 @@
 #include <fstream>
 #include <sstream>
 
+#include <boost/foreach.hpp>
+
 #include <arpa/inet.h>
 #include <gtest/gtest.h>
 
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcp6/config_parser.h>
 #include <config/ccsession.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/subnet.h>
 #include <dhcp/cfgmgr.h>
+#include <dhcp/option6_ia.h>
 
 using namespace std;
 using namespace isc;
@@ -43,12 +47,138 @@ public:
         // deal with sockets here, just check if configuration handling
         // is sane.
         srv_ = new Dhcpv6Srv(0);
+        // Create instances of option definitions and put them into storage.
+        // This is normally initialized by the server when calling run()
+        // run() function.
+        LibDHCP::initStdOptionDefs(Option::V6);
     }
 
     ~Dhcp6ParserTest() {
         delete srv_;
     };
 
+    /// @brief Create the simple configuration with single option.
+    ///
+    /// This function allows to set one of the parameters that configure
+    /// option value. These parameters are: "name", "code" and "data".
+    ///
+    /// @param param_value string holiding option parameter value to be
+    /// injected into the configuration string.
+    /// @param parameter name of the parameter to be configured with
+    /// param value.
+    std::string createConfigWithOption(const std::string& param_value,
+                                       const std::string& parameter) {
+        std::map<std::string, std::string> params;
+        if (parameter == "name") {
+            params["name"] = param_value;
+            params["code"] = "80";
+            params["data"] = "AB CDEF0105";
+        } else if (parameter == "code") {
+            params["name"] = "option_foo";
+            params["code"] = param_value;
+            params["data"] = "AB CDEF0105";
+        } else if (parameter == "data") {
+            params["name"] = "option_foo";
+            params["code"] = "80";
+            params["data"] = param_value;
+        }
+        return (createConfigWithOption(params));
+    }
+
+    std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
+        std::ostringstream stream;
+        stream << "{ \"interface\": [ \"all\" ],"
+            "\"preferred-lifetime\": 3000,"
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"subnet6\": [ { "
+            "    \"pool\": [ \"2001:db8:1::/80\" ],"
+            "    \"subnet\": \"2001:db8:1::/64\", "
+            "    \"option-data\": [ {";
+        bool first = true;
+        typedef std::pair<std::string, std::string> ParamPair;
+        BOOST_FOREACH(ParamPair param, params) {
+            if (!first) {
+                stream << ", ";
+            } else {
+                first = false;
+            }
+            if (param.first == "name") {
+                stream << "\"name\": \"" << param.second << "\"";
+            } else if (param.first == "code") {
+                stream << "\"code\": " << param.second << "";
+            } else if (param.first == "data") {
+                stream << "\"data\": \"" << param.second << "\"";
+            }
+        }
+        stream <<
+            "        } ]"
+            " } ],"
+            "\"valid-lifetime\": 4000 }";
+        return (stream.str());
+    }
+
+    /// @brief Test invalid option parameter value.
+    ///
+    /// This test function constructs the simple configuration
+    /// string and injects invalid option configuration into it.
+    /// It expects that parser will fail with provided option code.
+    ///
+    /// @param param_value string holding invalid option parameter value
+    /// to be injected into configuration string.
+    /// @param parameter name of the parameter to be configured with
+    /// param_value (can be any of "name", "code", "data")
+    void testInvalidOptionParam(const std::string& param_value,
+                                const std::string& parameter) {
+        ConstElementPtr x;
+        std::string config = createConfigWithOption(param_value, parameter);
+        ElementPtr json = Element::fromJSON(config);
+        EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+        ASSERT_TRUE(x);
+        comment_ = parseAnswer(rcode_, x);
+        ASSERT_EQ(1, rcode_);
+    }
+
+    /// @brief Test option against given code and data.
+    ///
+    /// @param option_desc option descriptor that carries the option to
+    /// be tested.
+    /// @param expected_code expected code of the option.
+    /// @param expected_data expected data in the option.
+    /// @param expected_data_len length of the reference data.
+    /// @param extra_data if true extra data is allowed in an option
+    /// after tested data.
+    void testOption(const Subnet::OptionDescriptor& option_desc,
+                    uint16_t expected_code, const uint8_t* expected_data,
+                    size_t expected_data_len,
+                    bool extra_data = false) {
+        // Check if option descriptor contains valid option pointer.
+        ASSERT_TRUE(option_desc.option);
+        // Verify option type.
+        EXPECT_EQ(expected_code, option_desc.option->getType());
+        // We may have many different option types being created. Some of them
+        // have dedicated classes derived from Option class. In such case if
+        // we want to verify the option contents against expected_data we have
+        // to prepare raw buffer with the contents of the option. The easiest
+        // way is to call pack() which will prepare on-wire data.
+        util::OutputBuffer buf(option_desc.option->getData().size());
+        option_desc.option->pack(buf);
+        if (extra_data) {
+            // The length of the buffer must be at least equal to size of the
+            // reference data but it can sometimes be greater than that. This is
+            // because some options carry suboptions that increase the overall
+            // length.
+            ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
+                      expected_data_len);
+        } else {
+            ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
+                      expected_data_len);
+        }
+        // Verify that the data is correct. However do not verify suboptions.
+        const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+        EXPECT_TRUE(memcmp(expected_data, data, expected_data_len));
+    }
+
     Dhcpv6Srv* srv_;
 
     int rcode_;
@@ -73,7 +203,7 @@ TEST_F(Dhcp6ParserTest, version) {
 
 /// The goal of this test is to verify that the code accepts only
 /// valid commands and malformed or unsupported parameters are rejected.
-TEST_F(Dhcp6ParserTest, bogus_command) {
+TEST_F(Dhcp6ParserTest, bogusCommand) {
 
     ConstElementPtr x;
 
@@ -89,7 +219,7 @@ TEST_F(Dhcp6ParserTest, bogus_command) {
 /// The goal of this test is to verify if wrongly defined subnet will
 /// be rejected. Properly defined subnet must include at least one
 /// pool definition.
-TEST_F(Dhcp6ParserTest, empty_subnet) {
+TEST_F(Dhcp6ParserTest, emptySubnet) {
 
     ConstElementPtr status;
 
@@ -109,7 +239,7 @@ TEST_F(Dhcp6ParserTest, empty_subnet) {
 
 /// The goal of this test is to verify if defined subnet uses global
 /// parameter timer definitions.
-TEST_F(Dhcp6ParserTest, subnet_global_defaults) {
+TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
 
     ConstElementPtr status;
 
@@ -144,7 +274,7 @@ TEST_F(Dhcp6ParserTest, subnet_global_defaults) {
 
 // This test checks if it is possible to override global values
 // on a per subnet basis.
-TEST_F(Dhcp6ParserTest, subnet_local) {
+TEST_F(Dhcp6ParserTest, subnetLocal) {
 
     ConstElementPtr status;
 
@@ -181,7 +311,7 @@ TEST_F(Dhcp6ParserTest, subnet_local) {
 
 // Test verifies that a subnet with pool values that do not belong to that
 // pool are rejected.
-TEST_F(Dhcp6ParserTest, pool_out_of_subnet) {
+TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
 
     ConstElementPtr status;
 
@@ -209,7 +339,7 @@ TEST_F(Dhcp6ParserTest, pool_out_of_subnet) {
 // Goal of this test is to verify if pools can be defined
 // using prefix/length notation. There is no separate test for min-max
 // notation as it was tested in several previous tests.
-TEST_F(Dhcp6ParserTest, pool_prefix_len) {
+TEST_F(Dhcp6ParserTest, poolPrefixLen) {
 
     ConstElementPtr x;
 
@@ -240,4 +370,372 @@ TEST_F(Dhcp6ParserTest, pool_prefix_len) {
     EXPECT_EQ(4000, subnet->getValid());
 }
 
+// Goal of this test is to verify that global option
+// data is configured for the subnet if the subnet
+// configuration does not include options configuration.
+TEST_F(Dhcp6ParserTest, optionDataDefaults) {
+    ConstElementPtr x;
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"option_foo\","
+        "    \"code\": 100,"
+        "    \"data\": \"AB CDEF0105\""
+        " },"
+        " {"
+        "    \"name\": \"option_foo2\","
+        "    \"code\": 101,"
+        "    \"data\": \"01\""
+        " } ],"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    const Subnet::OptionContainer& options = subnet->getOptions();
+    ASSERT_EQ(2, options.size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(100);
+    // Expect single option with the code equal to 100.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    const uint8_t foo_expected[] = {
+        0xAB, 0xCD, 0xEF, 0x01, 0x05
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+
+    range = idx.equal_range(101);
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // Do another round of testing with second option.
+    const uint8_t foo2_expected[] = {
+        0x01
+    };
+    testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+
+    // Check that options with other option codes are not returned.
+    for (uint16_t code = 102; code < 110; ++code) {
+        range = idx.equal_range(code);
+        EXPECT_EQ(0, std::distance(range.first, range.second));
+    }
+}
+
+// Goal of this test is to verify options configuration
+// for a single subnet. In particular this test checks
+// that local options configuration overrides global
+// option setting.
+TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
+    ConstElementPtr x;
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ {"
+        "      \"name\": \"option_foo\","
+        "      \"code\": 100,"
+        "      \"data\": \"AB\""
+        " } ],"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\", "
+        "    \"option-data\": [ {"
+        "          \"name\": \"option_foo\","
+        "          \"code\": 100,"
+        "          \"data\": \"AB CDEF0105\""
+        "        },"
+        "        {"
+        "          \"name\": \"option_foo2\","
+        "          \"code\": 101,"
+        "          \"data\": \"01\""
+        "        } ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    const Subnet::OptionContainer& options = subnet->getOptions();
+    ASSERT_EQ(2, options.size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(100);
+    // Expect single option with the code equal to 100.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    const uint8_t foo_expected[] = {
+        0xAB, 0xCD, 0xEF, 0x01, 0x05
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+
+    range = idx.equal_range(101);
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // Do another round of testing with second option.
+    const uint8_t foo2_expected[] = {
+        0x01
+    };
+    testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+}
+
+// Goal of this test is to verify options configuration
+// for multiple subnets.
+TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
+    ConstElementPtr x;
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\", "
+        "    \"option-data\": [ {"
+        "          \"name\": \"option_foo\","
+        "          \"code\": 100,"
+        "          \"data\": \"0102030405060708090A\""
+        "        } ]"
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\", "
+        "    \"option-data\": [ {"
+        "          \"name\": \"option_foo2\","
+        "          \"code\": 101,"
+        "          \"data\": \"FFFEFDFCFB\""
+        "        } ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet1);
+    const Subnet::OptionContainer& options1 = subnet1->getOptions();
+    ASSERT_EQ(1, options1.size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx1 = options1.get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range1 =
+        idx1.equal_range(100);
+    // Expect single option with the code equal to 100.
+    ASSERT_EQ(1, std::distance(range1.first, range1.second));
+    const uint8_t foo_expected[] = {
+        0x01, 0x02, 0x03, 0x04, 0x05,
+        0x06, 0x07, 0x08, 0x09, 0x0A
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range1.first, 100, foo_expected, sizeof(foo_expected));
+
+    // Test another subnet in the same way.
+    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
+    ASSERT_TRUE(subnet2);
+    const Subnet::OptionContainer& options2 = subnet2->getOptions();
+    ASSERT_EQ(1, options2.size());
+
+    const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range2 =
+        idx2.equal_range(101);
+    ASSERT_EQ(1, std::distance(range2.first, range2.second));
+
+    const uint8_t foo2_expected[] = {
+        0xFF, 0xFE, 0xFD, 0xFC, 0xFB
+    };
+    testOption(*range2.first, 101, foo2_expected, sizeof(foo2_expected));
+}
+
+// Verify that empty option name is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionNameEmpty) {
+    // Empty option names not allowed.
+    testInvalidOptionParam("", "name");
+}
+
+// Verify that empty option name with spaces is rejected
+// in the configuration.
+TEST_F(Dhcp6ParserTest, optionNameSpaces) {
+    // Spaces in option names not allowed.
+    testInvalidOptionParam("option foo", "name");
+}
+
+// Verify that negative option code is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionCodeNegative) {
+    // Check negative option code -4. This should fail too.
+    testInvalidOptionParam("-4", "code");
+}
+
+// Verify that out of bounds option code is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionCodeNonUint16) {
+    // The valid option codes are uint16_t values so passing
+    // uint16_t maximum value incremented by 1 should result
+    // in failure.
+    testInvalidOptionParam("65536", "code");
+}
+
+// Verify that out of bounds option code is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionCodeHighNonUint16) {
+    // Another check for uint16_t overflow but this time
+    // let's pass even greater option code value.
+    testInvalidOptionParam("70000", "code");
+}
+
+// Verify that zero option code is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionCodeZero) {
+    // Option code 0 is reserved and should not be accepted
+    // by configuration parser.
+    testInvalidOptionParam("0", "code");
+}
+
+// Verify that option data which contains non hexadecimal characters
+// is rejected by the configuration.
+TEST_F(Dhcp6ParserTest, optionDataInvalidChar) {
+    // Option code 0 is reserved and should not be accepted
+    // by configuration parser.
+    testInvalidOptionParam("01020R", "data");
+}
+
+// Verify that option data containins '0x' prefix is rejected
+// by the configuration.
+TEST_F(Dhcp6ParserTest, optionDataUnexpectedPrefix) {
+    // Option code 0 is reserved and should not be accepted
+    // by configuration parser.
+    testInvalidOptionParam("0x0102", "data");
+}
+
+// Verify that option data consisting od an odd number of
+// hexadecimal digits is rejected in the configuration.
+TEST_F(Dhcp6ParserTest, optionDataOddLength) {
+    // Option code 0 is reserved and should not be accepted
+    // by configuration parser.
+    testInvalidOptionParam("123", "data");
+}
+
+// Verify that either lower or upper case characters are allowed
+// to specify the option data.
+TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
+    ConstElementPtr x;
+    std::string config = createConfigWithOption("0a0b0C0D", "data");
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    const Subnet::OptionContainer& options = subnet->getOptions();
+    ASSERT_EQ(1, options.size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(80);
+    // Expect single option with the code equal to 100.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    const uint8_t foo_expected[] = {
+        0x0A, 0x0B, 0x0C, 0x0D
+    };
+    // Check if option is valid in terms of code and carried data.
+    testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
+}
+
+// Verify that specific option object is returned for standard
+// option which has dedicated option class derived from Option.
+TEST_F(Dhcp6ParserTest, stdOptionData) {
+    ConstElementPtr x;
+    std::map<std::string, std::string> params;
+    params["name"] = "OPTION_IA_NA";
+    // Option code 3 means OPTION_IA_NA.
+    params["code"] = "3";
+    params["data"] = "ABCDEF01 02030405 06070809";
+    
+    std::string config = createConfigWithOption(params);
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    const Subnet::OptionContainer& options = subnet->getOptions();
+    ASSERT_EQ(1, options.size());
+
+    // Get the search index. Index #1 is to search using option code.
+    const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+
+    // Get the options for specified index. Expecting one option to be
+    // returned but in theory we may have multiple options with the same
+    // code so we get the range.
+    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+              Subnet::OptionContainerTypeIndex::const_iterator> range =
+        idx.equal_range(D6O_IA_NA);
+    // Expect single option with the code equal to IA_NA option code.
+    ASSERT_EQ(1, std::distance(range.first, range.second));
+    // The actual pointer to the option is held in the option field
+    // in the structure returned.
+    OptionPtr option = range.first->option;
+    ASSERT_TRUE(option);
+    // Option object returned for here is expected to be Option6IA
+    // which is derived from Option. This class is dedicated to
+    // represent standard option IA_NA.
+    boost::shared_ptr<Option6IA> optionIA =
+        boost::dynamic_pointer_cast<Option6IA>(option);
+    // If cast is unsuccessful than option returned was of a
+    // differnt type than Option6IA. This is wrong.
+    ASSERT_TRUE(optionIA);
+    // If cast was successful we may use accessors exposed by
+    // Option6IA to validate that the content of this option
+    // has been set correctly.
+    EXPECT_EQ(0xABCDEF01, optionIA->getIAID());
+    EXPECT_EQ(0x02030405, optionIA->getT1());
+    EXPECT_EQ(0x06070809, optionIA->getT2());
+}
+
 };

+ 185 - 21
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -12,31 +12,41 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <config.h>
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <gtest/gtest.h>
 
 #include <asiolink/io_address.h>
+#include <boost/scoped_ptr.hpp>
+#include <config/ccsession.h>
+#include <dhcp/cfgmgr.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/duid.h>
-#include <dhcp/cfgmgr.h>
+#include <dhcp/lease_mgr.h>
 #include <dhcp/option.h>
+#include <dhcp/option6_addrlst.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_int_array.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp6/dhcp6_srv.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <util/buffer.h>
 #include <util/range_utilities.h>
-#include <dhcp/lease_mgr.h>
-#include <boost/scoped_ptr.hpp>
-#include <config.h>
-#include <iostream>
-#include <fstream>
-#include <sstream>
-#include <gtest/gtest.h>
 
-using namespace std;
+using namespace boost;
 using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::util;
-using namespace isc::asiolink;
-using namespace boost;
+using namespace std;
 
 // namespace has to be named, because friends are defined in Dhcpv6Srv class
 // Maybe it should be isc::test?
@@ -56,7 +66,7 @@ public:
 class Dhcpv6SrvTest : public ::testing::Test {
 public:
     // these are empty for now, but let's keep them around
-    Dhcpv6SrvTest() {
+    Dhcpv6SrvTest() : rcode_(-1) {
         subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
                                          2000, 3000, 4000));
         pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
@@ -181,6 +191,9 @@ public:
 
     // A DUID used in most tests (typically as client-id)
     DuidPtr duid_;
+
+    int rcode_;
+    ConstElementPtr comment_;
 };
 
 // Test verifies that the Dhcpv6_srv class can be instantiated. It checks a mode
@@ -191,19 +204,17 @@ TEST_F(Dhcpv6SrvTest, basic) {
     // interfaces.txt instead. It will pretend to have detected
     // fe80::1234 link-local address on eth0 interface. Obviously
     // an attempt to bind this socket will fail.
-    Dhcpv6Srv* srv = NULL;
+    boost::scoped_ptr<Dhcpv6Srv> srv;
+
     ASSERT_NO_THROW( {
         // Skip opening any sockets
-        srv = new Dhcpv6Srv(0);
+        srv.reset(new Dhcpv6Srv(0));
     });
-
-    delete srv;
-
-    ASSERT_NO_THROW( {
+    srv.reset();
+    ASSERT_NO_THROW({
         // open an unpriviledged port
-        srv = new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000);
+        srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
     });
-    delete srv;
 }
 
 // Test checks that DUID is generated properly
@@ -282,6 +293,159 @@ TEST_F(Dhcpv6SrvTest, DUID) {
     }
 }
 
+TEST_F(Dhcpv6SrvTest, solicitBasic1) {
+    ConstElementPtr x;
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1234::/80\" ],"
+        "    \"subnet\": \"2001:db8:1234::/64\", "
+        "    \"option-data\": [ {"
+        "          \"name\": \"OPTION_DNS_SERVERS\","
+        "          \"code\": 23,"
+        "          \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
+        "2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
+        "        },"
+        "        {"
+        "          \"name\": \"OPTION_FOO\","
+        "          \"code\": 1000,"
+        "          \"data\": \"1234\""
+        "        } ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    boost::scoped_ptr<NakedDhcpv6Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv6Srv(0)));
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+
+    ASSERT_EQ(0, rcode_);
+
+    // a dummy content for client-id
+    OptionBuffer clntDuid(32);
+    for (int i = 0; i < 32; i++) {
+        clntDuid[i] = 100 + i;
+    }
+
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+    boost::shared_ptr<Option6IA> ia =
+        boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, 234));
+    ia->setT1(1501);
+    ia->setT2(2601);
+    sol->addOption(ia);
+
+    // Let's not send address in solicit yet
+    /*    boost::shared_ptr<Option6IAAddr>
+        addr(new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:ffff::ffff"), 5001, 7001));
+    ia->addOption(addr);
+    sol->addOption(ia); */
+
+    // constructed very simple SOLICIT message with:
+    // - client-id option (mandatory)
+    // - IA option (a request for address, without any addresses)
+
+    // expected returned ADVERTISE message:
+    // - copy of client-id
+    // - server-id
+    // - IA that includes IAADDR
+
+    OptionPtr clientid = OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+                                              clntDuid.begin(),
+                                              clntDuid.begin() + 16));
+    sol->addOption(clientid);
+
+    boost::shared_ptr<Pkt6> reply = srv->processSolicit(sol);
+
+    // check if we get response at all
+    ASSERT_TRUE(reply);
+
+    EXPECT_EQ(DHCPV6_ADVERTISE, reply->getType());
+    EXPECT_EQ(1234, reply->getTransid());
+
+    // We have not requested option with code 1000 so it should not
+    // be included in the response.
+    ASSERT_FALSE(reply->getOption(1000));
+
+    // Let's now request option with code 1000.
+    // We expect that server will include this option in its reply.
+    boost::shared_ptr<Option6IntArray<uint16_t> >
+        option_oro(new Option6IntArray<uint16_t>(D6O_ORO));
+    // Create vector with one code equal to 1000.
+    std::vector<uint16_t> codes(1, 1000);
+    // Pass this code to option.
+    option_oro->setValues(codes);
+    // Append ORO to SOLICIT message.
+    sol->addOption(option_oro);
+    
+    // Need to process SOLICIT again after requesting new option.
+    reply = srv->processSolicit(sol);
+    ASSERT_TRUE(reply);
+
+    EXPECT_EQ(DHCPV6_ADVERTISE, reply->getType());
+
+    OptionPtr tmp = reply->getOption(D6O_IA_NA);
+    ASSERT_TRUE(tmp);
+
+    boost::shared_ptr<Option6IA> reply_ia =
+        boost::dynamic_pointer_cast<Option6IA>(tmp);
+    ASSERT_TRUE(reply_ia);
+    EXPECT_EQ(234, reply_ia->getIAID());
+
+    // check that there's an address included
+    EXPECT_TRUE(reply_ia->getOption(D6O_IAADDR));
+
+    // check that server included our own client-id
+    tmp = reply->getOption(D6O_CLIENTID);
+    ASSERT_TRUE(tmp);
+    EXPECT_EQ(clientid->getType(), tmp->getType());
+    ASSERT_EQ(clientid->len(), tmp->len());
+
+    EXPECT_TRUE(clientid->getData() == tmp->getData());
+
+    // check that server included its server-id
+    tmp = reply->getOption(D6O_SERVERID);
+    EXPECT_EQ(tmp->getType(), srv->getServerID()->getType());
+    ASSERT_EQ(tmp->len(),  srv->getServerID()->len());
+
+    EXPECT_TRUE(tmp->getData() == srv->getServerID()->getData());
+ 
+    tmp = reply->getOption(D6O_NAME_SERVERS);
+    ASSERT_TRUE(tmp);
+    
+    boost::shared_ptr<Option6AddrLst> reply_nameservers =
+        boost::dynamic_pointer_cast<Option6AddrLst>(tmp);
+    ASSERT_TRUE(reply_nameservers);
+
+    Option6AddrLst::AddressContainer addrs = reply_nameservers->getAddresses();
+    ASSERT_EQ(2, addrs.size());
+    EXPECT_TRUE(addrs[0] == IOAddress("2001:db8:1234:FFFF::1"));
+    EXPECT_TRUE(addrs[1] == IOAddress("2001:db8:1234:FFFF::2"));
+
+    // There is a dummy option with code 1000 we requested from a server.
+    // Expect that this option is in server's response.
+    tmp = reply->getOption(1000);
+    ASSERT_TRUE(tmp);
+
+    // Check that the option contains valid data (from configuration).
+    std::vector<uint8_t> data = tmp->getData();
+    ASSERT_EQ(2, data.size());
+
+    const uint8_t foo_expected[] = {
+        0x12, 0x34
+    };
+    EXPECT_EQ(0, memcmp(&data[0], foo_expected, 2));
+
+    // more checks to be implemented
+}
+
+
 // There are no dedicated tests for Dhcpv6Srv::handleIA_NA and Dhcpv6Srv::assignLeases
 // as they are indirectly tested in Solicit and Request tests.
 
@@ -299,7 +463,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
 // - copy of client-id
 // - server-id
 // - IA that includes IAADDR
-TEST_F(Dhcpv6SrvTest, SolicitBasic) {
+TEST_F(Dhcpv6SrvTest, SolicitBasic2) {
     boost::scoped_ptr<NakedDhcpv6Srv> srv;
     ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
 

+ 2 - 1
src/bin/sysinfo/.gitignore

@@ -1,3 +1,4 @@
 /isc-sysinfo
-/sysinfo.py
 /isc-sysinfo.1
+/run_sysinfo.sh
+/sysinfo.py

+ 1 - 0
src/bin/sysinfo/Makefile.am

@@ -1,4 +1,5 @@
 bin_SCRIPTS = isc-sysinfo
+noinst_SCRIPTS = run_sysinfo.sh
 
 CLEANFILES = isc-sysinfo sysinfo.pyc
 

+ 38 - 0
src/bin/sysinfo/run_sysinfo.sh.in

@@ -0,0 +1,38 @@
+#! /bin/sh
+
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
+export PYTHON_EXEC
+
+SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
+
+# Note: we shouldn't need log_messages except for the seemingly necessary
+# dependency due to the automatic import in the isc package (its __init__.py
+# imports some other modules)
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages
+export PYTHONPATH
+
+# Likewise, we need only because isc.log requires some loadable modules.
+# sysinfo itself shouldn't need any of them.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+	@ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+	export @ENV_LIBRARY_PATH@
+fi
+
+cd ${SYSINFO_PATH}
+exec ${PYTHON_EXEC} -O sysinfo.py "$@"

+ 2 - 4
src/bin/sysinfo/sysinfo.py.in

@@ -22,11 +22,8 @@ ISC sysinfo program.
 
 import sys; sys.path.append ('@@PYTHONPATH@@')
 import getopt
-import isc.util.process
 from isc.sysinfo import *
 
-isc.util.process.rename()
-
 def usage():
     print("Usage: %s [-h] [-o <output-file>]" % sys.argv[0], \
               file=sys.stderr)
@@ -88,7 +85,8 @@ def main():
 
     write_value(f, ' + Machine name: %s\n', s.get_platform_machine)
     write_value(f, ' + Hostname: %s\n', s.get_platform_hostname)
-    write_value(f, ' + Uptime: %d seconds\n', s.get_uptime)
+    write_value(f, ' + Uptime: %d seconds', s.get_uptime)
+    write_value(f, ' (%s)\n', s.get_uptime_desc)
 
     write_value(f, ' + Loadavg: %f %f %f\n', s.get_loadavg)
 

+ 7 - 0
src/bin/tests/process_rename_test.py.in

@@ -39,6 +39,11 @@ class TestRename(unittest.TestCase):
         Then scan them by looking at the source text
         (without actually running them)
         """
+
+        # Scripts named in this list are not expected to be renamed and
+        # should be excluded from the scan.
+        EXCLUDED_SCRIPTS = ['isc-sysinfo']
+
         # Regexp to find all the *_SCRIPTS = something lines (except for
         # noinst_SCRIPTS, which are scripts for tests), including line
         # continuations (backslash and newline)
@@ -59,6 +64,8 @@ class TestRename(unittest.TestCase):
                 for (var, _) in lines.findall(re.sub(excluded_lines, '',
                                                      makefile)):
                     for (script, _) in scripts.findall(var):
+                        if script in EXCLUDED_SCRIPTS:
+                            continue
                         self.__scan(d, script, fun)
 
 if __name__ == "__main__":

+ 0 - 1
src/lib/bench/example/search_bench.cc

@@ -116,7 +116,6 @@ main(int argc, char* argv[]) {
         }
     }
     argc -= optind;
-    argv += optind;
     if (argc != 0) {
         usage();
     }

+ 0 - 1
src/lib/datasrc/memory/benchmarks/rdata_reader_bench.cc

@@ -183,7 +183,6 @@ main(int argc, char* argv[]) {
         }
     }
     argc -= optind;
-    argv += optind;
     if (argc != 0) {
         usage();
     }

+ 0 - 1
src/lib/datasrc/memory/benchmarks/rrset_render_bench.cc

@@ -179,7 +179,6 @@ main(int argc, char* argv[]) {
         }
     }
     argc -= optind;
-    argv += optind;
     if (argc != 0) {
         usage();
     }

+ 6 - 2
src/lib/datasrc/tests/client_list_unittest.cc

@@ -27,6 +27,8 @@
 
 #include <gtest/gtest.h>
 
+#include <boost/shared_ptr.hpp>
+
 #include <set>
 #include <fstream>
 
@@ -36,7 +38,9 @@ using isc::datasrc::memory::ZoneTableSegment;
 using isc::datasrc::memory::InMemoryZoneFinder;
 using namespace isc::data;
 using namespace isc::dns;
-using namespace boost;
+// don't import the entire boost namespace.  It will unexpectedly hide uintXX_t
+// for some systems.
+using boost::shared_ptr;
 using namespace std;
 
 namespace {
@@ -313,7 +317,7 @@ public:
                   result.life_keeper_);
         if (from_cache) {
             EXPECT_NE(shared_ptr<InMemoryZoneFinder>(),
-                      dynamic_pointer_cast<InMemoryZoneFinder>(
+                      boost::dynamic_pointer_cast<InMemoryZoneFinder>(
                           result.finder_)) << "Finder is not from cache";
             EXPECT_TRUE(NULL !=
                         dynamic_cast<InMemoryClient*>(result.dsrc_client_));

+ 6 - 7
src/lib/dhcp/addr_utilities.cc

@@ -46,14 +46,13 @@ const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
 /// @param len prefix length
 isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
                                             uint8_t len) {
-
     if (len > 128) {
-        isc_throw(BadValue, "IPv6 prefix length too large:" << len);
+        isc_throw(isc::BadValue,
+                  "Too large netmask. 0..128 is allowed in IPv6");
     }
 
-    uint8_t packed[V6ADDRESS_LEN];
-
     // First we copy the whole address as 16 bytes.
+    uint8_t packed[V6ADDRESS_LEN];
     memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
 
     // If the length is divisible by 8, it is simple. We just zero out the host
@@ -126,12 +125,12 @@ isc::asiolink::IOAddress lastAddrInPrefix4(const isc::asiolink::IOAddress& prefi
 isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
                                            uint8_t len) {
     if (len > 128) {
-        isc_throw(BadValue, "IPv6 prefix length too large:" << len);
+        isc_throw(isc::BadValue,
+                  "Too large netmask. 0..128 is allowed in IPv6");
     }
 
-    uint8_t packed[V6ADDRESS_LEN];
-
     // First we copy the whole address as 16 bytes.
+    uint8_t packed[V6ADDRESS_LEN];
     memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
 
     // if the length is divisible by 8, it is simple. We just fill the host part

+ 4 - 2
src/lib/dhcp/alloc_engine.cc

@@ -15,6 +15,8 @@
 #include <alloc_engine.h>
 #include <string.h>
 
+#include <cstring>
+
 using namespace isc::asiolink;
 
 namespace isc {
@@ -32,11 +34,11 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
     // First we copy the whole address as 16 bytes.
     if (addr.getFamily()==AF_INET) {
         // IPv4
-        memcpy(packed, addr.getAddress().to_v4().to_bytes().data(), 4);
+        std::memcpy(packed, addr.getAddress().to_v4().to_bytes().data(), 4);
         len = 4;
     } else {
         // IPv6
-        memcpy(packed, addr.getAddress().to_v6().to_bytes().data(), 16);
+        std::memcpy(packed, addr.getAddress().to_v6().to_bytes().data(), 16);
         len = 16;
     }
 

+ 102 - 0
src/lib/dhcp/libdhcp++.cc

@@ -23,6 +23,8 @@
 #include <dhcp/option.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option6_int_array.h>
 
 using namespace std;
 using namespace isc::dhcp;
@@ -34,6 +36,23 @@ std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
 // static array with factories for options
 std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
 
+// Static container with DHCPv4 option definitions.
+OptionDefContainer LibDHCP::v4option_defs_;
+
+// Static container with DHCPv6 option definitions.
+OptionDefContainer LibDHCP::v6option_defs_;
+
+const OptionDefContainer&
+LibDHCP::getOptionDefs(Option::Universe u) {
+    switch (u) {
+    case Option::V4:
+        return (v4option_defs_);
+    case Option::V6:
+        return (v6option_defs_);
+    default:
+        isc_throw(isc::BadValue, "invalid universe " << u << " specified");
+    }
+}
 
 OptionPtr
 LibDHCP::optionFactory(Option::Universe u,
@@ -88,6 +107,11 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
                                               buf.begin() + offset,
                                               buf.begin() + offset + opt_len));
             break;
+        case D6O_ORO:
+            opt = OptionPtr(new Option6IntArray<uint16_t>(opt_type,
+                                                          buf.begin() + offset,
+                                                          buf.begin() + offset + opt_len));
+            break;
         default:
             opt = OptionPtr(new Option(Option::V6,
                                        opt_type,
@@ -201,3 +225,81 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u,
 
     return;
 }
+
+void
+LibDHCP::initStdOptionDefs(Option::Universe u) {
+    switch (u) {
+    case Option::V4:
+        initStdOptionDefs4();
+        break;
+    case Option::V6:
+        initStdOptionDefs6();
+        break;
+    default:
+        isc_throw(isc::BadValue, "invalid universe " << u << " specified");
+    }
+}
+
+void
+LibDHCP::initStdOptionDefs4() {
+    isc_throw(isc::NotImplemented, "initStdOptionDefs4 is not implemented");
+}
+
+void
+LibDHCP::initStdOptionDefs6() {
+    v6option_defs_.clear();
+
+    struct OptionParams {
+        std::string name;
+        uint16_t code;
+        OptionDefinition::DataType type;
+        bool array;
+    };
+    OptionParams params[] = {
+        { "CLIENTID", D6O_CLIENTID, OptionDefinition::BINARY_TYPE, false },
+        { "SERVERID", D6O_SERVERID, OptionDefinition::BINARY_TYPE, false },
+        { "IA_NA", D6O_IA_NA, OptionDefinition::RECORD_TYPE, false },
+        { "IAADDR", D6O_IAADDR, OptionDefinition::RECORD_TYPE, false },
+        { "ORO", D6O_ORO, OptionDefinition::UINT16_TYPE, true },
+        { "ELAPSED_TIME", D6O_ELAPSED_TIME, OptionDefinition::UINT16_TYPE, false },
+        { "STATUS_CODE", D6O_STATUS_CODE, OptionDefinition::RECORD_TYPE, false },
+        { "RAPID_COMMIT", D6O_RAPID_COMMIT, OptionDefinition::EMPTY_TYPE, false },
+        { "DNS_SERVERS", D6O_NAME_SERVERS, OptionDefinition::IPV6_ADDRESS_TYPE, true }
+    };
+    const int params_size = sizeof(params) / sizeof(params[0]);
+
+    for (int i = 0; i < params_size; ++i) {
+        OptionDefinitionPtr definition(new OptionDefinition(params[i].name,
+                                                            params[i].code,
+                                                            params[i].type,
+                                                            params[i].array));
+        switch(params[i].code) {
+        case D6O_IA_NA:
+            for (int j = 0; j < 3; ++j) {
+                definition->addRecordField(OptionDefinition::UINT32_TYPE);
+            }
+            break;
+        case D6O_IAADDR:
+            definition->addRecordField(OptionDefinition::IPV6_ADDRESS_TYPE);
+            definition->addRecordField(OptionDefinition::UINT32_TYPE);
+            definition->addRecordField(OptionDefinition::UINT32_TYPE);
+            break;
+        case D6O_STATUS_CODE:
+            definition->addRecordField(OptionDefinition::UINT16_TYPE);
+            definition->addRecordField(OptionDefinition::STRING_TYPE);
+            break;
+        default:
+            // The default case is intentionally left empty
+            // as it does not need any processing.
+            ;
+        }
+        try {
+            definition->validate();
+        } catch (const Exception& ex) {
+            isc_throw(isc::Unexpected, "internal server error: invalid definition of standard"
+                      << " DHCPv6 option (with code " << params[i].code << "): "
+                      << ex.what());
+        }
+        v6option_defs_.push_back(definition);
+    }
+}

+ 53 - 4
src/lib/dhcp/libdhcp++.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,10 @@
 #ifndef LIBDHCP_H_
 #define LIBDHCP_H_
 
-#include <iostream>
-#include <util/buffer.h>
+#include <dhcp/option_definition.h>
 #include <dhcp/pkt6.h>
+#include <util/buffer.h>
+#include <iostream>
 
 namespace isc {
 namespace dhcp {
@@ -29,6 +30,12 @@ public:
     /// Map of factory functions.
     typedef std::map<unsigned short, Option::Factory*>  FactoryMap;
 
+    /// @brief Return collection of option definitions.
+    ///
+    /// @param u universe of the options (V4 or V6).
+    /// @return collection of option definitions.
+    static const OptionDefContainer& getOptionDefs(Option::Universe u);
+
     /// @brief Factory function to create instance of option.
     ///
     /// Factory method creates instance of specified option. The option
@@ -102,12 +109,54 @@ public:
     static void OptionFactoryRegister(Option::Universe u,
                                       uint16_t type,
                                       Option::Factory * factory);
-protected:
+
+    /// Initialize standard DHCP options (V4 or V6).
+    ///
+    /// The method creates option definitions for all options
+    /// (DHCPv4 or DHCPv6 depending on universe specified).
+    /// Currently DHCPv4 option definitions initialization is not
+    /// implemented thus this function will throw isc::NotImplemented
+    /// if V4 universe is specified.
+    ///
+    /// @param u universe
+    /// @throw isc::Unexpected if internal error occured during option
+    /// definitions creation.
+    /// @throw std::bad_alloc if system went out of memory.
+    /// @throw isc::NotImplemented when V4 universe specified.
+    static void initStdOptionDefs(Option::Universe u);
+
+private:
+
+    /// Initialize standard DHCPv4 option definitions.
+    ///
+    /// The method creates option definitions for all DHCPv4 options.
+    /// Currently this function is not implemented.
+    ///
+    /// @todo implemend this function.
+    ///
+    /// @throw isc::NotImplemeneted
+    static void initStdOptionDefs4();
+
+    /// Initialize standard DHCPv6 option definitions.
+    ///
+    /// The method creates option definitions for all DHCPv6 options.
+    ///
+    /// @throw isc::Unexpected if internal error occured during option
+    /// definitions creation.
+    /// @throw std::bad_alloc if system went out of memory.
+    static void initStdOptionDefs6();
+
     /// pointers to factories that produce DHCPv6 options
     static FactoryMap v4factories_;
 
     /// pointers to factories that produce DHCPv6 options
     static FactoryMap v6factories_;
+
+    /// Container with DHCPv4 option definitions.
+    static OptionDefContainer v4option_defs_;
+
+    /// Container with DHCPv6 option definitions.
+    static OptionDefContainer v6option_defs_;
 };
 
 }

+ 6 - 1
src/lib/dhcp/option_definition.cc

@@ -29,6 +29,7 @@ namespace dhcp {
 
 OptionDefinition::DataTypeUtil::DataTypeUtil() {
     data_types_["empty"] = EMPTY_TYPE;
+    data_types_["binary"] = BINARY_TYPE;
     data_types_["boolean"] = BOOLEAN_TYPE;
     data_types_["int8"] = INT8_TYPE;
     data_types_["int16"] = INT16_TYPE;
@@ -97,11 +98,15 @@ OptionDefinition::addRecordField(const DataType data_type) {
 
 Option::Factory*
 OptionDefinition::getFactory() const {
+    validate();
+
     // @todo This function must be extended to return more factory
     // functions that create instances of more specialized options.
     // This requires us to first implement those more specialized
     // options that will be derived from Option class.
-    if (type_ == IPV6_ADDRESS_TYPE && array_type_) {
+    if (type_ == BINARY_TYPE) {
+        return (factoryGeneric);
+    } else if (type_ == IPV6_ADDRESS_TYPE && array_type_) {
         return (factoryAddrList6);
     } else if (type_ == IPV4_ADDRESS_TYPE && array_type_) {
         return (factoryAddrList4);

+ 85 - 4
src/lib/dhcp/option_definition.h

@@ -16,13 +16,42 @@
 #define OPTION_DEFINITION_H_
 
 #include <dhcp/option_data_types.h>
-#include <dhcp/option6_int.h>
-#include <dhcp/option6_int_array.h>
 #include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
 
 namespace isc {
 namespace dhcp {
 
+/// @brief Forward declaration to OptionDefinition.
+class OptionDefinition;
+
+/// @brief Pointer to option definition object.
+typedef boost::shared_ptr<OptionDefinition> OptionDefinitionPtr;
+
+/// @brief Forward declaration to Option6Int.
+///
+/// This forward declaration is needed to access Option6Int class
+/// without having to include option6_int.h header. This is because
+/// this header includes libdhcp++.h and this causes circular
+/// inclusion between libdhcp++.h, option_definition.h and
+/// option6_int.h.
+template<typename T>
+class Option6Int;
+
+/// @brief Forward declaration to Option6IntArray.
+///
+/// This forward declaration is needed to access Option6IntArray class
+/// without having to include option6_int_array.h header. This is because
+/// this header includes libdhcp++.h and this causes circular
+/// inclusion between libdhcp++.h, option_definition.h and
+/// option6_int_array.h.
+template<typename T>
+class Option6IntArray;
+
 /// @brief Base class representing a DHCP option definition.
 ///
 /// This is a base class representing a DHCP option definition, which describes
@@ -52,7 +81,7 @@ namespace dhcp {
 ///
 /// Should the option comprise data fields of different types, the "record"
 /// option type is used. In such cases the data field types within the record
-/// are specified using \ref OptionDefinition::addRecordField.
+/// are specified using \ref OptioDefinition::addRecordField.
 ///
 /// When the OptionDefinition object has been sucessfully created, it can be
 /// queried to return the appropriate option factory function for the specified
@@ -84,6 +113,7 @@ public:
     /// Data types of DHCP option fields.
     enum DataType {
         EMPTY_TYPE,
+        BINARY_TYPE,
         BOOLEAN_TYPE,
         INT8_TYPE,
         INT16_TYPE,
@@ -202,7 +232,9 @@ public:
 
     /// @brief Return factory function for the given definition.
     ///
-    /// @return pointer to factory function.
+    /// @throw isc::OutOfRange if \ref validate returns it.
+    /// @throw isc::BadValue if \ref validate returns it.
+    /// @return pointer to a factory function.
     Option::Factory* getFactory() const;
 
     /// @brief Return option name.
@@ -377,6 +409,55 @@ private:
 };
 
 
+/// @brief Multi index container for DHCP option definitions.
+///
+/// This container allows to search for DHCP option definition
+/// using two indexes:
+/// - sequenced: used to access elements in the order they have
+/// been added to the container
+/// - option code: used to search defintions of options
+/// with a specified option code (aka option type).
+/// Note that this container can hold multiple options with the
+/// same code. For this reason, the latter index can be used to
+/// obtain a range of options for a particular option code.
+/// 
+/// @todo: need an index to search options using option space name
+/// once option spaces are implemented.
+typedef boost::multi_index_container<
+    // Container comprises elements of OptionDefinition type.
+    OptionDefinitionPtr,
+    // Here we start enumerating various indexes.
+    boost::multi_index::indexed_by<
+        // Sequenced index allows accessing elements in the same way
+        // as elements in std::list. Sequenced is an index #0.
+        boost::multi_index::sequenced<>,
+        // Start definition of index #1.
+        boost::multi_index::hashed_non_unique<
+            // Use option type as the index key. The type is held
+            // in OptionDefinition object so we have to call
+            // OptionDefinition::getCode to retrieve this key
+            // for each element. The option code is non-unique so
+            // multiple elements with the same option code can
+            // be returned by this index.
+            boost::multi_index::const_mem_fun<
+                OptionDefinition,
+                uint16_t,
+                &OptionDefinition::getCode
+            >
+        >
+    >
+> OptionDefContainer;
+
+/// Type of the index #1 - option type.
+typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
+/// Pair of iterators to represent the range of options definitions
+///  having the same option type value. The first element in this pair
+///  represents the begining of the range, the second element
+///  represents the end.
+typedef std::pair<OptionDefContainerTypeIndex::const_iterator,
+                  OptionDefContainerTypeIndex::const_iterator> OptionDefContainerTypeRange;
+
+
 } // namespace isc::dhcp
 } // namespace isc
 

+ 6 - 1
src/lib/dhcp/subnet.h

@@ -193,6 +193,11 @@ public:
 
     /// Type of the index #1 - option type.
     typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
+    /// Pair of iterators to represent the range of options having the
+    /// same option type value. The first element in this pair represents
+    /// the begining of the range, the second element represents the end.
+    typedef std::pair<OptionContainerTypeIndex::const_iterator,
+                      OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
     /// Type of the index #2 - option persistency flag.
     typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
 
@@ -245,7 +250,7 @@ public:
     /// @return reference to collection of options configured for a subnet.
     /// The returned reference is valid as long as the Subnet object which
     /// returned it still exists.
-    const OptionContainer& getOptions() {
+    const OptionContainer& getOptions() const {
         return (options_);
     }
 

+ 11 - 25
src/lib/dhcp/tests/alloc_engine_unittest.cc

@@ -20,6 +20,7 @@
 #include <dhcp/cfgmgr.h>
 #include <dhcp/memfile_lease_mgr.h>
 #include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
 #include <iostream>
 #include <sstream>
 #include <map>
@@ -30,7 +31,6 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test; // Memfile_LeaseMgr
-using namespace boost;
 
 namespace {
 
@@ -96,7 +96,6 @@ public:
 // This test checks if the Allocation Engine can be instantiated and that it
 // parses parameters string properly.
 TEST_F(AllocEngineTest, constructor) {
-
     AllocEngine* x = NULL;
 
     // Hashed and random allocators are not supported yet
@@ -131,9 +130,8 @@ detailCompareLease6(const Lease6Ptr& first, const Lease6Ptr& second) {
 
 // This test checks if the simple allocation can succeed
 TEST_F(AllocEngineTest, simpleAlloc) {
-
-    AllocEngine* engine = NULL;
-    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
@@ -151,15 +149,12 @@ TEST_F(AllocEngineTest, simpleAlloc) {
 
     // Now check that the lease in LeaseMgr has the same parameters
     detailCompareLease6(lease, from_mgr);
-
-    delete engine;
 }
 
 // This test checks if the fake allocation (for SOLICIT) can succeed
 TEST_F(AllocEngineTest, fakeAlloc) {
-
-    AllocEngine* engine = NULL;
-    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
@@ -179,9 +174,8 @@ TEST_F(AllocEngineTest, fakeAlloc) {
 // This test checks if the allocation with a hint that is valid (in range,
 // in pool and free) can succeed
 TEST_F(AllocEngineTest, allocWithValidHint) {
-
-    AllocEngine* engine = NULL;
-    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
     Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_,
@@ -203,16 +197,13 @@ TEST_F(AllocEngineTest, allocWithValidHint) {
 
     // Now check that the lease in LeaseMgr has the same parameters
     detailCompareLease6(lease, from_mgr);
-
-    delete engine;
 }
 
 // This test checks if the allocation with a hint that is in range,
 // in pool, but is currently used) can succeed
 TEST_F(AllocEngineTest, allocWithUsedHint) {
-
-    AllocEngine* engine = NULL;
-    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
     // let's create a lease and put it in the LeaseMgr
@@ -245,16 +236,13 @@ TEST_F(AllocEngineTest, allocWithUsedHint) {
 
     // Now check that the lease in LeaseMgr has the same parameters
     detailCompareLease6(lease, from_mgr);
-
-    delete engine;
 }
 
 // This test checks if the allocation with a hint that is out the blue
 // can succeed. The invalid hint should be ignored completely.
 TEST_F(AllocEngineTest, allocBogusHint) {
-
-    AllocEngine* engine = NULL;
-    ASSERT_NO_THROW(engine = new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
     ASSERT_TRUE(engine);
 
     // Client would like to get a 3000::abc lease, which does not belong to any
@@ -278,8 +266,6 @@ TEST_F(AllocEngineTest, allocBogusHint) {
 
     // Now check that the lease in LeaseMgr has the same parameters
     detailCompareLease6(lease, from_mgr);
-
-    delete engine;
 }
 
 // This test verifies that the allocator picks addresses that belong to the

+ 1 - 0
src/lib/dhcp/tests/cfgmgr_unittest.cc

@@ -75,6 +75,7 @@ TEST_F(CfgMgrTest, subnet4) {
 
 // This test verifies if the configuration manager is able to hold and return
 // valid leases
+
 TEST_F(CfgMgrTest, subnet6) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
 

+ 82 - 0
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -21,6 +21,11 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_int.h>
+#include <dhcp/option6_int_array.h>
+#include <dhcp/option6_addrlst.h>
 #include "config.h"
 
 using namespace std;
@@ -46,6 +51,57 @@ public:
         Option* option = new Option(u, type, buf);
         return OptionPtr(option);
     }
+
+    /// @brief Test option option definition.
+    ///
+    /// This function tests if option definition for standard
+    /// option has been initialized correctly.
+    ///
+    /// @param code option code.
+    /// @param bug buffer to be used to create option instance.
+    /// @param expected_type type of the option created by the
+    /// factory function returned by the option definition.
+    static void testInitOptionDefs6(const uint16_t code,
+                             const OptionBuffer& buf,
+                             const std::type_info& expected_type) {
+        // Initialize stdandard options definitions. They are held
+        // in the static container throughout the program.
+        LibDHCP::initStdOptionDefs(Option::V6);
+        // Get all option definitions, we will use them to extract
+        // the definition for a particular option code.
+        OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
+        // Get the container index #1. This one allows for searching
+        // option definitions using option code.
+        const OptionDefContainerTypeIndex& idx = options.get<1>();
+        // Get 'all' option definitions for a particular option code.
+        // For standard options we expect that the range returned
+        // will contain single option as their codes are unique.
+        OptionDefContainerTypeRange range = idx.equal_range(code);
+        ASSERT_EQ(1, std::distance(range.first, range.second));
+        // If we have single option definition returned, the
+        // first iterator holds it.
+        OptionDefinitionPtr def = *(range.first);
+        // It should not happen that option definition is NULL but
+        // let's make sure (test should take things like that into
+        // account).
+        ASSERT_TRUE(def);
+        // Check that option definition is valid.
+        ASSERT_NO_THROW(def->validate());
+        // Get the factory function for the particular option
+        // definition. We will use this factory function to
+        // create option instance.
+        Option::Factory* factory = NULL;
+        ASSERT_NO_THROW(factory = def->getFactory());
+        OptionPtr option;
+        // Create the option.
+        ASSERT_NO_THROW(option = factory(Option::V6, code, buf));
+        // Make sure it is not NULL.
+        ASSERT_TRUE(option);
+        // And the actual object type is the one that we expect.
+        // Note that for many options there are dedicated classes
+        // derived from Option class to represent them.
+        EXPECT_TRUE(typeid(*option) == expected_type);
+    }
 };
 
 static const uint8_t packed[] = {
@@ -311,4 +367,30 @@ TEST(LibDhcpTest, unpackOptions4) {
     EXPECT_TRUE(x == options.end()); // option 2 not found
 }
 
+// Test that definitions of standard options have been initialized
+// correctly.
+// @todo Only limited number of option definitions are now created
+// This test have to be extended once all option definitions are
+// created.
+TEST(LibDhcpTest, initStdOptionDefs) {
+    LibDhcpTest::testInitOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
+                                     typeid(Option));
+    LibDhcpTest::testInitOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
+                                     typeid(Option));
+    LibDhcpTest::testInitOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
+                                     typeid(Option6IA));
+    LibDhcpTest::testInitOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
+                                     typeid(Option6IAAddr));
+    LibDhcpTest::testInitOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
+                                     typeid(Option6IntArray<uint16_t>));
+    LibDhcpTest::testInitOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
+                                     typeid(Option6Int<uint16_t>));
+    LibDhcpTest::testInitOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
+                                     typeid(Option));
+    LibDhcpTest::testInitOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
+                                     typeid(Option));
+    LibDhcpTest::testInitOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
+                                     typeid(Option6AddrLst));
+}
+
 }

+ 50 - 0
src/lib/dhcp/tests/option_definition_unittest.cc

@@ -281,6 +281,56 @@ TEST_F(OptionDefinitionTest, factoryEmpty) {
     EXPECT_THROW(factory(Option::V6, D6O_RAPID_COMMIT,OptionBuffer(2)),isc::BadValue);
 }
 
+TEST_F(OptionDefinitionTest, factoryBinary) {
+    // Binary option is the one that is represented by the generic
+    // Option class. In fact all options can be represented by this
+    // class but for some of them it is just natural. The SERVERID
+    // option consists of the option code, length and binary data so
+    // this one was picked for this test.
+    OptionDefinition opt_def("OPTION_SERVERID", D6O_SERVERID, "binary");
+    Option::Factory* factory(NULL);
+    EXPECT_NO_THROW(factory = opt_def.getFactory());
+    ASSERT_TRUE(factory != NULL);
+
+    // Prepare some dummy data (serverid): 0, 1, 2 etc.
+    OptionBuffer buf(14);
+    for (int i = 0; i < 14; ++i) {
+        buf[i] = i;
+    }
+    // Create option instance with the factory function.
+    // If the OptionDefinition code works properly than
+    // object of the type Option should be returned.
+    OptionPtr option_v6;
+    ASSERT_NO_THROW(
+        option_v6 = factory(Option::V6, D6O_SERVERID, buf);
+    );
+    // Expect base option type returned.
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
+    // Sanity check on universe, length and size. These are
+    // the basic parameters identifying any option.
+    EXPECT_EQ(Option::V6, option_v6->getUniverse());
+    EXPECT_EQ(4, option_v6->getHeaderLen());
+    ASSERT_EQ(buf.size(), option_v6->getData().size());
+
+    // Get the server id data from the option and compare
+    // against reference buffer. They are expected to match.
+    EXPECT_TRUE(std::equal(option_v6->getData().begin(),
+                           option_v6->getData().end(),
+                           buf.begin()));
+
+    // Repeat the same test scenario for DHCPv4 option.
+    OptionPtr option_v4;
+    ASSERT_NO_THROW(option_v4 = factory(Option::V4, 214, buf));
+    // Expect 'empty' DHCPv4 option.
+    EXPECT_EQ(Option::V4, option_v4->getUniverse());
+    EXPECT_EQ(2, option_v4->getHeaderLen());
+    ASSERT_EQ(buf.size(), option_v4->getData().size());
+
+    EXPECT_TRUE(std::equal(option_v6->getData().begin(),
+                           option_v6->getData().end(),
+                           buf.begin()));
+}
+
 TEST_F(OptionDefinitionTest, factoryIA6) {
     // This option consists of IAID, T1 and T2 fields (each 4 bytes long).
     const int option6_ia_len = 12;

+ 4 - 0
src/lib/dns/Makefile.am

@@ -93,8 +93,10 @@ libb10_dns___la_LDFLAGS = -no-undefined -version-info 2:0:0
 libb10_dns___la_SOURCES =
 libb10_dns___la_SOURCES += edns.h edns.cc
 libb10_dns___la_SOURCES += exceptions.h exceptions.cc
+libb10_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
 libb10_dns___la_SOURCES += labelsequence.h labelsequence.cc
 libb10_dns___la_SOURCES += masterload.h masterload.cc
+libb10_dns___la_SOURCES += master_lexer.h master_lexer.cc
 libb10_dns___la_SOURCES += message.h message.cc
 libb10_dns___la_SOURCES += messagerenderer.h messagerenderer.cc
 libb10_dns___la_SOURCES += name.h name.cc
@@ -145,6 +147,8 @@ libdns___include_HEADERS = \
 	exceptions.h \
 	labelsequence.h \
 	message.h \
+	master_lexer.h \
+	masterload.h \
 	messagerenderer.h \
 	name.h \
 	question.h \

+ 0 - 1
src/lib/dns/benchmarks/message_renderer_bench.cc

@@ -145,7 +145,6 @@ main(int argc, char* argv[]) {
         }
     }
     argc -= optind;
-    argv += optind;
     if (argc != 0) {
         usage();
     }

+ 47 - 0
src/lib/dns/master_lexer.cc

@@ -0,0 +1,47 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/master_lexer.h>
+
+#include <cassert>
+#include <string>
+
+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
+};
+const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]);
+}
+
+namespace isc {
+namespace dns {
+
+std::string
+MasterLexer::Token::getErrorText() const {
+    if (type_ != ERROR) {
+        isc_throw(InvalidOperation,
+                  "Token::getErrorText() for non error type");
+    }
+
+    // The class integrity ensures the following:
+    assert(val_.error_code_ < error_text_max_count);
+    return (error_text[val_.error_code_]);
+}
+
+
+} // end of namespace dns
+} // end of namespace isc

+ 241 - 0
src/lib/dns/master_lexer.h

@@ -0,0 +1,241 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MASTER_LEXER_H
+#define MASTER_LEXER_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <string>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+
+class MasterLexer {
+public:
+    class Token;       // we define it separately for better readability
+};
+
+/// \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 MasterLexer::Token {
+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 (if asked for detecting it)
+        END_OF_FILE, ///< End of file detected (if asked for detecting it)
+        INITIAL_WS,  ///< White spaces at the beginning of a line
+        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
+        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.  On the other hand, it is not ensured that the string
+    /// is nul-terminated.  So the usual string manipulation API may not work
+    /// as expected.
+    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 Token(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.
+    Token(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 Token(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 Token(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 {
+        if (type_ != STRING && type_ != QSTRING) {
+            isc_throw(InvalidOperation,
+                      "Token::getString() for non string-variant type");
+        }
+        return (std::string(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_;
+};
+
+} // namespace dns
+} // namespace isc
+#endif  // MASTER_LEXER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 154 - 0
src/lib/dns/master_lexer_inputsource.cc

@@ -0,0 +1,154 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/master_lexer_inputsource.h>
+
+#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());
+}
+
+} // end of unnamed namespace
+
+InputSource::InputSource(std::istream& input_stream) :
+    at_eof_(false),
+    line_(1),
+    saved_line_(line_),
+    buffer_pos_(0),
+    name_(createStreamName(input_stream)),
+    input_(input_stream)
+{}
+
+InputSource::InputSource(const char* filename) :
+    at_eof_(false),
+    line_(1),
+    saved_line_(line_),
+    buffer_pos_(0),
+    name_(filename),
+    input_(file_stream_)
+{
+    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(OpenError, error_txt);
+    }
+}
+
+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(ReadError,
+                      "Error reading from the input stream: " << getName());
+        }
+        buffer_.push_back(c);
+    }
+
+    const int c = buffer_[buffer_pos_];
+    ++buffer_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_;
+        if (buffer_[buffer_pos_] == '\n') {
+            --line_;
+        }
+    }
+}
+
+void
+InputSource::ungetAll() {
+    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

+ 164 - 0
src/lib/dns/master_lexer_inputsource.h

@@ -0,0 +1,164 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_INPUTSOURCE_H
+#define DNS_INPUTSOURCE_H 1
+
+#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.
+    static const int END_OF_STREAM;
+
+    /// \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 read from the input
+    /// stream or file.
+    struct ReadError : public Unexpected {
+        ReadError(const char* file, size_t line, const char* what) :
+            Unexpected(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.
+    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.
+    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 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 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_;
+
+    const std::string name_;
+    std::ifstream file_stream_;
+    std::istream& input_;
+};
+
+const int InputSource::END_OF_STREAM = -1;
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc
+
+#endif  // DNS_INPUTSOURCE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 109 - 26
src/lib/dns/name.cc

@@ -123,15 +123,18 @@ typedef enum {
     ft_ordinary,                // parsing an ordinary label
     ft_initialescape,           // just found '\'
     ft_escape,                  // begin of handling a '\'-escaped sequence
-    ft_escdecimal,              // parsing a '\DDD' octet.
-
-    // Unused at this moment.  We'll revisit this when we support master file
-    // parser where @ is used to mean an origin name.
-    ft_at
+    ft_escdecimal               // parsing a '\DDD' octet.
 } ft_state;
-}
 
-Name::Name(const std::string &namestring, bool downcase) {
+// 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.
     //
@@ -142,17 +145,14 @@ Name::Name(const std::string &namestring, bool downcase) {
     //
     // Set up the state machine.
     //
-    std::string::const_iterator s = namestring.begin();
-    std::string::const_iterator send = namestring.end();
     bool done = false;
     bool is_root = false;
+    const bool empty = s == send;
     ft_state state = ft_init;
 
-    NameOffsets offsets;
+    // Prepare the output buffers.
     offsets.reserve(Name::MAX_LABELS);
     offsets.push_back(0);
-
-    NameString ndata;
     ndata.reserve(Name::MAX_WIRE);
 
     // should we refactor this code using, e.g, the state pattern?  Probably
@@ -171,7 +171,8 @@ Name::Name(const std::string &namestring, bool downcase) {
             if (c == '.') {
                 if (s != send) {
                     isc_throw(EmptyLabel,
-                              "non terminating empty label in " << namestring);
+                              "non terminating empty label in " <<
+                              string(orig_s, send));
                 }
                 is_root = true;
             } else if (c == '@' && s == send) {
@@ -200,7 +201,7 @@ Name::Name(const std::string &namestring, bool downcase) {
             if (c == '.') {
                 if (count == 0) {
                     isc_throw(EmptyLabel,
-                              "duplicate period in " << namestring);
+                              "duplicate period in " << string(orig_s, send));
                 }
                 ndata.at(offsets.back()) = count;
                 offsets.push_back(ndata.size());
@@ -212,9 +213,9 @@ Name::Name(const std::string &namestring, bool downcase) {
             } else if (c == '\\') {
                 state = ft_escape;
             } else {
-                if (++count > MAX_LABELLEN) {
+                if (++count > Name::MAX_LABELLEN) {
                     isc_throw(TooLongLabel,
-                              "label is too long in " << namestring);
+                              "label is too long in " << string(orig_s, send));
                 }
                 ndata.push_back(downcase ? maptolower[c] : c);
             }
@@ -224,15 +225,15 @@ Name::Name(const std::string &namestring, bool downcase) {
                 // This looks like a bitstring label, which was deprecated.
                 // Intentionally drop it.
                 isc_throw(BadLabelType,
-                          "invalid label type in " << namestring);
+                          "invalid label type in " << string(orig_s, send));
             }
             state = ft_escape;
             // FALLTHROUGH
         case ft_escape:
             if (!isdigit(c & 0xff)) {
-                if (++count > MAX_LABELLEN) {
+                if (++count > Name::MAX_LABELLEN) {
                     isc_throw(TooLongLabel,
-                              "label is too long in " << namestring);
+                              "label is too long in " << string(orig_s, send));
                 }
                 ndata.push_back(downcase ? maptolower[c] : c);
                 state = ft_ordinary;
@@ -246,7 +247,7 @@ Name::Name(const std::string &namestring, bool downcase) {
             if (!isdigit(c & 0xff)) {
                 isc_throw(BadEscape,
                           "mixture of escaped digit and non-digit in "
-                          << namestring);
+                          << string(orig_s, send));
             }
             value *= 10;
             value += digitvalue[c];
@@ -255,11 +256,11 @@ Name::Name(const std::string &namestring, bool downcase) {
                 if (value > 255) {
                     isc_throw(BadEscape,
                               "escaped decimal is too large in "
-                              << namestring);
+                              << string(orig_s, send));
                 }
-                if (++count > MAX_LABELLEN) {
+                if (++count > Name::MAX_LABELLEN) {
                     isc_throw(TooLongLabel,
-                              "label is too long in " << namestring);
+                              "label is too long in " << string(orig_s, send));
                 }
                 ndata.push_back(downcase ? maptolower[value] : value);
                 state = ft_ordinary;
@@ -274,13 +275,14 @@ Name::Name(const std::string &namestring, bool downcase) {
     if (!done) {                // no trailing '.' was found.
         if (ndata.size() == Name::MAX_WIRE) {
             isc_throw(TooLongName,
-                      "name is too long for termination in " << namestring);
+                      "name is too long for termination in " <<
+                      string(orig_s, send));
         }
         assert(s == send);
-        if (state != ft_ordinary && state != ft_at) {
+        if (state != ft_ordinary) {
             isc_throw(IncompleteName,
                       "incomplete textual name in " <<
-                      (namestring.empty() ? "<empty>" : namestring));
+                      (empty ? "<empty>" : string(orig_s, send)));
         }
         if (state == ft_ordinary) {
             assert(count != 0);
@@ -291,12 +293,93 @@ Name::Name(const std::string &namestring, bool downcase) {
             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();
+    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 == NULL || 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 == NULL) {
+        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();
     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 (NameOffsets::iterator 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 {

+ 42 - 0
src/lib/dns/name.h

@@ -105,6 +105,17 @@ public:
         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().
@@ -261,6 +272,37 @@ public:
     /// \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

+ 2 - 0
src/lib/dns/tests/Makefile.am

@@ -22,8 +22,10 @@ if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES = unittest_util.h unittest_util.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 += name_unittest.cc
 run_unittests_SOURCES += nsec3hash_unittest.cc
 run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc

+ 325 - 0
src/lib/dns/tests/master_lexer_inputsource_unittest.cc

@@ -0,0 +1,325 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/master_lexer_inputsource.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 {
+
+class InputSourceTest : public ::testing::Test {
+protected:
+    InputSourceTest() :
+        str_("Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n"),
+        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_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, 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());
+
+    // 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());
+        // 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());
+}
+
+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());
+
+    // 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());
+}
+
+} // end namespace

+ 156 - 0
src/lib/dns/tests/master_lexer_token_unittest.cc

@@ -0,0 +1,156 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#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(MasterLexer::Token::END_OF_FILE),
+        token_str(TEST_STRING, TEST_STRING_LEN),
+        token_num(42),
+        token_err(MasterLexer::Token::UNEXPECTED_END)
+    {}
+
+    const MasterLexer::Token token_eof; // an example of non-value type token
+    const MasterLexer::Token token_str;
+    const MasterLexer::Token token_num;
+    const MasterLexer::Token token_err;
+};
+
+
+TEST_F(MasterLexerTokenTest, strings) {
+    // basic construction and getter checks
+    EXPECT_EQ(MasterLexer::Token::STRING, token_str.getType());
+    EXPECT_EQ(std::string("string token"), token_str.getString());
+    const MasterLexer::Token::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,
+              MasterLexer::Token(TEST_STRING, TEST_STRING_LEN + 1).getString());
+
+    // Construct type of qstring
+    EXPECT_EQ(MasterLexer::Token::QSTRING,
+              MasterLexer::Token(TEST_STRING, sizeof(TEST_STRING), true).
+              getType());
+    // if we explicitly set 'quoted' to false, it should be normal string
+    EXPECT_EQ(MasterLexer::Token::STRING,
+              MasterLexer::Token(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_num.getString(), 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(MasterLexer::Token::NUMBER, token_num.getType());
+
+    // It's copyable and assignable.
+    MasterLexer::Token token(token_num);
+    EXPECT_EQ(42, token.getNumber());
+    EXPECT_EQ(MasterLexer::Token::NUMBER, token.getType());
+
+    token = token_num;
+    EXPECT_EQ(42, token.getNumber());
+    EXPECT_EQ(MasterLexer::Token::NUMBER, token.getType());
+
+    // it's okay to replace it with a different type of token
+    token = token_eof;
+    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, token.getType());
+
+    // Possible max value
+    token = MasterLexer::Token(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(MasterLexer::Token::END_OF_FILE, token_eof.getType());
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE,
+              MasterLexer::Token(MasterLexer::Token::END_OF_LINE).getType());
+    EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
+              MasterLexer::Token(MasterLexer::Token::INITIAL_WS).getType());
+
+    // Special types of tokens cannot have value-based types
+    EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::STRING),
+                 isc::InvalidParameter);
+    EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::QSTRING),
+                 isc::InvalidParameter);
+    EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::NUMBER),
+                 isc::InvalidParameter);
+    EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::ERROR),
+                 isc::InvalidParameter);
+}
+
+TEST_F(MasterLexerTokenTest, errors) {
+    EXPECT_EQ(MasterLexer::Token::ERROR, token_err.getType());
+    EXPECT_EQ(MasterLexer::Token::UNEXPECTED_END, token_err.getErrorCode());
+    EXPECT_EQ("unexpected end of input", token_err.getErrorText());
+    EXPECT_EQ("lexer not started",
+              MasterLexer::Token(MasterLexer::Token::NOT_STARTED).
+              getErrorText());
+    EXPECT_EQ("unbalanced parentheses",
+              MasterLexer::Token(MasterLexer::Token::UNBALANCED_PAREN).
+              getErrorText());
+    EXPECT_EQ("unbalanced quotes",
+              MasterLexer::Token(MasterLexer::Token::UNBALANCED_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 '4' (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).
+    EXPECT_THROW(MasterLexer::Token(MasterLexer::Token::ErrorCode(4)),
+                 isc::InvalidParameter);
+
+    // Check the coexistence of "from number" and "from error-code"
+    // constructors won't cause confusion.
+    EXPECT_EQ(MasterLexer::Token::NUMBER,
+              MasterLexer::Token(static_cast<uint32_t>(
+                                     MasterLexer::Token::NOT_STARTED)).
+              getType());
+}
+}

+ 110 - 14
src/lib/dns/tests/name_unittest.cc

@@ -40,6 +40,22 @@ using namespace isc::util;
 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:
@@ -47,6 +63,8 @@ protected:
                  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)
     {}
 
@@ -54,6 +72,8 @@ protected:
     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;
 
     //
@@ -137,6 +157,11 @@ checkBadTextName(const string& txt) {
     // 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, fromText) {
@@ -201,27 +226,99 @@ TEST_F(NameTest, fromText) {
                                   "123456789.123456789.123456789.123456789."
                                   "123456789.1234");
     // This is a possible longest name and should be accepted
-    EXPECT_NO_THROW(Name("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"));
+    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("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.");
+    Name maxlabels = Name(string(max_labels_str));
     EXPECT_EQ(Name::MAX_LABELS, maxlabels.getLabelCount());
 }
 
+// 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, NULL), 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, NULL));
+}
+
+// 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, NULL), MissingNameOrigin);
+}
+
+// When we don't provide the data, it throws
+TEST_F(NameTest, noDataProvided) {
+    EXPECT_THROW(Name(NULL, 10, NULL), isc::InvalidParameter);
+    EXPECT_THROW(Name(NULL, 10, &origin_name), isc::InvalidParameter);
+    EXPECT_THROW(Name("www", 0, NULL), 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, NULL), 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 agains 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, NULL).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.
@@ -520,7 +617,6 @@ TEST_F(NameTest, downcase) {
     // confirm the calling object is actually modified
     example_name_upper.downcase();
     compareInWireFormat(example_name_upper, example_name);
-    
 }
 
 TEST_F(NameTest, at) {

+ 30 - 7
src/lib/python/isc/sysinfo/sysinfo.py

@@ -22,6 +22,7 @@ import subprocess
 import os.path
 import platform
 import time
+from datetime import timedelta
 
 class SysInfo:
     def __init__(self):
@@ -93,6 +94,18 @@ class SysInfo:
         """Returns the uptime in seconds."""
         return self._uptime
 
+    def get_uptime_desc(self):
+        """Returns the uptime in human readable form.
+
+        The format is the result of str() method of the standard library
+        datetime.timedelta class.  It returns None if _uptime is None.
+
+        """
+        if self._uptime is None:
+            return None
+
+        return str(timedelta(seconds=self._uptime))
+
     def get_loadavg(self):
         """Returns the load average as 3 floating point values in an array."""
         return self._loadavg
@@ -333,11 +346,11 @@ class SysInfoOpenBSD(SysInfoBSD):
             pass
 
         try:
+            # We use the size of free-list from the vmstat result.
             s = subprocess.check_output(['vmstat'])
             lines = s.decode('utf-8').split('\n')
             v = re.split('\s+', lines[2])
-            used = int(v[4]) * 1024
-            self._mem_free = self._mem_total - used
+            self._mem_free = int(v[5]) * 1024
         except (subprocess.CalledProcessError, OSError):
             pass
 
@@ -389,17 +402,27 @@ class SysInfoFreeBSD(SysInfoFreeBSDOSX):
         super().__init__()
 
         try:
-            s = subprocess.check_output(['sysctl', '-n', 'kern.smp.active'])
-            self._platform_is_smp = int(s.decode('utf-8').strip()) > 0
-        except (subprocess.CalledProcessError, OSError):
+            # There doesn't seem to be an easy way to reliably detect whether
+            # the kernel was built with SMP support on FreeBSD.  We use
+            # a sysctl variable that is only defined in SMP kernels.
+            # This assumption seems to hold for several recent versions of
+            # FreeBSD, but it may not always be so for future versions.
+            s = subprocess.check_output(['sysctl', '-n',
+                                         'kern.smp.forward_signal_enabled'])
+            self._platform_is_smp = True # the value doesn't matter
+        except subprocess.CalledProcessError:
+            # if this variable isn't defined we should see this exception.
+            # intepret it as an indication of non-SMP kernel.
+            self._platform_is_smp = False
+        except OSError:
             pass
 
         try:
+            # We use the size of free-list from the vmstat result.
             s = subprocess.check_output(['vmstat', '-H'])
             lines = s.decode('utf-8').split('\n')
             v = re.split('\s+', lines[2])
-            used = int(v[4]) * 1024
-            self._mem_free = self._mem_total - used
+            self._mem_free = int(v[5]) * 1024
         except (subprocess.CalledProcessError, OSError):
             pass
 

+ 53 - 12
src/lib/python/isc/sysinfo/tests/sysinfo_test.py

@@ -49,7 +49,7 @@ class MyLinuxFile:
         elif self._filename == '/proc/version':
             return 'An SMP version string'
         elif self._filename == '/proc/uptime':
-            return '86400.75 139993.71'
+            return '172800.75 139993.71'
         elif self._filename == '/proc/loadavg':
             return '0.1 0.2 0.3 0.4'
         else:
@@ -171,26 +171,33 @@ def _my_freebsd_os_sysconf(key):
 def _my_freebsd_platform_uname():
     return ('FreeBSD', 'freebsd', '8.2-RELEASE', '', 'i386')
 
-def _my_freebsd_osx_subprocess_check_output(command):
+def _my_freebsd_osx_subprocess_check_output(command, faked_output={}):
     '''subprocess output shared for freebsd and osx'''
     assert type(command) == list, 'command argument is not a list'
     if command == ['sysctl', '-n', 'kern.boottime']:
-        return bytes('{ sec = ' + str(int(time.time() - 76632)) + ', usec = 0 }\n', 'utf-8')
+        if 'boottime-sysctl' in faked_output:
+            return faked_output['boottime-sysctl']
+        return bytes('{ sec = ' + str(int(time.time() - 76632)) +
+                     ', usec = 0 }\n', 'utf-8')
     elif command == ['sysctl', '-n', 'vm.loadavg']:
         return b'{ 0.2 0.4 0.6 }\n'
     else:
         return _my_bsd_subprocess_check_output(command)
 
-def _my_freebsd_subprocess_check_output(command):
+def _my_freebsd_subprocess_check_output(command, faked_output):
     assert type(command) == list, 'command argument is not a list'
-    if command == ['sysctl', '-n', 'kern.smp.active']:
-        return b'1\n'
+    if command == ['sysctl', '-n', 'kern.smp.forward_signal_enabled']:
+        output = faked_output['smp-sysctl']
+        if isinstance(output, Exception):
+            raise output
+        return output
     elif command == ['vmstat', '-H']:
         return b' procs    memory       page                    disks    traps          cpu\n r b w    avm     fre  flt  re  pi  po  fr  sr wd0 cd0  int   sys   cs us sy id\n 0 0 0   343434  123456   47   0   0   0   0   0   2   0    2    80   14  0  1 99\n'
     elif command == ['swapctl', '-s', '-k']:
         return b'Total:         1013216    0\n'
     else:
-        freebsd_osx_output = _my_freebsd_osx_subprocess_check_output(command)
+        freebsd_osx_output = \
+            _my_freebsd_osx_subprocess_check_output(command, faked_output)
         if freebsd_osx_output is not None:
             return freebsd_osx_output
         else:
@@ -252,6 +259,7 @@ class SysInfoTest(unittest.TestCase):
         self.assertEqual('Unknown', s.get_platform_machine())
         self.assertFalse(s.get_platform_is_smp())
         self.assertEqual(None, s.get_uptime())
+        self.assertEqual(None, s.get_uptime_desc())
         self.assertEqual(None, s.get_loadavg())
         self.assertEqual(None, s.get_mem_total())
         self.assertEqual(None, s.get_mem_free())
@@ -267,7 +275,11 @@ class SysInfoTest(unittest.TestCase):
 
     def test_sysinfo_factory(self):
         """Test that SysInfoFromFactory returns a valid system-specific
-        SysInfo implementation."""
+        SysInfo implementation.
+
+        See sysinfo.SysInfoTestcase() for some of the parameters.
+
+        """
 
         old_platform_system = platform.system
         platform.system = _my_testcase_platform_system
@@ -281,6 +293,8 @@ class SysInfoTest(unittest.TestCase):
         self.assertEqual('Unknown', s.get_platform_machine())
         self.assertFalse(s.get_platform_is_smp())
         self.assertEqual(131072, s.get_uptime())
+        # We check that we do NOT add 's' to 'day' (because it's singular):
+        self.assertEqual('1 day, 12:24:32', s.get_uptime_desc())
         self.assertEqual(None, s.get_loadavg())
         self.assertEqual(None, s.get_mem_total())
         self.assertEqual(None, s.get_mem_free())
@@ -312,7 +326,10 @@ class SysInfoTest(unittest.TestCase):
         self.assertEqual(NPROCESSORS_LINUX, s.get_num_processors())
         self.assertEqual('myhostname', s.get_platform_hostname())
         self.assertTrue(s.get_platform_is_smp())
-        self.assertEqual(86401, s.get_uptime())
+        self.assertEqual(172801, s.get_uptime())
+        # We check that we add 's' to 'day', and that the mm part has an
+        # additional 0, i.e., not '0:0' but '0:00':
+        self.assertEqual('2 days, 0:00:01', s.get_uptime_desc())
         self.assertEqual((0.1, 0.2, 0.3), s.get_loadavg())
         self.assertEqual(3157884928, s.get_mem_total())
         self.assertEqual(891383808, s.get_mem_free())
@@ -366,7 +383,7 @@ class SysInfoTest(unittest.TestCase):
         self.assertEqual((0.7, 0.9, 0.8), s.get_loadavg())
         self.assertFalse(s.get_platform_is_smp())
         self.assertEqual(543214321, s.get_mem_total())
-        self.assertEqual(543214321 - (121212 * 1024), s.get_mem_free())
+        self.assertEqual(123456 * 1024, s.get_mem_free())
         self.assertEqual(566791168, s.get_mem_swap_total())
         self.assertEqual(566789120, s.get_mem_swap_free())
 
@@ -379,18 +396,42 @@ class SysInfoTest(unittest.TestCase):
         # with mock ones for testing.
         platform.system = _my_freebsd_platform_system
         os.sysconf = _my_freebsd_os_sysconf
-        subprocess.check_output = _my_freebsd_subprocess_check_output
+
+        # We use a lambda object so we can tweak the subprocess output during
+        # the tests later.
+        faked_process_output = { 'smp-sysctl': b'1\n' }
+        subprocess.check_output = lambda command : \
+            _my_freebsd_subprocess_check_output(command, faked_process_output)
+
         os.uname = _my_freebsd_platform_uname
 
         s = SysInfoFromFactory()
         self.assertEqual(NPROCESSORS_FREEBSD, s.get_num_processors())
         self.assertTrue(s.get_platform_is_smp())
 
+        # We check the kernel SMP support by the availability of a sysctl
+        # variable.  The value (especially a 0 value) shouldn't matter.
+        faked_process_output['smp-sysctl'] = b'0\n'
+        s = SysInfoFromFactory()
+        self.assertTrue(s.get_platform_is_smp())
+
+        # if the sysctl raises CalledProcessError, we treat it as non-SMP
+        # kernel.
+        faked_process_output['smp-sysctl'] = \
+            subprocess.CalledProcessError(1, 'sysctl')
+        s = SysInfoFromFactory()
+        self.assertFalse(s.get_platform_is_smp())
+
+        # if it results in OSError, no SMP information will be provided.
+        faked_process_output['smp-sysctl'] = OSError()
+        s = SysInfoFromFactory()
+        self.assertIsNone(s.get_platform_is_smp())
+
         self.check_bsd_values(s)
 
         self.assertEqual((0.2, 0.4, 0.6), s.get_loadavg())
         self.assertEqual(543214321, s.get_mem_total())
-        self.assertEqual(543214321 - (343434 * 1024), s.get_mem_free())
+        self.assertEqual(123456 * 1024, s.get_mem_free())
         self.assertEqual(1037533184, s.get_mem_swap_total())
         self.assertEqual(1037533184, s.get_mem_swap_free())
 

+ 6 - 5
src/lib/statistics/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES) $(MULTITHREADING_FLAG)
@@ -17,8 +17,9 @@ if USE_CLANGPP
 AM_CXXFLAGS += -Wno-unused-parameter
 endif
 
-lib_LTLIBRARIES = libb10-statistics.la
-libb10_statistics_la_SOURCES  = counter.h counter.cc
-libb10_statistics_la_SOURCES  += counter_dict.h counter_dict.cc
-
 CLEANFILES = *.gcno *.gcda
+
+# These are header-only shared classes and required to build BIND 10.
+# Include them in the distributed tarball with EXTRA_DIST (like as
+# external sources in ext/).
+EXTRA_DIST = counter.h counter_dict.h

+ 0 - 82
src/lib/statistics/counter.cc

@@ -1,82 +0,0 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <vector>
-
-#include <boost/noncopyable.hpp>
-
-#include <statistics/counter.h>
-
-namespace {
-const unsigned int InitialValue = 0;
-} // namespace
-
-namespace isc {
-namespace statistics {
-
-class CounterImpl : boost::noncopyable {
-    private:
-        std::vector<Counter::Value> counters_;
-    public:
-        CounterImpl(const size_t nelements);
-        ~CounterImpl();
-        void inc(const Counter::Type&);
-        const Counter::Value& get(const Counter::Type&) const;
-};
-
-CounterImpl::CounterImpl(const size_t items) :
-    counters_(items, InitialValue)
-{
-    if (items == 0) {
-        isc_throw(isc::InvalidParameter, "Items must not be 0");
-    }
-}
-
-CounterImpl::~CounterImpl() {}
-
-void
-CounterImpl::inc(const Counter::Type& type) {
-    if(type >= counters_.size()) {
-        isc_throw(isc::OutOfRange, "Counter type is out of range");
-    }
-    ++counters_.at(type);
-    return;
-}
-
-const Counter::Value&
-CounterImpl::get(const Counter::Type& type) const {
-    if(type >= counters_.size()) {
-        isc_throw(isc::OutOfRange, "Counter type is out of range");
-    }
-    return (counters_.at(type));
-}
-
-Counter::Counter(const size_t items) : impl_(new CounterImpl(items))
-{}
-
-Counter::~Counter() {}
-
-void
-Counter::inc(const Type& type) {
-    impl_->inc(type);
-    return;
-}
-
-const Counter::Value&
-Counter::get(const Type& type) const {
-    return (impl_->get(type));
-}
-
-}   // namespace statistics
-}   // namespace isc

+ 33 - 11
src/lib/statistics/counter.h

@@ -15,24 +15,29 @@
 #ifndef COUNTER_H
 #define COUNTER_H 1
 
+#include <exceptions/exceptions.h>
+
 #include <boost/noncopyable.hpp>
 #include <boost/scoped_ptr.hpp>
 
-#include <exceptions/exceptions.h>
+#include <vector>
+
+namespace {
+const unsigned int InitialValue = 0;
+} // anonymous namespace
 
 namespace isc {
 namespace statistics {
 
-// forward declaration for pImpl idiom
-class CounterImpl;
-
 class Counter : boost::noncopyable {
-private:
-    boost::scoped_ptr<CounterImpl> impl_;
 public:
     typedef unsigned int Type;
     typedef unsigned int Value;
 
+private:
+    std::vector<Counter::Value> counters_;
+
+public:
     /// The constructor.
     ///
     /// This constructor is mostly exception free. But it may still throw
@@ -41,29 +46,46 @@ public:
     /// \param items A number of counter items to hold (greater than 0)
     ///
     /// \throw isc::InvalidParameter \a items is 0
-    Counter(const size_t items);
+    explicit Counter(const size_t items) :
+        counters_(items, InitialValue)
+    {
+        if (items == 0) {
+            isc_throw(isc::InvalidParameter, "Items must not be 0");
+        }
+    };
 
     /// The destructor.
     ///
     /// This method never throws an exception.
-    ~Counter();
+    ~Counter() {};
 
     /// \brief Increment a counter item specified with \a type.
     ///
     /// \param type %Counter item to increment
     ///
     /// \throw isc::OutOfRange \a type is invalid
-    void inc(const Type& type);
+    void inc(const Counter::Type type) {
+        if (type >= counters_.size()) {
+            isc_throw(isc::OutOfRange, "Counter type is out of range");
+        }
+        ++counters_.at(type);
+        return;
+    };
 
     /// \brief Get the value of a counter item specified with \a type.
     ///
     /// \param type %Counter item to get the value of
     ///
     /// \throw isc::OutOfRange \a type is invalid
-    const Value& get(const Type& type) const;
+    const Counter::Value& get(const Counter::Type type) const {
+        if (type >= counters_.size()) {
+            isc_throw(isc::OutOfRange, "Counter type is out of range");
+        }
+        return (counters_.at(type));
+    };
 };
 
 }   // namespace statistics
 }   // namespace isc
 
-#endif
+#endif // __COUNTER_H

+ 0 - 265
src/lib/statistics/counter_dict.cc

@@ -1,265 +0,0 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <cassert>
-#include <stdexcept>
-#include <iterator>
-#include <map>
-#include <boost/noncopyable.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <statistics/counter_dict.h>
-
-namespace {
-typedef boost::shared_ptr<isc::statistics::Counter> CounterPtr;
-typedef std::map<std::string, CounterPtr> DictionaryMap;
-}
-
-namespace isc {
-namespace statistics {
-
-// Implementation detail class for CounterDictionary::ConstIterator
-class CounterDictionaryConstIteratorImpl;
-
-class CounterDictionaryImpl : boost::noncopyable {
-private:
-    DictionaryMap dictionary_;
-    std::vector<std::string> elements_;
-    const size_t items_;
-    // Default constructor is forbidden; number of counter items must be
-    // specified at the construction of this class.
-    CounterDictionaryImpl();
-public:
-    CounterDictionaryImpl(const size_t items);
-    ~CounterDictionaryImpl();
-    void addElement(const std::string& name);
-    void deleteElement(const std::string& name);
-    Counter& getElement(const std::string& name);
-public:
-    CounterDictionaryConstIteratorImpl begin() const;
-    CounterDictionaryConstIteratorImpl end() const;
-};
-
-// Constructor with number of items
-CounterDictionaryImpl::CounterDictionaryImpl(const size_t items) :
-    items_(items)
-{
-    // The number of items must not be 0
-    if (items == 0) {
-        isc_throw(isc::InvalidParameter, "Items must not be 0");
-    }
-}
-
-// Destructor
-CounterDictionaryImpl::~CounterDictionaryImpl() {}
-
-void
-CounterDictionaryImpl::addElement(const std::string& name) {
-    // throw if the element already exists
-    if (dictionary_.count(name) != 0) {
-        isc_throw(isc::InvalidParameter,
-                  "Element " << name << " already exists");
-    }
-    assert(items_ != 0);
-    // Create a new Counter and add to the map
-    dictionary_.insert(
-        DictionaryMap::value_type(name, CounterPtr(new Counter(items_))));
-}
-
-void
-CounterDictionaryImpl::deleteElement(const std::string& name) {
-    size_t result = dictionary_.erase(name);
-    if (result != 1) {
-        // If an element with specified name does not exist, throw
-        // isc::OutOfRange.
-        isc_throw(isc::OutOfRange, "Element " << name << " does not exist");
-    }
-}
-
-Counter&
-CounterDictionaryImpl::getElement(const std::string& name) {
-    DictionaryMap::const_iterator i = dictionary_.find(name);
-    if (i != dictionary_.end()) {
-        // the key was found. return the element.
-        return (*(i->second));
-    } else {
-        // If an element with specified name does not exist, throw
-        // isc::OutOfRange.
-        isc_throw(isc::OutOfRange, "Element " << name << " does not exist");
-    }
-}
-
-// Constructor
-// Initialize impl_
-CounterDictionary::CounterDictionary(const size_t items) :
-    impl_(new CounterDictionaryImpl(items))
-{}
-
-// Destructor
-// impl_ will be freed automatically with scoped_ptr
-CounterDictionary::~CounterDictionary() {}
-
-void
-CounterDictionary::addElement(const std::string& name) {
-    impl_->addElement(name);
-}
-
-void
-CounterDictionary::deleteElement(const std::string& name) {
-    impl_->deleteElement(name);
-}
-
-Counter&
-CounterDictionary::getElement(const std::string& name) const {
-    return (impl_->getElement(name));
-}
-
-Counter&
-CounterDictionary::operator[](const std::string& name) const {
-    return (impl_->getElement(name));
-}
-
-// Implementation detail class for CounterDictionary::ConstIterator
-class CounterDictionaryConstIteratorImpl {
-    public:
-        CounterDictionaryConstIteratorImpl();
-        ~CounterDictionaryConstIteratorImpl();
-        CounterDictionaryConstIteratorImpl(
-            const CounterDictionaryConstIteratorImpl &other);
-        CounterDictionaryConstIteratorImpl &operator=(
-            const CounterDictionaryConstIteratorImpl &source);
-        CounterDictionaryConstIteratorImpl(
-            DictionaryMap::const_iterator iterator);
-    public:
-        void increment();
-        const CounterDictionary::ConstIterator::value_type&
-            dereference() const;
-        bool equal(const CounterDictionaryConstIteratorImpl& other) const;
-    private:
-        DictionaryMap::const_iterator iterator_;
-};
-
-CounterDictionaryConstIteratorImpl::CounterDictionaryConstIteratorImpl() {}
-
-CounterDictionaryConstIteratorImpl::~CounterDictionaryConstIteratorImpl() {}
-
-// Copy constructor: deep copy of iterator_
-CounterDictionaryConstIteratorImpl::CounterDictionaryConstIteratorImpl(
-    const CounterDictionaryConstIteratorImpl &other) :
-    iterator_(other.iterator_)
-{}
-
-// Assignment operator: deep copy of iterator_
-CounterDictionaryConstIteratorImpl &
-CounterDictionaryConstIteratorImpl::operator=(
-    const CounterDictionaryConstIteratorImpl &source)
-{
-    iterator_ = source.iterator_;
-    return (*this);
-}
-
-// Constructor from implementation detail DictionaryMap::const_iterator
-CounterDictionaryConstIteratorImpl::CounterDictionaryConstIteratorImpl(
-    DictionaryMap::const_iterator iterator) :
-    iterator_(iterator)
-{}
-
-CounterDictionaryConstIteratorImpl
-CounterDictionaryImpl::begin() const {
-    return (CounterDictionaryConstIteratorImpl(dictionary_.begin()));
-}
-
-CounterDictionaryConstIteratorImpl
-CounterDictionaryImpl::end() const {
-    return (CounterDictionaryConstIteratorImpl(dictionary_.end()));
-}
-
-void
-CounterDictionaryConstIteratorImpl::increment() {
-    ++iterator_;
-    return;
-}
-
-const CounterDictionary::ConstIterator::value_type&
-CounterDictionaryConstIteratorImpl::dereference() const {
-    return (iterator_->first);
-}
-
-bool
-CounterDictionaryConstIteratorImpl::equal(
-    const CounterDictionaryConstIteratorImpl& other) const
-{
-    return (iterator_ == other.iterator_);
-}
-
-CounterDictionary::ConstIterator
-CounterDictionary::begin() const {
-    return (CounterDictionary::ConstIterator(
-               CounterDictionaryConstIteratorImpl(impl_->begin())));
-}
-
-CounterDictionary::ConstIterator
-CounterDictionary::end() const {
-    return (CounterDictionary::ConstIterator(
-               CounterDictionaryConstIteratorImpl(impl_->end())));
-}
-
-CounterDictionary::ConstIterator::ConstIterator() :
-    impl_(new CounterDictionaryConstIteratorImpl())
-{}
-
-CounterDictionary::ConstIterator::~ConstIterator() {}
-
-// Copy constructor: deep copy of impl_
-CounterDictionary::ConstIterator::ConstIterator(
-    const CounterDictionary::ConstIterator& source) :
-    impl_(new CounterDictionaryConstIteratorImpl(*(source.impl_)))
-{}
-
-// Assignment operator: deep copy of impl_
-CounterDictionary::ConstIterator &
-CounterDictionary::ConstIterator::operator=(
-    const CounterDictionary::ConstIterator &source)
-{
-    *impl_ = *source.impl_;
-    return (*this);
-}
-
-// The constructor from implementation detail
-CounterDictionary::ConstIterator::ConstIterator(
-    const CounterDictionaryConstIteratorImpl& source) :
-    impl_(new CounterDictionaryConstIteratorImpl(source))
-{}
-
-const CounterDictionary::ConstIterator::value_type&
-CounterDictionary::ConstIterator::dereference() const
-{
-    return (impl_->dereference());
-}
-
-bool
-CounterDictionary::ConstIterator::equal(
-    CounterDictionary::ConstIterator const& other) const
-{
-    return (impl_->equal(*(other.impl_)));
-}
-
-void
-CounterDictionary::ConstIterator::increment() {
-    impl_->increment();
-    return;
-}
-
-}   // namespace statistics
-}   // namespace isc

+ 91 - 89
src/lib/statistics/counter_dict.h

@@ -15,67 +15,83 @@
 #ifndef COUNTER_DICT_H
 #define COUNTER_DICT_H 1
 
-#include <string>
-#include <vector>
-#include <utility>
+#include <statistics/counter.h>
+#include <exceptions/exceptions.h>
+
 #include <boost/noncopyable.hpp>
 #include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
 #include <boost/iterator/iterator_facade.hpp>
 
-#include <exceptions/exceptions.h>
-#include <statistics/counter.h>
+#include <cassert>
+#include <stdexcept>
+#include <string>
+#include <vector>
+#include <map>
+#include <iterator>
+#include <utility>
+
+namespace {
+typedef boost::shared_ptr<isc::statistics::Counter> CounterPtr;
+typedef std::map<std::string, CounterPtr> DictionaryMap;
+}
 
 namespace isc {
 namespace statistics {
 
-class CounterDictionaryImpl;
-class CounterDictionaryConstIteratorImpl;
-
 class CounterDictionary : boost::noncopyable {
 private:
-    boost::scoped_ptr<CounterDictionaryImpl> impl_;
+    DictionaryMap dictionary_;
+    std::vector<std::string> elements_;
+    const size_t items_;
     // Default constructor is forbidden; number of counter items must be
     // specified at the construction of this class.
     CounterDictionary();
 public:
-    /// The constructor.
-    /// This constructor is mostly exception free. But it may still throw
-    /// a standard exception if memory allocation fails inside the method.
-    ///
-    /// \param items A number of counter items to hold (greater than 0)
-    ///
-    /// \throw isc::InvalidParameter \a items is 0
-    CounterDictionary(const size_t items);
-
-    /// The destructor.
-    ///
-    /// This method never throws an exception.
-    ~CounterDictionary();
-
-    /// \brief Add an element
-    ///
-    /// \throw isc::InvalidParameter \a element already exists.
-    ///
-    /// \param name A name of the element to append
-    void addElement(const std::string& name);
-
-    /// \brief Delete
-    ///
-    /// \throw isc::OutOfRange \a element does not exist.
-    ///
-    /// \param name A name of the element to delete
-    void deleteElement(const std::string& name);
-
-    /// \brief Lookup
-    ///
-    /// \throw isc::OutOfRange \a element does not exist.
-    ///
-    /// \param name A name of the element to get the counters
-    Counter& getElement(const std::string &name) const;
-
-    /// Same as getElement()
-    Counter& operator[](const std::string &name) const;
-
+    explicit CounterDictionary(const size_t items) :
+        items_(items)
+    {
+        // The number of items must not be 0
+        if (items == 0) {
+            isc_throw(isc::InvalidParameter, "Items must not be 0");
+        }
+    };
+    ~CounterDictionary() {};
+    void addElement(const std::string& name) {
+        // throw if the element already exists
+        if (dictionary_.count(name) != 0) {
+            isc_throw(isc::InvalidParameter,
+                      "Element " << name << " already exists");
+        }
+        assert(items_ != 0);
+        // Create a new Counter and add to the map
+        dictionary_.insert(
+            DictionaryMap::value_type(name, CounterPtr(new Counter(items_))));
+    };
+    void deleteElement(const std::string& name) {
+        size_t result = dictionary_.erase(name);
+        if (result != 1) {
+            // If an element with specified name does not exist, throw
+            // isc::OutOfRange.
+            isc_throw(isc::OutOfRange,
+                      "Element " << name << " does not exist");
+        }
+    };
+    Counter& getElement(const std::string& name) {
+        DictionaryMap::const_iterator i = dictionary_.find(name);
+        if (i != dictionary_.end()) {
+            // the key was found. return the element.
+            return (*(i->second));
+        } else {
+            // If an element with specified name does not exist, throw
+            // isc::OutOfRange.
+            isc_throw(isc::OutOfRange,
+                      "Element " << name << " does not exist");
+        }
+    };
+    Counter& operator[](const std::string& name) {
+        return (getElement(name));
+    };
     /// \brief \c ConstIterator is a constant iterator that provides an
     /// interface for enumerating name of zones stored in CounterDictionary.
     ///
@@ -90,67 +106,53 @@ public:
                                 const std::string,
                                 boost::forward_traversal_tag>
     {
-        private:
-            boost::scoped_ptr<CounterDictionaryConstIteratorImpl> impl_;
         public:
             /// The constructor.
             ///
             /// This constructor is mostly exception free. But it may still
             /// throw a standard exception if memory allocation fails
             /// inside the method.
-            ConstIterator();
+            ConstIterator() {}
             /// The destructor.
             ///
             /// This method never throws an exception.
-            ~ConstIterator();
-            /// The assignment operator.
-            ///
-            /// This method is mostly exception free. But it may still
-            /// throw a standard exception if memory allocation fails
-            /// inside the method.
-            ConstIterator& operator=(const ConstIterator &source);
-            /// The copy constructor.
-            ///
-            /// This constructor is mostly exception free. But it may still
-            /// throw a standard exception if memory allocation fails
-            /// inside the method.
-            ConstIterator(const ConstIterator& source);
-            /// The constructor from implementation detail.
-            ///
-            /// This method is used to create an instance of ConstIterator
-            /// by CounterDict::begin() and CounterDict::end().
-            ///
-            /// This constructor is mostly exception free. But it may still
-            /// throw a standard exception if memory allocation fails
-            /// inside the method.
+            ~ConstIterator() {}
+            /// Constructor from implementation detail DictionaryMap::const_iterator
             ConstIterator(
-                const CounterDictionaryConstIteratorImpl& source);
+                DictionaryMap::const_iterator iterator) :
+                iterator_(iterator)
+            {}
+
         private:
             /// \brief An internal method to increment this iterator.
-            void increment();
+            void increment() {
+                ++iterator_;
+                return;
+            }
+
             /// \brief An internal method to check equality.
-            bool equal(const ConstIterator& other) const;
+            bool equal(const ConstIterator& other) const {
+                return (iterator_ == other.iterator_);
+            }
+
             /// \brief An internal method to dereference this iterator.
-            const value_type& dereference() const;
+            const value_type& dereference() const {
+                return (iterator_->first);
+            }
+
         private:
             friend class boost::iterator_core_access;
+            DictionaryMap::const_iterator iterator_;
     };
 
-    typedef ConstIterator const_iterator;
-
-    /// \brief Return an iterator corresponding to the beginning of the
-    /// elements stored in CounterDictionary.
-    ///
-    /// This method is mostly exception free. But it may still throw a
-    /// standard exception if memory allocation fails inside the method.
-    const_iterator begin() const;
+    ConstIterator begin() const {
+        return (CounterDictionary::ConstIterator(dictionary_.begin()));
+    };
+    ConstIterator end() const {
+        return (CounterDictionary::ConstIterator(dictionary_.end()));
+    };
 
-    /// \brief Return an iterator corresponding to the end of the elements
-    /// stored in CounterDictionary.
-    ///
-    /// This method is mostly exception free. But it may still throw a
-    /// standard exception if memory allocation fails inside the method.
-    const_iterator end() const;
+    typedef ConstIterator const_iterator;
 };
 
 }   // namespace statistics

+ 0 - 1
src/lib/statistics/tests/Makefile.am

@@ -28,7 +28,6 @@ run_unittests_SOURCES += counter_dict_unittest.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 
 run_unittests_LDADD  = $(GTEST_LDADD)
-run_unittests_LDADD += $(top_builddir)/src/lib/statistics/libb10-statistics.la
 run_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la

+ 4 - 3
tests/lettuce/features/ddns_system.feature

@@ -39,7 +39,7 @@ Feature: DDNS System
         # Test 5
         When I use DDNS to set the SOA serial to 1237
         # also check if Auth server reloaded
-        And wait for new bind10 stderr message AUTH_LOAD_ZONE
+        And wait for new bind10 stderr message AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE
         The DDNS response should be SUCCESS
         And the SOA serial for example.org should be 1237
 
@@ -53,11 +53,12 @@ Feature: DDNS System
 
         # Test 8
         When I use DDNS to set the SOA serial to 1238
+        And wait for new bind10 stderr message AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE
         The DDNS response should be SUCCESS
         And the SOA serial for example.org should be 1238
 
         When I use DDNS to set the SOA serial to 1239
-        And wait for new bind10 stderr message AUTH_LOAD_ZONE
+        And wait for new bind10 stderr message AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE
         The DDNS response should be SUCCESS
         And the SOA serial for example.org should be 1239
 
@@ -69,7 +70,7 @@ Feature: DDNS System
 
         # Test 10
         When I use DDNS to set the SOA serial to 1240
-        And wait for new bind10 stderr message AUTH_LOAD_ZONE
+        And wait for new bind10 stderr message AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE
         The DDNS response should be SUCCESS
         And the SOA serial for example.org should be 1240
 

+ 1 - 1
tests/lettuce/features/inmemory_over_sqlite3.feature

@@ -33,7 +33,7 @@ Feature: In-memory zone using SQLite3 backend
         A query for mail.example.org to [::1]:47806 should have rcode NXDOMAIN
         When I send bind10 the command Xfrin retransfer example.org IN ::1 47807
         Then wait for new bind10 stderr message XFRIN_TRANSFER_SUCCESS not XFRIN_XFR_PROCESS_FAILURE
-        Then wait for new bind10 stderr message AUTH_LOAD_ZONE
+        Then wait for new bind10 stderr message AUTH_DATASRC_CLIENTS_BUILDER_LOAD_ZONE
 
         A query for www.example.org to [::1]:47807 should have rcode NOERROR
         The answer section of the last query response should be