Parcourir la source

[fdunparse2] Rebased, still reservations to do

Francis Dupont il y a 8 ans
Parent
commit
0beffc6e25
91 fichiers modifiés avec 3238 ajouts et 642 suppressions
  1. 87 26
      configure.ac
  2. 3 4
      src/bin/dhcp4/dhcp4to6_ipc.cc
  3. 1 16
      src/bin/dhcp4/json_config_parser.cc
  4. 3 0
      src/bin/dhcp4/main.cc
  5. 1 2
      src/bin/dhcp4/simple_parser4.cc
  6. 0 8
      src/bin/dhcp4/tests/config_parser_unittest.cc
  7. 1 2
      src/bin/dhcp4/tests/simple_parser4_unittest.cc
  8. 3 4
      src/bin/dhcp6/dhcp6to4_ipc.cc
  9. 1 28
      src/bin/dhcp6/json_config_parser.cc
  10. 3 0
      src/bin/dhcp6/main.cc
  11. 1 2
      src/bin/dhcp6/simple_parser6.cc
  12. 0 7
      src/bin/dhcp6/tests/config_parser_unittest.cc
  13. 2 3
      src/bin/dhcp6/tests/simple_parser6_unittest.cc
  14. 236 0
      src/lib/cc/data.cc
  15. 44 0
      src/lib/cc/data.h
  16. 1 1
      src/lib/cc/simple_parser.cc
  17. 41 3
      src/lib/cc/simple_parser.h
  18. 192 4
      src/lib/cc/tests/data_unittests.cc
  19. 3 8
      src/lib/cc/tests/simple_parser_unittest.cc
  20. 1 1
      src/lib/dhcpsrv/Makefile.am
  21. 76 1
      src/lib/dhcpsrv/addr_utilities.cc
  22. 13 1
      src/lib/dhcpsrv/addr_utilities.h
  23. 9 3
      src/lib/dhcpsrv/cfg_4o6.h
  24. 64 2
      src/lib/dhcpsrv/cfg_db_access.cc
  25. 39 2
      src/lib/dhcpsrv/cfg_db_access.h
  26. 37 1
      src/lib/dhcpsrv/cfg_duid.cc
  27. 8 2
      src/lib/dhcpsrv/cfg_duid.h
  28. 33 1
      src/lib/dhcpsrv/cfg_expiration.cc
  29. 8 2
      src/lib/dhcpsrv/cfg_expiration.h
  30. 15 1
      src/lib/dhcpsrv/cfg_host_operations.cc
  31. 8 2
      src/lib/dhcpsrv/cfg_host_operations.h
  32. 31 1
      src/lib/dhcpsrv/cfg_iface.cc
  33. 8 2
      src/lib/dhcpsrv/cfg_iface.h
  34. 47 21
      src/lib/dhcpsrv/cfg_mac_source.cc
  35. 7 1
      src/lib/dhcpsrv/cfg_mac_source.h
  36. 90 1
      src/lib/dhcpsrv/cfg_option.cc
  37. 8 2
      src/lib/dhcpsrv/cfg_option.h
  38. 58 1
      src/lib/dhcpsrv/cfg_option_def.cc
  39. 8 2
      src/lib/dhcpsrv/cfg_option_def.h
  40. 15 1
      src/lib/dhcpsrv/cfg_rsoo.cc
  41. 8 2
      src/lib/dhcpsrv/cfg_rsoo.h
  42. 113 1
      src/lib/dhcpsrv/cfg_subnets4.cc
  43. 8 2
      src/lib/dhcpsrv/cfg_subnets4.h
  44. 180 1
      src/lib/dhcpsrv/cfg_subnets6.cc
  45. 8 2
      src/lib/dhcpsrv/cfg_subnets6.h
  46. 2 1
      src/lib/dhcpsrv/cfgmgr.cc
  47. 13 0
      src/lib/dhcpsrv/cfgmgr.h
  48. 44 2
      src/lib/dhcpsrv/client_class_def.cc
  49. 28 4
      src/lib/dhcpsrv/client_class_def.h
  50. 46 1
      src/lib/dhcpsrv/d2_client_cfg.cc
  51. 9 2
      src/lib/dhcpsrv/d2_client_cfg.h
  52. 57 1
      src/lib/dhcpsrv/logging_info.cc
  53. 8 2
      src/lib/dhcpsrv/logging_info.h
  54. 59 56
      src/lib/dhcpsrv/parsers/client_class_def_parser.cc
  55. 1 1
      src/lib/dhcpsrv/parsers/dbaccess_parser.h
  56. 33 76
      src/lib/dhcpsrv/parsers/dhcp_parsers.cc
  57. 16 15
      src/lib/dhcpsrv/parsers/dhcp_parsers.h
  58. 47 92
      src/lib/dhcpsrv/parsers/duid_config_parser.cc
  59. 0 51
      src/lib/dhcpsrv/parsers/duid_config_parser.h
  60. 33 30
      src/lib/dhcpsrv/parsers/expiration_config_parser.cc
  61. 2 2
      src/lib/dhcpsrv/parsers/ifaces_config_parser.h
  62. 2 2
      src/lib/dhcpsrv/pool.h
  63. 98 0
      src/lib/dhcpsrv/srv_config.cc
  64. 10 4
      src/lib/dhcpsrv/srv_config.h
  65. 11 1
      src/lib/dhcpsrv/subnet.h
  66. 1 0
      src/lib/dhcpsrv/tests/Makefile.am
  67. 90 1
      src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
  68. 22 1
      src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc
  69. 23 5
      src/lib/dhcpsrv/tests/cfg_duid_unittest.cc
  70. 15 1
      src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc
  71. 9 1
      src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc
  72. 34 1
      src/lib/dhcpsrv/tests/cfg_iface_unittest.cc
  73. 38 1
      src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc
  74. 57 1
      src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc
  75. 43 1
      src/lib/dhcpsrv/tests/cfg_option_unittest.cc
  76. 10 1
      src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc
  77. 117 1
      src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
  78. 177 1
      src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
  79. 12 0
      src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
  80. 10 32
      src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
  81. 71 9
      src/lib/dhcpsrv/tests/client_class_def_unittest.cc
  82. 22 1
      src/lib/dhcpsrv/tests/d2_client_unittest.cc
  83. 329 38
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  84. 13 7
      src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc
  85. 0 9
      src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc
  86. 12 2
      src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc
  87. 17 1
      src/lib/dhcpsrv/tests/logging_info_unittest.cc
  88. 63 3
      src/lib/dhcpsrv/tests/srv_config_unittest.cc
  89. 9 1
      src/lib/dhcpsrv/tests/subnet_unittest.cc
  90. 1 3
      src/lib/hooks/hooks_config.cc
  91. 1 0
      src/lib/testutils/Makefile.am

+ 87 - 26
configure.ac

@@ -167,12 +167,11 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
 	feature="final method"
 	AC_COMPILE_IFELSE(
 		[AC_LANG_PROGRAM(
-			[],
 			[class Foo {
 			 public:
 			 	virtual ~Foo() {};
 				virtual void bar() final;
-			 };])],
+			 };],[])],
 		 [AC_MSG_RESULT([yes])],
 		 [AC_MSG_RESULT([no])
 		  continue])
@@ -201,6 +200,31 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
 		[AC_MSG_RESULT([no])
 		 continue])
 
+	AC_MSG_CHECKING(static_assert support)
+	feature="static_assert"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[static_assert(1 + 1 == 2, "");],
+			[])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
+	AC_MSG_CHECKING(template alias)
+	feature="template alias"
+	AC_COMPILE_IFELSE(
+		[AC_LANG_PROGRAM(
+			[template<int i>
+			 class I {
+			 public: int get() { return i; };
+			 };
+			 using Zero = I<0>;],
+			[Zero Z;
+			 return Z.get();])],
+		[AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])
+		 continue])
+
 	AC_MSG_CHECKING(lambda support)
 	feature="lambda"
 	AC_COMPILE_IFELSE(
@@ -213,6 +237,19 @@ for retry in "none" "--std=c++11" "--std=c++0x" "--std=c++1x" "fail"; do
 		 continue])
 done
 
+# Check for std::is_base_of support
+AC_MSG_CHECKING([for std::is_base_of])
+AC_COMPILE_IFELSE(
+	[AC_LANG_PROGRAM(
+		[#include <type_traits>
+		 class A {};
+		 class B : A {};]
+		[static_assert(std::is_base_of<A, B>::value, "");])],
+	[AC_MSG_RESULT(yes)
+	 AC_DEFINE([HAVE_IS_BASE_OF], [1],
+	 [Define to 1 if std::is_base_of is available])],
+	[AC_MSG_RESULT(no)])
+
 dnl Determine if we are using GNU sed
 GNU_SED=no
 $SED --version 2> /dev/null | grep GNU > /dev/null 2>&1
@@ -1415,6 +1452,31 @@ if test $enable_gtest != "no"; then
     CPPFLAGS=$CPPFLAGS_SAVED
 fi
 	    
+# Check for CreateUnifiedDiff from gtest >= 1.8.0
+if test $enable_gtest != "no"; then
+   AC_MSG_CHECKING([for CreateUnifiedDiff in $GTEST_INCLUDES/gtest.h])
+   CPPFLAGS_SAVED=$CPPFLAGS
+   CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES $GTEST_INCLUDES"
+   AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+           [#include <boost/algorithm/string.hpp>
+	    #include <gtest/gtest.h>
+	    #include <string>
+	    #include <vector>
+	    std::string nodiff(std::string text) {
+	        std::vector<std::string> lines;
+		boost::split(lines, text, boost::is_any_of("\n"));
+		using namespace testing::internal;
+		return (edit_distance::CreateUnifiedDiff(lines, lines));
+	    }],
+	    [return 0;])],
+	[AC_MSG_RESULT(yes)
+	 AC_DEFINE([HAVE_CREATE_UNIFIED_DIFF], [1],
+	 [Define to 1 if gtest defines edit_distance::CreateUnifiedDiff])],
+	[AC_MSG_RESULT(no)])
+    CPPFLAGS=$CPPFLAGS_SAVED
+fi    
+
 #
 # ASIO: we extensively use it as the C++ event management module.
 #
@@ -1467,11 +1529,11 @@ AC_ARG_ENABLE(generate_parser, [AC_HELP_STRING([--enable-generate-parser],
    enable_generate_parser=$enableval, enable_generate_parser=no)
 
 # Check if flex is avaible. Flex is not needed for building Kea sources,
-# unless you want to regenerate grammar in src/lib/eval
+# unless you want to regenerate grammars
 AC_PROG_LEX
 
 # Check if bison is available. Bison is not needed for building Kea sources,
-# unless you want to regenerate grammar in src/lib/eval
+# unless you want to regenerate grammars
 AC_PROG_YACC
 
 if test "x$enable_generate_parser" != "xno"; then
@@ -1485,7 +1547,7 @@ if test "x$enable_generate_parser" != "xno"; then
     fi
 
 # Ok, let's check if we have at least 3.0.0 version of the bison. The code used
-# to generate src/lib/eval parser is roughly based on bison 3.0 examples.
+# to generate parsers is roughly based on bison 3.0 examples.
    cat > bisontest.y << EOF
 %require "3.0.0"
 %token X
@@ -1599,20 +1661,28 @@ AM_COND_IF([HAVE_OPTRESET], [AC_DEFINE([HAVE_OPTRESET], [1], [Check for optreset
 
 AC_DEFINE([CONFIG_H_WAS_INCLUDED], [1], [config.h inclusion marker])
 
-AC_CONFIG_FILES([compatcheck/Makefile
+AC_CONFIG_FILES([Makefile
+                 compatcheck/Makefile
                  dns++.pc
-                 doc/design/datasrc/Makefile
+                 doc/Makefile
                  doc/design/Makefile
+                 doc/design/datasrc/Makefile
                  doc/guide/Makefile
-                 doc/Makefile
                  doc/version.ent
+                 ext/Makefile
                  ext/coroutine/Makefile
                  ext/gtest/Makefile
-                 ext/Makefile
                  m4macros/Makefile
-                 Makefile
                  src/Makefile
                  src/bin/Makefile
+                 src/bin/admin/Makefile
+                 src/bin/admin/kea-admin
+                 src/bin/admin/tests/Makefile
+                 src/bin/admin/tests/cql_tests.sh
+                 src/bin/admin/tests/data/Makefile
+                 src/bin/admin/tests/memfile_tests.sh
+                 src/bin/admin/tests/mysql_tests.sh
+                 src/bin/admin/tests/pgsql_tests.sh
                  src/bin/agent/Makefile
                  src/bin/agent/tests/Makefile
                  src/bin/agent/tests/ca_process_tests.sh
@@ -1644,15 +1714,6 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/bin/perfdhcp/Makefile
                  src/bin/perfdhcp/tests/Makefile
                  src/bin/perfdhcp/tests/testdata/Makefile
-                 src/bin/admin/Makefile
-                 src/bin/admin/kea-admin
-                 src/bin/admin/tests/Makefile
-                 src/bin/admin/tests/data/Makefile
-                 src/bin/admin/tests/memfile_tests.sh
-                 src/bin/admin/tests/mysql_tests.sh
-                 src/bin/admin/tests/pgsql_tests.sh
-                 src/bin/admin/tests/cql_tests.sh
-                 src/bin/agent/tests/test_libraries.h
                  src/hooks/Makefile
                  src/hooks/dhcp/Makefile
                  src/hooks/dhcp/user_chk/Makefile
@@ -1685,6 +1746,8 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/lib/dns/gen-rdatacode.py
                  src/lib/dns/tests/Makefile
                  src/lib/dns/tests/testdata/Makefile
+                 src/lib/eval/Makefile
+                 src/lib/eval/tests/Makefile
                  src/lib/exceptions/Makefile
                  src/lib/exceptions/tests/Makefile
                  src/lib/hooks/Makefile
@@ -1710,10 +1773,10 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/lib/process/spec_config.h.pre
                  src/lib/process/tests/Makefile
                  src/lib/process/testutils/Makefile
-                 src/lib/testutils/Makefile
-                 src/lib/testutils/dhcp_test_lib.sh
                  src/lib/stats/Makefile
                  src/lib/stats/tests/Makefile
+                 src/lib/testutils/Makefile
+                 src/lib/testutils/dhcp_test_lib.sh
                  src/lib/util/Makefile
                  src/lib/util/io/Makefile
                  src/lib/util/python/Makefile
@@ -1723,11 +1786,10 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/lib/util/threads/Makefile
                  src/lib/util/threads/tests/Makefile
                  src/lib/util/unittests/Makefile
-                 src/lib/eval/Makefile
-                 src/lib/eval/tests/Makefile
                  src/share/Makefile
                  src/share/database/Makefile
                  src/share/database/scripts/Makefile
+                 src/share/database/scripts/cql/Makefile
                  src/share/database/scripts/mysql/Makefile
                  src/share/database/scripts/mysql/upgrade_1.0_to_2.0.sh
                  src/share/database/scripts/mysql/upgrade_2.0_to_3.0.sh
@@ -1737,17 +1799,16 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/share/database/scripts/pgsql/Makefile
                  src/share/database/scripts/pgsql/upgrade_1.0_to_2.0.sh
                  src/share/database/scripts/pgsql/upgrade_2.0_to_3.0.sh
-                 src/share/database/scripts/cql/Makefile
                  tools/Makefile
                  tools/path_replacer.sh
 ])
 
- AC_CONFIG_COMMANDS([permissions], [
+AC_CONFIG_COMMANDS([permissions], [
+           chmod +x src/bin/admin/kea-admin
            chmod +x src/bin/dhcp4/tests/dhcp4_process_tests.sh
            chmod +x src/bin/dhcp6/tests/dhcp6_process_tests.sh
            chmod +x src/bin/keactrl/keactrl
            chmod +x src/bin/keactrl/tests/keactrl_tests.sh
-           chmod +x src/bin/admin/kea-admin
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/log/tests/console_test.sh
            chmod +x src/lib/log/tests/destination_test.sh

+ 3 - 4
src/bin/dhcp4/dhcp4to6_ipc.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -35,7 +35,7 @@ Dhcp4to6Ipc& Dhcp4to6Ipc::instance() {
 }
 
 void Dhcp4to6Ipc::open() {
-    uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
+    uint16_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
     if (port == 0) {
         Dhcp4o6IpcBase::close();
         return;
@@ -45,8 +45,7 @@ void Dhcp4to6Ipc::open() {
     }
 
     int old_fd = socket_fd_;
-    socket_fd_ = Dhcp4o6IpcBase::open(static_cast<uint16_t>(port),
-                                      ENDPOINT_TYPE_V4);
+    socket_fd_ = Dhcp4o6IpcBase::open(port, ENDPOINT_TYPE_V4);
     if ((old_fd == -1) && (socket_fd_ != old_fd)) {
         IfaceMgr::instance().addExternalSocket(socket_fd_,
                                                Dhcp4to6Ipc::handler);

+ 1 - 16
src/bin/dhcp4/json_config_parser.cc

@@ -341,24 +341,9 @@ public:
         cfg->setDeclinePeriod(probation_period);
 
         // Set the DHCPv4-over-DHCPv6 interserver port.
-        // @todo Change for uint16_t
-        uint32_t dhcp4o6_port = getUint32(global, "dhcp4o6-port");
+        uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
         cfg->setDhcp4o6Port(dhcp4o6_port);
     }
-
-private:
-
-    /// @brief Returns a value converted to uint32_t
-    ///
-    /// Instantiation of getIntType() to uint32_t
-    ///
-    /// @param scope specified parameter will be extracted from this scope
-    /// @param name name of the parameter
-    /// @return an uint32_t value
-    uint32_t getUint32(isc::data::ConstElementPtr scope,
-                       const std::string& name) {
-        return (getIntType<uint32_t>(scope, name));
-    }
 };
 
 } // anonymous namespace

+ 3 - 0
src/bin/dhcp4/main.cc

@@ -124,6 +124,9 @@ main(int argc, char* argv[]) {
         usage();
     }
 
+    // This is the DHCPv4 server
+    CfgMgr::instance().setFamily(AF_INET);
+
     if (check_mode) {
         try {
 

+ 1 - 2
src/bin/dhcp4/simple_parser4.cc

@@ -45,8 +45,7 @@ const SimpleDefaults SimpleParser4::OPTION4_DEF_DEFAULTS = {
 /// for those option-data declarations.
 const SimpleDefaults SimpleParser4::OPTION4_DEFAULTS = {
     { "space",        Element::string,  "dhcp4"},
-    { "csv-format",   Element::boolean, "true"},
-    { "encapsulate",  Element::string,  "" }
+    { "csv-format",   Element::boolean, "true"}
 };
 
 /// @brief This table defines default global values for DHCPv4

+ 0 - 8
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -4557,14 +4557,6 @@ TEST_F(Dhcp4ParserTest, invalidClientClassDictionary) {
         " } ] \n"
         "} \n";
 
-    ConstElementPtr json;
-    ASSERT_NO_THROW(json = parseJSON(config));
-
-    ConstElementPtr status;
-    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(status);
-    checkResult(status, 1);
-
     EXPECT_THROW(parseDHCP4(config), Dhcp4ParseError);
 }
 

+ 1 - 2
src/bin/dhcp4/tests/simple_parser4_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -176,7 +176,6 @@ TEST_F(SimpleParser4Test, optionDataDefaults4) {
     // we should have appropriate default value set. See
     // SimpleParser4::OPTION4_DEFAULTS for a list of default values.
     checkStringValue(option, "space", "dhcp4");
-    checkStringValue(option, "encapsulate", "");
     checkBoolValue(option, "csv-format", true);
 }
 

+ 3 - 4
src/bin/dhcp6/dhcp6to4_ipc.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -34,7 +34,7 @@ Dhcp6to4Ipc& Dhcp6to4Ipc::instance() {
 }
 
 void Dhcp6to4Ipc::open() {
-    uint32_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
+    uint16_t port = CfgMgr::instance().getStagingCfg()->getDhcp4o6Port();
     if (port == 0) {
         Dhcp4o6IpcBase::close();
         return;
@@ -44,8 +44,7 @@ void Dhcp6to4Ipc::open() {
     }
 
     int old_fd = socket_fd_;
-    socket_fd_ = Dhcp4o6IpcBase::open(static_cast<uint16_t>(port),
-                                      ENDPOINT_TYPE_V6);
+    socket_fd_ = Dhcp4o6IpcBase::open(port, ENDPOINT_TYPE_V6);
     if ((old_fd == -1) && (socket_fd_ != old_fd)) {
         IfaceMgr::instance().addExternalSocket(socket_fd_,
                                                Dhcp6to4Ipc::handler);

+ 1 - 28
src/bin/dhcp6/json_config_parser.cc

@@ -214,18 +214,6 @@ public:
 
 private:
 
-    /// @brief Get an uint8_t value
-    ///
-    /// Instantiation of getIntType() to uint8_t
-    ///
-    /// @param scope specified parameter will be extracted from this scope
-    /// @param name name of the parameter
-    /// @return uint8_t value
-    /// @throw isc::dhcp::DhcpConfigError when it is not an uint8_t
-    uint8_t getUint8(ConstElementPtr scope, const std::string& name) {
-        return (getIntType<uint8_t>(scope, name));
-    }
-
     /// Pointer to the created pool object.
     isc::dhcp::Pool6Ptr pool_;
 
@@ -547,24 +535,9 @@ public:
         srv_config->setDeclinePeriod(probation_period);
 
         // Set the DHCPv4-over-DHCPv6 interserver port.
-        // @todo Change for uint16_t
-        uint32_t dhcp4o6_port = getUint32(global, "dhcp4o6-port");
+        uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
         srv_config->setDhcp4o6Port(dhcp4o6_port);
     }
-
-private:
-
-    /// @brief Returns a value converted to uint32_t
-    ///
-    /// Instantiation of getIntType() to uint32_t
-    ///
-    /// @param scope specified parameter will be extracted from this scope
-    /// @param name name of the parameter
-    /// @return an uint32_t value
-    uint32_t getUint32(isc::data::ConstElementPtr scope,
-                       const std::string& name) {
-        return (getIntType<uint32_t>(scope, name));
-    }
 };
 
 } // anonymous namespace

+ 3 - 0
src/bin/dhcp6/main.cc

@@ -127,6 +127,9 @@ main(int argc, char* argv[]) {
         usage();
     }
 
+    // This is the DHCPv6 server
+    CfgMgr::instance().setFamily(AF_INET6);
+
     if (check_mode) {
         try {
             // We need to initialize logging, in case any error messages are to be printed.

+ 1 - 2
src/bin/dhcp6/simple_parser6.cc

@@ -45,8 +45,7 @@ const SimpleDefaults SimpleParser6::OPTION6_DEF_DEFAULTS = {
 /// for those option-data declarations.
 const SimpleDefaults SimpleParser6::OPTION6_DEFAULTS = {
     { "space",        Element::string,  "dhcp6"},
-    { "csv-format",   Element::boolean, "true"},
-    { "encapsulate",  Element::string,  "" }
+    { "csv-format",   Element::boolean, "true"}
 };
 
 /// @brief This table defines default global values for DHCPv6

+ 0 - 7
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -4969,13 +4969,6 @@ TEST_F(Dhcp6ParserTest, invalidClientClassDictionary) {
         " } ] \n"
         "} \n";
 
-    ConstElementPtr json = parseJSON(config);
-
-    ConstElementPtr status;
-    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(status);
-    checkResult(status, 1);
-
     EXPECT_THROW(parseDHCP6(config), Dhcp6ParseError);
 }
 

+ 2 - 3
src/bin/dhcp6/tests/simple_parser6_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -159,7 +159,7 @@ TEST_F(SimpleParser6Test, subnetDefaults6) {
 
 // This test checks if the parameters in option-data are assigned default values
 // if not explicitly specified.
-TEST_F(SimpleParser6Test, optionDataDefaults4) {
+TEST_F(SimpleParser6Test, optionDataDefaults6) {
     ElementPtr global = parseJSON("{ \"renew-timer\": 1,"
                                   "  \"rebind-timer\": 2,"
                                   "  \"preferred-lifetime\": 3,"
@@ -179,7 +179,6 @@ TEST_F(SimpleParser6Test, optionDataDefaults4) {
     // we should have appropriate default value set. See
     // SimpleParser4::OPTION4_DEFAULTS for a list of default values.
     checkStringValue(option, "space", "dhcp6");
-    checkStringValue(option, "encapsulate", "");
     checkBoolValue(option, "csv-format", true);
 }
 

+ 236 - 0
src/lib/cc/data.cc

@@ -11,6 +11,7 @@
 #include <cstring>
 #include <cassert>
 #include <climits>
+#include <list>
 #include <map>
 #include <cstdio>
 #include <iostream>
@@ -1067,6 +1068,241 @@ merge(ElementPtr element, ConstElementPtr other) {
     }
 }
 
+ElementPtr
+copy(ConstElementPtr from, int level) {
+    if (isNull(from)) {
+        isc_throw(BadValue, "copy got a null pointer");
+    }
+    int from_type = from->getType();
+    if (from_type == Element::integer) {
+        return (ElementPtr(new IntElement(from->intValue())));
+    } else if (from_type == Element::real) {
+        return (ElementPtr(new DoubleElement(from->doubleValue())));
+    } else if (from_type == Element::boolean) {
+        return (ElementPtr(new BoolElement(from->boolValue())));
+    } else if (from_type == Element::null) {
+        return (ElementPtr(new NullElement()));
+    } else if (from_type == Element::string) {
+        return (ElementPtr(new StringElement(from->stringValue())));
+    } else if (from_type == Element::list) {
+        ElementPtr result = ElementPtr(new ListElement());
+        typedef std::vector<ElementPtr> ListType;
+        const ListType& value = from->listValue();
+        for (ListType::const_iterator it = value.cbegin();
+             it != value.cend(); ++it) {
+            if (level == 0) {
+                result->add(*it);
+            } else {
+                result->add(copy(*it, level - 1));
+            }
+        }
+        return (result);
+    } else if (from_type == Element::map) {
+        ElementPtr result = ElementPtr(new MapElement());
+        typedef std::map<std::string, ConstElementPtr> MapType;
+        const MapType& value = from->mapValue();
+        for (MapType::const_iterator it = value.cbegin();
+             it != value.cend(); ++it) {
+            if (level == 0) {
+                result->set(it->first, it->second);
+            } else {
+                result->set(it->first, copy(it->second, level - 1));
+            }
+        }
+        return (result);
+    } else {
+        isc_throw(BadValue, "copy got an element of type: " << from_type);
+    }
+}
+
+namespace {
+
+// Helper function which blocks infinite recursion
+bool
+isEquivalent0(ConstElementPtr a, ConstElementPtr b, unsigned level)
+{
+    // check looping forever on cycles
+    if (!level) {
+        isc_throw(BadValue, "isEquivalent got infinite recursion: "
+                  "arguments include cycles");
+    }
+    if (!a || !b) {
+        isc_throw(BadValue, "isEquivalent got a null pointer");
+    }
+    // check types
+    if (a->getType() != b->getType()) {
+        return (false);
+    }
+    if (a->getType() == Element::list) {
+        // check empty
+        if (a->empty()) {
+            return (b->empty());
+        }
+        // check size
+        if (a->size() != b->size()) {
+            return (false);
+        }
+
+        // copy b into a list
+        const size_t s = a->size();
+        typedef std::list<ConstElementPtr> ListType;
+        ListType l;
+        for (size_t i = 0; i < s; ++i) {
+            l.push_back(b->get(i));
+        }
+
+        // iterate on a
+        for (size_t i = 0; i < s; ++i) {
+            ConstElementPtr item = a->get(i);
+            // lookup this item in the list
+            bool found = false;
+            for (ListType::iterator it = l.begin();
+                 it != l.end(); ++it) {
+                // if found in the list remove it
+                if (isEquivalent0(item, *it, level - 1)) {
+                    found = true;
+                    l.erase(it);
+                    break;
+                }
+            }
+            // if not found argument differs
+            if (!found) {
+                return (false);
+            }
+        }
+
+        // sanity check: the list must be empty
+        if (!l.empty()) {
+            isc_throw(Unexpected, "isEquivalent internal error");
+        }
+        return (true);
+    } else if (a->getType() == Element::map) {
+        // iterate on the first map
+        typedef std::map<std::string, ConstElementPtr> MapType;
+        const MapType& ma = a->mapValue();
+        for (MapType::const_iterator it = ma.begin();
+             it != ma.end() ; ++it) {
+            // get the b value for the given keyword and recurse
+            ConstElementPtr item = b->get(it->first);
+            if (!item || !isEquivalent0(it->second, item, level - 1)) {
+                return (false);
+            }
+        }
+        // iterate on the second map
+        const MapType& mb = b->mapValue();
+        for (MapType::const_iterator it = mb.begin();
+             it != mb.end() ; ++it) {
+            // check if the keyword exists
+            if (!a->contains(it->first)) {
+                return (false);
+            }
+        }
+        return (true);
+    } else {
+        return (a->equals(*b));
+    }
+}
+
+}
+
+bool
+isEquivalent(ConstElementPtr a, ConstElementPtr b) {
+    return (isEquivalent0(a, b, 100));
+}
+
+void
+prettyPrint(ConstElementPtr element, std::ostream& out,
+            unsigned indent, unsigned step) {
+    if (!element) {
+        isc_throw(BadValue, "prettyPrint got a null pointer");
+    }
+    if (element->getType() == Element::list) {
+        // empty list case
+        if (element->empty()) {
+            out << "[ ]";
+            return;
+        }
+
+        // complex ? multiline : oneline
+        if (!element->get(0)) {
+            isc_throw(BadValue, "prettyPrint got a null pointer");
+        }
+        int first_type = element->get(0)->getType();
+        bool complex = false;
+        if ((first_type == Element::list) || (first_type == Element::map)) {
+            complex = true;
+        }
+        std::string separator = complex ? ",\n" : ", ";
+
+        // open the list
+        out << "[" << (complex ? "\n" : " ");
+        
+        // iterate on items
+        typedef std::vector<ElementPtr> ListType;
+        const ListType& l = element->listValue();
+        for (ListType::const_iterator it = l.begin();
+             it != l.end(); ++it) {
+            // add the separator if not the first item
+            if (it != l.begin()) {
+                out << separator;
+            }
+            // add indentation
+            if (complex) {
+                out << std::string(indent + step, ' ');
+            }
+            // recursive call
+            prettyPrint(*it, out, indent + step, step);
+        }
+
+        // close the list
+        if (complex) {
+            out << "\n" << std::string(indent, ' ');
+        } else {
+            out << " ";
+        }
+        out << "]";
+    } else if (element->getType() == Element::map) {
+        // empty map case
+        if (element->size() == 0) {
+            out << "{ }";
+            return;
+        }
+
+        // open the map
+        out << "{\n";
+
+        // iterate on keyword: value
+        typedef std::map<std::string, ConstElementPtr> MapType;
+        const MapType& m = element->mapValue();
+        for (MapType::const_iterator it = m.begin();
+             it != m.end(); ++it) {
+            // add the separator if not the first item
+            if (it != m.begin()) {
+                out << ",\n";
+            }
+            // add indentation
+            out << std::string(indent + step, ' ');
+            // add keyword:
+            out << "\"" << it->first << "\": ";
+            // recusive call
+            prettyPrint(it->second, out, indent + step, step);
+        }
+
+        // close the map
+        out << "\n" << std::string(indent, ' ') << "}";
+    } else {
+        // not a list or a map
+        element->toJSON(out);
+    }
+}
+
+std::string
+prettyPrint(ConstElementPtr element, unsigned indent, unsigned step) {
+    std::stringstream ss;
+    prettyPrint(element, ss, indent, step);
+    return (ss.str());
+}
+
 void Element::preprocess(std::istream& in, std::stringstream& out) {
 
     std::string line;

+ 44 - 0
src/lib/cc/data.h

@@ -732,6 +732,50 @@ ConstElementPtr removeIdentical(ConstElementPtr a, ConstElementPtr b);
 /// Raises a TypeError if either ElementPtr is not a MapElement
 void merge(ElementPtr element, ConstElementPtr other);
 
+/// \brief Copy the data up to a nesting level.
+///
+/// The copy is a deep copy so nothing is shared if it is not
+/// under the given nesting level.
+///
+/// \param from the pointer to the element to copy
+/// \param level nesting level (default is 100, 0 means shallow copy,
+/// negative means outbound and perhaps looping forever).
+/// \return a pointer to a fresh copy
+/// \throw raises a BadValue is a null pointer occurs.
+ElementPtr copy(ConstElementPtr from, int level = 100); 
+
+/// \brief Compares the data with other using unordered lists
+///
+/// This comparison function handles lists (JSON arrays) as
+/// unordered multi sets (multi means an item can occurs more
+/// than once as soon as it occurs the same number of times).
+bool isEquivalent(ConstElementPtr a, ConstElementPtr b);
+
+/// \brief Pretty prints the data into stream.
+///
+/// This operator converts the \c ConstElementPtr into a string and
+/// inserts it into the output stream \c out with an initial
+/// indentation \c indent and add at each level \c step spaces.
+///
+/// \param element A \c ConstElementPtr to pretty print
+/// \param out A \c std::ostream on which the print operation is performed
+/// \param indent An initial number of spaces to add each new line
+/// \param step A number of spaces to add to indentation at a new level
+void prettyPrint(ConstElementPtr element, std::ostream& out,
+                 unsigned indent = 0, unsigned step = 2);
+
+/// \brief Pretty prints the data into string
+///
+/// This operator converts the \c ConstElementPtr into a string with
+/// an initial indentation \c indent and add at each level \c step spaces.
+///
+/// \param element A \c ConstElementPtr to pretty print
+/// \param indent An initial number of spaces to add each new line
+/// \param step A number of spaces to add to indentation at a new level
+/// \return a string where element was pretty printed
+std::string prettyPrint(ConstElementPtr element,
+                        unsigned indent = 0, unsigned step = 2);
+
 ///
 /// \brief Insert Element::Position as a string into stream.
 ///

+ 1 - 1
src/lib/cc/simple_parser.cc

@@ -74,7 +74,7 @@ SimpleParser::getPosition(const std::string& name, const data::ConstElementPtr p
     }
     ConstElementPtr elem = parent->get(name);
     if (!elem) {
-        return (data::Element::ZERO_POSITION());
+        return (parent->getPosition());
     }
     return (elem->getPosition());
 }

+ 41 - 3
src/lib/cc/simple_parser.h

@@ -104,9 +104,9 @@ class SimpleParser {
 
     /// @brief Utility method that returns position of an element
     ///
-    /// It's mostly useful for logging. When any necessary parameter is
-    /// missing (either parent is null or it doesn't contain specified
-    /// name) ZERO_POSITION is returned.
+    /// It's mostly useful for logging. If the element is missing
+    /// the parent position is returned or ZERO_POSITION if parent
+    /// is null.
     ///
     /// @param name position of that element will be returned
     /// @param parent parent element (optional)
@@ -203,6 +203,44 @@ protected:
                       << "' (" << getPosition(name, scope) << ")");
         }
     }
+
+    /// @brief Returns a value converted to uint32_t
+    ///
+    /// Instantiation of getIntType() to uint32_t
+    ///
+    /// @param scope specified parameter will be extracted from this scope
+    /// @param name name of the parameter
+    /// @return an uint32_t value
+    /// @throw isc::dhcp::DhcpConfigError when it is not an uint32_t
+    uint32_t getUint32(isc::data::ConstElementPtr scope,
+                       const std::string& name) {
+        return (getIntType<uint32_t>(scope, name));
+    }
+
+    /// @brief Returns a value converted to uint16_t
+    ///
+    /// Instantiation of getIntType() to uint16_t
+    ///
+    /// @param scope specified parameter will be extracted from this scope
+    /// @param name name of the parameter
+    /// @return an uint16_t value
+    /// @throw isc::dhcp::DhcpConfigError when it is not an uint16_t
+    uint16_t getUint16(isc::data::ConstElementPtr scope,
+                       const std::string& name) {
+        return (getIntType<uint16_t>(scope, name));
+    }
+
+    /// @brief Get an uint8_t value
+    ///
+    /// Instantiation of getIntType() to uint8_t
+    ///
+    /// @param scope specified parameter will be extracted from this scope
+    /// @param name name of the parameter
+    /// @return uint8_t value
+    /// @throw isc::dhcp::DhcpConfigError when it is not an uint8_t
+    uint8_t getUint8(ConstElementPtr scope, const std::string& name) {
+        return (getIntType<uint8_t>(scope, name));
+    }
 };
 
 };

+ 192 - 4
src/lib/cc/tests/data_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2009-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,10 +6,12 @@
 
 #include <gtest/gtest.h>
 #include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
 #include <boost/assign/std/vector.hpp>
 #include <climits>
 
 #include <cc/data.h>
+#include <util/unittests/check_valgrind.h>
 
 using namespace isc::data;
 
@@ -100,7 +102,7 @@ TEST(Element, from_and_to_json) {
 
     BOOST_FOREACH(const std::string& s, sv) {
         // Test two types of fromJSON(): with string and istream.
-        for (int i = 0; i < 2; ++i) {
+        for (unsigned i = 0; i < 2; ++i) {
             // test << operator, which uses Element::str()
             if (i == 0) {
                 el = Element::fromJSON(s);
@@ -555,6 +557,35 @@ TEST(Element, escape) {
     EXPECT_NO_THROW(Element::fromJSON("\"\\\"\\\"\""));
     // A whitespace test
     EXPECT_NO_THROW(Element::fromJSON("\"  \n  \r \t \f  \n \n    \t\""));
+    // Escape for forward slash is optional
+    ASSERT_NO_THROW(Element::fromJSON("\"foo\\/bar\""));
+    EXPECT_EQ("foo/bar", Element::fromJSON("\"foo\\/bar\"")->stringValue());
+    // Control characters
+    StringElement bell("foo\abar");
+    EXPECT_EQ("\"foo\\u0007bar\"", bell.str());
+}
+
+// This test verifies that strings are copied.
+TEST(Element, stringCopy) {
+    // StringElement constructor copies its string argument.
+    std::string foo = "foo";
+    ElementPtr elem = ElementPtr(new StringElement(foo));
+    EXPECT_EQ(foo, elem->stringValue());
+    foo[1] = 'O';
+    EXPECT_EQ("fOo", foo);
+    EXPECT_NE(foo, elem->stringValue());
+
+    // Map keys are copied too.
+    ElementPtr map = ElementPtr(new MapElement());
+    std::string bar = "bar";
+    map->set(bar, ElementPtr(new IntElement(1)));
+    ConstElementPtr item = map->get("bar");
+    ASSERT_TRUE(item);
+    EXPECT_EQ(1, item->intValue());
+    bar[0] = 'B';
+    EXPECT_EQ("Bar", bar);
+    EXPECT_TRUE(map->get("bar"));
+    EXPECT_FALSE(map->get(bar));
 }
 
 // This test verifies that a backslash can be used in element content
@@ -655,6 +686,12 @@ TEST(Element, MapElement) {
     el->set(long_maptag, Element::create("bar"));
     EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
 
+    // Null pointer value
+    el.reset(new MapElement());
+    ConstElementPtr null_ptr;
+    el->set("value", null_ptr);
+    EXPECT_FALSE(el->get("value"));
+    EXPECT_EQ("{ \"value\": None }", el->str());
 }
 
 TEST(Element, to_and_from_wire) {
@@ -857,8 +894,9 @@ TEST(Element, constRemoveIdentical) {
     c = Element::fromJSON("{ \"a\": 1 }");
     EXPECT_EQ(*removeIdentical(a, b), *c);
 
-    EXPECT_THROW(removeIdentical(Element::create(1), Element::create(2)),
-                 TypeError);
+    // removeIdentical() is overloaded so force the first argument to const
+    ConstElementPtr bad = Element::create(1);
+    EXPECT_THROW(removeIdentical(bad, Element::create(2)), TypeError);
 }
 
 TEST(Element, merge) {
@@ -947,6 +985,152 @@ TEST(Element, merge) {
 
 }
 
+// This test checks copy.
+TEST(Element, copy) {
+    // Null pointer
+    ElementPtr elem;
+    EXPECT_THROW(copy(elem, 0), isc::BadValue);
+    EXPECT_THROW(copy(elem), isc::BadValue);
+    EXPECT_THROW(copy(elem, -1), isc::BadValue);
+
+    // Basic types
+    elem.reset(new IntElement(1));
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("1")));
+    EXPECT_EQ("1", elem->str());
+    ElementPtr copied;
+    ASSERT_NO_THROW(copied = copy(elem, 0));
+    EXPECT_TRUE(elem->equals(*copied));
+
+    elem.reset(new DoubleElement(1.0));
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("1.0")));
+    ASSERT_NO_THROW(copied = copy(elem, 0));
+    EXPECT_TRUE(elem->equals(*copied));
+
+    elem.reset(new BoolElement(true));
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("true")));
+    ASSERT_NO_THROW(copied = copy(elem, 0));
+    EXPECT_TRUE(elem->equals(*copied));
+
+    elem.reset(new NullElement());
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("null")));
+    ASSERT_NO_THROW(copied = copy(elem, 0));
+    EXPECT_TRUE(elem->equals(*copied));
+
+    elem.reset(new StringElement("foo"));
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("\"foo\"")));
+    ASSERT_NO_THROW(copied = copy(elem, 0));
+    EXPECT_TRUE(elem->equals(*copied));
+    ASSERT_NO_THROW(elem->setValue(std::string("bar")));
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("\"bar\"")));
+    EXPECT_FALSE(elem->equals(*copied));
+
+    elem.reset(new ListElement());
+    ElementPtr item = ElementPtr(new IntElement(1));
+    elem->add(item);
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("[ 1 ]")));
+    ASSERT_NO_THROW(copied = copy(elem, 0));
+    EXPECT_TRUE(elem->equals(*copied));
+    ElementPtr deep;
+    ASSERT_NO_THROW(deep = copy(elem));
+    EXPECT_TRUE(elem->equals(*deep));
+    ASSERT_NO_THROW(item = elem->getNonConst(0));
+    ASSERT_NO_THROW(item->setValue(2));
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("[ 2 ]")));
+    EXPECT_TRUE(elem->equals(*copied));
+    EXPECT_FALSE(elem->equals(*deep));
+
+    elem.reset(new MapElement());
+    item.reset(new StringElement("bar"));
+    elem->set("foo", item);
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("{ \"foo\": \"bar\" }")));
+    ASSERT_NO_THROW(copied = copy(elem, 0));
+    EXPECT_TRUE(elem->equals(*copied));
+    ASSERT_NO_THROW(deep = copy(elem));
+    EXPECT_TRUE(elem->equals(*deep));
+    ASSERT_NO_THROW(item->setValue(std::string("Bar")));
+    EXPECT_TRUE(elem->equals(*Element::fromJSON("{ \"foo\": \"Bar\" }")));
+    EXPECT_TRUE(elem->equals(*copied));
+    EXPECT_FALSE(elem->equals(*deep));
+
+    // Complex example
+    std::string input = "{ \n"
+        "\"integer\": 1,\n"
+        "\"double\": 1.0,\n"
+        "\"boolean\": true,\n"
+        "\"null\": null,\n"
+        "\"string\": \"foobar\",\n"
+        "\"list\": [ 1, 2 ],\n"
+        "\"map\": { \"foo\": \"bar\" } }\n";
+    ConstElementPtr complex;
+    ASSERT_NO_THROW(complex = Element::fromJSON(input));
+    ASSERT_NO_THROW(copied = copy(complex, 0));
+    EXPECT_TRUE(copied->equals(*complex));
+    ASSERT_NO_THROW(deep = copy(complex));
+    EXPECT_TRUE(deep->equals(*complex));
+    ElementPtr shallow;
+    ASSERT_NO_THROW(shallow = copy(complex, 1));
+    EXPECT_TRUE(shallow->equals(*complex));
+    // Try to modify copies
+    ASSERT_NO_THROW(item = deep->get("list")->getNonConst(1));
+    ASSERT_NO_THROW(item->setValue(3));
+    EXPECT_FALSE(deep->equals(*complex));
+    EXPECT_TRUE(shallow->equals(*complex));
+    ASSERT_NO_THROW(item = boost::const_pointer_cast<Element>(shallow->get("string")));
+    ASSERT_NO_THROW(item->setValue(std::string("FooBar")));
+    EXPECT_FALSE(shallow->equals(*complex));
+    EXPECT_TRUE(copied->equals(*complex));
+}
+
+// This test checks the isEquivalent function.
+TEST(Element, isEquivalent) {
+    // All are different but a is equivalent to b
+    string texta = "{ \"a\": 1, \"b\": [ ], \"c\": [ 1, 1, 2 ] }";
+    string textb = "{ \"b\": [ ], \"a\": 1, \"c\": [ 1, 2, 1 ] }";
+    string textc = "{ \"a\": 2, \"b\": [ ], \"c\": [ 1, 1, 2 ] }";
+    string textd = "{ \"a\": 1, \"c\": [ ], \"b\": [ 1, 1, 2 ] }";
+    string texte = "{ \"a\": 1, \"b\": [ ], \"c\": [ 1, 2, 2 ] }";
+
+    ElementPtr a = Element::fromJSON(texta);
+    ElementPtr b = Element::fromJSON(textb);
+    ElementPtr c = Element::fromJSON(textc);
+    ElementPtr d = Element::fromJSON(textd);
+    ElementPtr e = Element::fromJSON(texte);
+
+    EXPECT_TRUE(isEquivalent(a, b));
+    EXPECT_NE(a, b);
+    EXPECT_FALSE(isEquivalent(a, c));
+    EXPECT_FALSE(isEquivalent(a, d));
+    EXPECT_FALSE(isEquivalent(a, e));
+
+    // Verifies isEquivalent handles cycles
+    if (isc::util::unittests::runningOnValgrind()) {
+        ElementPtr l = Element::createList();
+        l->add(l);
+        EXPECT_THROW(isEquivalent(l, l), isc::BadValue);
+    }
+}
+
+
+// This test checks the pretty print function.
+TEST(Element, prettyPrint) {
+
+    // default step is 2, order is alphabetic, no \n at the end
+    string text = "{\n"
+        "  \"boolean\": true,\n"
+        "  \"empty-list\": [ ],\n"
+        "  \"empty-map\": { },\n"
+        "  \"integer\": 1,\n"
+        "  \"list\": [ 1, 2, 3 ],\n"
+        "  \"map\": {\n"
+        "    \"item\": null\n"
+        "  },\n"
+        "  \"string\": \"foobar\"\n"
+        "}";
+    ElementPtr json = Element::fromJSON(text);
+    string pprinted = prettyPrint(json);
+    EXPECT_EQ(text, pprinted);
+}
+
 // This test checks whether it is possible to ignore comments. It also checks
 // that the comments are ignored only when told to.
 TEST(Element, preprocessor) {
@@ -999,6 +1183,10 @@ TEST(Element, preprocessor) {
     EXPECT_THROW(Element::fromJSON(dbl_head_comment), JSONError);
     EXPECT_THROW(Element::fromJSON(dbl_mid_comment), JSONError);
     EXPECT_THROW(Element::fromJSON(dbl_tail_comment), JSONError);
+
+    // For coverage
+    std::istringstream iss(no_comment);
+    EXPECT_TRUE(exp->equals(*Element::fromJSON(iss, true)));
 }
 
 TEST(Element, getPosition) {

+ 3 - 8
src/lib/cc/tests/simple_parser_unittest.cc

@@ -58,14 +58,9 @@ public:
 
 class SimpleParserClassTest : public SimpleParser {
 public:
-    /// @brief Instantiation of getIntType for uint8_t
-    ///
-    /// @param scope specified parameter will be extracted from this scope
-    /// @param name name of the parameter for error report
-    /// @return an uint8_t value
-    uint8_t getUint8(ConstElementPtr scope, const std::string& name) {
-        return (getIntType<uint8_t>(scope, name));
-    }
+
+    /// Make getUint8 public
+    using SimpleParser::getUint8;
 
     /// @brief Instantiation of getAndConvert
     ///

+ 1 - 1
src/lib/dhcpsrv/Makefile.am

@@ -85,7 +85,7 @@ libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
 libkea_dhcpsrv_la_SOURCES += alloc_engine_log.cc alloc_engine_log.h
 libkea_dhcpsrv_la_SOURCES += base_host_data_source.h
 libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
-libkea_dhcpsrv_la_SOURCES += cfg_4o6.h
+libkea_dhcpsrv_la_SOURCES += cfg_4o6.cc cfg_4o6.h
 libkea_dhcpsrv_la_SOURCES += cfg_db_access.cc cfg_db_access.h
 libkea_dhcpsrv_la_SOURCES += cfg_duid.cc cfg_duid.h
 libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h

+ 76 - 1
src/lib/dhcpsrv/addr_utilities.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -32,6 +32,10 @@ const uint32_t bitMask4[] = { 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
 /// @brief mask used for first/last address calculation in a IPv6 prefix
 const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
 
+/// @brief mask used for IPv6 prefix calculation
+const uint8_t revMask6[]= { 0xff, 0x7f, 0x3f, 0x1f, 0xf, 0x7, 0x3, 0x1 };
+
+
 /// @brief calculates the first IPv6 address in a IPv6 prefix
 ///
 /// Note: This is a private function. Do not use it directly.
@@ -270,6 +274,77 @@ addrsInRange(const isc::asiolink::IOAddress& min,
     }
 }
 
+int
+prefixLengthFromRange(const isc::asiolink::IOAddress& min,
+                      const isc::asiolink::IOAddress& max) {
+    if (min.getFamily() != max.getFamily()) {
+        isc_throw(BadValue, "Both addresses have to be the same family");
+    }
+
+    if (max < min) {
+        isc_throw(BadValue, min.toText() << " must not be greater than "
+                  << max.toText());
+    }
+
+    if (min.isV4()) {
+        // Get addresses as integers
+        uint32_t max_numeric = max.toUint32();
+        uint32_t min_numeric = min.toUint32();
+
+        // Get the exclusive or which must be one of the bit masks
+        uint32_t xor_numeric = max_numeric ^ min_numeric;
+        for (uint8_t prefix_len = 0; prefix_len <= 32; ++prefix_len) {
+            if (xor_numeric == bitMask4[prefix_len]) {
+                // Got it: the wanted value is also the index
+                return (static_cast<int>(prefix_len));
+            }
+        }
+
+        // If it was not found the range is not from a prefix / prefix_len
+        return (-1);
+    } else {
+        // Get addresses as 16 bytes
+        uint8_t min_packed[V6ADDRESS_LEN];
+        memcpy(min_packed, &min.toBytes()[0], 16);
+        uint8_t max_packed[V6ADDRESS_LEN];
+        memcpy(max_packed, &max.toBytes()[0], 16);
+
+        // Scan the exclusive or of addresses to find a difference
+        int candidate = 128;
+        bool zeroes = true;
+        for (uint8_t i = 0; i < 16; ++i) {
+            uint8_t xor_byte = min_packed[i] ^ max_packed[i];
+            if (zeroes) {
+                // Skipping zero bits searching for one bits
+                if (xor_byte == 0) {
+                    continue;
+                }
+                // Found a one bit: note the fact
+                zeroes = false;
+                // Compare the exclusive or to masks
+                for (uint8_t j = 0; j < 8; ++j) {
+                    if (xor_byte == revMask6[j]) {
+                        // Got it the prefix length: note it
+                        candidate = static_cast<int>((i * 8) + j);
+                    }
+                }
+                if (candidate == 128) {
+                    // Not found? The range is not from a prefix / prefix_len
+                    return (-1);
+                }
+            } else {
+                // Checking that trailing bits are on bits
+                if (xor_byte == 0xff) {
+                    continue;
+                }
+                // Not all ones is bad
+                return (-1);
+            }
+        }
+        return (candidate);
+    }
+}
+
 uint64_t prefixesInRange(const uint8_t pool_len, const uint8_t delegated_len) {
     if (delegated_len < pool_len) {
         return (0);

+ 13 - 1
src/lib/dhcpsrv/addr_utilities.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -63,6 +63,18 @@ isc::asiolink::IOAddress getNetmask4(uint8_t len);
 uint64_t addrsInRange(const isc::asiolink::IOAddress& min,
                       const isc::asiolink::IOAddress& max);
 
+/// @brief Returns prefix length from the specified range (min - max).
+///
+/// This can be considered as log2(addrsInRange)
+///
+/// @throw BadValue if min and max do not define a prefix.
+///
+/// @param min the first address in range
+/// @param max the last address in range
+/// @return the prefix length or -1 if the range is not from a prefix
+int prefixLengthFromRange(const isc::asiolink::IOAddress& min,
+                          const isc::asiolink::IOAddress& max);
+
 /// @brief Returns number of available IPv6 prefixes in the specified prefix.
 ///
 /// Note that if the answer is bigger than uint64_t can hold, it will return

+ 9 - 3
src/lib/dhcpsrv/cfg_4o6.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,8 +7,9 @@
 #ifndef CFG_4OVER6_H
 #define CFG_4OVER6_H
 
-#include <string>
 #include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
+#include <string>
 
 namespace isc {
 namespace dhcp {
@@ -17,7 +18,7 @@ namespace dhcp {
 ///
 /// DHCP4o6 is completely optional. If it is not enabled, this structure
 /// does not contain any information.
-struct Cfg4o6 {
+struct Cfg4o6 : public isc::data::CfgToElement {
 
     /// the default constructor.
     ///
@@ -78,6 +79,11 @@ struct Cfg4o6 {
         enabled_ = true;
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// Specifies if 4o6 is enabled on this subnet.

+ 64 - 2
src/lib/dhcpsrv/cfg_db_access.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,7 +9,13 @@
 #include <dhcpsrv/host_data_source_factory.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
 #include <sstream>
+#include <vector>
+
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -59,7 +65,63 @@ CfgDbAccess::getAccessString(const std::string& access_string) const {
     return (s.str());
 }
 
-
+ElementPtr
+CfgDbAccess::toElementDbAccessString(const std::string& dbaccess) {
+    ElementPtr result = Element::createMap();
+    // Code from DatabaseConnection::parse
+    if (dbaccess.empty()) {
+        return (result);
+    }
+    std::vector<std::string> tokens;
+    boost::split(tokens, dbaccess, boost::is_any_of(std::string("\t ")));
+    BOOST_FOREACH(std::string token, tokens) {
+        size_t pos = token.find("=");
+        if (pos != std::string::npos) {
+            std::string keyword = token.substr(0, pos);
+            std::string value = token.substr(pos + 1);
+            if ((keyword == "lfc-interval") ||
+                (keyword == "connect-timeout") ||
+                (keyword == "port")) {
+                // integer parameters
+                int64_t int_value;
+                try {
+                    int_value = boost::lexical_cast<int64_t>(value);
+                    result->set(keyword, Element::create(int_value));
+                } catch (...) {
+                    isc_throw(ToElementError, "invalid DB access "
+                              << "integer parameter: "
+                              << keyword << "=" << value);
+                }
+            } else if ((keyword == "persist") ||
+                       (keyword == "readonly")) {
+                if (value == "true") {
+                    result->set(keyword, Element::create(true));
+                } else if (value == "false") {
+                    result->set(keyword, Element::create(false));
+                } else {
+                    isc_throw(ToElementError, "invalid DB access "
+                              << "boolean parameter: "
+                              << keyword << "=" << value);
+                }
+            } else if ((keyword == "type") ||
+                       (keyword == "user") ||
+                       (keyword == "password") ||
+                       (keyword == "host") ||
+                       (keyword == "name") ||
+                       (keyword == "contact_points") ||
+                       (keyword == "keyspace")) {
+                result->set(keyword, Element::create(value));
+            } else {
+                isc_throw(ToElementError, "unknown DB access parameter: "
+                          << keyword << "=" << value);
+            }
+        } else {
+            isc_throw(ToElementError, "Cannot unparse " << token
+                      << ", expected format is name=value");
+        }
+    }
+    return (result);
+}
 
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 39 - 2
src/lib/dhcpsrv/cfg_db_access.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
 #ifndef CFG_DBACCESS_H
 #define CFG_DBACCESS_H
 
+#include <cc/cfg_to_element.h>
 #include <boost/shared_ptr.hpp>
 #include <string>
 
@@ -63,7 +64,14 @@ public:
     /// according to the configuration specified.
     void createManagers() const;
 
-private:
+    /// @brief Unparse an access string
+    ///
+    /// @param dbaccess the database access string
+    /// @return a pointer to configuration
+    static
+    isc::data::ElementPtr toElementDbAccessString(const std::string& dbaccess);
+
+protected:
 
     /// @brief Returns lease or host database access string.
     ///
@@ -88,6 +96,35 @@ typedef boost::shared_ptr<CfgDbAccess> CfgDbAccessPtr;
 /// @brief A pointer to the const @c CfgDbAccess.
 typedef boost::shared_ptr<const CfgDbAccess> ConstCfgDbAccessPtr;
 
+/// @brief utility class for unparsing
+struct CfgLeaseDbAccess : public CfgDbAccess, public isc::data::CfgToElement {
+    /// @brief Constructor
+    CfgLeaseDbAccess(const CfgDbAccess& super) : CfgDbAccess(super) { }
+
+    /// @brief Unparse
+    ///
+    /// @ref CfgToElement::toElement
+    ///
+    /// @result a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const {
+        return (CfgDbAccess::toElementDbAccessString(lease_db_access_));
+    }
+};
+
+struct CfgHostDbAccess : public CfgDbAccess, public isc::data::CfgToElement {
+    /// @brief Constructor
+    CfgHostDbAccess(const CfgDbAccess& super) : CfgDbAccess(super) { }
+
+    /// @brief Unparse
+    ///
+    /// @ref CfgToElement::toElement
+    ///
+    /// @result a pointer to a configuration
+    virtual isc::data::ElementPtr toElement() const {
+        return (CfgDbAccess::toElementDbAccessString(host_db_access_));
+    }
+};
+
 }
 }
 

+ 37 - 1
src/lib/dhcpsrv/cfg_duid.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,8 +9,11 @@
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 #include <iostream>
+#include <string>
+#include <string.h>
 
 using namespace isc;
+using namespace isc::data;
 using namespace isc::util::encode;
 using namespace isc::util::str;
 
@@ -71,6 +74,39 @@ CfgDUID::create(const std::string& duid_file_path) const {
     return (factory.get());
 }
 
+ElementPtr
+CfgDUID::toElement() const {
+    ElementPtr result = Element::createMap();
+    // The type item is required
+    std::string duid_type = "LLT";
+    switch (type_) {
+    case DUID::DUID_LLT:
+        break;
+    case DUID::DUID_EN:
+        duid_type = "EN";
+        break;
+    case DUID::DUID_LL:
+        duid_type = "LL";
+        break;
+    default:
+        isc_throw(ToElementError, "invalid DUID type: " << getType());
+        break;
+    }
+    result->set("type", Element::create(duid_type));
+    // Set the identifier
+    result->set("identifier",
+                Element::create(util::encode::encodeHex(identifier_)));
+    // Set the hardware type
+    result->set("htype", Element::create(htype_));
+    // Set the time
+    result->set("time", Element::create(static_cast<long long>(time_)));
+    // Set the enterprise id
+    result->set("enterprise-id",
+                Element::create(static_cast<long long>(enterprise_id_)));
+    // Set the persistence flag
+    result->set("persist", Element::create(persist_));
+    return (result);
+}
 
 }
 }

+ 8 - 2
src/lib/dhcpsrv/cfg_duid.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
 #define CFG_DUID_H
 
 #include <dhcp/duid.h>
+#include <cc/cfg_to_element.h>
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 #include <vector>
@@ -25,7 +26,7 @@ namespace dhcp {
 /// generate. It also allows for overriding entire default DUID or parts of
 /// it via configuration file. This class holds the DUID configuration
 /// specified in the server configuration file.
-class CfgDUID {
+class CfgDUID : public isc::data::CfgToElement {
 public:
 
     /// @brief Constructor.
@@ -114,6 +115,11 @@ public:
     /// @return Pointer to an instance of new DUID.
     DuidPtr create(const std::string& duid_file_path) const;
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief DUID type.

+ 33 - 1
src/lib/dhcpsrv/cfg_expiration.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,8 @@
 #include <exceptions/exceptions.h>
 #include <limits>
 
+using namespace isc::data;
+
 namespace isc {
 namespace dhcp {
 
@@ -103,5 +105,35 @@ CfgExpiration::rangeCheck(const int64_t value, const uint64_t max_value,
     }
 }
 
+ElementPtr
+CfgExpiration::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set reclaim-timer-wait-time
+    result->set("reclaim-timer-wait-time",
+                Element::create(static_cast<long long>
+                                (reclaim_timer_wait_time_)));
+    // Set flush-reclaimed-timer-wait-time
+    result->set("flush-reclaimed-timer-wait-time",
+                Element::create(static_cast<long long>
+                                (flush_reclaimed_timer_wait_time_)));
+    // Set hold-reclaimed-time
+    result->set("hold-reclaimed-time",
+                Element::create(static_cast<long long>
+                                (hold_reclaimed_time_)));
+    // Set max-reclaim-leases
+    result->set("max-reclaim-leases",
+                Element::create(static_cast<long long>
+                                (max_reclaim_leases_)));
+    // Set max-reclaim-time
+    result->set("max-reclaim-time",
+                Element::create(static_cast<long long>
+                                (max_reclaim_time_)));
+    // Set unwarned-reclaim-cycles
+    result->set("unwarned-reclaim-cycles",
+                Element::create(static_cast<long long>
+                                (unwarned_reclaim_cycles_)));
+    return (result);
+}
+
 }
 }

+ 8 - 2
src/lib/dhcpsrv/cfg_expiration.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
 #define CFG_EXPIRATION_H
 
 #include <asiolink/interval_timer.h>
+#include <cc/cfg_to_element.h>
 #include <dhcpsrv/timer_mgr.h>
 #include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
@@ -57,7 +58,7 @@ namespace dhcp {
 /// The @c CfgExpiration class provides a collection of accessors and
 /// modifiers to manage the data. Each accessor checks if the given value
 /// is in range allowed for this value.
-class CfgExpiration {
+class CfgExpiration : public isc::data::CfgToElement {
 public:
 
     /// @name Default values.
@@ -223,6 +224,11 @@ public:
                      void (Instance::*delete_fun)(const uint32_t),
                      Instance* instance_ptr) const;
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Checks if the value being set by one of the modifiers is

+ 15 - 1
src/lib/dhcpsrv/cfg_host_operations.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,9 @@
 #include <exceptions/exceptions.h>
 #include <dhcpsrv/cfg_host_operations.h>
 #include <algorithm>
+#include <string>
+
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -52,5 +55,16 @@ CfgHostOperations::clearIdentifierTypes() {
     identifier_types_.clear();
 }
 
+ElementPtr
+CfgHostOperations::toElement() const {
+    ElementPtr result = Element::createList();
+    for (IdentifierTypes::const_iterator id = identifier_types_.begin();
+         id != identifier_types_.end(); ++id) {
+        const std::string& name = Host::getIdentifierName(*id);
+        result->add(Element::create(name));
+    }
+    return (result);
+}
+
 }
 }

+ 8 - 2
src/lib/dhcpsrv/cfg_host_operations.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
 #ifndef CFG_HOST_OPERATIONS_H
 #define CFG_HOST_OPERATIONS_H
 
+#include <cc/cfg_to_element.h>
 #include <dhcpsrv/host.h>
 #include <boost/shared_ptr.hpp>
 #include <list>
@@ -39,7 +40,7 @@ ConstCfgHostOperationsPtr;
 /// An administrator selects which identifiers the server should
 /// use and in which order to search for host reservations to
 /// optimize performance of the server.
-class CfgHostOperations {
+class CfgHostOperations : public isc::data::CfgToElement {
 public:
 
     /// @brief Type of the container holding ordered list of identifiers.
@@ -77,6 +78,11 @@ public:
     /// @brief Removes existing identifier types.
     void clearIdentifierTypes();
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Holds ordered collection of identifiers to be used by the

+ 31 - 1
src/lib/dhcpsrv/cfg_iface.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,6 +14,7 @@
 #include <algorithm>
 
 using namespace isc::asiolink;
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -398,5 +399,34 @@ CfgIface::useSocketType(const uint16_t family,
     useSocketType(family, textToSocketType(socket_type_name));
 }
 
+ElementPtr
+CfgIface::toElement() const {
+    ElementPtr result = Element::createMap();
+
+    // Set interfaces
+    ElementPtr ifaces = Element::createList();
+    if (wildcard_used_) {
+        ifaces->add(Element::create(std::string(ALL_IFACES_KEYWORD)));
+    }
+    for (IfaceSet::const_iterator iface = iface_set_.cbegin();
+         iface != iface_set_.cend(); ++iface) {
+        ifaces->add(Element::create(*iface));
+    }
+    for (ExplicitAddressMap::const_iterator address = address_map_.cbegin();
+         address != address_map_.cend(); ++address) {
+        std::string spec = address->first + "/" + address->second.toText();
+        ifaces->add(Element::create(spec));
+    }
+    result->set("interfaces", ifaces);
+
+    // Set dhcp-socket-type (no default because it is DHCPv4 specific)
+    // @todo emit raw if and only if DHCPv4
+    if (socket_type_ != SOCKET_RAW) {
+        result->set("dhcp-socket-type", Element::create(std::string("udp")));
+    }
+
+    return (result);
+}
+
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 8 - 2
src/lib/dhcpsrv/cfg_iface.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/iface_mgr.h>
+#include <cc/cfg_to_element.h>
 #include <boost/shared_ptr.hpp>
 #include <map>
 #include <set>
@@ -125,7 +126,7 @@ public:
 /// to which it is bound. It is allowed to select multiple addresses on the
 /// particular interface explicitly, e.g. "eth0/192.168.8.1",
 /// "eth0/192.168.8.2".
-class CfgIface {
+class CfgIface : public isc::data::CfgToElement {
 public:
 
     /// @brief Socket type used by the DHCPv4 server.
@@ -251,6 +252,11 @@ public:
         return (!equals(other));
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Checks if multiple IPv4 addresses has been activated on any

+ 47 - 21
src/lib/dhcpsrv/cfg_mac_source.cc

@@ -9,6 +9,32 @@
 #include <exceptions/exceptions.h>
 #include <dhcp/hwaddr.h>
 
+using namespace isc::data;
+
+namespace {
+
+using namespace isc::dhcp;
+
+struct {
+    const char * name;
+    uint32_t type;
+} sources[] = {
+    { "any", HWAddr::HWADDR_SOURCE_ANY },
+    { "raw", HWAddr::HWADDR_SOURCE_RAW },
+    { "duid", HWAddr::HWADDR_SOURCE_DUID },
+    { "ipv6-link-local", HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL },
+    { "client-link-addr-option", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
+    { "rfc6939", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
+    { "remote-id", HWAddr::HWADDR_SOURCE_REMOTE_ID },
+    { "rfc4649", HWAddr::HWADDR_SOURCE_REMOTE_ID },
+    { "subscriber-id", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
+    { "rfc4580", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
+    { "docsis-cmts", HWAddr::HWADDR_SOURCE_DOCSIS_CMTS },
+    { "docsis-modem", HWAddr::HWADDR_SOURCE_DOCSIS_MODEM }
+};
+
+};
+
 namespace isc {
 namespace dhcp {
 
@@ -19,26 +45,7 @@ CfgMACSource::CfgMACSource() {
 }
 
 uint32_t CfgMACSource::MACSourceFromText(const std::string& name) {
-
-    struct {
-        const char * name;
-        uint32_t type;
-    } sources[] = {
-        { "any", HWAddr::HWADDR_SOURCE_ANY },
-        { "raw", HWAddr::HWADDR_SOURCE_RAW },
-        { "duid", HWAddr::HWADDR_SOURCE_DUID },
-        { "ipv6-link-local", HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL },
-        { "client-link-addr-option", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
-        { "rfc6939", HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION },
-        { "remote-id", HWAddr::HWADDR_SOURCE_REMOTE_ID },
-        { "rfc4649", HWAddr::HWADDR_SOURCE_REMOTE_ID },
-        { "subscriber-id", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
-        { "rfc4580", HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID },
-        { "docsis-cmts", HWAddr::HWADDR_SOURCE_DOCSIS_CMTS },
-        { "docsis-modem", HWAddr::HWADDR_SOURCE_DOCSIS_MODEM }
-    };
-
-    for (int i=0; i < sizeof(sources)/sizeof(sources[0]); ++i) {
+    for (unsigned i = 0; i < sizeof(sources)/sizeof(sources[0]); ++i) {
         if (name.compare(sources[i].name) == 0) {
             return (sources[i].type);
         }
@@ -51,12 +58,31 @@ void CfgMACSource::add(uint32_t source) {
     for (CfgMACSources::const_iterator it = mac_sources_.begin();
          it != mac_sources_.end(); ++it) {
         if (*it == source) {
-            isc_throw(InvalidParameter, "mac-source paramter " << source
+            isc_throw(InvalidParameter, "mac-source parameter " << source
                       << "' specified twice.");
         }
     }
     mac_sources_.push_back(source);
 }
 
+ElementPtr CfgMACSource::toElement() const {
+    ElementPtr result = Element::createList();
+    for (CfgMACSources::const_iterator source = mac_sources_.cbegin();
+         source != mac_sources_.cend(); ++source) {
+        std::string name;
+        for (unsigned i = 0; i < sizeof(sources)/sizeof(sources[0]); ++i) {
+            if (sources[i].type == *source) {
+                name = sources[i].name;
+                break;
+            }
+        }
+        if (name.empty()) {
+            isc_throw(ToElementError, "invalid MAC source: " << *source);
+        }
+        result->add(Element::create(name));
+    }
+    return (result);
+}
+
 };
 };

+ 7 - 1
src/lib/dhcpsrv/cfg_mac_source.h

@@ -7,6 +7,7 @@
 #ifndef CFG_MAC_SOURCE_H
 #define CFG_MAC_SOURCE_H
 
+#include <cc/cfg_to_element.h>
 #include <stdint.h>
 #include <vector>
 #include <string>
@@ -21,7 +22,7 @@ typedef std::vector<uint32_t> CfgMACSources;
 ///
 /// It's a simple wrapper around a vector of uint32_t, with each entry
 /// holding one MAC source.
-class CfgMACSource {
+class CfgMACSource : public isc::data::CfgToElement {
 
  public:
     /// @brief Default constructor.
@@ -68,6 +69,11 @@ class CfgMACSource {
         mac_sources_.clear();
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
  protected:
     /// @brief Actual MAC sources storage
     CfgMACSources mac_sources_;

+ 90 - 1
src/lib/dhcpsrv/cfg_option.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,7 +8,12 @@
 #include <dhcp/option_space.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcp/dhcp6.h>
+#include <util/encode/hex.h>
 #include <string>
+#include <sstream>
+#include <vector>
+
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -183,5 +188,89 @@ CfgOption::getAll(const uint32_t vendor_id) const {
     return (vendor_options_.getItems(vendor_id));
 }
 
+ElementPtr
+CfgOption::toElement() const {
+    // option-data value is a list of maps
+    ElementPtr result = Element::createList();
+    // Iterate first on options using space names
+    const std::list<std::string>& names = options_.getOptionSpaceNames();
+    for (std::list<std::string>::const_iterator name = names.begin();
+         name != names.end(); ++name) {
+        OptionContainerPtr opts = getAll(*name);
+        for (OptionContainer::const_iterator opt = opts->begin();
+             opt != opts->end(); ++opt) {
+            // Get and fill the map for this option
+            ElementPtr map = Element::createMap();
+            // First set space from parent iterator
+            map->set("space", Element::create(*name));
+            // Set the code
+            uint16_t code = opt->option_->getType();
+            map->set("code", Element::create(code));
+            // Set the name (always for standard options else when asked for)
+            OptionDefinitionPtr def = LibDHCP::getOptionDef(*name, code);
+            if (!def) {
+                def = LibDHCP::getRuntimeOptionDef(*name, code);
+            }
+            if (def) {
+                map->set("name", Element::create(def->getName()));
+            }
+            // Set the data item
+            if (!opt->formatted_value_.empty()) {
+                map->set("csv-format", Element::create(true));
+                map->set("data", Element::create(opt->formatted_value_));
+            } else {
+                map->set("csv-format", Element::create(false));
+                std::vector<uint8_t> bin = opt->option_->toBinary();
+                std::string repr = util::encode::encodeHex(bin);
+                map->set("data", Element::create(repr));
+            }
+            // Push on the list
+            result->add(map);
+        }
+    }
+    // Iterate first on vendor_options using vendor ids
+    const std::list<uint32_t>& ids = vendor_options_.getOptionSpaceNames();
+    for (std::list<uint32_t>::const_iterator id = ids.begin();
+         id != ids.end(); ++id) {
+        OptionContainerPtr opts = getAll(*id);
+        for (OptionContainer::const_iterator opt = opts->begin();
+             opt != opts->end(); ++opt) {
+            // Get and fill the map for this option
+            ElementPtr map = Element::createMap();
+            // First set space from parent iterator
+            std::ostringstream oss;
+            oss << "vendor-" << *id;
+            map->set("space", Element::create(oss.str()));
+            // Set the code
+            uint16_t code = opt->option_->getType();
+            map->set("code", Element::create(code));
+            // Set the name
+            Option::Universe universe = opt->option_->getUniverse();
+            OptionDefinitionPtr def =
+                LibDHCP::getVendorOptionDef(universe, *id, code);
+            if (!def) {
+                // vendor-XXX space is in oss
+                def = LibDHCP::getRuntimeOptionDef(oss.str(), code);
+            }
+            if (def) {
+                map->set("name", Element::create(def->getName()));
+            }
+            // Set the data item
+            if (!opt->formatted_value_.empty()) {
+                map->set("csv-format", Element::create(true));
+                map->set("data", Element::create(opt->formatted_value_));
+            } else {
+                map->set("csv-format", Element::create(false));
+                std::vector<uint8_t> bin = opt->option_->toBinary();
+                std::string repr = util::encode::encodeHex(bin);
+                map->set("data", Element::create(repr));
+            }
+            // Push on the list
+            result->add(map);
+        }
+    }
+    return (result);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 8 - 2
src/lib/dhcpsrv/cfg_option.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
 
 #include <dhcp/option.h>
 #include <dhcp/option_space_container.h>
+#include <cc/cfg_to_element.h>
 #include <dhcpsrv/key_from_key.h>
 #include <boost/multi_index_container.hpp>
 #include <boost/multi_index/hashed_index.hpp>
@@ -210,7 +211,7 @@ typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
 /// options is useful when the client requests stateless configuration from
 /// the DHCP server and no subnet is selected for this client. This client
 /// will only receive global options.
-class CfgOption {
+class CfgOption : public isc::data::CfgToElement {
 public:
 
     /// @brief default constructor
@@ -394,6 +395,11 @@ public:
     /// @return List comprising option space names for vendor options.
     std::list<std::string> getVendorIdsSpaceNames() const;
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Appends encapsulated options to the options in an option space.

+ 58 - 1
src/lib/dhcpsrv/cfg_option_def.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,9 +6,13 @@
 
 #include <config.h>
 #include <dhcp/libdhcp++.h>
+#include <dhcp/option_data_types.h>
 #include <dhcp/option_definition.h>
 #include <dhcp/option_space.h>
 #include <dhcpsrv/cfg_option_def.h>
+#include <sstream>
+
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -148,5 +152,58 @@ CfgOptionDef::get(const std::string& option_space,
     return (OptionDefinitionPtr());
 }
 
+ElementPtr
+CfgOptionDef::toElement() const {
+    // option-defs value is a list of maps
+    ElementPtr result = Element::createList();
+    // Iterate through the container by names and definitions
+    const std::list<std::string>& names =
+        option_definitions_.getOptionSpaceNames();
+    for (std::list<std::string>::const_iterator name = names.begin();
+         name != names.end(); ++name) {
+        OptionDefContainerPtr defs = getAll(*name);
+        for (OptionDefContainer::const_iterator def = defs->begin();
+             def != defs->end(); ++def) {
+            // Get and fill the map for this definition
+            ElementPtr map = Element::createMap();
+            // First set space from parent iterator
+            map->set("space", Element::create(*name));
+            // Set required items: name, code and type
+            map->set("name", Element::create((*def)->getName()));
+            map->set("code", Element::create((*def)->getCode()));
+            std::string data_type =
+                OptionDataTypeUtil::getDataTypeName((*def)->getType());
+            map->set("type", Element::create(data_type));
+            // Set the array type
+            bool array_type = (*def)->getArrayType();
+            map->set("array", Element::create(array_type));
+            // Set the encapsulate space
+            std::string encapsulates = (*def)->getEncapsulatedSpace();
+            map->set("encapsulate", Element::create(encapsulates));
+            // Set the record field types
+            OptionDefinition::RecordFieldsCollection fields =
+                (*def)->getRecordFields();
+            if (!fields.empty()) {
+                std::ostringstream oss;
+                for (OptionDefinition::RecordFieldsCollection::const_iterator
+                         field = fields.begin();
+                     field != fields.end(); ++field) {
+                    if (field != fields.begin()) {
+                        oss << ", ";
+                    }
+                    oss << OptionDataTypeUtil::getDataTypeName(*field);
+                }
+                map->set("record-types", Element::create(oss.str()));
+            } else {
+                map->set("record-types", Element::create(std::string()));
+            }
+            // Push on the list
+            result->add(map);
+        }
+    }
+    return (result);
+}
+
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 8 - 2
src/lib/dhcpsrv/cfg_option_def.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
 
 #include <dhcp/option_definition.h>
 #include <dhcp/option_space_container.h>
+#include <cc/cfg_to_element.h>
 #include <string>
 
 namespace isc {
@@ -26,7 +27,7 @@ namespace dhcp {
 /// following names: "dhcp4" and "dhcp6" are reserved, though. They are
 /// names of option spaces used for standard top-level DHCPv4 and DHCPv6
 /// options respectively.
-class CfgOptionDef {
+class CfgOptionDef : public isc::data::CfgToElement {
 public:
 
     /// @brief Copies this configuration to a new configuration.
@@ -117,6 +118,11 @@ public:
         return (option_definitions_);
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief A collection of option definitions.

+ 15 - 1
src/lib/dhcpsrv/cfg_rsoo.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,6 +6,9 @@
 
 #include <dhcp/dhcp6.h>
 #include <dhcpsrv/cfg_rsoo.h>
+#include <boost/lexical_cast.hpp>
+
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -33,6 +36,17 @@ CfgRSOO::enable(const uint16_t code) {
     }
 }
 
+ElementPtr
+CfgRSOO::toElement() const {
+    ElementPtr result = Element::createList();
+    // We can use LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, *opt) too...
+    for (std::set<uint16_t>::const_iterator opt = rsoo_options_.cbegin();
+         opt != rsoo_options_.cend(); ++opt) {
+        const std::string& code = boost::lexical_cast<std::string>(*opt);
+        result->add(Element::create(code));
+    }
+    return (result);
+}
 
 }
 }

+ 8 - 2
src/lib/dhcpsrv/cfg_rsoo.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
 #ifndef CFG_RSOO_H
 #define CFG_RSOO_H
 
+#include <cc/cfg_to_element.h>
 #include <boost/shared_ptr.hpp>
 #include <stdint.h>
 #include <set>
@@ -21,7 +22,7 @@ namespace dhcp {
 /// 65 is officially RSSO-enabled. The list may be extended in the future
 /// and this class allows for specifying any future RSOO-enabled options.
 /// The administrator may also use existing options as RSOO-enabled.
-class CfgRSOO {
+class CfgRSOO : public isc::data::CfgToElement {
 public:
 
     /// @brief Constructor.
@@ -46,6 +47,11 @@ public:
     /// @param code option to be enabled in RSOO
     void enable(const uint16_t code);
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Contains a set of options that are allowed in RSOO option

+ 113 - 1
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,8 +13,10 @@
 #include <dhcpsrv/addr_utilities.h>
 #include <asiolink/io_address.h>
 #include <stats/stats_mgr.h>
+#include <sstream>
 
 using namespace isc::asiolink;
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -273,5 +275,115 @@ CfgSubnets4::updateStatistics() {
     }
 }
 
+ElementPtr
+CfgSubnets4::toElement() const {
+    ElementPtr result = Element::createList();
+    // Iterate subnets
+    for (Subnet4Collection::const_iterator subnet = subnets_.cbegin();
+         subnet != subnets_.cend(); ++subnet) {
+        // Prepare the map
+        ElementPtr map = Element::createMap();
+        // Set subnet id
+        SubnetID id = (*subnet)->getID();
+        map->set("id", Element::create(static_cast<long long>(id)));
+        // Set relay info
+        const Subnet::RelayInfo& relay_info = (*subnet)->getRelayInfo();
+        ElementPtr relay = Element::createMap();
+        relay->set("ip-address", Element::create(relay_info.addr_.toText()));
+        map->set("relay", relay);
+        // Set subnet
+        map->set("subnet", Element::create((*subnet)->toText()));
+        // Set interface
+        const std::string& iface = (*subnet)->getIface();
+        map->set("interface", Element::create(iface));
+        // Set renew-timer
+        map->set("renew-timer",
+                 Element::create(static_cast<long long>
+                                 ((*subnet)->getT1().get())));
+        // Set rebind-timer
+        map->set("rebind-timer",
+                 Element::create(static_cast<long long>
+                                 ((*subnet)->getT2().get())));
+        // Set valid-lifetime
+        map->set("valid-lifetime",
+                 Element::create(static_cast<long long>
+                                 ((*subnet)->getValid().get())));
+        // Set pools
+        const PoolCollection& pools = (*subnet)->getPools(Lease::TYPE_V4);
+        ElementPtr pool_list = Element::createList();
+        for (PoolCollection::const_iterator pool = pools.cbegin();
+             pool != pools.cend(); ++pool) {
+            // Prepare the map for a pool
+            ElementPtr pool_map = Element::createMap();
+            // Set pool
+            const IOAddress& first = (*pool)->getFirstAddress();
+            const IOAddress& last = (*pool)->getLastAddress();
+            std::string range = first.toText() + "-" + last.toText();
+            // Try to output a prefix (vs a range)
+            int prefix_len = prefixLengthFromRange(first, last);
+            if (prefix_len >= 0) {
+                std::ostringstream oss;
+                oss << first.toText() << "/" << prefix_len;
+                range = oss.str();
+            }
+            pool_map->set("pool", Element::create(range));
+            // Set user-context
+            ConstElementPtr context = (*pool)->getContext();
+            if (!isNull(context)) {
+                pool_map->set("user-context", context);
+            }
+            // Set pool options
+            ConstCfgOptionPtr opts = (*pool)->getCfgOption();
+            pool_map->set("option-data", opts->toElement());
+            // Push on the pool list
+            pool_list->add(pool_map);
+        }
+        map->set("pools", pool_list);
+        // Set host reservation-mode
+        Subnet::HRMode hrmode = (*subnet)->getHostReservationMode();
+        std::string mode;
+        switch (hrmode) {
+        case Subnet::HR_DISABLED:
+            mode = "disabled";
+            break;
+        case Subnet::HR_OUT_OF_POOL:
+            mode = "out-of-pool";
+            break;
+        case Subnet::HR_ALL:
+            mode = "all";
+            break;
+        default:
+            isc_throw(ToElementError,
+                      "invalid host reservation mode: " << hrmode);
+        }
+        map->set("reservation-mode", Element::create(mode));
+        // Set match-client-id
+        map->set("match-client-id",
+                 Element::create((*subnet)->getMatchClientId()));
+        // Set next-server
+        map->set("next-server",
+                 Element::create((*subnet)->getSiaddr().toText()));
+        // Set DHCP4o6
+        const Cfg4o6& d4o6 = (*subnet)->get4o6();
+        merge(map, d4o6.toElement());
+        // Set client-class
+        const ClientClasses& cclasses = (*subnet)->getClientClasses();
+        if (cclasses.size() > 1) {
+            isc_throw(ToElementError, "client-classes has too many items: "
+                      << cclasses.size());
+        } else if (!cclasses.empty()) {
+            map->set("client-class", Element::create(*cclasses.cbegin()));
+        }
+        // Set options
+        ConstCfgOptionPtr opts = (*subnet)->getCfgOption();
+        map->set("option-data", opts->toElement());
+        // Not supported: interface-id
+        // Not supported: rapid-commit
+        // Push on the list
+        result->add(map);
+    }
+    return (result);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 8 - 2
src/lib/dhcpsrv/cfg_subnets4.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
 #define CFG_SUBNETS4_H
 
 #include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <boost/shared_ptr.hpp>
@@ -25,7 +26,7 @@ namespace dhcp {
 ///
 /// See @c CfgSubnets4::selectSubnet documentation for more details on how the
 /// subnet is selected for the client.
-class CfgSubnets4 {
+class CfgSubnets4 : public isc::data::CfgToElement {
 public:
 
     /// @brief Adds new subnet to the configuration.
@@ -183,6 +184,11 @@ public:
     /// configuration and also subnet-ids may change.
     void removeStatistics();
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Checks that the IPv4 subnet with the given id already exists.

+ 180 - 1
src/lib/dhcpsrv/cfg_subnets6.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,9 +9,13 @@
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/addr_utilities.h>
 #include <stats/stats_mgr.h>
+#include <string.h>
+#include <sstream>
 
 using namespace isc::asiolink;
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -227,5 +231,180 @@ CfgSubnets6::updateStatistics() {
     }
 }
 
+ElementPtr
+CfgSubnets6::toElement() const {
+    ElementPtr result = Element::createList();
+    // Iterate subnets
+    for (Subnet6Collection::const_iterator subnet = subnets_.cbegin();
+         subnet != subnets_.cend(); ++subnet) {
+        // Prepare the map
+        ElementPtr map = Element::createMap();
+        // Set subnet id
+        SubnetID id = (*subnet)->getID();
+        map->set("id", Element::create(static_cast<long long>(id)));
+        // Set relay info
+        const Subnet::RelayInfo& relay_info = (*subnet)->getRelayInfo();
+        ElementPtr relay = Element::createMap();
+        relay->set("ip-address", Element::create(relay_info.addr_.toText()));
+        map->set("relay", relay);
+        // Set subnet
+        map->set("subnet", Element::create((*subnet)->toText()));
+        // Set interface
+        const std::string& iface = (*subnet)->getIface();
+        map->set("interface", Element::create(iface));
+        // Set interface-id
+        const OptionPtr& ifaceid = (*subnet)->getInterfaceId();
+        if (ifaceid) {
+            std::vector<uint8_t> bin = ifaceid->getData();
+            std::string ifid;
+            ifid.resize(bin.size());
+            if (!bin.empty()) {
+                std::memcpy(&ifid[0], &bin[0], bin.size());
+            }
+            map->set("interface-id", Element::create(ifid));
+        } else {
+            map->set("interface-id", Element::create(std::string()));
+        }
+        // Set renew-timer
+        map->set("renew-timer",
+                 Element::create(static_cast<long long>
+                                 ((*subnet)->getT1().get())));
+        // Set rebind-timer
+        map->set("rebind-timer",
+                 Element::create(static_cast<long long>
+                                 ((*subnet)->getT2().get())));
+        // Set preferred-lifetime
+        map->set("preferred-lifetime",
+                 Element::create(static_cast<long long>
+                                 ((*subnet)->getPreferred().get())));
+        // Set valid-lifetime
+        map->set("valid-lifetime",
+                 Element::create(static_cast<long long>
+                                 ((*subnet)->getValid().get())));
+        // Set rapid-commit
+        bool rapid_commit = (*subnet)->getRapidCommit();
+        map->set("rapid-commit", Element::create(rapid_commit));
+        // Set pools
+        const PoolCollection& pools = (*subnet)->getPools(Lease::TYPE_NA);
+        ElementPtr pool_list = Element::createList();
+        for (PoolCollection::const_iterator pool = pools.cbegin();
+             pool != pools.cend(); ++pool) {
+            // Prepare the map for a pool
+            ElementPtr pool_map = Element::createMap();
+            // Set pool
+            const IOAddress& first = (*pool)->getFirstAddress();
+            const IOAddress& last = (*pool)->getLastAddress();
+            std::string range = first.toText() + "-" + last.toText();
+            // Try to output a prefix (vs a range)
+            int prefix_len = prefixLengthFromRange(first, last);
+            if (prefix_len >= 0) {
+                std::ostringstream oss;
+                oss << first.toText() << "/" << prefix_len;
+                range = oss.str();
+            }
+            pool_map->set("pool", Element::create(range));
+            // Set user-context
+            ConstElementPtr context = (*pool)->getContext();
+            if (!isNull(context)) {
+                pool_map->set("user-context", context);
+            }
+            // Set pool options
+            ConstCfgOptionPtr opts = (*pool)->getCfgOption();
+            pool_map->set("option-data", opts->toElement());
+            // Push on the pool list
+            pool_list->add(pool_map);
+        }
+        map->set("pools", pool_list);
+        // Set pd-pools
+        const PoolCollection& pdpools = (*subnet)->getPools(Lease::TYPE_PD);
+        ElementPtr pdpool_list = Element::createList();
+        for (PoolCollection::const_iterator pool = pdpools.cbegin();
+             pool != pdpools.cend(); ++pool) {
+            // Get it as a Pool6
+            const Pool6* pdpool = dynamic_cast<Pool6*>(pool->get());
+            if (!pdpool) {
+                isc_throw(ToElementError, "invalid pd-pool pointer");
+            }
+            // Prepare the map for a pd-pool
+            ElementPtr pool_map = Element::createMap();
+            // Set prefix
+            const IOAddress& prefix = pdpool->getFirstAddress();
+            pool_map->set("prefix", Element::create(prefix.toText()));
+            // Set prefix-len (get it from min - max)
+            const IOAddress& last = pdpool->getLastAddress();
+            int prefix_len = prefixLengthFromRange(prefix, last);
+            if (prefix_len < 0) {
+                // The pool is bad: give up
+                isc_throw(ToElementError, "invalid prefix range "
+                          << prefix.toText() << "-" << last.toText());
+            }
+            pool_map->set("prefix-len", Element::create(prefix_len));
+            // Set delegated-len
+            uint8_t len = pdpool->getLength();
+            pool_map->set("delegated-len",
+                          Element::create(static_cast<int>(len)));
+            // Set excluded prefix
+            const Option6PDExcludePtr& xopt =
+                pdpool->getPrefixExcludeOption();
+            if (xopt) {
+                const IOAddress& xprefix =
+                    xopt->getExcludedPrefix(prefix, len);
+                pool_map->set("excluded-prefix",
+                              Element::create(xprefix.toText()));
+                uint8_t xlen = xopt->getExcludedPrefixLength();
+                pool_map->set("excluded-prefix-len",
+                              Element::create(static_cast<int>(xlen)));
+            } else {
+                pool_map->set("excluded-prefix",
+                              Element::create(std::string("::")));
+                pool_map->set("excluded-prefix-len", Element::create(0));
+            }
+            // Set user-context
+            ConstElementPtr context = pdpool->getContext();
+            if (!isNull(context)) {
+                pool_map->set("user-context", context);
+            }
+            // Set pool options
+            ConstCfgOptionPtr opts = pdpool->getCfgOption();
+            pool_map->set("option-data", opts->toElement());
+            // Push on the pool list
+            pdpool_list->add(pool_map);
+        }
+        map->set("pd-pools", pdpool_list);
+        // Set host reservation-mode
+        Subnet::HRMode hrmode = (*subnet)->getHostReservationMode();
+        std::string mode;
+        switch (hrmode) {
+        case Subnet::HR_DISABLED:
+            mode = "disabled";
+            break;
+        case Subnet::HR_OUT_OF_POOL:
+            mode = "out-of-pool";
+            break;
+        case Subnet::HR_ALL:
+            mode = "all";
+            break;
+        default:
+            isc_throw(ToElementError,
+                      "invalid host reservation mode: " << hrmode);
+        }
+        map->set("reservation-mode", Element::create(mode));
+        // Set client-class
+        const ClientClasses& cclasses = (*subnet)->getClientClasses();
+        if (cclasses.size() > 1) {
+            isc_throw(ToElementError, "client-classes has too many items: "
+                      << cclasses.size());
+        } else if (!cclasses.empty()) {
+            map->set("client-class", Element::create(*cclasses.cbegin()));
+        }
+        // Set options
+        ConstCfgOptionPtr opts = (*subnet)->getCfgOption();
+        map->set("option-data", opts->toElement());
+        // Push on the list
+        result->add(map);
+    }
+    return (result);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 8 - 2
src/lib/dhcpsrv/cfg_subnets6.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
+#include <cc/cfg_to_element.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <util/optional_value.h>
@@ -26,7 +27,7 @@ namespace dhcp {
 ///
 /// See @c CfgSubnets6::selectSubnet documentation for more details on how the subnet
 /// is selected for the client.
-class CfgSubnets6 {
+class CfgSubnets6 : public isc::data::CfgToElement {
 public:
 
     /// @brief Adds new subnet to the configuration.
@@ -141,6 +142,11 @@ public:
     /// configuration and also subnet-ids may change.
     void removeStatistics();
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Selects a subnet using the interface name.

+ 2 - 1
src/lib/dhcpsrv/cfgmgr.cc

@@ -167,7 +167,8 @@ CfgMgr::getStagingCfg() {
 }
 
 CfgMgr::CfgMgr()
-    : datadir_(DHCP_DATA_DIR), d2_client_mgr_(), verbose_mode_(false) {
+    : datadir_(DHCP_DATA_DIR), d2_client_mgr_(),
+      verbose_mode_(false), family_(AF_INET) {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
     // See AM_CPPFLAGS definition in Makefile.am

+ 13 - 0
src/lib/dhcpsrv/cfgmgr.h

@@ -248,6 +248,16 @@ public:
         return (default_logger_name_);
     }
 
+    /// @brief Sets address family (AF_INET or AF_INET6)
+    void setFamily(uint16_t family) {
+        family_ = family == AF_INET ? AF_INET : AF_INET6;
+    }
+
+    /// @brief Returns address family.
+    uint16_t getFamily() const {
+        return (family_);
+    }
+
     //@}
 
 protected:
@@ -299,6 +309,9 @@ private:
 
     /// @brief Default logger name.
     std::string default_logger_name_;
+
+    /// @brief Address family.
+    uint16_t family_;
 };
 
 } // namespace isc::dhcp

+ 44 - 2
src/lib/dhcpsrv/client_class_def.cc

@@ -1,12 +1,14 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-#include "client_class_def.h"
+#include <dhcpsrv/client_class_def.h>
 #include <boost/foreach.hpp>
 
+using namespace isc::data;
+
 namespace isc {
 namespace dhcp {
 
@@ -74,6 +76,16 @@ ClientClassDef::setMatchExpr(const ExpressionPtr& match_expr) {
     match_expr_ = match_expr;
 }
 
+std::string
+ClientClassDef::getTest() const {
+    return (test_);
+}
+
+void
+ClientClassDef::setTest(const std::string& test) {
+    test_ = test;
+}
+
 const CfgOptionPtr&
 ClientClassDef::getCfgOption() const {
     return (cfg_option_);
@@ -98,6 +110,24 @@ ClientClassDef::equals(const ClientClassDef& other) const {
             (filename_ == other.filename_));
 }
 
+ElementPtr
+ClientClassDef:: toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set name
+    result->set("name", Element::create(name_));
+    // Set original match expression
+    result->set("test", Element::create(test_));
+    // Set option-data
+    result->set("option-data", cfg_option_->toElement());
+    // Set next-server
+    result->set("next-server", Element::create(next_server_.toText()));
+    // Set server-hostname
+    result->set("server-hostname", Element::create(sname_));
+    // Set boot-file-name
+    result->set("boot-file-name", Element::create(filename_));
+    return (result);
+}
+
 std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
     os << "ClientClassDef:" << x.getName();
     return (os);
@@ -123,11 +153,13 @@ ClientClassDictionary::~ClientClassDictionary() {
 void
 ClientClassDictionary::addClass(const std::string& name,
                                 const ExpressionPtr& match_expr,
+                                const std::string& test,
                                 const CfgOptionPtr& cfg_option,
                                 asiolink::IOAddress next_server,
                                 const std::string& sname,
                                 const std::string& filename) {
     ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
+    cclass->setTest(test);
     cclass->setNextServer(next_server);
     cclass->setSname(sname);
     cclass->setFilename(filename);
@@ -191,6 +223,16 @@ ClientClassDictionary::equals(const ClientClassDictionary& other) const {
     return (true);
 }
 
+ElementPtr
+ClientClassDictionary::toElement() const {
+    ElementPtr result = Element::createList();
+    // Iterate on the map
+    for (ClientClassDefMap::iterator this_class = classes_->begin();
+         this_class != classes_->end(); ++this_class) {
+        result->add(this_class->second->toElement());
+    }
+    return (result);
+}
 
 } // namespace isc::dhcp
 } // namespace isc

+ 28 - 4
src/lib/dhcpsrv/client_class_def.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
 #ifndef CLIENT_CLASS_DEF_H
 #define CLIENT_CLASS_DEF_H
 
+#include <cc/cfg_to_element.h>
 #include <dhcpsrv/cfg_option.h>
 #include <eval/token.h>
 #include <exceptions/exceptions.h>
@@ -37,7 +38,7 @@ public:
 };
 
 /// @brief Embodies a single client class definition
-class ClientClassDef {
+class ClientClassDef : public isc::data::CfgToElement {
 public:
     /// @brief Constructor
     ///
@@ -70,6 +71,14 @@ public:
     /// @param match_expr the expression to assign the class
     void setMatchExpr(const ExpressionPtr& match_expr);
 
+    /// @brief Fetches the class's original match expression
+    std::string getTest() const;
+
+    /// @brief Sets the class's original match expression
+    ///
+    /// @param test the original expression to assign the class
+    void setTest(const std::string& test);
+
     /// @brief Fetches the class's option collection
     const CfgOptionPtr& getCfgOption() const;
 
@@ -145,6 +154,11 @@ public:
         return (filename_);
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
     /// @brief Unique text identifier by which this class is known.
     std::string name_;
@@ -153,6 +167,10 @@ private:
     /// this class.
     ExpressionPtr match_expr_;
 
+    /// @brief The original expression which determines membership in
+    /// this class.
+    std::string test_;
+
     /// @brief The option data configuration for this class
     CfgOptionPtr cfg_option_;
 
@@ -188,7 +206,7 @@ typedef boost::shared_ptr<ClientClassDefMap> ClientClassDefMapPtr;
 typedef std::pair<std::string, ClientClassDefPtr> ClientClassMapPair;
 
 /// @brief Maintains a list of ClientClassDef's
-class ClientClassDictionary {
+class ClientClassDictionary : public isc::data::CfgToElement {
 
 public:
     /// @brief Constructor
@@ -203,6 +221,7 @@ public:
     ///
     /// @param name Name to assign to this class
     /// @param match_expr Expression the class will use to determine membership
+    /// @param test Original version of match_expr
     /// @param options Collection of options members should be given
     /// @param next_server next-server value for this class (optional)
     /// @param sname server-name value for this class (optional)
@@ -212,7 +231,7 @@ public:
     /// dictionary.  See @ref dhcp::ClientClassDef::ClientClassDef() for
     /// others.
     void addClass(const std::string& name, const ExpressionPtr& match_expr,
-                  const CfgOptionPtr& options,
+                  const std::string& test, const CfgOptionPtr& options,
                   asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"),
                   const std::string& sname = std::string(),
                   const std::string& filename = std::string());
@@ -271,6 +290,11 @@ public:
         return (!equals(other));
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Map of the class definitions

+ 46 - 1
src/lib/dhcpsrv/d2_client_cfg.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,6 +15,8 @@
 #include <string>
 
 using namespace std;
+using namespace isc::asiolink;
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -217,6 +219,49 @@ D2ClientConfig::toText() const {
     return (stream.str());
 }
 
+ElementPtr
+D2ClientConfig::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set enable-updates
+    result->set("enable-updates", Element::create(enable_updates_));
+    // Set qualifying-suffix
+    result->set("qualifying-suffix", Element::create(qualifying_suffix_));
+    // Set server-ip
+    result->set("server-ip", Element::create(server_ip_.toText()));
+    // Set server-port
+    result->set("server-port",
+                Element::create(static_cast<long long>(server_port_)));
+    // Set sender-ip
+    result->set("sender-ip", Element::create(sender_ip_.toText()));
+    // Set sender-port
+    result->set("sender-port",
+                Element::create(static_cast<long long>(sender_port_)));
+    // Set max-queue-size
+    result->set("max-queue-size",
+                Element::create(static_cast<long long>(max_queue_size_)));
+    // Set ncr-protocol
+    result->set("ncr-protocol",
+                Element::create(dhcp_ddns::
+                                ncrProtocolToString(ncr_protocol_)));
+    // Set ncr-format
+    result->set("ncr-format",
+                Element::create(dhcp_ddns::ncrFormatToString(ncr_format_)));
+    // Set always-include-fqdn
+    result->set("always-include-fqdn", Element::create(always_include_fqdn_));
+    // Set override-no-update
+    result->set("override-no-update", Element::create(override_no_update_));
+    // Set override-client-update
+    result->set("override-client-update",
+                Element::create(override_client_update_));
+    // Set replace-client-name
+    result->set("replace-client-name",
+                Element::create(replaceClientNameModeToString
+                                (replace_client_name_mode_)));
+    // Set generated-prefix
+    result->set("generated-prefix", Element::create(generated_prefix_));
+    return (result);
+}
+
 std::ostream&
 operator<<(std::ostream& os, const D2ClientConfig& config) {
     os << config.toText();

+ 9 - 2
src/lib/dhcpsrv/d2_client_cfg.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,7 +11,9 @@
 /// This file defines the classes Kea uses to manage configuration needed to
 /// act as a client of the kea-dhcp-ddns module (aka D2).
 ///
+
 #include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
 #include <dhcp_ddns/ncr_io.h>
 #include <exceptions/exceptions.h>
 
@@ -46,7 +48,7 @@ public:
 /// parameters associated with DHCP-DDNS and acting as a client of D2.
 /// Instances of this class may be constructed through configuration parsing.
 ///
-class D2ClientConfig {
+class D2ClientConfig : public isc::data::CfgToElement {
 public:
     /// @brief Default configuration constants.
     /// @todo For now these are hard-coded as configuration layer cannot
@@ -230,6 +232,11 @@ public:
     /// "unknown" if not.
     static std::string replaceClientNameModeToString(const ReplaceClientNameMode& mode);
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 protected:
     /// @brief Validates member values.
     ///

+ 57 - 1
src/lib/dhcpsrv/logging_info.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
 #include <log/logger_name.h>
 
 using namespace isc::log;
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -137,5 +138,60 @@ LoggingInfo::toSpec() const {
     return (spec);
 }
 
+ElementPtr
+LoggingInfo::toElement() const {
+    ElementPtr result = Element::createMap();
+    // Set name
+    result->set("name", Element::create(name_));
+    // Set output_options
+    ElementPtr options = Element::createList();
+    for (std::vector<LoggingDestination>::const_iterator dest =
+             destinations_.cbegin();
+         dest != destinations_.cend(); ++dest) {
+        ElementPtr map = Element::createMap();
+        // Set output
+        map->set("output", Element::create(dest->output_));
+        // Set maxver
+        map->set("maxver", Element::create(dest->maxver_));
+        // Set maxsize
+        map->set("maxsize",
+                 Element::create(static_cast<long long>(dest->maxsize_)));
+        // Set flush
+        map->set("flush", Element::create(dest->flush_));
+        // Push on output option list
+        options->add(map);
+    }
+    result->set("output_options", options);
+    // Set severity
+    std::string severity;
+    switch (severity_) {
+    case isc::log::DEBUG:
+        severity = "DEBUG";
+        break;
+    case isc::log::INFO:
+        severity = "INFO";
+        break;
+    case isc::log::WARN:
+        severity = "WARN";
+        break;
+    case isc::log::ERROR:
+        severity = "ERROR";
+        break;
+    case isc::log::FATAL:
+        severity = "FATAL";
+        break;
+    case isc::log::NONE:
+        severity = "NONE";
+        break;
+    default:
+        isc_throw(ToElementError, "illegal severity: " << severity_);
+        break;
+    }
+    result->set("severity", Element::create(severity));
+    // Set debug level
+    result->set("debuglevel", Element::create(debuglevel_));
+    return (result);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 8 - 2
src/lib/dhcpsrv/logging_info.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
 
 #include <log/logger_level.h>
 #include <log/logger_specification.h>
+#include <cc/cfg_to_element.h>
 #include <stdint.h>
 #include <vector>
 
@@ -65,7 +66,7 @@ struct LoggingDestination {
 ///            "severity": "WARN",
 ///            "debuglevel": 99
 ///        },
-struct LoggingInfo {
+struct LoggingInfo : public isc::data::CfgToElement {
 
     /// @brief logging name
     std::string name_;
@@ -116,6 +117,11 @@ struct LoggingInfo {
 
     /// @brief Converts logger configuration to a spec.
     isc::log::LoggerSpecification toSpec() const;
+
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
 };
 
 /// @brief storage for logging information in log4cplus format

+ 59 - 56
src/lib/dhcpsrv/parsers/client_class_def_parser.cc

@@ -62,88 +62,91 @@ void
 ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
                             ConstElementPtr class_def_cfg,
                             uint16_t family) {
+    // name is now mandatory
+    std::string name = getString(class_def_cfg, "name");
+    if (name.empty()) {
+        isc_throw(DhcpConfigError,
+                  "not empty parameter 'name' is required "
+                  << getPosition("name", class_def_cfg) << ")");
+    }
 
-    try {
-        std::string name;
-        std::string next_server_txt = "0.0.0.0";
-        std::string sname;
-        std::string filename;
-        ExpressionPtr match_expr;
-        CfgOptionPtr options(new CfgOption());
-
-        // Parse the elements that make up the client class definition.
-        BOOST_FOREACH(ConfigPair param, class_def_cfg->mapValue()) {
-            std::string entry(param.first);
-            ConstElementPtr value(param.second);
-
-            if (entry == "name") {
-                name = value->stringValue();
-
-            } else if (entry == "test") {
-                ExpressionParser parser;
-                parser.parse(match_expr, value, family);
-                
-            } else if (entry == "option-data") {
-                OptionDataListParser opts_parser(family);
-                opts_parser.parse(options, value);
-
-            } else if (entry == "next-server") {
-                next_server_txt = value->stringValue();
-
-            } else if (entry == "server-hostname") {
-                sname = value->stringValue();
-
-            } else if (entry == "boot-file-name") {
-                filename = value->stringValue();
-
-            } else {
-                isc_throw(DhcpConfigError, "invalid parameter '" << entry
-                          << "' (" << value->getPosition() << ")");
-            }
-        }
+    // Parse matching expression
+    ExpressionPtr match_expr;
+    ConstElementPtr test_cfg = class_def_cfg->get("test");
+    std::string test;
+    if (test_cfg) {
+        ExpressionParser parser;
+        parser.parse(match_expr, test_cfg, family);
+        test = test_cfg->stringValue();
+    }
 
-        // name is now mandatory
-        if (name.empty()) {
-            isc_throw(DhcpConfigError,
-                      "not empty parameter 'name' is required");
-        }
+    // Parse option data
+    CfgOptionPtr options(new CfgOption());
+    ConstElementPtr option_data = class_def_cfg->get("option-data");
+    if (option_data) {
+        OptionDataListParser opts_parser(family);
+        opts_parser.parse(options, option_data);
+    }
 
-        // Let's parse the next-server field
-        IOAddress next_server("0.0.0.0");
+    // Let's try to parse the next-server field
+    IOAddress next_server("0.0.0.0");
+    if (class_def_cfg->contains("next-server")) {
+        std::string next_server_txt = getString(class_def_cfg, "next-server");
         try {
             next_server = IOAddress(next_server_txt);
         } catch (const IOError& ex) {
-            isc_throw(DhcpConfigError, "Invalid next-server value specified: '"
-                      << next_server_txt);
+            isc_throw(DhcpConfigError,
+                      "Invalid next-server value specified: '"
+                      << next_server_txt << "' ("
+                      << getPosition("next-server", class_def_cfg) << ")");
         }
 
         if (next_server.getFamily() != AF_INET) {
             isc_throw(DhcpConfigError, "Invalid next-server value: '"
-                      << next_server_txt << "', must be IPv4 address");
+                      << next_server_txt
+                      << "', must be IPv4 address ("
+                      << getPosition("next-server", class_def_cfg) << ")");
         }
 
         if (next_server.isV4Bcast()) {
             isc_throw(DhcpConfigError, "Invalid next-server value: '"
-                      << next_server_txt << "', must not be a broadcast");
+                      << next_server_txt
+                      << "', must not be a broadcast ("
+                      << getPosition("next-server", class_def_cfg) << ")");
         }
+    }
+
+    // Let's try to parse server-hostname
+    std::string sname;
+    if (class_def_cfg->contains("server-hostname")) {
+        sname = getString(class_def_cfg, "server-hostname");
 
-        // Let's try to parse server-hostname
         if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
             isc_throw(DhcpConfigError, "server-hostname must be at most "
                       << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
-                      << sname.length());
+                      << sname.length() << " ("
+                      << getPosition("server-hostname", class_def_cfg) << ")");
         }
+    }
+
+    // Let's try to parse boot-file-name
+    std::string filename;
+    if (class_def_cfg->contains("boot-file-name")) {
+        filename = getString(class_def_cfg, "boot-file-name");
 
-        // Let's try to parse boot-file-name
         if (filename.length() > Pkt4::MAX_FILE_LEN) {
             isc_throw(DhcpConfigError, "boot-file-name must be at most "
                       << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
-                      << filename.length());
+                      << filename.length() << " ("
+                      << getPosition("boot-file-name", class_def_cfg) << ")");
         }
 
-        // Add the client class definition
-        class_dictionary->addClass(name, match_expr, options, next_server,
-                                   sname, filename);
+    }
+
+    // Add the client class definition
+    try {
+        class_dictionary->addClass(name, match_expr, test, options,
+                                   next_server, sname, filename);
     } catch (const std::exception& ex) {
         isc_throw(DhcpConfigError, ex.what()
                   << " (" << class_def_cfg->getPosition() << ")");

+ 1 - 1
src/lib/dhcpsrv/parsers/dbaccess_parser.h

@@ -43,7 +43,7 @@ public:
     /// @brief Constructor
     ///
     /// @param db_type Specifies database type (lease or hosts)
-    DbAccessParser(DBType db_type);
+    explicit DbAccessParser(DBType db_type);
 
     /// The destructor.
     virtual ~DbAccessParser()

+ 33 - 76
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -295,10 +295,7 @@ OptionDataParser::extractSpace(ConstElementPtr parent) const {
         }
 
     } catch (std::exception& ex) {
-        // Append position of the option space parameter. Note, that in the case
-        // when 'space' was not specified a default value will be used and we
-        // should never get here. Therefore, it is ok to call getPosition for
-        // the space parameter here as this parameter will always be specified.
+        // Append position of the option space parameter.
         isc_throw(DhcpConfigError, ex.what() << " ("
                   << getPosition("space", parent) << ")");
     }
@@ -445,12 +442,15 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
         // Option definition has been found so let's use it to create
         // an instance of our option.
         try {
-            OptionPtr option =
-                !csv_format_param.isSpecified() || csv_format_param ?
+            bool use_csv = !csv_format_param.isSpecified() || csv_format_param;
+            OptionPtr option = use_csv ?
                 def->optionFactory(universe, def->getCode(), data_tokens) :
                 def->optionFactory(universe, def->getCode(), binary);
             desc.option_ = option;
             desc.persistent_ = false;
+            if (use_csv) {
+                desc.formatted_value_ = data_param;
+            }
         } catch (const isc::Exception& ex) {
             isc_throw(DhcpConfigError, "option data does not match"
                       << " option definition (space: " << space_param
@@ -479,7 +479,8 @@ void OptionDataListParser::parse(const CfgOptionPtr& cfg,
     BOOST_FOREACH(ConstElementPtr data, option_data_list->listValue()) {
         std::pair<OptionDescriptor, std::string> option =
             option_parser.parse(data);
-        cfg->add(option.first.option_, option.first.persistent_, option.second);
+        // Use the option description to keep the formatted value
+        cfg->add(option.first, option.second);
         cfg->encapsulate();
     }
 }
@@ -600,57 +601,31 @@ RelayInfoParser::RelayInfoParser(const Option::Universe& family)
     : family_(family) {
 };
 
+// Can't use a constructor as a function
+namespace {
+IOAddress buildIOAddress(const std::string& str) { return (IOAddress(str)); }
+};
+
+IOAddress
+RelayInfoParser::getIOAddress(ConstElementPtr scope,
+                              const std::string& name) {
+    return (getAndConvert<IOAddress,
+            buildIOAddress>(scope, name, "address"));
+}
+
 void
 RelayInfoParser::parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
                        ConstElementPtr relay_info) {
-    // Let's start with some sanity checks.
-    if (!relay_info || !cfg) {
-        isc_throw(DhcpConfigError, "Logic error: RelayInfoParser::parse() called "
-                  "with at least one NULL parameter.");
-    }
-
-    if (relay_info->getType() != Element::map) {
-        isc_throw(DhcpConfigError, "Configuration error: RelayInfoParser::parse() "
-                  "called with non-map parameter");
-    }
-
-    // Now create the default value.
-    isc::asiolink::IOAddress ip(family_ == Option::V4 ? IOAddress::IPV4_ZERO_ADDRESS()
-                                : IOAddress::IPV6_ZERO_ADDRESS());
-
-    // Now iterate over all parameters. Currently there's only one supported
-    // parameter, so it should be an easy thing to check.
-    bool ip_address_specified = false;
-    BOOST_FOREACH(ConfigPair param, relay_info->mapValue()) {
-        if (param.first == "ip-address") {
-            ip_address_specified = true;
-
-            try {
-                ip = asiolink::IOAddress(param.second->stringValue());
-            } catch (...)  {
-                isc_throw(DhcpConfigError, "Failed to parse ip-address "
-                          "value: " << param.second
-                          << " (" << param.second->getPosition() << ")");
-            }
-
-            // Check if the address family matches.
-            if ( (ip.isV4() && family_ != Option::V4) ||
-                 (ip.isV6() && family_ != Option::V6) ) {
-                isc_throw(DhcpConfigError, "ip-address field " << ip.toText()
-                          << " does not have IP address of expected family type: "
-                          << (family_ == Option::V4 ? "IPv4" : "IPv6")
-                          << " (" << param.second->getPosition() << ")");
-            }
-        } else {
-            isc_throw(NotImplemented,
-                      "parser error: RelayInfoParser parameter not supported: "
-                      << param.second);
-        }
-    }
-
-    if (!ip_address_specified) {
-        isc_throw(DhcpConfigError, "'relay' specified, but mandatory 'ip-address' "
-                  "paramter in it is missing");
+    // There is only one parameter which is mandatory
+    IOAddress ip = getIOAddress(relay_info, "ip-address");
+
+    // Check if the address family matches.
+    if ((ip.isV4() && family_ != Option::V4) ||
+        (ip.isV6() && family_ != Option::V6) ) {
+        isc_throw(DhcpConfigError, "ip-address field " << ip.toText()
+                  << " does not have IP address of expected family type: "
+                  << (family_ == Option::V4 ? "IPv4" : "IPv6")
+                  << " (" << getPosition("ip-address", relay_info) << ")");
     }
 
     // Ok, we're done with parsing. Let's store the result in the structure
@@ -914,17 +889,10 @@ SubnetConfigParser::createSubnet(ConstElementPtr params) {
     try {
         std::string hr_mode = getString(params, "reservation-mode");
         subnet_->setHostReservationMode(hrModeFromText(hr_mode));
-    } catch (const BadValue& ex) {
-        ConstElementPtr mode = params->get("reservation-mode");
-        string pos;
-        if (mode) {
-            pos = mode->getPosition().str();
-        } else {
-            pos = params->getPosition().str();
-        }
-        isc_throw(DhcpConfigError, "Failed to process specified value "
+    } catch (const BadValue& ex) { 
+       isc_throw(DhcpConfigError, "Failed to process specified value "
                   " of reservation-mode parameter: " << ex.what()
-                  << "(" << pos << ")");
+                  << "(" << getPosition("reservation-mode", params) << ")");
     }
 
     // Try setting up client class.
@@ -943,17 +911,6 @@ SubnetConfigParser::createSubnet(ConstElementPtr params) {
 
 //**************************** D2ClientConfigParser **********************
 
-uint32_t
-D2ClientConfigParser::getUint32(ConstElementPtr scope,
-                                const std::string& name) {
-    return (getIntType<uint32_t>(scope, name));
-}
-
-// Can't use a constructor as a function
-namespace {
-IOAddress buildIOAddress(const std::string& str) { return (IOAddress(str)); }
-};
-
 IOAddress
 D2ClientConfigParser::getIOAddress(ConstElementPtr scope,
                                    const std::string& name) {

+ 16 - 15
src/lib/dhcpsrv/parsers/dhcp_parsers.h

@@ -365,7 +365,7 @@ public:
     /// @brief Constructor.
     ///
     /// @param address_family Address family: @c AF_INET or @c AF_INET6.
-    OptionDataParser(const uint16_t address_family);
+    explicit OptionDataParser(const uint16_t address_family);
 
     /// @brief Parses ElementPtr containing option definition
     ///
@@ -477,7 +477,7 @@ public:
     /// @brief Constructor.
     ///
     /// @param address_family Address family: @c AF_INET or AF_INET6
-    OptionDataListParser(const uint16_t address_family);
+    explicit OptionDataListParser(const uint16_t address_family);
 
     /// @brief Parses a list of options, instantiates them and stores in cfg
     ///
@@ -620,7 +620,7 @@ public:
 
     /// @brief constructor
     /// @param family specifies protocol family (IPv4 or IPv6)
-    RelayInfoParser(const isc::dhcp::Option::Universe& family);
+    explicit RelayInfoParser(const isc::dhcp::Option::Universe& family);
 
     /// @brief parses the actual relay parameters
     ///
@@ -632,7 +632,18 @@ public:
     void parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
                isc::data::ConstElementPtr relay_info);
 
-protected:
+private:
+
+    /// @brief Returns a value converted to IOAddress
+    ///
+    /// Instantiation of getAndConvert() to IOAddress
+    ///
+    /// @param scope specified parameter will be extracted from this scope
+    /// @param name name of the parameter
+    /// @return an IOAddress value
+    isc::asiolink::IOAddress
+    getIOAddress(isc::data::ConstElementPtr scope, const std::string& name);
+
     /// Protocol family (IPv4 or IPv6)
     Option::Universe family_;
 };
@@ -664,7 +675,7 @@ public:
     /// @brief constructor
     ///
     /// @param family address family: @c AF_INET or @c AF_INET6
-    SubnetConfigParser(uint16_t family);
+    explicit SubnetConfigParser(uint16_t family);
 
     /// @brief virtual destructor (does nothing)
     virtual ~SubnetConfigParser() { }
@@ -776,16 +787,6 @@ public:
 
 private:
 
-    /// @brief Returns a value converted to uint32_t
-    ///
-    /// Instantiation of getIntType() to uint32_t
-    ///
-    /// @param scope specified parameter will be extracted from this scope
-    /// @param name name of the parameter
-    /// @return an uint32_t value
-    uint32_t
-    getUint32(isc::data::ConstElementPtr scope, const std::string& name);
-
     /// @brief Returns a value converted to IOAddress
     ///
     /// Instantiation of getAndConvert() to IOAddress

+ 47 - 92
src/lib/dhcpsrv/parsers/duid_config_parser.cc

@@ -24,107 +24,62 @@ namespace isc {
 namespace dhcp {
 
 void
-DUIDConfigParser::parse(const CfgDUIDPtr& cfg, isc::data::ConstElementPtr duid_configuration) {
-    if (!cfg) {
-        isc_throw(DhcpConfigError, "Must provide valid pointer to cfg when parsing duid");
-    }
-
-    bool type_present = false;
-    BOOST_FOREACH(ConfigPair element, duid_configuration->mapValue()) {
-        try {
-            if (element.first == "type") {
-                type_present = true;
-                setType(cfg, element.second->stringValue());
-            } else if (element.first == "identifier") {
-                setIdentifier(cfg, element.second->stringValue());
-            } else if (element.first == "htype") {
-                setHType(cfg, element.second->intValue());
-            } else if (element.first == "time") {
-                setTime(cfg, element.second->intValue());
-            } else if (element.first == "enterprise-id") {
-                setEnterpriseId(cfg, element.second->intValue());
-            } else if (element.first == "persist") {
-                setPersist(cfg, element.second->boolValue());
-            } else {
-                isc_throw(DhcpConfigError, "unsupported configuration "
-                          "parameter '" << element.first << "'");
-            }
-        } catch (const std::exception& ex) {
-            // Append position.
-            isc_throw(DhcpConfigError, ex.what() << " ("
-                      << element.second->getPosition() << ")");
+DUIDConfigParser::parse(const CfgDUIDPtr& cfg,
+                        isc::data::ConstElementPtr duid_configuration) {
+
+    std::string param;
+    try {
+        param = "type";
+        std::string duid_type = getString(duid_configuration, "type");
+        // Map DUID type represented as text into numeric value.
+        DUID::DUIDType numeric_type = DUID::DUID_UNKNOWN;
+        if (duid_type == "LLT") {
+            numeric_type = DUID::DUID_LLT;
+        } else if (duid_type == "EN") {
+            numeric_type = DUID::DUID_EN;
+        } else if (duid_type == "LL") {
+            numeric_type = DUID::DUID_LL;
+        } else {
+            isc_throw(BadValue, "unsupported DUID type '"
+                      << duid_type << "'. Expected: LLT, EN or LL");
         }
-    }
-
-    // "type" is mandatory
-    if (!type_present) {
-        isc_throw(DhcpConfigError, "mandatory parameter \"type\" not specified"
-                  " for the DUID configuration ("
-                  << duid_configuration->getPosition() << ")");
-    }
-
-    LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIGURE_SERVERID);
-}
 
-void
-DUIDConfigParser::setType(const CfgDUIDPtr& cfg, const std::string& duid_type) const {
-    // Map DUID type represented as text into numeric value.
-    DUID::DUIDType numeric_type = DUID::DUID_UNKNOWN;
-    if (duid_type == "LLT") {
-        numeric_type = DUID::DUID_LLT;
-    } else if (duid_type == "EN") {
-        numeric_type = DUID::DUID_EN;
-    } else if (duid_type == "LL") {
-        numeric_type = DUID::DUID_LL;
-    } else {
-        isc_throw(DhcpConfigError, "unsupported DUID type '"
-                  << duid_type << "'. Expected: LLT, EN or LL");
-    }
-
-    cfg->setType(static_cast<DUID::DUIDType>(numeric_type));
-}
+        cfg->setType(static_cast<DUID::DUIDType>(numeric_type));
 
-void
-DUIDConfigParser::setIdentifier(const CfgDUIDPtr& cfg, const std::string& identifier) const {
-    cfg->setIdentifier(identifier);
-}
-
-void
-DUIDConfigParser::setHType(const CfgDUIDPtr& cfg, const int64_t htype) const {
-    checkRange<uint16_t>("htype", htype);
-    cfg->setHType(static_cast<uint16_t>(htype));
-}
+        param = "identifier";
+        if (duid_configuration->contains(param)) {
+            cfg->setIdentifier(getString(duid_configuration, param));
+        }
 
-void
-DUIDConfigParser::setTime(const CfgDUIDPtr& cfg, const int64_t new_time) const {
-    checkRange<uint32_t>("time", new_time);
-    cfg->setTime(static_cast<uint32_t>(new_time));
-}
+        param = "htype";
+        if (duid_configuration->contains(param)) {
+            cfg->setHType(getUint16(duid_configuration, param));
+        }
 
-void
-DUIDConfigParser::setEnterpriseId(const CfgDUIDPtr& cfg, const int64_t enterprise_id) const {
-    checkRange<uint32_t>("enterprise-id", enterprise_id);
-    cfg->setEnterpriseId(static_cast<uint32_t>(enterprise_id));
-}
+        param = "time";
+        if (duid_configuration->contains(param)) {
+            cfg->setTime(getUint32(duid_configuration, param));
+        }
 
-void
-DUIDConfigParser::setPersist(const CfgDUIDPtr& cfg, const bool persist) {
-    cfg->setPersist(persist);
-}
+        param = "enterprise-id";
+        if (duid_configuration->contains(param)) {
+            cfg->setEnterpriseId(getUint32(duid_configuration, param));
+        }
 
-template<typename NumericType>
-void
-DUIDConfigParser::checkRange(const std::string& parameter_name,
-                             const int64_t parameter_value) const {
-    if ((parameter_value < 0) ||
-        (parameter_value > std::numeric_limits<NumericType>::max())) {
-        isc_throw(DhcpConfigError, "out of range value '" << parameter_value
-                  << "' specified for parameter '" << parameter_name
-                  << "'; expected value in range of [0.."
-                  << std::numeric_limits<NumericType>::max() << "]");
+        param = "persist";
+        if (duid_configuration->contains(param)) {
+            cfg->setPersist(getBoolean(duid_configuration, param));
+        }
+    } catch (const DhcpConfigError&) {
+        throw;
+    } catch (const std::exception& ex) {
+        // Append position.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << getPosition(param, duid_configuration) << ")");
     }
-}
 
+    LOG_WARN(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIGURE_SERVERID);
+}
 
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 0 - 51
src/lib/dhcpsrv/parsers/duid_config_parser.h

@@ -33,57 +33,6 @@ public:
     ///
     /// @throw DhcpConfigError If the configuration is invalid.
     void parse(const CfgDUIDPtr& cfg, isc::data::ConstElementPtr duid_configuration);
-private:
-
-    /// @brief Validate and set DUID type.
-    ///
-    /// @param cfg parsed information will be stored here
-    /// @param duid_type DUID type in textual format.
-    void setType(const CfgDUIDPtr& cfg, const std::string& duid_type) const;
-
-    /// @brief Validate and set identifier.
-    ///
-    /// @param cfg parsed information will be stored here
-    /// @param identifier Identifier.
-    void setIdentifier(const CfgDUIDPtr& cfg, const std::string& identifier) const;
-
-    /// @brief Validate and set hardware type.
-    ///
-    /// @param cfg parsed information will be stored here
-    /// @param htype Hardware type.
-    void setHType(const CfgDUIDPtr& cfg, const int64_t htype) const;
-
-    /// @brief Validate and set time value.
-    ///
-    /// @param cfg parsed information will be stored here
-    /// @param new_time Time value to be used for DUID.
-    void setTime(const CfgDUIDPtr& cfg, const int64_t new_time) const;
-
-    /// @brief Validate and set enterprise id.
-    ///
-    /// @param cfg parsed information will be stored here
-    /// @param enterprise_id Enterprise id.
-    void setEnterpriseId(const CfgDUIDPtr& cfg, const int64_t enterprise_id) const;
-
-    /// @brief Set persistence flag.
-    ///
-    /// @param cfg parsed information will be stored here
-    /// @param persist A boolean value indicating if the server
-    /// identifier should be stored on the disk (if true) or
-    /// not (if false).
-    void setPersist(const CfgDUIDPtr& cfg, const bool persist);
-
-    /// @brief Verifies if the specified parameter is in range.
-    ///
-    /// Each numeric value must be in range of [0 .. max_value], where
-    /// max_value is a maximum value for the numeric type used for this
-    /// parameter.
-    ///
-    /// @param parameter_name Parameter name.
-    /// @tparam Numeric type of the specified parameter.
-    template<typename NumericType>
-    void checkRange(const std::string& parameter_name,
-                    const int64_t parameter_value) const;
 };
 
 }

+ 33 - 30
src/lib/dhcpsrv/parsers/expiration_config_parser.cc

@@ -20,43 +20,46 @@ void
 ExpirationConfigParser::parse(ConstElementPtr expiration_config) {
     CfgExpirationPtr cfg = CfgMgr::instance().getStagingCfg()->getCfgExpiration();
 
-    BOOST_FOREACH(ConfigPair config_element, expiration_config->mapValue()) {
+    std::string param;
 
-        // Get parameter name and value.
-        std::string param_name = config_element.first;
-        ConstElementPtr param_value = config_element.second;
-
-        try {
-            // Set configuration parameters.
-            if (param_name == "reclaim-timer-wait-time") {
-                cfg->setReclaimTimerWaitTime(param_value->intValue());
-
-            } else if (param_name == "flush-reclaimed-timer-wait-time") {
-                cfg->setFlushReclaimedTimerWaitTime(param_value->intValue());
-
-            } else if (param_name == "hold-reclaimed-time") {
-                cfg->setHoldReclaimedTime(param_value->intValue());
+    try {
+        param = "reclaim-timer-wait-time";
+        if (expiration_config->contains(param)) {
+            cfg->setReclaimTimerWaitTime(getInteger(expiration_config, param));
+        }
 
-            } else if (param_name == "max-reclaim-leases") {
-                cfg->setMaxReclaimLeases(param_value->intValue());
+        param = "flush-reclaimed-timer-wait-time";
+        if (expiration_config->contains(param)) {
+            cfg->setFlushReclaimedTimerWaitTime(getInteger(expiration_config,
+                                                           param));
+        }
 
-            } else if (param_name == "max-reclaim-time") {
-                cfg->setMaxReclaimTime(param_value->intValue());
+        param = "hold-reclaimed-time";
+        if (expiration_config->contains(param)) {
+            cfg->setHoldReclaimedTime(getInteger(expiration_config, param));
+        }
 
-            } else if (param_name == "unwarned-reclaim-cycles") {
-                cfg->setUnwarnedReclaimCycles(param_value->intValue());
+        param = "max-reclaim-leases";
+        if (expiration_config->contains(param)) {
+            cfg->setMaxReclaimLeases(getInteger(expiration_config, param));
+        }                          
 
-            } else {
-                isc_throw(DhcpConfigError, "unsupported parameter '"
-                          << param_name << "'");
-            }
+        param = "max-reclaim-time";
+        if (expiration_config->contains(param)) {
+            cfg->setMaxReclaimTime(getInteger(expiration_config, param));
+        }
 
-        } catch (const std::exception& ex) {
-            // Append position of the configuration parameter to the error
-            // message.
-            isc_throw(DhcpConfigError, ex.what() << " ("
-                      << param_value->getPosition() << ")");
+        param = "unwarned-reclaim-cycles";
+        if (expiration_config->contains(param)) {
+            cfg->setUnwarnedReclaimCycles(
+                getInteger(expiration_config, param));
         }
+    } catch (const DhcpConfigError&) {
+        throw;
+    } catch (const std::exception& ex) {
+        // Append position of the configuration parameter to the error message.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << getPosition(param, expiration_config) << ")");
     }
 }
 

+ 2 - 2
src/lib/dhcpsrv/parsers/ifaces_config_parser.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -29,7 +29,7 @@ public:
     /// @brief Constructor
     ///
     /// @param protocol AF_INET for DHCPv4 and AF_INET6 for DHCPv6.
-    IfacesConfigParser(const uint16_t protocol);
+    explicit IfacesConfigParser(const uint16_t protocol);
 
     /// @brief Parses content of the "interfaces-config".
     ///

+ 2 - 2
src/lib/dhcpsrv/pool.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -263,7 +263,7 @@ public:
     /// This may be useful for "prefix/len" style definition for
     /// addresses, but is mostly useful for prefix pools.
     /// @return prefix length (1-128)
-    uint8_t getLength() {
+    uint8_t getLength() const {
         return (prefix_len_);
     }
 

+ 98 - 0
src/lib/dhcpsrv/srv_config.cc

@@ -15,6 +15,7 @@
 #include <sstream>
 
 using namespace isc::log;
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -200,5 +201,102 @@ SrvConfig::updateStatistics() {
     }
 }
 
+ElementPtr
+SrvConfig::toElement() const {
+    // Get family for the configuration manager
+    uint16_t family = CfgMgr::instance().getFamily();
+    // Toplevel map
+    ElementPtr result = Element::createMap();
+    // DhcpX global map
+    ElementPtr dhcp = Element::createMap();
+    // Set decline-probation-period
+    dhcp->set("decline-probation-period",
+              Element::create(static_cast<long long>(decline_timer_)));
+    // Set echo-client-id (DHCPv4)
+    if (family == AF_INET) {
+        dhcp->set("echo-client-id", Element::create(echo_v4_client_id_));
+    }
+    // Set dhcp4o6-port
+    dhcp->set("dhcp4o6-port",
+              Element::create(static_cast<int>(dhcp4o6_port_)));
+    // Set dhcp-ddns
+    dhcp->set("dhcp-ddns", d2_client_config_->toElement());
+    // Set interfaces-config
+    ConstElementPtr ifaces = cfg_iface_->toElement();
+    dhcp->set("interfaces-config", cfg_iface_->toElement());
+    // Set option-def
+    ConstElementPtr option_def = cfg_option_def_->toElement();
+    dhcp->set("option-def", option_def);
+    // Set option-data
+    ConstElementPtr option_data = cfg_option_->toElement();
+    dhcp->set("option-data", option_data);
+    // Set subnets
+    if (family == AF_INET) {
+        ConstElementPtr subnets = cfg_subnets4_->toElement();
+        // @todo Insert reservations
+        dhcp->set("subnet4", subnets);
+    } else {
+        ConstElementPtr subnets = cfg_subnets6_->toElement();
+        // @todo Insert reservations
+        dhcp->set("subnet6", subnets);
+    }
+    // Set relay-supplied-options (DHCPv6)
+    if (family == AF_INET6) {
+        dhcp->set("relay-supplied-options", cfg_rsoo_->toElement());
+    }
+    // Set expired-leases-processing
+    ConstElementPtr expired = cfg_expiration_->toElement();
+    dhcp->set("expired-leases-processing", expired);
+    // Set server-id (DHCPv6)
+    if (family == AF_INET6) {
+        dhcp->set("server-id", cfg_duid_->toElement());
+    }
+    // Set lease-database
+    CfgLeaseDbAccess lease_db(*cfg_db_access_);
+    dhcp->set("lease-database", lease_db.toElement());
+    // Set hosts-database
+    CfgHostDbAccess host_db(*cfg_db_access_);
+    dhcp->set("hosts-database", host_db.toElement());
+    // Set host-reservation-identifiers
+    ConstElementPtr host_ids;
+    if (family == AF_INET) {
+        host_ids = cfg_host_operations4_->toElement();
+    } else {
+        host_ids = cfg_host_operations6_->toElement();
+    }
+    dhcp->set("host-reservation-identifiers", host_ids);
+    // Set mac-sources (DHCPv6)
+    if (family == AF_INET6) {
+        dhcp->set("mac-sources", cfg_mac_source_.toElement());
+    }
+    // Set control-socket (skip if null as empty is not legal)
+    if (!isNull(control_socket_)) {
+        dhcp->set("control-socket", control_socket_);
+    }
+    // Set client-classes
+    ConstElementPtr client_classes = class_dictionary_->toElement();
+    dhcp->set("client-classes", client_classes);
+    // Set hooks-libraries
+    ConstElementPtr hooks_libs = hooks_config_.toElement();
+    dhcp->set("hooks-libraries", hooks_libs);
+    // Set DhcpX
+    result->set(family == AF_INET ? "Dhcp4" : "Dhcp6", dhcp);
+
+    // Logging global map (skip if loggers is empty)
+    ElementPtr logging = Element::createMap();
+    // Set loggers list
+    ElementPtr loggers = Element::createList();
+    for (LoggingInfoStorage::const_iterator logger = logging_info_.cbegin();
+         logger != logging_info_.cend(); ++logger) {
+        loggers->add(logger->toElement());
+    }
+    if (!loggers->empty()) {
+        logging->set("loggers", loggers);
+        result->set("Logging", logging);
+    }
+
+    return (result);
+}
+
 }
 }

+ 10 - 4
src/lib/dhcpsrv/srv_config.h

@@ -7,6 +7,7 @@
 #ifndef DHCPSRV_CONFIG_H
 #define DHCPSRV_CONFIG_H
 
+#include <cc/cfg_to_element.h>
 #include <dhcpsrv/cfg_db_access.h>
 #include <dhcpsrv/cfg_duid.h>
 #include <dhcpsrv/cfg_expiration.h>
@@ -37,7 +38,7 @@ class CfgMgr;
 /// @brief Specifies current DHCP configuration
 ///
 /// @todo Migrate all other configuration parameters from cfgmgr.h here
-class SrvConfig {
+class SrvConfig : public isc::data::CfgToElement {
 public:
     /// @name Constants for selection of parameters returned by @c getConfigSummary
     ///
@@ -507,7 +508,7 @@ public:
     /// this socket is bound and connected to this port and port + 1
     ///
     /// @param port port and port + 1 to use
-    void setDhcp4o6Port(uint32_t port) {
+    void setDhcp4o6Port(uint16_t port) {
         /// @todo: Port is supposed to be uint16_t, not uint32_t
         dhcp4o6_port_ = port;
     }
@@ -516,7 +517,7 @@ public:
     ///
     /// See @ref setDhcp4o6Port for brief discussion.
     /// @return value of DHCP4o6 IPC port
-    uint32_t getDhcp4o6Port() {
+    uint16_t getDhcp4o6Port() {
         return (dhcp4o6_port_);
     }
 
@@ -536,6 +537,11 @@ public:
         d2_client_config_ = d2_client_config;
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration
+    virtual isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Sequence number identifying the configuration.
@@ -624,7 +630,7 @@ private:
     ///
     /// DHCPv4-over-DHCPv6 uses a UDP socket for interserver communication,
     /// this socket is bound and connected to this port and port + 1
-    uint32_t dhcp4o6_port_;
+    uint16_t dhcp4o6_port_;
 
     D2ClientConfigPtr d2_client_config_;
 };

+ 11 - 1
src/lib/dhcpsrv/subnet.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -310,6 +310,16 @@ public:
     void
     allowClientClass(const isc::dhcp::ClientClass& class_name);
 
+    /// @brief returns the client class white list
+    ///
+    /// @note The returned reference is only valid as long as the object
+    /// returned it is valid.
+    ///
+    /// @return client classes @ref white_list_
+    const isc::dhcp::ClientClasses& getClientClasses() const {
+        return (white_list_);
+    }
+
     /// @brief Specifies what type of Host Reservations are supported.
     ///
     /// Host reservations may be either in-pool (they reserve an address that

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

@@ -160,6 +160,7 @@ libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dns/libkea-dns++.la

+ 90 - 1
src/lib/dhcpsrv/tests/addr_utilities_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -244,6 +244,95 @@ TEST(AddrUtilitiesTest, addrsInRange6) {
                  isc::BadValue);
 }
 
+// Checks if IPv4 address ranges can be converted to prefix / prefix_len
+TEST(AddrUtilitiesTest, prefixLengthFromRange4) {
+    // Use a shorter name
+    const auto& plfr = prefixLengthFromRange;
+
+    // Let's start with something simple
+    EXPECT_EQ(32, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.0")));
+    EXPECT_EQ(31, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.1")));
+    EXPECT_EQ(30, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.3")));
+    EXPECT_EQ(29, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.7")));
+    EXPECT_EQ(28, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.15")));
+    EXPECT_EQ(27, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.31")));
+    EXPECT_EQ(26, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.63")));
+    EXPECT_EQ(25, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.127")));
+    EXPECT_EQ(24, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.2.255")));
+    EXPECT_EQ(23, plfr(IOAddress("192.0.2.0"), IOAddress("192.0.3.255")));
+    EXPECT_EQ(16, plfr(IOAddress("10.0.0.0"), IOAddress("10.0.255.255")));
+    EXPECT_EQ(8, plfr(IOAddress("10.0.0.0"), IOAddress("10.255.255.255")));
+    EXPECT_EQ(0, plfr(IOAddress("0.0.0.0"), IOAddress("255.255.255.255")));
+
+    // Fail if a network boundary is crossed
+    EXPECT_EQ(-1, plfr(IOAddress("10.0.0.255"), IOAddress("10.0.1.1")));
+
+    // The upper bound cannot be smaller than the lower bound
+    EXPECT_THROW(plfr(IOAddress("192.0.2.5"), IOAddress("192.0.2.4")),
+                 isc::BadValue);
+}
+
+// Checks if IPv6 address ranges can be converted to prefix / prefix_len
+TEST(AddrUtilitiesTest, prefixLengthFromRange6) {
+    // Use a shorter name
+    const auto& plfr = prefixLengthFromRange;
+
+    // Let's start with something simple
+    EXPECT_EQ(128, plfr(IOAddress("::"), IOAddress("::")));
+    EXPECT_EQ(112, plfr(IOAddress("fe80::"),  IOAddress("fe80::ffff")));
+    EXPECT_EQ(96, plfr(IOAddress("fe80::"),  IOAddress("fe80::ffff:ffff")));
+    EXPECT_EQ(80, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::ffff:ffff:ffff")));
+    EXPECT_EQ(64, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(63, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::1:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(62, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::3:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(61, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::7:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(60, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::f:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(59, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::1f:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(58, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::3f:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(57, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::7f:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(56, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::ff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(55, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::1ff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(54, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::3ff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(53, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::7ff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(52, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::fff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(51, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::1fff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(50, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::3fff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(49, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::7fff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(48, plfr(IOAddress("fe80::"),
+                       IOAddress("fe80::ffff:ffff:ffff:ffff:ffff")));
+    EXPECT_EQ(0, plfr(IOAddress("::"),
+                      IOAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")));
+
+    // Fail if a network boundary is crossed
+    EXPECT_EQ(-1, plfr(IOAddress("2001:db8::ffff"),
+                       IOAddress("2001:db8::1:1")));
+
+    // The upper bound cannot be smaller than the lower bound
+    EXPECT_THROW(plfr(IOAddress("fe80::5"), IOAddress("fe80::4")),
+                 isc::BadValue);
+
+    // Address family must match
+    EXPECT_THROW(plfr(IOAddress("192.0.2.0"), IOAddress("fe80::1")),
+                 isc::BadValue);
+}
+
 // Checks if prefixInRange returns valid number of prefixes in specified range.
 TEST(AddrUtilitiesTest, prefixesInRange) {
 

+ 22 - 1
src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc

@@ -1,21 +1,24 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+#include <cc/data.h>
 #include <dhcpsrv/cfg_db_access.h>
 #include <dhcpsrv/host_data_source_factory.h>
 #include <dhcpsrv/host_mgr.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/testutils/mysql_schema.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
+using namespace isc::test;
 
 namespace {
 
@@ -23,7 +26,11 @@ namespace {
 TEST(CfgDbAccessTest, defaults) {
     CfgDbAccess cfg;
     EXPECT_EQ("type=memfile", cfg.getLeaseDbAccessString());
+    std::string expected = "{ \"type\": \"memfile\" }";
+    runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
     EXPECT_TRUE(cfg.getHostDbAccessString().empty());
+    runToElementTest<CfgHostDbAccess>("{ }", CfgHostDbAccess(cfg));
 }
 
 // This test verifies that it is possible to set the lease database
@@ -33,10 +40,17 @@ TEST(CfgDbAccessTest, setLeaseDbAccessString) {
     ASSERT_NO_THROW(cfg.setLeaseDbAccessString("type=mysql"));
     EXPECT_EQ("type=mysql", cfg.getLeaseDbAccessString());
 
+    // Check unparse
+    std::string expected = "{ \"type\": \"mysql\" }";
+    runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
     // Append additional parameter.
     ASSERT_NO_THROW(cfg.setAppendedParameters("universe=4"));
     EXPECT_EQ("type=mysql universe=4", cfg.getLeaseDbAccessString());
 
+    // Additional parameters are not in lease_db_access_
+    runToElementTest<CfgLeaseDbAccess>(expected, CfgLeaseDbAccess(cfg));
+
     // If access string is empty, no parameters will be appended.
     ASSERT_NO_THROW(cfg.setLeaseDbAccessString(""));
     EXPECT_TRUE(cfg.getLeaseDbAccessString().empty());
@@ -50,10 +64,17 @@ TEST(CfgDbAccessTest, setHostDbAccessString) {
     ASSERT_NO_THROW(cfg.setHostDbAccessString("type=mysql"));
     EXPECT_EQ("type=mysql", cfg.getHostDbAccessString());
 
+    // Check unparse
+    std::string expected = "{ \"type\": \"mysql\" }";
+    runToElementTest<CfgHostDbAccess>(expected, CfgHostDbAccess(cfg));
+
     // Append additional parameter.
     ASSERT_NO_THROW(cfg.setAppendedParameters("universe=4"));
     EXPECT_EQ("type=mysql universe=4", cfg.getHostDbAccessString());
 
+    // Additional parameters are not in host_db_access_
+    runToElementTest<CfgHostDbAccess>(expected, CfgHostDbAccess(cfg));
+
     // If access string is empty, no parameters will be appended.
     ASSERT_NO_THROW(cfg.setHostDbAccessString(""));
     EXPECT_TRUE(cfg.getHostDbAccessString().empty());

+ 23 - 5
src/lib/dhcpsrv/tests/cfg_duid_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
 #include <dhcpsrv/cfg_duid.h>
 #include <exceptions/exceptions.h>
 #include <testutils/io_utils.h>
+#include <testutils/test_to_element.h>
 #include <util/encode/hex.h>
 #include <gtest/gtest.h>
 #include <stdint.h>
@@ -20,6 +21,7 @@
 
 using namespace isc;
 using namespace isc::dhcp;
+using namespace isc::test;
 
 namespace {
 
@@ -92,6 +94,14 @@ TEST_F(CfgDUIDTest, defaults) {
     EXPECT_EQ(0, cfg_duid.getTime());
     EXPECT_EQ(0, cfg_duid.getEnterpriseId());
     EXPECT_TRUE(cfg_duid.persist());
+
+    std::string expected = "{ \"type\": \"LLT\",\n"
+        "\"identifier\": \"\",\n"
+        "\"htype\": 0,\n"
+        "\"time\": 0,\n"
+        "\"enterprise-id\": 0,\n"
+        "\"persist\": true }";
+    runToElementTest<CfgDUID>(expected, cfg_duid);
 }
 
 // This test verifies that it is possible to set values for the CfgDUID.
@@ -112,6 +122,14 @@ TEST_F(CfgDUIDTest, setValues) {
     EXPECT_EQ(32100, cfg_duid.getTime());
     EXPECT_EQ(10, cfg_duid.getEnterpriseId());
     EXPECT_FALSE(cfg_duid.persist());
+
+    std::string expected = "{ \"type\": \"EN\",\n"
+        " \"identifier\": \"ABCDEF\",\n"
+        " \"htype\": 100,\n"
+        " \"time\": 32100,\n"
+        " \"enterprise-id\": 10,\n"
+        " \"persist\": false }";
+    runToElementTest<CfgDUID>(expected, cfg_duid);
 }
 
 // This test checks positive scenarios for setIdentifier.
@@ -165,7 +183,7 @@ TEST_F(CfgDUIDTest, createLLT) {
               duid->toText());
 
     // Verify that the DUID file has been created.
-    EXPECT_TRUE(isc::test::fileExists(absolutePath(DUID_FILE_NAME)));
+    EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
 }
 
 // This method checks that the DUID-EN can be created from the
@@ -185,7 +203,7 @@ TEST_F(CfgDUIDTest, createEN) {
     EXPECT_EQ("00:02:00:00:10:10:25:0f:3e:26:a7:62", duid->toText());
 
     // Verify that the DUID file has been created.
-    EXPECT_TRUE(isc::test::fileExists(absolutePath(DUID_FILE_NAME)));
+    EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
 }
 
 // This method checks that the DUID-LL can be created from the
@@ -205,7 +223,7 @@ TEST_F(CfgDUIDTest, createLL) {
     EXPECT_EQ("00:03:00:02:12:41:34:a4:b3:67", duid->toText());
 
     // Verify that the DUID file has been created.
-    EXPECT_TRUE(isc::test::fileExists(absolutePath(DUID_FILE_NAME)));
+    EXPECT_TRUE(fileExists(absolutePath(DUID_FILE_NAME)));
 }
 
 // This test verifies that it is possible to disable storing
@@ -226,7 +244,7 @@ TEST_F(CfgDUIDTest, createDisableWrite) {
     EXPECT_EQ("00:02:00:00:10:10:25:0f:3e:26:a7:62", duid->toText());
 
     // DUID persistence is disabled so there should be no DUID file.
-    EXPECT_FALSE(isc::test::fileExists(absolutePath(DUID_FILE_NAME)));
+    EXPECT_FALSE(fileExists(absolutePath(DUID_FILE_NAME)));
 }
 
 } // end of anonymous namespace

+ 15 - 1
src/lib/dhcpsrv/tests/cfg_expiration_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
 #include <dhcpsrv/cfg_expiration.h>
 #include <dhcpsrv/timer_mgr.h>
 #include <exceptions/exceptions.h>
+#include <testutils/test_to_element.h>
 #include <util/stopwatch.h>
 #include <boost/function.hpp>
 #include <boost/shared_ptr.hpp>
@@ -113,6 +114,19 @@ TEST(CfgExpirationTest, defaults) {
               cfg.getUnwarnedReclaimCycles());
 }
 
+/// @brief Tests that unparse returns an expected value
+TEST(CfgExpirationTest, unparse) {
+    CfgExpiration cfg;
+    std::string defaults = "{\n"
+        "\"reclaim-timer-wait-time\": 10,\n"
+        "\"flush-reclaimed-timer-wait-time\": 25,\n"
+        "\"hold-reclaimed-time\": 3600,\n"
+        "\"max-reclaim-leases\": 100,\n"
+        "\"max-reclaim-time\": 250,\n"
+        "\"unwarned-reclaim-cycles\": 5 }";
+    isc::test::runToElementTest<CfgExpiration>(defaults, cfg);
+}
+
 // Test the {get,set}ReclaimTimerWaitTime.
 TEST(CfgExpirationTest, getReclaimTimerWaitTime) {
     testAccessModify<uint16_t>(CfgExpiration::LIMIT_RECLAIM_TIMER_WAIT_TIME,

+ 9 - 1
src/lib/dhcpsrv/tests/cfg_host_operations_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,12 +8,14 @@
 #include <dhcp/dhcp6.h>
 #include <dhcpsrv/cfg_host_operations.h>
 #include <dhcpsrv/host.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 #include <algorithm>
 #include <iterator>
 
 using namespace isc;
 using namespace isc::dhcp;
+using namespace isc::test;
 
 namespace {
 
@@ -51,6 +53,7 @@ identifierAtPosition(const CfgHostOperations& cfg, const Host::IdentifierType& i
 TEST(CfgHostOperationsTest, defaults) {
     CfgHostOperations cfg;
     EXPECT_TRUE(cfg.getIdentifierTypes().empty());
+    runToElementTest<CfgHostOperations>("[ ]", cfg);
 }
 
 // This test verifies that identifier types can be added into an
@@ -76,9 +79,14 @@ TEST(CfgHostOperationsTest, addIdentifier) {
     EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_DUID, 1));
     EXPECT_TRUE(identifierAtPosition(cfg, Host::IDENT_CIRCUIT_ID, 2));
 
+    // Check unparse
+    std::string ids = "[ \"hw-address\", \"duid\", \"circuit-id\" ]";
+    runToElementTest<CfgHostOperations>(ids, cfg);
+
     // Let's clear and make sure no identifiers are present.
     ASSERT_NO_THROW(cfg.clearIdentifierTypes());
     EXPECT_TRUE(cfg.getIdentifierTypes().empty());
+    runToElementTest<CfgHostOperations>("[ ]", cfg);
 }
 
 // This test verifies that the default DHCPv4 configuration is created

+ 34 - 1
src/lib/dhcpsrv/tests/cfg_iface_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,11 +8,13 @@
 #include <dhcp/dhcp4.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfg_iface.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 
 using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
+using namespace isc::test;
 
 namespace {
 
@@ -358,6 +360,31 @@ TEST_F(CfgIfaceTest, equality) {
     EXPECT_FALSE(cfg1 != cfg2);
 }
 
+// This test verifies that it is possible to unparse the interface config.
+TEST_F(CfgIfaceTest, unparse) {
+    CfgIface cfg4;
+
+    // Add things in it
+    EXPECT_NO_THROW(cfg4.use(AF_INET, "*"));
+    EXPECT_NO_THROW(cfg4.use(AF_INET, "eth0"));
+    EXPECT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
+    
+    // Check unparse
+    std::string expected =
+        "{ \"interfaces\": [ \"*\", \"eth0\", \"eth1/192.0.2.3\" ] }";
+    runToElementTest<CfgIface>(expected, cfg4);
+
+    // Now check IPv6
+    CfgIface cfg6;
+    EXPECT_NO_THROW(cfg6.use(AF_INET6, "*"));
+    EXPECT_NO_THROW(cfg6.use(AF_INET6, "eth1"));
+    EXPECT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
+
+    expected =
+        "{ \"interfaces\": [ \"*\", \"eth1\", \"eth0/2001:db8:1::1\" ] }";
+    runToElementTest<CfgIface>(expected, cfg6);
+}
+
 // This test verifies that it is possible to specify the socket
 // type to be used by the DHCPv4 server.
 // This test is enabled on LINUX and BSD only, because the
@@ -372,6 +399,12 @@ TEST(CfgIfaceNoStubTest, useSocketType) {
     // For datagram sockets, the direct traffic is not supported.
     ASSERT_TRUE(!IfaceMgr::instance().isDirectResponseSupported());
 
+    // Check unparse
+    std::string expected = "{\n"
+        " \"interfaces\": [ ],\n"
+        " \"dhcp-socket-type\": \"udp\" }";
+    runToElementTest<CfgIface>(expected, cfg);
+
     // Select raw sockets.
     ASSERT_NO_THROW(cfg.useSocketType(AF_INET, "raw"));
     EXPECT_EQ("raw", cfg.socketTypeToText());

+ 38 - 1
src/lib/dhcpsrv/tests/cfg_mac_source_unittest.cc

@@ -1,18 +1,23 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+#include <config.h>
+
 #include <dhcpsrv/cfg_mac_source.h>
 #include <dhcp/hwaddr.h>
 #include <exceptions/exceptions.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
+#include <string>
 
 namespace {
 
 using namespace isc;
 using namespace isc::dhcp;
+using namespace isc::test;
 
 // Checks whether CfgMACSource::MACSourceFromText is working correctly.
 // Technically, this is a Pkt, not Pkt6 test, but since there is no separate
@@ -44,4 +49,36 @@ TEST(CfgMACSourceTest, MACSourceFromText) {
               CfgMACSource::MACSourceFromText("docsis-modem"));
 }
 
+// Checks whether the opposite operation is working correctly.
+TEST(CfgMACSourceTest, unparse) {
+    CfgMACSource cfg;
+    // any was added by the constructor
+    cfg.add(HWAddr::HWADDR_SOURCE_RAW);
+    cfg.add(HWAddr::HWADDR_SOURCE_DUID);
+    cfg.add(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL);
+    cfg.add(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION);
+    cfg.add(HWAddr::HWADDR_SOURCE_REMOTE_ID);
+    cfg.add(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID);
+    cfg.add(HWAddr::HWADDR_SOURCE_DOCSIS_CMTS);
+    cfg.add(HWAddr::HWADDR_SOURCE_DOCSIS_MODEM);
+
+    // Unparse
+    std::string expected = "["
+        "\"any\","
+        "\"raw\","
+        "\"duid\","
+        "\"ipv6-link-local\","
+        "\"client-link-addr-option\","
+        "\"remote-id\","
+        "\"subscriber-id\","
+        "\"docsis-cmts\","
+        "\"docsis-modem\""
+        "]";
+    runToElementTest<CfgMACSource>(expected, cfg);
+
+    // Add an unknown type
+    cfg.add(0x12345678);
+    ASSERT_THROW(cfg.toElement(), ToElementError);
+}
+
 };

+ 57 - 1
src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/option_space.h>
 #include <dhcpsrv/cfg_option_def.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 
 using namespace isc;
@@ -245,4 +246,59 @@ TEST(CfgOptionDefTest, addNegative) {
     EXPECT_THROW(cfg.add(def, "isc"), DuplicateOptionDefinition);
 }
 
+// This test verifies that the funtion that unparses configuration
+// works as expected.
+TEST(CfgOptionDefTest, unparse) {
+    CfgOptionDef cfg;
+
+    // Add some options.
+    cfg.add(OptionDefinitionPtr(new 
+        OptionDefinition("option-foo", 5, "uint16")), "isc");
+    cfg.add(OptionDefinitionPtr(new
+        OptionDefinition("option-bar", 5, "uint16", true)), "dns");
+    cfg.add(OptionDefinitionPtr(new
+        OptionDefinition("option-baz", 6, "uint16", "dns")), "isc");
+    OptionDefinitionPtr rec(new OptionDefinition("option-rec", 6, "record"));
+    rec->addRecordField("uint16");
+    rec->addRecordField("uint16");
+    cfg.add(rec, "dns");
+    
+    // Unparse
+    std::string expected = "[\n"
+        "{\n"
+        "    \"name\": \"option-bar\",\n"
+        "    \"code\": 5,\n"
+        "    \"type\": \"uint16\",\n"
+        "    \"array\": true,\n"
+        "    \"record-types\": \"\",\n"
+        "    \"encapsulate\": \"\",\n"
+        "    \"space\": \"dns\"\n"
+        "},{\n"
+        "    \"name\": \"option-rec\",\n"
+        "    \"code\": 6,\n"
+        "    \"type\": \"record\",\n"
+        "    \"array\": false,\n"
+        "    \"record-types\": \"uint16, uint16\",\n"
+        "    \"encapsulate\": \"\",\n"
+        "    \"space\": \"dns\"\n"
+        "},{\n"
+        "    \"name\": \"option-foo\",\n"
+        "    \"code\": 5,\n"
+        "    \"type\": \"uint16\",\n"
+        "    \"array\": false,\n"
+        "    \"record-types\": \"\",\n"
+        "    \"encapsulate\": \"\",\n"
+        "    \"space\": \"isc\"\n"
+        "},{\n"
+        "    \"name\": \"option-baz\",\n"
+        "    \"code\": 6,\n"
+        "    \"type\": \"uint16\",\n"
+        "    \"array\": false,\n"
+        "    \"record-types\": \"\",\n"
+        "    \"encapsulate\": \"dns\",\n"
+        "    \"space\": \"isc\"\n"
+        "}]\n";
+    isc::test::runToElementTest<CfgOptionDef>(expected, cfg);
+}
+
 }

+ 43 - 1
src/lib/dhcpsrv/tests/cfg_option_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,6 +10,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_space.h>
 #include <dhcpsrv/cfg_option.h>
+#include <testutils/test_to_element.h>
 #include <boost/foreach.hpp>
 #include <boost/pointer_cast.hpp>
 #include <gtest/gtest.h>
@@ -598,5 +599,46 @@ TEST_F(CfgOptionTest, getVendorIdsSpaceNames) {
     }
 }
 
+// This test verifies that the unparse function returns what is expected.
+TEST_F(CfgOptionTest, unparse) {
+    CfgOption cfg;
+
+    // Add some options.
+    OptionPtr opt1(new Option(Option::V6, 100, OptionBuffer(4, 0x12)));
+    cfg.add(opt1, false, "dns");
+    OptionPtr opt2(new Option(Option::V6, 101, OptionBuffer(4, 12)));
+    OptionDescriptor desc2(opt2, false, "12, 12, 12, 12");
+    cfg.add(desc2, "dns");
+    OptionPtr opt3(new Option(Option::V6, D6O_STATUS_CODE, OptionBuffer(2, 0)));
+    cfg.add(opt3, false, DHCP6_OPTION_SPACE);
+    OptionPtr opt4(new Option(Option::V6, 100, OptionBuffer(4, 0x21)));
+    cfg.add(opt4, false, "vendor-1234");
+
+    // Unparse
+    std::string expected = "[\n"
+        "{\n"
+        "    \"code\": 100,\n"
+        "    \"space\": \"dns\",\n"
+        "    \"csv-format\": false,\n"
+        "    \"data\": \"12121212\"\n"
+        "},{\n"
+        "    \"code\": 101,\n"
+        "    \"space\": \"dns\",\n"
+        "    \"csv-format\": true,\n"
+        "    \"data\": \"12, 12, 12, 12\"\n"
+        "},{\n"
+        "    \"code\": 13,\n"
+        "    \"name\": \"status-code\",\n"
+        "    \"space\": \"dhcp6\",\n"
+        "    \"csv-format\": false,\n"
+        "    \"data\": \"0000\"\n"
+        "},{\n"
+        "    \"code\": 100,\n"
+        "    \"space\": \"vendor-1234\",\n"
+        "    \"csv-format\": false,\n"
+        "    \"data\": \"21212121\"\n"
+        "}]\n";
+    isc::test::runToElementTest<CfgOption>(expected, cfg);
+}
 
 } // end of anonymous namespace

+ 10 - 1
src/lib/dhcpsrv/tests/cfg_rsoo_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
 #include <config.h>
 #include <dhcp/dhcp6.h>
 #include <dhcpsrv/cfg_rsoo.h>
+#include <testutils/test_to_element.h>
 
 #include <gtest/gtest.h>
 
@@ -88,4 +89,12 @@ TEST(CfgRSOOTest, enableTwice) {
     ASSERT_FALSE(rsoo.enabled(88));
 }
 
+// This test verifies that the unparse function returns what is expected.
+TEST(CfgRSOOTest, unparse) {
+    CfgRSOO rsoo;
+    // option codes are put in strings
+    isc::test::runToElementTest<CfgRSOO>("[ \"65\" ]", rsoo);
+    // isc::test::runToElementTest<CfgRSOO>("[ 65 ]", rsoo);
+}
+
 } // end of anonymous namespace

+ 117 - 1
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,6 +11,7 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_selector.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 #include <vector>
 
@@ -18,6 +19,7 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
+using namespace isc::test;
 
 namespace {
 
@@ -438,5 +440,119 @@ TEST(CfgSubnets4Test, 4o6subnetMatchByInterfaceName) {
     EXPECT_EQ(subnet2, cfg.selectSubnet4o6(selector));
 }
 
+// This test check if IPv4 subnets can be unparsed in a predictable way,
+TEST(CfgSubnets4Test, unparseSubnet) {
+    CfgSubnets4 cfg;
+
+    // Add some subnets.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 123));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3, 124));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3, 125));
+    subnet1->allowClientClass("foo");
+    subnet2->setIface("lo");
+    subnet2->setRelayInfo(IOAddress("10.0.0.1"));
+    subnet3->setIface("eth1");
+
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Unparse
+    std::string expected = "[\n"
+        "{\n"
+        "    \"id\": 123,\n"
+        "    \"subnet\": \"192.0.2.0/26\",\n"
+        "    \"relay\": { \"ip-address\": \"0.0.0.0\" },\n"
+        "    \"interface\": \"\",\n"
+        "    \"match-client-id\": true,\n"
+        "    \"next-server\": \"0.0.0.0\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"valid-lifetime\": 3,\n"
+        "    \"client-class\": \"foo\",\n"
+        "    \"4o6-interface\": \"\",\n"
+        "    \"4o6-interface-id\": \"\",\n"
+        "    \"4o6-subnet\": \"\",\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"pools\": [ ]\n"
+        "},{\n"
+        "    \"id\": 124,\n"
+        "    \"subnet\": \"192.0.2.64/26\",\n"
+        "    \"relay\": { \"ip-address\": \"10.0.0.1\" },\n"
+        "    \"interface\": \"lo\",\n"
+        "    \"match-client-id\": true,\n"
+        "    \"next-server\": \"0.0.0.0\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"valid-lifetime\": 3,\n"
+        "    \"4o6-interface\": \"\",\n"
+        "    \"4o6-interface-id\": \"\",\n"
+        "    \"4o6-subnet\": \"\",\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"pools\": [ ]\n"
+        "},{\n"
+        "    \"id\": 125,\n"
+        "    \"subnet\": \"192.0.2.128/26\",\n"
+        "    \"relay\": { \"ip-address\": \"0.0.0.0\" },\n"
+        "    \"interface\": \"eth1\",\n"
+        "    \"match-client-id\": true,\n"
+        "    \"next-server\": \"0.0.0.0\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"valid-lifetime\": 3,\n"
+        "    \"4o6-interface\": \"\",\n"
+        "    \"4o6-interface-id\": \"\",\n"
+        "    \"4o6-subnet\": \"\",\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"option-data\": [ ],\n"
+        "    \"pools\": [ ]\n"
+        "} ]\n";
+    runToElementTest<CfgSubnets4>(expected, cfg);
+}
+
+// This test check if IPv4 pools can be unparsed in a predictable way,
+TEST(CfgSubnets4Test, unparsePool) {
+    CfgSubnets4 cfg;
+
+    // Add a subnet with pools
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 123));
+    Pool4Ptr pool1(new Pool4(IOAddress("192.0.2.1"), IOAddress("192.0.2.10")));
+    Pool4Ptr pool2(new Pool4(IOAddress("192.0.2.64"), 26));
+
+    subnet->addPool(pool1);
+    subnet->addPool(pool2);
+    cfg.add(subnet);
+    
+    // Unparse
+    std::string expected = "[\n"
+        "{\n"
+        "    \"id\": 123,\n"
+        "    \"subnet\": \"192.0.2.0/24\",\n"
+        "    \"relay\": { \"ip-address\": \"0.0.0.0\" },\n"
+        "    \"interface\": \"\",\n"
+        "    \"match-client-id\": true,\n"
+        "    \"next-server\": \"0.0.0.0\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"valid-lifetime\": 3,\n"
+        "    \"4o6-interface\": \"\",\n"
+        "    \"4o6-interface-id\": \"\",\n"
+        "    \"4o6-subnet\": \"\",\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"option-data\": [],\n"
+        "    \"pools\": [\n"
+        "        {\n"
+        "            \"pool\": \"192.0.2.1-192.0.2.10\",\n"
+        "            \"option-data\": []\n"
+        "        },{\n"
+        "            \"pool\": \"192.0.2.64/26\",\n"
+        "            \"option-data\": []\n"
+        "        }\n"
+        "    ]\n"
+        "} ]\n";
+    runToElementTest<CfgSubnets4>(expected, cfg);
+}
 
 } // end of anonymous namespace

+ 177 - 1
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -12,12 +12,14 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_selector.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 #include <string>
 
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
+using namespace isc::test;
 
 namespace {
 
@@ -342,4 +344,178 @@ TEST(CfgSubnets6Test, duplication) {
     EXPECT_THROW(cfg.add(subnet3), isc::dhcp::DuplicateSubnetID);
 }
 
+// This test check if IPv6 subnets can be unparsed in a predictable way,
+TEST(CfgSubnets6Test, unparseSubnet) {
+    CfgSubnets6 cfg;
+
+    // Add some subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"),
+                                   48, 1, 2, 3, 4, 123));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"),
+                                   48, 1, 2, 3, 4, 124));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"),
+                                   48, 1, 2, 3, 4, 125));
+
+    OptionPtr ifaceid = generateInterfaceId("relay.eth0");
+    subnet1->setInterfaceId(ifaceid);
+    subnet1->allowClientClass("foo");
+    subnet2->setIface("lo");
+    subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
+    subnet3->setIface("eth1");
+
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Unparse
+    std::string expected = "[\n"
+        "{\n"
+        "    \"id\": 123,\n"
+        "    \"subnet\": \"2001:db8:1::/48\",\n"
+        "    \"relay\": { \"ip-address\": \"::\" },\n"
+        "    \"interface-id\": \"relay.eth0\",\n"
+        "    \"interface\": \"\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"preferred-lifetime\": 3,\n"
+        "    \"valid-lifetime\": 4,\n"
+        "    \"rapid-commit\": false,\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"client-class\": \"foo\",\n"
+        "    \"pools\": [ ],\n"
+        "    \"pd-pools\": [ ],\n"
+        "    \"option-data\": [ ]\n"
+        "},{\n"
+        "    \"id\": 124,\n"
+        "    \"subnet\": \"2001:db8:2::/48\",\n"
+        "    \"relay\": { \"ip-address\": \"2001:db8:ff::2\" },\n"
+        "    \"interface-id\": \"\",\n"
+        "    \"interface\": \"lo\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"preferred-lifetime\": 3,\n"
+        "    \"valid-lifetime\": 4,\n"
+        "    \"rapid-commit\": false,\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"pools\": [ ],\n"
+        "    \"pd-pools\": [ ],\n"
+        "    \"option-data\": [ ]\n"
+        "},{\n"
+        "    \"id\": 125,\n"
+        "    \"subnet\": \"2001:db8:3::/48\",\n"
+        "    \"relay\": { \"ip-address\": \"::\" },\n"
+        "    \"interface-id\": \"\",\n"
+        "    \"interface\": \"eth1\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"preferred-lifetime\": 3,\n"
+        "    \"valid-lifetime\": 4,\n"
+        "    \"rapid-commit\": false,\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"pools\": [ ],\n"
+        "    \"pd-pools\": [ ],\n"
+        "    \"option-data\": [ ]\n"
+        "} ]\n";
+    runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
+// This test check if IPv6 pools can be unparsed in a predictable way,
+TEST(CfgSubnets6Test, unparsePool) {
+    CfgSubnets6 cfg;
+
+    // Add a subnet with pools
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"),
+                                  48, 1, 2, 3, 4, 123));
+    Pool6Ptr pool1(new Pool6(Lease::TYPE_NA,
+                             IOAddress("2001:db8:1::100"),
+                             IOAddress("2001:db8:1::199")));
+    Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"), 64));
+
+    subnet->addPool(pool1);
+    subnet->addPool(pool2);
+    cfg.add(subnet);
+    
+    // Unparse
+    std::string expected = "[\n"
+        "{\n"
+        "    \"id\": 123,\n"
+        "    \"subnet\": \"2001:db8:1::/48\",\n"
+        "    \"relay\": { \"ip-address\": \"::\" },\n"
+        "    \"interface-id\": \"\",\n"
+        "    \"interface\": \"\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"preferred-lifetime\": 3,\n"
+        "    \"valid-lifetime\": 4,\n"
+        "    \"rapid-commit\": false,\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"pools\": [\n"
+        "        {\n"
+        "            \"pool\": \"2001:db8:1::100-2001:db8:1::199\",\n"
+        "            \"option-data\": [ ]\n"
+        "        },{\n"
+        "            \"pool\": \"2001:db8:1:1::/64\",\n"
+        "            \"option-data\": [ ]\n"
+        "        }\n"
+        "    ],\n"
+        "    \"pd-pools\": [ ],\n"
+        "    \"option-data\": [ ]\n"
+        "} ]\n";
+    runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
+// This test check if IPv6 prefix delegation pools can be unparsed
+// in a predictable way,
+TEST(CfgSubnets6Test, unparsePdPool) {
+    CfgSubnets6 cfg;
+
+    // Add a subnet with pd-pools
+    Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"),
+                                  48, 1, 2, 3, 4, 123));
+    Pool6Ptr pdpool1(new Pool6(Lease::TYPE_PD,
+                               IOAddress("2001:db8:2::"), 48, 64));
+    Pool6Ptr pdpool2(new Pool6(IOAddress("2001:db8:3::"), 48, 56,
+                               IOAddress("2001:db8:3::"), 64));
+
+    subnet->addPool(pdpool1);
+    subnet->addPool(pdpool2);
+    cfg.add(subnet);
+    
+    // Unparse
+    std::string expected = "[\n"
+        "{\n"
+        "    \"id\": 123,\n"
+        "    \"subnet\": \"2001:db8:1::/48\",\n"
+        "    \"relay\": { \"ip-address\": \"::\" },\n"
+        "    \"interface-id\": \"\",\n"
+        "    \"interface\": \"\",\n"
+        "    \"renew-timer\": 1,\n"
+        "    \"rebind-timer\": 2,\n"
+        "    \"preferred-lifetime\": 3,\n"
+        "    \"valid-lifetime\": 4,\n"
+        "    \"rapid-commit\": false,\n"
+        "    \"reservation-mode\": \"all\",\n"
+        "    \"pools\": [ ],\n"
+        "    \"pd-pools\": [\n"
+        "        {\n"
+        "            \"prefix\": \"2001:db8:2::\",\n"
+        "            \"prefix-len\": 48,\n"
+        "            \"delegated-len\": 64,\n"
+        "            \"excluded-prefix\": \"::\",\n"
+        "            \"excluded-prefix-len\": 0,\n"
+        "            \"option-data\": [ ]\n"
+        "        },{\n"
+        "            \"prefix\": \"2001:db8:3::\",\n"
+        "            \"prefix-len\": 48,\n"
+        "            \"delegated-len\": 56,\n"
+        "            \"excluded-prefix\": \"2001:db8:3::\",\n"
+        "            \"excluded-prefix-len\": 64,\n"
+        "            \"option-data\": [ ]\n"
+        "        }\n"
+        "    ],\n"
+        "    \"option-data\": [ ]\n"
+        "} ]\n";
+    runToElementTest<CfgSubnets6>(expected, cfg);
+}
+
 } // end of anonymous namespace

+ 12 - 0
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -505,6 +505,18 @@ TEST_F(CfgMgrTest, verbosity) {
     EXPECT_FALSE(CfgMgr::instance().isVerbose());
 }
 
+// This test verifies that the address family can be set and obtained
+// from the configuration manager.
+TEST_F(CfgMgrTest, family) {
+    ASSERT_EQ(AF_INET, CfgMgr::instance().getFamily());
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    ASSERT_EQ(AF_INET6, CfgMgr::instance().getFamily());
+
+    CfgMgr::instance().setFamily(AF_INET);
+    EXPECT_EQ(AF_INET, CfgMgr::instance().getFamily());
+}
+
 // This test verifies that once the configuration is committed, statistics
 // are updated appropriately.
 TEST_F(CfgMgrTest, commitStats4) {

+ 10 - 32
src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc

@@ -295,10 +295,11 @@ TEST_F(ClientClassDefParserTest, nameOnlyValid) {
 // @todo same with AF_INET6
 TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
 
+    std::string test = "option[100].text == 'works right'";
     std::string cfg_text =
         "{ \n"
         "    \"name\": \"class_one\", \n"
-        "    \"test\": \"option[100].text == 'works right'\" \n"
+        "    \"test\": \"" + test + "\" \n"
         "} \n";
 
     ClientClassDefPtr cclass;
@@ -322,6 +323,9 @@ TEST_F(ClientClassDefParserTest, nameAndExpressionClass) {
     ExpressionPtr match_expr = cclass->getMatchExpr();
     ASSERT_TRUE(match_expr);
 
+    // Verify the original expression was saved.
+    EXPECT_EQ(test, cclass->getTest());
+
     // Build a packet that will fail evaluation.
     Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
     EXPECT_FALSE(evaluate(*match_expr, *pkt4));
@@ -373,10 +377,11 @@ TEST_F(ClientClassDefParserTest, nameAndOptionsClass) {
 // @todo same with AF_INET6
 TEST_F(ClientClassDefParserTest, basicValidClass) {
 
+    std::string test = "option[100].text == 'booya'";
     std::string cfg_text =
         "{ \n"
         "    \"name\": \"MICROSOFT\", \n"
-        "    \"test\": \"option[100].text == 'booya'\", \n"
+        "    \"test\": \"" + test + "\", \n"
         "    \"option-data\": [ \n"
         "        { \n"
         "           \"name\": \"domain-name-servers\", \n"
@@ -404,6 +409,9 @@ TEST_F(ClientClassDefParserTest, basicValidClass) {
     ExpressionPtr match_expr = cclass->getMatchExpr();
     ASSERT_TRUE(match_expr);
 
+    // Verify the original expression was saved.
+    EXPECT_EQ(test, cclass->getTest());
+
     // Build a packet that will fail evaluation.
     Pkt4Ptr pkt4(new Pkt4(DHCPDISCOVER, 123));
     EXPECT_FALSE(evaluate(*match_expr, *pkt4));
@@ -459,20 +467,6 @@ TEST_F(ClientClassDefParserTest, blankClassName) {
                  DhcpConfigError);
 }
 
-
-// Verifies that a class with an unknown element, fails to parse.
-TEST_F(ClientClassDefParserTest, unknownElement) {
-    std::string cfg_text =
-        "{ \n"
-        "    \"name\": \"one\", \n"
-        "    \"bogus\": \"bad\" \n"
-        "} \n";
-
-    ClientClassDefPtr cclass;
-    ASSERT_THROW(cclass = parseClientClassDef(cfg_text, AF_INET),
-                 DhcpConfigError);
-}
-
 // Verifies that a class with an invalid expression, fails to parse.
 TEST_F(ClientClassDefParserTest, invalidExpression) {
     std::string cfg_text =
@@ -565,22 +559,6 @@ TEST_F(ClientClassDefListParserTest, duplicateClass) {
                  DhcpConfigError);
 }
 
-// Verifies that a class list containing an invalid class entry, fails to
-// parse.
-TEST_F(ClientClassDefListParserTest, invalidClass) {
-    std::string cfg_text =
-        "[ \n"
-        "   { \n"
-        "       \"name\": \"one\", \n"
-        "       \"bogus\": \"bad\" \n"
-        "   } \n"
-        "] \n";
-
-    ClientClassDictionaryPtr dictionary;
-    ASSERT_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET6),
-                 DhcpConfigError);
-}
-
 // Test verifies that without any class specified, the fixed fields have their
 // default, empty value.
 // @todo same with AF_INET6

+ 71 - 9
src/lib/dhcpsrv/tests/client_class_def_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
 #include <config.h>
 #include <dhcpsrv/client_class_def.h>
 #include <dhcp/option_space.h>
+#include <testutils/test_to_element.h>
 #include <exceptions/exceptions.h>
 #include <boost/scoped_ptr.hpp>
 #include <asiolink/io_address.h>
@@ -20,6 +21,7 @@ using namespace std;
 using namespace isc::dhcp;
 using namespace isc::util;
 using namespace isc::asiolink;
+using namespace isc::test;
 using namespace isc;
 
 namespace {
@@ -44,7 +46,7 @@ TEST(ClientClassDef, construction) {
     // Verify we get an empty collection of cfg_option
     cfg_option = cclass->getCfgOption();
     ASSERT_TRUE(cfg_option);
-    //EXPECT_EQ(0, cfg_option->size());
+    EXPECT_TRUE(cfg_option->empty());
 }
 
 // Tests options operations.  Note we just do the basics
@@ -205,15 +207,15 @@ TEST(ClientClassDictionary, basics) {
 
     // Verify that we can add classes with both addClass variants
     // First addClass(name, expression, cfg_option)
-    ASSERT_NO_THROW(dictionary->addClass("cc1", expr, cfg_option));
-    ASSERT_NO_THROW(dictionary->addClass("cc2", expr, cfg_option));
+    ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", cfg_option));
+    ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", cfg_option));
 
     // Verify duplicate add attempt throws
-    ASSERT_THROW(dictionary->addClass("cc2", expr, cfg_option),
+    ASSERT_THROW(dictionary->addClass("cc2", expr, "", cfg_option),
                  DuplicateClientClassDef);
 
     // Verify that you cannot add a class with no name.
-    ASSERT_THROW(dictionary->addClass("", expr, cfg_option), BadValue);
+    ASSERT_THROW(dictionary->addClass("", expr, "", cfg_option), BadValue);
 
     // Now with addClass(class pointer)
     ASSERT_NO_THROW(cclass.reset(new ClientClassDef("cc3", expr, cfg_option)));
@@ -269,9 +271,9 @@ TEST(ClientClassDictionary, copyAndEquality) {
     CfgOptionPtr options;
 
     dictionary.reset(new ClientClassDictionary());
-    ASSERT_NO_THROW(dictionary->addClass("one", expr, options));
-    ASSERT_NO_THROW(dictionary->addClass("two", expr, options));
-    ASSERT_NO_THROW(dictionary->addClass("three", expr, options));
+    ASSERT_NO_THROW(dictionary->addClass("one", expr, "", options));
+    ASSERT_NO_THROW(dictionary->addClass("two", expr, "", options));
+    ASSERT_NO_THROW(dictionary->addClass("three", expr, "", options));
 
     // Copy constructor should succeed.
     ASSERT_NO_THROW(dictionary2.reset(new ClientClassDictionary(*dictionary)));
@@ -353,4 +355,64 @@ TEST(ClientClassDef, fixedFieldsBasics) {
 }
 
 
+// Verifies the unparse method of option class definitions
+TEST(ClientClassDef, unparseDef) {
+    boost::scoped_ptr<ClientClassDef> cclass;
+
+    // Get a client class definition and fill it
+    std::string name = "class1";
+    ExpressionPtr expr;
+    ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+    std::string test = "option[12].text == 'foo'";
+    cclass->setTest(test);
+    std::string next_server = "1.2.3.4";
+    cclass->setNextServer(IOAddress(next_server));
+    std::string sname = "my-server.example.com";
+    cclass->setSname(sname);
+    std::string filename = "/boot/kernel";
+    cclass->setFilename(filename);
+
+    // Unparse it
+    std::string expected = "{\n"
+        "\"name\": \"" + name + "\",\n"
+        "\"test\": \"" + test + "\",\n"
+        "\"next-server\": \"" + next_server + "\",\n"
+        "\"server-hostname\": \"" + sname + "\",\n"
+        "\"boot-file-name\": \"" + filename + "\",\n"
+        "\"option-data\": [ ] }\n";
+    runToElementTest<ClientClassDef>(expected, *cclass);
+}
+
+// Verifies the unparse method of client class dictionaries
+TEST(ClientClassDictionary, unparseDict) {
+    ClientClassDictionaryPtr dictionary;
+    ExpressionPtr expr;
+    CfgOptionPtr options;
+
+    // Get a client class dictionary and fill it
+    dictionary.reset(new ClientClassDictionary());
+    ASSERT_NO_THROW(dictionary->addClass("one", expr, "", options));
+    ASSERT_NO_THROW(dictionary->addClass("two", expr, "", options));
+    ASSERT_NO_THROW(dictionary->addClass("three", expr, "", options));
+
+    // Unparse it
+    auto add_defaults =
+        [](std::string name) {
+            return ("{\n"
+                    "\"name\": \"" + name + "\",\n"
+                    "\"test\": \"\",\n"
+                    "\"next-server\": \"0.0.0.0\",\n"
+                    "\"server-hostname\": \"\",\n"
+                    "\"boot-file-name\": \"\",\n"
+                    "\"option-data\": [ ] }");
+    };
+
+    std::string expected = "[\n" +
+        add_defaults("one") + ",\n" +
+        add_defaults("two") + ",\n" +
+        add_defaults("three") + "]\n";
+
+    runToElementTest<ClientClassDictionary>(expected, *dictionary);
+}
+
 } // end of anonymous namespace

+ 22 - 1
src/lib/dhcpsrv/tests/d2_client_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option6_client_fqdn.h>
 #include <dhcpsrv/d2_client_mgr.h>
+#include <testutils/test_to_element.h>
 #include <exceptions/exceptions.h>
 
 #include <gtest/gtest.h>
@@ -16,6 +17,7 @@ using namespace std;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::util;
+using namespace isc::test;
 using namespace isc;
 
 namespace {
@@ -121,6 +123,25 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
     ASSERT_NO_THROW(std::cout << "toText test:" << std::endl <<
                     *d2_client_config << std::endl);
 
+    // Verify what toElement returns.
+    std::string expected = "{\n"
+        "\"enable-updates\": true,\n"
+        "\"server-ip\": \"127.0.0.1\",\n"
+        "\"server-port\": 477,\n"
+        "\"sender-ip\": \"127.0.0.1\",\n"
+        "\"sender-port\": 478,\n"
+        "\"max-queue-size\": 2048,\n"
+        "\"ncr-protocol\": \"UDP\",\n"
+        "\"ncr-format\": \"JSON\",\n"
+        "\"always-include-fqdn\": true,\n"
+        "\"override-no-update\": true,\n"
+        "\"override-client-update\": true,\n"
+        "\"replace-client-name\": \"when-present\",\n"
+        "\"generated-prefix\": \"the_prefix\",\n"
+        "\"qualifying-suffix\": \"the.suffix.\"\n"
+        "}\n";
+    runToElementTest<D2ClientConfig>(expected, *d2_client_config);
+
     // Verify that constructor does not allow use of NCR_TCP.
     /// @todo obviously this becomes invalid once TCP is supported.
     ASSERT_THROW(d2_client_config.reset(new

+ 329 - 38
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -23,6 +23,7 @@
 #include <exceptions/exceptions.h>
 #include <hooks/hooks_parser.h>
 #include <hooks/hooks_manager.h>
+#include <testutils/test_to_element.h>
 
 #include <gtest/gtest.h>
 #include <boost/foreach.hpp>
@@ -40,6 +41,7 @@ using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::hooks;
+using namespace isc::test;
 
 namespace {
 
@@ -467,6 +469,21 @@ public:
         return (cnt);
     }
 
+    /// This table defines default values for option definitions in DHCPv6
+    static const SimpleDefaults OPTION6_DEF_DEFAULTS;
+
+    /// This table defines default values for option definitions in DHCPv4
+    static const SimpleDefaults OPTION4_DEF_DEFAULTS;
+
+    /// This table defines default values for options in DHCPv6
+    static const SimpleDefaults OPTION6_DEFAULTS;
+
+    /// This table defines default values for options in DHCPv4
+    static const SimpleDefaults OPTION4_DEFAULTS;
+
+    /// This table defines default values for both DHCPv4 and DHCPv6
+    static const SimpleDefaults GLOBAL6_DEFAULTS;
+
     /// @brief sets all default values for DHCPv4 and DHCPv6
     ///
     /// This function largely duplicates what SimpleParser4 and SimpleParser6 classes
@@ -482,44 +499,6 @@ public:
     /// @param config configuration structure to be filled with default values
     /// @param v6 true = DHCPv6, false = DHCPv4
     void setAllDefaults(ElementPtr config, bool v6) {
-        /// This table defines default values for option definitions in DHCPv6
-        const SimpleDefaults OPTION6_DEF_DEFAULTS = {
-            { "record-types", Element::string,  ""},
-            { "space",        Element::string,  "dhcp6"},
-            { "array",        Element::boolean, "false"},
-            { "encapsulate",  Element::string,  "" }
-        };
-
-        /// This table defines default values for option definitions in DHCPv4
-        const SimpleDefaults OPTION4_DEF_DEFAULTS = {
-            { "record-types", Element::string,  ""},
-            { "space",        Element::string,  "dhcp4"},
-            { "array",        Element::boolean, "false"},
-            { "encapsulate",  Element::string,  "" }
-        };
-
-        /// This table defines default values for options in DHCPv6
-        const SimpleDefaults OPTION6_DEFAULTS = {
-            { "space",        Element::string,  "dhcp6"},
-            { "csv-format",   Element::boolean, "true"},
-            { "encapsulate",  Element::string,  "" }
-        };
-
-        /// This table defines default values for options in DHCPv4
-        const SimpleDefaults OPTION4_DEFAULTS = {
-            { "space",        Element::string,  "dhcp4"},
-            { "csv-format",   Element::boolean, "true"},
-            { "encapsulate",  Element::string,  "" }
-        };
-
-        /// This table defines default values for both DHCPv4 and DHCPv6
-        const SimpleDefaults GLOBAL6_DEFAULTS = {
-            { "renew-timer",        Element::integer, "900" },
-            { "rebind-timer",       Element::integer, "1800" },
-            { "preferred-lifetime", Element::integer, "3600" },
-            { "valid-lifetime",     Element::integer, "7200" }
-        };
-
         if (v6) {
             setAllDefaults(config, GLOBAL6_DEFAULTS, OPTION6_DEFAULTS,
                            OPTION6_DEF_DEFAULTS);
@@ -623,6 +602,132 @@ public:
     std::string error_text_;
 };
 
+/// This table defines default values for option definitions in DHCPv6
+const SimpleDefaults ParseConfigTest::OPTION6_DEF_DEFAULTS = {
+    { "record-types", Element::string,  ""},
+    { "space",        Element::string,  "dhcp6"},
+    { "array",        Element::boolean, "false"},
+    { "encapsulate",  Element::string,  "" }
+};
+
+/// This table defines default values for option definitions in DHCPv4
+const SimpleDefaults ParseConfigTest::OPTION4_DEF_DEFAULTS = {
+    { "record-types", Element::string,  ""},
+    { "space",        Element::string,  "dhcp4"},
+    { "array",        Element::boolean, "false"},
+    { "encapsulate",  Element::string,  "" }
+};
+
+/// This table defines default values for options in DHCPv6
+const SimpleDefaults ParseConfigTest::OPTION6_DEFAULTS = {
+    { "space",        Element::string,  "dhcp6"},
+    { "csv-format",   Element::boolean, "true"}
+};
+
+/// This table defines default values for options in DHCPv4
+const SimpleDefaults ParseConfigTest::OPTION4_DEFAULTS = {
+    { "space",        Element::string,  "dhcp4"},
+    { "csv-format",   Element::boolean, "true"}
+};
+
+/// This table defines default values for both DHCPv4 and DHCPv6
+const SimpleDefaults ParseConfigTest::GLOBAL6_DEFAULTS = {
+    { "renew-timer",        Element::integer, "900" },
+    { "rebind-timer",       Element::integer, "1800" },
+    { "preferred-lifetime", Element::integer, "3600" },
+    { "valid-lifetime",     Element::integer, "7200" }
+};
+
+/// @brief Option configuration class
+///
+/// This class handles option-def and option-data which can be recovered
+/// using the toElement() method
+class CfgOptionsTest : public CfgToElement {
+public:
+    /// @brief Constructor
+    ///
+    /// @param cfg the server configuration where to get option-{def,data}
+    CfgOptionsTest(SrvConfigPtr cfg) :
+        cfg_option_def_(cfg->getCfgOptionDef()),
+        cfg_option_(cfg->getCfgOption()) { }
+
+    /// @brief Unparse a configuration objet
+    ///
+    /// @return a pointer to unparsed configuration (a map with
+    /// not empty option-def and option-data lists)
+    ElementPtr toElement() const {
+        ElementPtr result = Element::createMap();
+        // Set option-def
+        ConstElementPtr option_def = cfg_option_def_->toElement();
+        if (!option_def->empty()) {
+            result->set("option-def", option_def);
+        }
+        // Set option-data
+        ConstElementPtr option_data = cfg_option_->toElement();
+        if (!option_data->empty()) {
+            result->set("option-data", option_data);
+        }
+        return (result);
+    }
+
+    /// @brief Run a toElement test (Element version)
+    ///
+    /// Use the runToElementTest template but add defaults to the config
+    ///
+    /// @param family the address family
+    /// @param config the expected result without defaults
+    void runCfgOptionsTest(uint16_t family, ConstElementPtr expected) {
+        ConstElementPtr option_def = expected->get("option-def");
+        if (option_def) {
+            SimpleParser::setListDefaults(option_def,
+                                          family == AF_INET ?
+                                          ParseConfigTest::OPTION4_DEF_DEFAULTS :
+                                          ParseConfigTest::OPTION6_DEF_DEFAULTS);
+        }
+        ConstElementPtr option_data = expected->get("option-data");
+        if (option_data) {
+            SimpleParser::setListDefaults(option_data,
+                                          family == AF_INET ?
+                                          ParseConfigTest::OPTION4_DEFAULTS :
+                                          ParseConfigTest::OPTION6_DEFAULTS);
+        }
+        runToElementTest<CfgOptionsTest>(expected, *this);
+    }
+
+    /// @brief Run a toElement test
+    ///
+    /// Use the runToElementTest template but add defaults to the config
+    ///
+    /// @param family the address family
+    /// @param expected the expected result without defaults
+    void runCfgOptionsTest(uint16_t family, std::string config) {
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = Element::fromJSON(config)) << config;
+        ConstElementPtr option_def = json->get("option-def");
+        if (option_def) {
+            SimpleParser::setListDefaults(option_def,
+                                          family == AF_INET ?
+                                          ParseConfigTest::OPTION4_DEF_DEFAULTS :
+                                          ParseConfigTest::OPTION6_DEF_DEFAULTS);
+        }
+        ConstElementPtr option_data = json->get("option-data");
+        if (option_data) {
+            SimpleParser::setListDefaults(option_data,
+                                          family == AF_INET ?
+                                          ParseConfigTest::OPTION4_DEFAULTS :
+                                          ParseConfigTest::OPTION6_DEFAULTS);
+        }
+        runToElementTest<CfgOptionsTest>(json, *this);
+    }
+
+private:
+    /// @brief Pointer to option definitions configuration.
+    CfgOptionDefPtr cfg_option_def_;
+
+    /// @brief Reference to options (data) configuration.
+    CfgOptionPtr cfg_option_;
+};
+
 /// @brief Check basic parsing of option definitions.
 ///
 /// Note that this tests basic operation of the OptionDefinitionListParser and
@@ -669,6 +774,10 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
     // but the values should be equal.
     EXPECT_TRUE(def_libdhcp != def);
     EXPECT_TRUE(*def_libdhcp == *def);
+
+    // Check if it can be unparsed.
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, config);
 }
 
 /// @brief Check minimal parsing of option definitions.
@@ -702,6 +811,10 @@ TEST_F(ParseConfigTest, minimalOptionDefTest) {
     EXPECT_FALSE(def->getArrayType());
     EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
     EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+    // Check if it can be unparsed.
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, config);
 }
 
 /// @brief Check parsing of option definitions using default dhcp6 space.
@@ -735,6 +848,10 @@ TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {
     EXPECT_FALSE(def->getArrayType());
     EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
     EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+
+    // Check if it can be unparsed.
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, config);
 }
 
 /// @brief Check basic parsing of options.
@@ -774,6 +891,10 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
     std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
 
     EXPECT_EQ(val, opt_ptr->toText());
+
+    // Check if it can be unparsed.
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, config);
 }
 
 /// @brief Check minimal parsing of options.
@@ -808,6 +929,12 @@ TEST_F(ParseConfigTest, minimalOptionDataTest) {
     std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
 
     EXPECT_EQ(val, opt_ptr->toText());
+
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("code", Element::create(100));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
 }
 
 /// @brief Check parsing of options with escape characters.
@@ -851,6 +978,12 @@ TEST_F(ParseConfigTest, escapedOptionDataTest) {
     EXPECT_EQ(Option::OPTION4_HDR_LEN + 23, buf.getLength());
 
     EXPECT_TRUE(0 == memcmp(buf.getData(), exp, 25));
+
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("code", Element::create(DHO_BOOT_FILE_NAME));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
 }
 
 // This test checks behavior of the configuration parser for option data
@@ -879,6 +1012,9 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
     ASSERT_TRUE(addr_opt);
     EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
 
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, config);
+
     // Explicitly enable csv-format.
     CfgMgr::instance().clear();
     config =
@@ -899,6 +1035,8 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
     ASSERT_TRUE(addr_opt);
     EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
 
+    // To make runToElementTest to work the csv-format must be removed...
+
     // Explicitly disable csv-format and use hex instead.
     CfgMgr::instance().clear();
     config =
@@ -918,6 +1056,9 @@ TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
         OptionCustom>(getOptionPtr(DHCP4_OPTION_SPACE, 16));
     ASSERT_TRUE(addr_opt);
     EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+
+    CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+    cfg2.runCfgOptionsTest(family_, config);
 }
 
 // This test verifies that definitions of standard encapsulated
@@ -962,6 +1103,12 @@ TEST_F(ParseConfigTest, encapsulatedOptionData) {
     EXPECT_EQ("192.0.2.0", ipv4_prefix.toText());
     EXPECT_EQ(64, ipv6_prefix.first.asUnsigned());
     EXPECT_EQ("2001:db8:1::", ipv6_prefix.second.toText());
+
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("code", Element::create(D6O_S46_RULE));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
 }
 
 // This test checks behavior of the configuration parser for option data
@@ -1019,6 +1166,13 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
     ASSERT_EQ(1, opt->getData().size());
     EXPECT_EQ(0, opt->getData()[0]);
 
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->remove("name");
+    opt_data->set("data", Element::create(std::string("00")));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
+
     CfgMgr::instance().clear();
     // When csv-format is not specified, the parser will check if the definition
     // exists or not. Since there is no definition, the parser will accept the
@@ -1040,6 +1194,12 @@ TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
     EXPECT_EQ(0x12, opt->getData()[0]);
     EXPECT_EQ(0x34, opt->getData()[1]);
     EXPECT_EQ(0x56, opt->getData()[2]);
+
+    expected = Element::fromJSON(config);
+    opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->remove("name");
+    CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+    cfg2.runCfgOptionsTest(family_, expected);
 }
 
 // This test verifies that the option name is not mandatory, if the option
@@ -1060,6 +1220,12 @@ TEST_F(ParseConfigTest, optionDataNoName) {
     ASSERT_TRUE(opt);
     ASSERT_EQ(1, opt->getAddresses().size());
     EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("name", Element::create(std::string("dns-servers")));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
 }
 
 // This test verifies that the option code is not mandatory, if the option
@@ -1080,6 +1246,12 @@ TEST_F(ParseConfigTest, optionDataNoCode) {
     ASSERT_TRUE(opt);
     ASSERT_EQ(1, opt->getAddresses().size());
     EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("code", Element::create(D6O_NAME_SERVERS));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
 }
 
 // This test verifies that the option data configuration with a minimal
@@ -1100,6 +1272,13 @@ TEST_F(ParseConfigTest, optionDataMinimal) {
     ASSERT_EQ(1, opt->getAddresses().size());
     EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());
 
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("code", Element::create(D6O_NAME_SERVERS));
+    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
+
     CfgMgr::instance().clear();
     // This time using an option code.
     config =
@@ -1116,6 +1295,13 @@ TEST_F(ParseConfigTest, optionDataMinimal) {
     ASSERT_TRUE(opt);
     ASSERT_EQ(1, opt->getAddresses().size());
     EXPECT_EQ( "2001:db8:1::20", opt->getAddresses()[0].toText());
+
+    expected = Element::fromJSON(config);
+    opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("name", Element::create(std::string("dns-servers")));
+    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+    CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+    cfg2.runCfgOptionsTest(family_, expected);
 }
 
 // This test verifies that the option data configuration with a minimal
@@ -1147,6 +1333,13 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
     EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
     EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());
 
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("code", Element::create(2345));
+    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
+
     CfgMgr::instance().clear();
     // Do the same test but now use an option code.
     config =
@@ -1173,6 +1366,12 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
     EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
     EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());
 
+    expected = Element::fromJSON(config);
+    opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("name", Element::create(std::string("foo-name")));
+    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+    CfgOptionsTest cfg2(CfgMgr::instance().getStagingCfg());
+    cfg2.runCfgOptionsTest(family_, expected);
 }
 
 // This test verifies an empty option data configuration is supported.
@@ -1191,6 +1390,15 @@ TEST_F(ParseConfigTest, emptyOptionData) {
         Option6AddrLst>(getOptionPtr(DHCP6_OPTION_SPACE, D6O_DHCPV4_O_DHCPV6_SERVER));
     ASSERT_TRUE(opt);
     ASSERT_EQ(0, opt->getAddresses().size());
+
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("code", Element::create(D6O_DHCPV4_O_DHCPV6_SERVER));
+    opt_data->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+    opt_data->set("csv-format", Element::create(false));
+    opt_data->set("data", Element::create(std::string("")));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
 }
 
 // This test verifies an option data without suboptions is supported
@@ -1211,6 +1419,15 @@ TEST_F(ParseConfigTest, optionDataNoSubOpion) {
     const OptionPtr opt = getOptionPtr(DHCP4_OPTION_SPACE, DHO_VENDOR_ENCAPSULATED_OPTIONS);
     ASSERT_TRUE(opt);
     ASSERT_EQ(0, opt->getOptions().size());
+
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->set("code", Element::create(DHO_VENDOR_ENCAPSULATED_OPTIONS));
+    opt_data->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+    opt_data->set("csv-format", Element::create(false));
+    opt_data->set("data", Element::create(std::string("")));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
 }
 
 // This tests option-data in CSV format and embedded commas.
@@ -1242,6 +1459,13 @@ TEST_F(ParseConfigTest, commaCSVFormatOptionData) {
     // Verify that the option data is correct.
     string val = "EST5EDT4,M3.2.0/02:00,M11.1.0/02:00";
     EXPECT_EQ(val, opt_str->getValue());
+
+    ElementPtr expected = Element::fromJSON(config);
+    ElementPtr opt_data = expected->get("option-data")->getNonConst(0);
+    opt_data->remove("csv-format");
+    opt_data->set("name", Element::create(std::string("new-posix-timezone")));
+    CfgOptionsTest cfg(CfgMgr::instance().getStagingCfg());
+    cfg.runCfgOptionsTest(family_, expected);
 }
 
 /// The next set of tests check basic operation of the HooksLibrariesParser.
@@ -1306,6 +1530,15 @@ TEST_F(ParseConfigTest, noHooksLibraries) {
     const int rcode = parseConfiguration(config);
     ASSERT_TRUE(rcode == 0) << error_text_;
 
+    // Verify that the configuration object unparses.
+    ConstElementPtr expected;
+    ASSERT_NO_THROW(expected =
+                    Element::fromJSON(config)->get("hooks-libraries"));
+    ASSERT_TRUE(expected);
+    const HooksConfig& cfg =
+        CfgMgr::instance().getStagingCfg()->getHooksConfig();
+    runToElementTest<HooksConfig>(expected, cfg);
+
     // Check that the parser recorded nothing.
     isc::hooks::HookLibsCollection libraries = getLibraries();
     EXPECT_TRUE(libraries.empty());
@@ -1328,6 +1561,15 @@ TEST_F(ParseConfigTest, oneHooksLibrary) {
     const int rcode = parseConfiguration(config);
     ASSERT_TRUE(rcode == 0) << error_text_;
 
+    // Verify that the configuration object unparses.
+    ConstElementPtr expected;
+    ASSERT_NO_THROW(expected =
+                    Element::fromJSON(config)->get("hooks-libraries"));
+    ASSERT_TRUE(expected);
+    const HooksConfig& cfg =
+        CfgMgr::instance().getStagingCfg()->getHooksConfig();
+    runToElementTest<HooksConfig>(expected, cfg);
+
     // Check that the parser recorded a single library.
     isc::hooks::HookLibsCollection libraries = getLibraries();
     ASSERT_EQ(1, libraries.size());
@@ -1353,6 +1595,15 @@ TEST_F(ParseConfigTest, twoHooksLibraries) {
     const int rcode = parseConfiguration(config);
     ASSERT_TRUE(rcode == 0) << error_text_;
 
+    // Verify that the configuration object unparses.
+    ConstElementPtr expected;
+    ASSERT_NO_THROW(expected =
+                    Element::fromJSON(config)->get("hooks-libraries"));
+    ASSERT_TRUE(expected);
+    const HooksConfig& cfg =
+        CfgMgr::instance().getStagingCfg()->getHooksConfig();
+    runToElementTest<HooksConfig>(expected, cfg);
+
     // Check that the parser recorded two libraries in the expected order.
     isc::hooks::HookLibsCollection libraries = getLibraries();
     ASSERT_EQ(2, libraries.size());
@@ -1381,6 +1632,15 @@ TEST_F(ParseConfigTest, reconfigureSameHooksLibraries) {
     int rcode = parseConfiguration(config);
     ASSERT_TRUE(rcode == 0) << error_text_;
 
+    // Verify that the configuration object unparses.
+    ConstElementPtr expected;
+    ASSERT_NO_THROW(expected =
+                    Element::fromJSON(config)->get("hooks-libraries"));
+    ASSERT_TRUE(expected);
+    const HooksConfig& cfg =
+        CfgMgr::instance().getStagingCfg()->getHooksConfig();
+    runToElementTest<HooksConfig>(expected, cfg);
+
     // The previous test shows that the parser correctly recorded the two
     // libraries and that they loaded correctly.
 
@@ -1391,6 +1651,9 @@ TEST_F(ParseConfigTest, reconfigureSameHooksLibraries) {
     // The list has not changed between the two parse operations. However,
     // the paramters (or the files they could point to) could have
     // changed, so the libraries are reloaded anyway.
+    const HooksConfig& cfg2 =
+        CfgMgr::instance().getStagingCfg()->getHooksConfig();
+    runToElementTest<HooksConfig>(expected, cfg2);
     isc::hooks::HookLibsCollection libraries = getLibraries();
     ASSERT_EQ(2, libraries.size());
     EXPECT_EQ(CALLOUT_LIBRARY_1, libraries[0].first);
@@ -1464,6 +1727,15 @@ TEST_F(ParseConfigTest, reconfigureZeroHooksLibraries) {
     rcode = parseConfiguration(config);
     ASSERT_TRUE(rcode == 0) << error_text_;
 
+    // Verify that the configuration object unparses.
+    ConstElementPtr expected;
+    ASSERT_NO_THROW(expected =
+                    Element::fromJSON(config)->get("hooks-libraries"));
+    ASSERT_TRUE(expected);
+    const HooksConfig& cfg =
+        CfgMgr::instance().getStagingCfg()->getHooksConfig();
+    runToElementTest<HooksConfig>(expected, cfg);
+
     // The list has changed, and this is what we should see.
     isc::hooks::HookLibsCollection libraries = getLibraries();
     EXPECT_TRUE(libraries.empty());
@@ -1640,6 +1912,15 @@ TEST_F(ParseConfigTest, HooksLibrariesParameters) {
     const int rcode = parseConfiguration(config);
     ASSERT_EQ(0, rcode);
 
+    // Verify that the configuration object unparses.
+    ConstElementPtr expected;
+    ASSERT_NO_THROW(expected =
+                    Element::fromJSON(config)->get("hooks-libraries"));
+    ASSERT_TRUE(expected);
+    const HooksConfig& cfg =
+        CfgMgr::instance().getStagingCfg()->getHooksConfig();
+    runToElementTest<HooksConfig>(expected, cfg);
+
     // Check that the parser recorded the names.
     isc::hooks::HookLibsCollection libraries = getLibraries();
     ASSERT_EQ(3, libraries.size());
@@ -1732,6 +2013,12 @@ TEST_F(ParseConfigTest, validD2Config) {
     EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
     EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
 
+    // Verify that the configuration object unparses.
+    ConstElementPtr expected;
+    ASSERT_NO_THROW(expected = Element::fromJSON(config_str)->get("dhcp-ddns"));
+    ASSERT_TRUE(expected);
+    runToElementTest<D2ClientConfig>(expected, *d2_client_config);
+
     // Another valid Configuration string.
     // This one is disabled, has IPV6 server ip, control flags false,
     // empty prefix/suffix
@@ -1776,6 +2063,10 @@ TEST_F(ParseConfigTest, validD2Config) {
     EXPECT_EQ(D2ClientConfig::RCM_NEVER, d2_client_config->getReplaceClientNameMode());
     EXPECT_EQ("", d2_client_config->getGeneratedPrefix());
     EXPECT_EQ("", d2_client_config->getQualifyingSuffix());
+
+    ASSERT_NO_THROW(expected = Element::fromJSON(config_str2)->get("dhcp-ddns"));
+    ASSERT_TRUE(expected);
+    runToElementTest<D2ClientConfig>(expected, *d2_client_config);
 }
 
 /// @brief Checks that D2 client can be configured with enable flag of

+ 13 - 7
src/lib/dhcpsrv/tests/duid_config_parser_unittest.cc

@@ -11,6 +11,7 @@
 #include <dhcpsrv/parsers/duid_config_parser.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dhcpsrv/testutils/config_result_check.h>
+#include <testutils/test_to_element.h>
 #include <util/encode/hex.h>
 #include <gtest/gtest.h>
 #include <limits>
@@ -182,13 +183,15 @@ TEST_F(DUIDConfigParserTest, noType) {
 // This test verifies that all parameters can be set.
 TEST_F(DUIDConfigParserTest, allParameters) {
     // Set all parameters.
-    ASSERT_NO_THROW(build("{ \"type\": \"EN\","
-                          "  \"identifier\": \"ABCDEF\","
-                          "  \"time\": 100,"
-                          "  \"htype\": 8,"
-                          "  \"enterprise-id\": 2024,"
-                          "  \"persist\": false"
-                          "}"));
+    std::string config = "{"
+        " \"type\": \"EN\","
+        " \"identifier\": \"ABCDEF\","
+        " \"time\": 100,"
+        " \"htype\": 8,"
+        " \"enterprise-id\": 2024,"
+        " \"persist\": false"
+        "}";
+    ASSERT_NO_THROW(build(config));
 
     // Verify that parameters have been set correctly.
     ASSERT_TRUE(cfg_duid_);
@@ -198,6 +201,9 @@ TEST_F(DUIDConfigParserTest, allParameters) {
     EXPECT_EQ(100, cfg_duid_->getTime());
     EXPECT_EQ(2024, cfg_duid_->getEnterpriseId());
     EXPECT_FALSE(cfg_duid_->persist());
+
+    // Check the config can be got back.
+    isc::test::runToElementTest<CfgDUID>(config, *cfg_duid_);
 }
 
 // Test out of range values for time.

+ 0 - 9
src/lib/dhcpsrv/tests/expiration_config_parser_unittest.cc

@@ -221,15 +221,6 @@ TEST_F(ExpirationConfigParserTest, otherParameters) {
     EXPECT_EQ(20, cfg->getUnwarnedReclaimCycles());
 }
 
-// This test verifies that the exception is thrown if unsupported
-// parameter is specified.
-TEST_F(ExpirationConfigParserTest, invalidParameter) {
-   addParam("reclaim-timer-wait-time", 20);
-   addParam("invalid-parameter", 20);
-
-   EXPECT_THROW(renderConfig(), DhcpConfigError);
-}
-
 // This test verifies that negative parameter values are not allowed.
 TEST_F(ExpirationConfigParserTest, outOfRangeValues) {
     testOutOfRange("reclaim-timer-wait-time",

+ 12 - 2
src/lib/dhcpsrv/tests/ifaces_config_parser_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -10,11 +10,13 @@
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/parsers/ifaces_config_parser.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 
 using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
+using namespace isc::test;
 
 namespace {
 
@@ -60,6 +62,9 @@ TEST_F(IfacesConfigParserTest, interfaces) {
     CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
     ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
 
+    // Check it can be unparsed.
+    runToElementTest<CfgIface>(config, *cfg_iface);
+
     // Open sockets according to the parsed configuration.
     SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
     ASSERT_TRUE(cfg);
@@ -81,6 +86,8 @@ TEST_F(IfacesConfigParserTest, interfaces) {
     cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
     ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
 
+    runToElementTest<CfgIface>(config, *cfg_iface);
+
     cfg = CfgMgr::instance().getStagingCfg();
     ASSERT_NO_THROW(cfg->getCfgIface()->openSockets(AF_INET, 10000));
 
@@ -122,7 +129,7 @@ TEST_F(IfacesConfigParserTest, socketTypeDatagram) {
     CfgIface cfg_ref;
 
     // Configuration with a datagram socket selected.
-    std::string config = "{ ""\"interfaces\": [ ],"
+    std::string config = "{ \"interfaces\": [ ],"
         " \"dhcp-socket-type\": \"udp\" }";
 
     ElementPtr config_element = Element::fromJSON(config);
@@ -132,6 +139,9 @@ TEST_F(IfacesConfigParserTest, socketTypeDatagram) {
     CfgIfacePtr cfg_iface = CfgMgr::instance().getStagingCfg()->getCfgIface();
     ASSERT_NO_THROW(parser.parse(cfg_iface, config_element));
 
+    // Check it can be unparsed.
+    runToElementTest<CfgIface>(config, *cfg_iface);
+
     // Compare the resulting configuration with a reference
     // configuration using the raw socket.
     SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();

+ 17 - 1
src/lib/dhcpsrv/tests/logging_info_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,9 +7,11 @@
 #include <config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/logging_info.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 
 using namespace isc::dhcp;
+using namespace isc::test;
 
 namespace {
 
@@ -69,6 +71,17 @@ TEST_F(LoggingInfoTest, defaults) {
     ASSERT_EQ(1, info_non_verbose.destinations_.size());
     EXPECT_EQ("stdout", info_non_verbose.destinations_[0].output_);
 
+    std::string header = "{\n"
+        "\"name\": \"kea\",\n"
+        "\"output_options\": [ {\n"
+        " \"output\": \"stdout\",\n \"maxsize\": 204800,\n"
+        " \"maxver\": 1,\n \"flush\": true } ],\n"
+        "\"severity\": \"";
+    std::string dbglvl = "\",\n\"debuglevel\": ";
+    std::string trailer = "\n}\n";
+    std::string expected = header + "INFO" + dbglvl + "0" + trailer;
+    runToElementTest<LoggingInfo>(expected, info_non_verbose);
+
     CfgMgr::instance().setVerbose(true);
     LoggingInfo info_verbose;
     EXPECT_EQ("kea", info_verbose.name_);
@@ -77,6 +90,9 @@ TEST_F(LoggingInfoTest, defaults) {
 
     ASSERT_EQ(1, info_verbose.destinations_.size());
     EXPECT_EQ("stdout", info_verbose.destinations_[0].output_);
+
+    expected = header + "DEBUG" + dbglvl + "99" + trailer;
+    runToElementTest<LoggingInfo>(expected, info_verbose);
 }
 
 // Checks if (in)equality operators work for LoggingInfo.

+ 63 - 3
src/lib/dhcpsrv/tests/srv_config_unittest.cc

@@ -10,6 +10,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/srv_config.h>
 #include <dhcpsrv/subnet.h>
+#include <testutils/test_to_element.h>
 #include <gtest/gtest.h>
 
 using namespace isc::asiolink;
@@ -64,9 +65,9 @@ public:
         }
 
         // Build our reference dictionary of client classes
-        ref_dictionary_->addClass("cc1", ExpressionPtr(), CfgOptionPtr());
-        ref_dictionary_->addClass("cc2", ExpressionPtr(), CfgOptionPtr());
-        ref_dictionary_->addClass("cc3", ExpressionPtr(), CfgOptionPtr());
+        ref_dictionary_->addClass("cc1", ExpressionPtr(), "", CfgOptionPtr());
+        ref_dictionary_->addClass("cc2", ExpressionPtr(), "", CfgOptionPtr());
+        ref_dictionary_->addClass("cc3", ExpressionPtr(), "", CfgOptionPtr());
     }
 
 
@@ -427,4 +428,63 @@ TEST_F(SrvConfigTest, hooksLibraries) {
     EXPECT_TRUE(copied.getHooksConfig().equal(conf.getHooksConfig()));
 }
 
+// Verifies that the toElement method works well (tests limited to
+// direct parameters)
+TEST_F(SrvConfigTest, unparse) {
+    SrvConfig conf(32);
+    std::string header4 = "{\n\"Dhcp4\": {\n";
+    std::string header6 = "{\n\"Dhcp6\": {\n";
+
+    std::string defaults = "\"decline-probation-period\": 0,\n";
+    defaults += "\"dhcp4o6-port\": 0,\n";
+    defaults += "\"interfaces-config\": { \"interfaces\": [ ] },\n";
+    defaults += "\"option-def\": [ ],\n";
+    defaults += "\"option-data\": [ ],\n";
+    defaults += "\"expired-leases-processing\": ";
+    defaults += conf.getCfgExpiration()->toElement()->str() + ",\n";
+    defaults += "\"lease-database\": { \"type\": \"memfile\" },\n";
+    defaults += "\"hosts-database\": { },\n";
+    defaults += "\"client-classes\": [ ],\n";
+    defaults += "\"hooks-libraries\": [ ],\n";
+    defaults += "\"dhcp-ddns\": \n";
+    defaults += conf.getD2ClientConfig()->toElement()->str() + ",\n";
+
+    std::string defaults4 = "\"echo-client-id\": true,\n";
+    defaults4 += "\"subnet4\": [ ],\n";
+    defaults4 += "\"host-reservation-identifiers\": ";
+    defaults4 += "[ \"hw-address\", \"duid\", \"circuit-id\" ],\n";
+
+    std::string defaults6 = "\"relay-supplied-options\": [ \"65\" ],\n";
+    defaults6 += "\"subnet6\": [ ],\n";
+    defaults6 += "\"server-id\": ";
+    defaults6 += conf.getCfgDUID()->toElement()->str() + ",\n";
+    defaults6 += "\"host-reservation-identifiers\": ";
+    defaults6 += "[ \"hw-address\", \"duid\" ],\n";
+    defaults6 += "\"dhcp4o6-port\": 0,\n";
+    defaults6 += "\"mac-sources\": [ \"any\" ]\n";
+
+    std::string params = "\"echo-client-id\": true,\n";
+    params += "\"dhcp4o6-port\": 0\n";
+    std::string trailer = "}\n}\n";
+    
+    // Verify DHCPv4
+    CfgMgr::instance().setFamily(AF_INET);
+    isc::test::runToElementTest<SrvConfig>
+        (header4 + defaults + defaults4 + params + trailer, conf);
+
+    // Verify DHCPv6
+    CfgMgr::instance().setFamily(AF_INET6);
+    isc::test::runToElementTest<SrvConfig>
+        (header6 + defaults + defaults6 + trailer, conf);
+
+    // Verify direct non-default parameters
+    CfgMgr::instance().setFamily(AF_INET);
+    conf.setEchoClientId(false);
+    conf.setDhcp4o6Port(6767);
+    params = "\"echo-client-id\": false,\n";
+    params += "\"dhcp4o6-port\": 6767\n";
+    isc::test::runToElementTest<SrvConfig>
+        (header4 + defaults + defaults4 + params + trailer, conf);
+}    
+
 } // end of anonymous namespace

+ 9 - 1
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2015,2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -293,6 +293,7 @@ TEST(Subnet4Test, clientClasses) {
     three_classes.insert("baz");
 
     // No class restrictions defined, any client should be supported
+    EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_TRUE(subnet->clientSupported(no_class));
     EXPECT_TRUE(subnet->clientSupported(foo_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
@@ -300,6 +301,7 @@ TEST(Subnet4Test, clientClasses) {
 
     // Let's allow only clients belonging to "bar" class.
     subnet->allowClientClass("bar");
+    EXPECT_EQ(1, subnet->getClientClasses().size());
 
     EXPECT_FALSE(subnet->clientSupported(no_class));
     EXPECT_FALSE(subnet->clientSupported(foo_class));
@@ -325,6 +327,7 @@ TEST(Subnet4Test, clientClassesMultiple) {
     bar_class.insert("bar");
 
     // No class restrictions defined, any client should be supported
+    EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_TRUE(subnet->clientSupported(no_class));
     EXPECT_TRUE(subnet->clientSupported(foo_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
@@ -332,6 +335,7 @@ TEST(Subnet4Test, clientClassesMultiple) {
     // Let's allow clients belonging to "bar" or "foo" class.
     subnet->allowClientClass("bar");
     subnet->allowClientClass("foo");
+    EXPECT_EQ(2, subnet->getClientClasses().size());
 
     // Class-less clients are to be rejected.
     EXPECT_FALSE(subnet->clientSupported(no_class));
@@ -740,6 +744,7 @@ TEST(Subnet6Test, clientClasses) {
     three_classes.insert("baz");
 
     // No class restrictions defined, any client should be supported
+    EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_TRUE(subnet->clientSupported(no_class));
     EXPECT_TRUE(subnet->clientSupported(foo_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
@@ -747,6 +752,7 @@ TEST(Subnet6Test, clientClasses) {
 
     // Let's allow only clients belonging to "bar" class.
     subnet->allowClientClass("bar");
+    EXPECT_EQ(1, subnet->getClientClasses().size());
 
     EXPECT_FALSE(subnet->clientSupported(no_class));
     EXPECT_FALSE(subnet->clientSupported(foo_class));
@@ -772,6 +778,7 @@ TEST(Subnet6Test, clientClassesMultiple) {
     bar_class.insert("bar");
 
     // No class restrictions defined, any client should be supported
+    EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_TRUE(subnet->clientSupported(no_class));
     EXPECT_TRUE(subnet->clientSupported(foo_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
@@ -779,6 +786,7 @@ TEST(Subnet6Test, clientClassesMultiple) {
     // Let's allow only clients belonging to "foo" or "bar" class.
     subnet->allowClientClass("foo");
     subnet->allowClientClass("bar");
+    EXPECT_EQ(2, subnet->getClientClasses().size());
 
     // Class-less clients are to be rejected.
     EXPECT_FALSE(subnet->clientSupported(no_class));

+ 1 - 3
src/lib/hooks/hooks_config.cc

@@ -113,11 +113,9 @@ HooksConfig::toElement() const {
         ElementPtr map = Element::createMap();
         // Set the library name
         map->set("library", Element::create(hl->first));
-        // Set parameters
+        // Set parameters (not set vs set empty map)
         if (!isNull(hl->second)) {
             map->set("parameters", hl->second);
-        } else {
-            map->set("parameters", Element::createMap());
         }
         // Push to the list
         result->add(map);

+ 1 - 0
src/lib/testutils/Makefile.am

@@ -9,6 +9,7 @@ noinst_LTLIBRARIES = libkea-testutils.la
 
 libkea_testutils_la_SOURCES  = io_utils.cc io_utils.h
 libkea_testutils_la_SOURCES += log_utils.cc log_utils.h
+libkea_testutils_la_SOURCES += test_to_element.cc test_to_element.h
 libkea_testutils_la_SOURCES += unix_control_client.h unix_control_client.cc
 libkea_testutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 libkea_testutils_la_LIBADD  = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la