Browse Source

[5110_fix] re-applying patch made on trac5110

Tomek Mrugalski 8 years ago
parent
commit
8390f9de7e
40 changed files with 12875 additions and 1660 deletions
  1. 32 0
      src/bin/d2/Makefile.am
  2. 143 111
      src/bin/d2/d2_cfg_mgr.cc
  3. 43 27
      src/bin/d2/d2_cfg_mgr.h
  4. 118 502
      src/bin/d2/d2_config.cc
  5. 62 407
      src/bin/d2/d2_config.h
  6. 16 1
      src/bin/d2/d2_controller.cc
  7. 12 1
      src/bin/d2/d2_controller.h
  8. 3607 0
      src/bin/d2/d2_lexer.cc
  9. 733 0
      src/bin/d2/d2_lexer.ll
  10. 2221 0
      src/bin/d2/d2_parser.cc
  11. 1562 0
      src/bin/d2/d2_parser.h
  12. 737 0
      src/bin/d2/d2_parser.yy
  13. 154 0
      src/bin/d2/d2_simple_parser.cc
  14. 84 0
      src/bin/d2/d2_simple_parser.h
  15. 193 0
      src/bin/d2/location.hh
  16. 159 0
      src/bin/d2/parser_context.cc
  17. 298 0
      src/bin/d2/parser_context.h
  18. 20 0
      src/bin/d2/parser_context_decl.h
  19. 181 0
      src/bin/d2/position.hh
  20. 158 0
      src/bin/d2/stack.hh
  21. 4 0
      src/bin/d2/tests/Makefile.am
  22. 211 314
      src/bin/d2/tests/d2_cfg_mgr_unittests.cc
  23. 2 1
      src/bin/d2/tests/d2_controller_unittests.cc
  24. 7 6
      src/bin/d2/tests/d2_process_unittests.cc
  25. 1149 0
      src/bin/d2/tests/d2_simple_parser_unittest.cc
  26. 25 0
      src/bin/d2/tests/nc_test_utils.cc
  27. 1 0
      src/bin/d2/tests/nc_test_utils.h
  28. 586 0
      src/bin/d2/tests/parser_unittest.cc
  29. 36 0
      src/bin/d2/tests/parser_unittest.h
  30. 64 62
      src/bin/d2/tests/testdata/d2_cfg_tests.json
  31. 1 1
      src/lib/cc/data.h
  32. 10 2
      src/lib/cc/simple_parser.cc
  33. 21 25
      src/lib/process/d_cfg_mgr.cc
  34. 32 32
      src/lib/process/d_cfg_mgr.h
  35. 15 5
      src/lib/process/d_controller.cc
  36. 55 4
      src/lib/process/d_controller.h
  37. 2 21
      src/lib/process/tests/d_cfg_mgr_unittests.cc
  38. 29 0
      src/lib/process/tests/d_controller_unittests.cc
  39. 35 71
      src/lib/process/testutils/d_test_stubs.cc
  40. 57 67
      src/lib/process/testutils/d_test_stubs.h

+ 32 - 0
src/bin/d2/Makefile.am

@@ -64,7 +64,10 @@ libd2_la_SOURCES += d2_log.cc d2_log.h
 libd2_la_SOURCES += d2_process.cc d2_process.h
 libd2_la_SOURCES += d2_config.cc d2_config.h
 libd2_la_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+libd2_la_SOURCES += d2_lexer.ll location.hh position.hh stack.hh
+libd2_la_SOURCES += d2_parser.cc d2_parser.h
 libd2_la_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
+libd2_la_SOURCES += d2_simple_parser.cc d2_simple_parser.h
 libd2_la_SOURCES += d2_update_message.cc d2_update_message.h
 libd2_la_SOURCES += d2_update_mgr.cc d2_update_mgr.h
 libd2_la_SOURCES += d2_zone.cc d2_zone.h
@@ -73,6 +76,7 @@ libd2_la_SOURCES += nc_add.cc nc_add.h
 libd2_la_SOURCES += nc_remove.cc nc_remove.h
 libd2_la_SOURCES += nc_trans.cc nc_trans.h
 libd2_la_SOURCES += d2_controller.cc d2_controller.h
+libd2_la_SOURCES += parser_context.cc parser_context.h parser_context_decl.h
 
 nodist_libd2_la_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
@@ -115,3 +119,31 @@ endif
 
 kea_dhcp_ddnsdir = $(pkgdatadir)
 kea_dhcp_ddns_DATA = dhcp-ddns.spec
+
+if GENERATE_PARSER
+
+parser: d2_lexer.cc location.hh position.hh stack.hh d2_parser.cc d2_parser.h
+	@echo "Flex/bison files regenerated"
+
+# --- Flex/Bison stuff below --------------------------------------------------
+# When debugging grammar issues, it's useful to add -v to bison parameters.
+# bison will generate parser.output file that explains the whole grammar.
+# It can be used to manually follow what's going on in the parser.
+# This is especially useful if yydebug_ is set to 1 as that variable
+# will cause parser to print out its internal state.
+# Call flex with -s to check that the default rule can be suppressed
+# Call bison with -W to get warnings like unmarked empty rules
+# Note C++11 deprecated register still used by flex < 2.6.0
+location.hh position.hh stack.hh d2_parser.cc d2_parser.h: d2_parser.yy
+	$(YACC) --defines=d2_parser.h --report=all --report-file=d2_parser.report -o d2_parser.cc d2_parser.yy
+
+d2_lexer.cc: d2_lexer.ll
+	$(LEX) --prefix d2_parser_ -o d2_lexer.cc d2_lexer.ll
+
+else
+
+parser location.hh position.hh stack.hh d2_parser.cc d2_parser.h d2_lexer.cc:
+	@echo Parser generation disabled. Configure with --enable-generate-parser to enable it.
+
+endif
+                                                                                        

+ 143 - 111
src/bin/d2/d2_cfg_mgr.cc

@@ -8,6 +8,7 @@
 
 #include <d2/d2_log.h>
 #include <d2/d2_cfg_mgr.h>
+#include <d2/d2_simple_parser.h>
 #include <util/encode/hex.h>
 
 #include <boost/foreach.hpp>
@@ -197,96 +198,164 @@ D2CfgMgr::getConfigSummary(const uint32_t) {
     return (getD2Params()->getConfigSummary());
 }
 
-void
-D2CfgMgr::buildParams(isc::data::ConstElementPtr params_config) {
-    // Base class build creates parses and invokes build on each parser.
-    // This populate the context scalar stores with all of the parameters.
-    DCfgMgrBase::buildParams(params_config);
-
-    // Fetch and validate the parameters from the context to create D2Params.
-    // We validate them here rather than just relying on D2Param constructor
-    // so we can spit out config text position info with errors.
+namespace {
 
-    // Fetch and validate ip_address.
-    D2CfgContextPtr context = getD2CfgContext();
-    isc::dhcp::StringStoragePtr strings = context->getStringStorage();
-    asiolink::IOAddress ip_address(D2Params::DFT_IP_ADDRESS);
+template <typename int_type> int_type
+getInt(const std::string& name, isc::data::ConstElementPtr value) {
+    int64_t val_int = value->intValue();
+    if ((val_int < std::numeric_limits<int_type>::min()) ||
+        (val_int > std::numeric_limits<int_type>::max())) {
+        isc_throw(D2CfgError, "out of range value (" << val_int
+                  << ") specified for parameter '" << name
+                  << "' (" << value->getPosition() << ")");
+    }
+    return (static_cast<int_type>(val_int));
+}
 
-    std::string ip_address_str = strings->getOptionalParam("ip-address",
-                                                            D2Params::
-                                                            DFT_IP_ADDRESS);
+isc::asiolink::IOAddress
+getIOAddress(const std::string& name, isc::data::ConstElementPtr value) {
+    std::string str = value->stringValue();
     try {
-        ip_address = asiolink::IOAddress(ip_address_str);
+        return (isc::asiolink::IOAddress(str));
     } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, "IP address invalid : \""
-                  << ip_address_str << "\" ("
-                  << strings->getPosition("ip-address") << ")");
-    }
-
-    if ((ip_address.toText() == "0.0.0.0") || (ip_address.toText() == "::")) {
-        isc_throw(D2CfgError, "IP address cannot be \"" << ip_address << "\" ("
-                   << strings->getPosition("ip-address") << ")");
+        isc_throw(D2CfgError, "invalid address (" << str
+                  << ") specified for parameter '" << name
+                  << "' (" << value->getPosition() << ")");
     }
+}
 
-    // Fetch and validate port.
-    isc::dhcp::Uint32StoragePtr ints = context->getUint32Storage();
-    uint32_t port = ints->getOptionalParam("port", D2Params::DFT_PORT);
-
-    if (port == 0) {
-        isc_throw(D2CfgError, "port cannot be 0 ("
-                  << ints->getPosition("port") << ")");
-    }
-
-    // Fetch and validate dns_server_timeout.
-    uint32_t dns_server_timeout
-        = ints->getOptionalParam("dns-server-timeout",
-                                 D2Params::DFT_DNS_SERVER_TIMEOUT);
-
-    if (dns_server_timeout < 1) {
-        isc_throw(D2CfgError, "DNS server timeout must be larger than 0 ("
-                  << ints->getPosition("dns-server-timeout") << ")");
+dhcp_ddns::NameChangeProtocol
+getProtocol(const std::string& name, isc::data::ConstElementPtr value) {
+    std::string str = value->stringValue();
+    try {
+        return (dhcp_ddns::stringToNcrProtocol(str));
+    } catch (const std::exception& ex) {
+        isc_throw(D2CfgError,
+                  "invalid NameChangeRequest protocol (" << str
+                  << ") specified for parameter '" << name
+                  << "' (" << value->getPosition() << ")");
     }
+}
 
-    // Fetch and validate ncr_protocol.
-    dhcp_ddns::NameChangeProtocol ncr_protocol;
+dhcp_ddns::NameChangeFormat
+getFormat(const std::string& name, isc::data::ConstElementPtr value) {
+    std::string str = value->stringValue();
     try {
-        ncr_protocol = dhcp_ddns::
-                       stringToNcrProtocol(strings->
-                                           getOptionalParam("ncr-protocol",
-                                                            D2Params::
-                                                            DFT_NCR_PROTOCOL));
+        return (dhcp_ddns::stringToNcrFormat(str));
     } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, "ncr-protocol : "
-                  << ex.what() << " ("
-                  << strings->getPosition("ncr-protocol") << ")");
+        isc_throw(D2CfgError,
+                  "invalid NameChangeRequest format (" << str
+                  << ") specified for parameter '" << name
+                  << "' (" << value->getPosition() << ")");
     }
+}
 
-    if (ncr_protocol != dhcp_ddns::NCR_UDP) {
-        isc_throw(D2CfgError, "ncr-protocol : "
-                  << dhcp_ddns::ncrProtocolToString(ncr_protocol)
-                  << " is not yet supported ("
-                  << strings->getPosition("ncr-protocol") << ")");
-    }
+} // anon
 
-    // Fetch and validate ncr_format.
-    dhcp_ddns::NameChangeFormat ncr_format;
+void
+D2CfgMgr::parseElement(const std::string& element_id,
+                       isc::data::ConstElementPtr element) {
     try {
-        ncr_format = dhcp_ddns::
-                     stringToNcrFormat(strings->
-                                       getOptionalParam("ncr-format",
-                                                        D2Params::
-                                                        DFT_NCR_FORMAT));
+        // Get D2 specific context.
+        D2CfgContextPtr context = getD2CfgContext();
+
+        if ((element_id == "ip-address") ||
+            (element_id == "ncr-protocol") ||
+            (element_id == "ncr-format") ||
+            (element_id == "port")  ||
+            (element_id == "dns-server-timeout"))  {
+            // global scalar params require nothing extra be done
+        } else if (element_id == "tsig-keys") {
+            TSIGKeyInfoListParser parser;
+            context->setKeys(parser.parse(element));
+        } else if (element_id ==  "forward-ddns") {
+            DdnsDomainListMgrParser parser;
+            DdnsDomainListMgrPtr mgr = parser.parse(element, element_id,
+                                                    context->getKeys());
+            context->setForwardMgr(mgr);
+        } else if (element_id ==  "reverse-ddns") {
+            DdnsDomainListMgrParser parser;
+            DdnsDomainListMgrPtr mgr = parser.parse(element, element_id,
+                                                    context->getKeys());
+            context->setReverseMgr(mgr);
+        } else {
+            // Shouldn't occur if the JSON parser is doing its job.
+            isc_throw(D2CfgError, "Unsupported element: "
+                      << element_id << element->getPosition());
+        }
+    } catch (const D2CfgError& ex) {
+        // Should already have a specific error and position info
+        throw ex;
     } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, "ncr-format : "
-                  << ex.what() << " ("
-                  << strings->getPosition("ncr-format") << ")");
+        isc_throw(D2CfgError, "element: " << element_id << " : "  << ex.what()
+                              << element->getPosition());
     }
+};
+
+void
+D2CfgMgr::setCfgDefaults(isc::data::ElementPtr mutable_config) {
+    D2SimpleParser::setAllDefaults(mutable_config);
+}
+
+void
+D2CfgMgr::buildParams(isc::data::ConstElementPtr params_config) {
+
+    // Base class build creates parses and invokes build on each parser.
+    // This populate the context scalar stores with all of the parameters.
+    DCfgMgrBase::buildParams(params_config);
+
+    // Fetch the parameters in the config, performing any logcial
+    // validation required.
+    asiolink::IOAddress ip_address(0);
+    uint32_t port = 0;
+    uint32_t dns_server_timeout = 0;
+    dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
+    dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
+
+    // Assumes that params_config has had defaults added
+    BOOST_FOREACH(isc::dhcp::ConfigPair param, params_config->mapValue()) {
+        std::string entry(param.first);
+        isc::data::ConstElementPtr value(param.second);
+        try {
+            if (entry == "ip-address") {
+                ip_address = getIOAddress(entry, value);
+                if ((ip_address.toText() == "0.0.0.0") ||
+                    (ip_address.toText() == "::")) {
+                    isc_throw(D2CfgError, "IP address cannot be \""
+                              << ip_address << "\""
+                            << " (" << value->getPosition() << ")");
+                }
+            } else if (entry == "port") {
+                port = getInt<uint32_t>(entry, value);
+            } else if (entry == "dns-server-timeout") {
+                dns_server_timeout = getInt<uint32_t>(entry, value);
+            } else if (entry == "ncr-protocol") {
+                ncr_protocol = getProtocol(entry, value);
+                if (ncr_protocol != dhcp_ddns::NCR_UDP) {
+                    isc_throw(D2CfgError, "ncr-protocol : "
+                              << dhcp_ddns::ncrProtocolToString(ncr_protocol)
+                              << " is not yet supported "
+                              << " (" << value->getPosition() << ")");
+                }
+            } else if (entry == "ncr-format") {
+                ncr_format = getFormat(entry, value);
+                if (ncr_format != dhcp_ddns::FMT_JSON) {
+                    isc_throw(D2CfgError, "NCR Format:"
+                              << dhcp_ddns::ncrFormatToString(ncr_format)
+                              << " is not yet supported"
+                              << " (" << value->getPosition() << ")");
+                }
+            } else {
+                isc_throw(D2CfgError,
+                          "unsupported parameter '" << entry
+                          << " (" << value->getPosition() << ")");
+            }
+        } catch (const isc::data::TypeError&) {
+            isc_throw(D2CfgError,
+                      "invalid value type specified for parameter '" << entry
+                      << " (" << value->getPosition() << ")");
+        }
 
-    if (ncr_format != dhcp_ddns::FMT_JSON) {
-        isc_throw(D2CfgError, "NCR Format:"
-                  << dhcp_ddns::ncrFormatToString(ncr_format)
-                  << " is not yet supported ("
-                  << strings->getPosition("ncr-format") << ")");
     }
 
     // Attempt to create the new client config. This ought to fly as
@@ -294,44 +363,7 @@ D2CfgMgr::buildParams(isc::data::ConstElementPtr params_config) {
     D2ParamsPtr params(new D2Params(ip_address, port, dns_server_timeout,
                                     ncr_protocol, ncr_format));
 
-    context->getD2Params() = params;
-}
-
-isc::dhcp::ParserPtr
-D2CfgMgr::createConfigParser(const std::string& config_id,
-                             const isc::data::Element::Position& pos) {
-    // Get D2 specific context.
-    D2CfgContextPtr context = getD2CfgContext();
-
-    // Create parser instance based on element_id.
-    isc::dhcp::ParserPtr parser;
-    if ((config_id.compare("port") == 0) ||
-        (config_id.compare("dns-server-timeout") == 0)) {
-        parser.reset(new isc::dhcp::Uint32Parser(config_id,
-                                                 context->getUint32Storage()));
-    } else if ((config_id.compare("ip-address") == 0) ||
-        (config_id.compare("ncr-protocol") == 0) ||
-        (config_id.compare("ncr-format") == 0)) {
-        parser.reset(new isc::dhcp::StringParser(config_id,
-                                                 context->getStringStorage()));
-    } else if (config_id ==  "forward-ddns") {
-        parser.reset(new DdnsDomainListMgrParser("forward-ddns",
-                                                 context->getForwardMgr(),
-                                                 context->getKeys()));
-    } else if (config_id ==  "reverse-ddns") {
-        parser.reset(new DdnsDomainListMgrParser("reverse-ddns",
-                                                 context->getReverseMgr(),
-                                                 context->getKeys()));
-    } else if (config_id ==  "tsig-keys") {
-        parser.reset(new TSIGKeyInfoListParser("tsig-key-list",
-                                               context->getKeys()));
-    } else {
-        isc_throw(NotImplemented,
-                  "parser error: D2CfgMgr parameter not supported : "
-                  " (" << config_id << pos << ")");
-    }
-
-    return (parser);
+    getD2CfgContext()->getD2Params() = params;
 }
 
 }; // end of isc::dhcp namespace

+ 43 - 27
src/bin/d2/d2_cfg_mgr.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
@@ -57,6 +57,12 @@ public:
         return (forward_mgr_);
     }
 
+    /// @brief Sets the forward domain list manager
+    /// @param forward_mgr pointer to the new forward manager
+    void setForwardMgr(DdnsDomainListMgrPtr forward_mgr) {
+        forward_mgr_ = forward_mgr;
+    }
+
     /// @brief Fetches the reverse DNS domain list manager.
     ///
     /// @return returns a pointer to the reverse manager.
@@ -64,6 +70,12 @@ public:
         return (reverse_mgr_);
     }
 
+    /// @brief Sets the reverse domain list manager
+    /// @param reverse_mgr pointer to the new reverse manager
+    void setReverseMgr(DdnsDomainListMgrPtr reverse_mgr) {
+        reverse_mgr_ = reverse_mgr;
+    }
+
     /// @brief Fetches the map of TSIG keys.
     ///
     /// @return returns a pointer to the key map.
@@ -71,6 +83,13 @@ public:
         return (keys_);
     }
 
+    /// @brief Sets the map of TSIG keys
+    ///
+    /// @param keys pointer to the new TSIG key map
+    void setKeys(const TSIGKeyInfoMapPtr& keys) {
+        keys_ = keys;
+    }
+
 protected:
     /// @brief Copy constructor for use by derivations in clone().
     D2CfgContext(const D2CfgContext& rhs);
@@ -239,6 +258,29 @@ public:
     virtual std::string getConfigSummary(const uint32_t selection);
 
 protected:
+
+    /// @brief Parses an element using alternate parsers
+    ///
+    /// Each element to be parsed is passed first into this method to allow
+    /// it to be processed by SimpleParser derivations if they've been
+    /// implemented. The method should return true if it has processed the
+    /// element or false if the element should be passed onto the original
+    /// DhcpConfigParer mechanisms.  This method is invoked in both
+    /// @c DCfgMgrBase::buildParams() and DCfgMgrBase::buildAndCommit().
+    ///
+    /// @param element_id name of the element as it is expected in the cfg
+    /// @param element value of the element as ElementPtr
+    virtual void parseElement(const std::string& element_id,
+                              isc::data::ConstElementPtr element);
+
+    /// @brief Adds default values to the given config
+    ///
+    /// Adds the D2 default values to the configuration Element map. This
+    /// method is invoked by @c DCfgMgrBase::paserConfig().
+    ///
+    /// @param mutable_config - configuration to which defaults should be added
+    virtual void setCfgDefaults(isc::data::ElementPtr mutable_config);
+
     /// @brief Performs the parsing of the given "params" element.
     ///
     /// Iterates over the set of parameters, creating a parser based on the
@@ -259,32 +301,6 @@ protected:
     /// -# ncr_format is invalid, currently only FMT_JSON is supported
     virtual void buildParams(isc::data::ConstElementPtr params_config);
 
-    /// @brief Given an element_id returns an instance of the appropriate
-    /// parser.
-    ///
-    /// It is responsible for top-level or outermost DHCP-DDNS configuration
-    /// elements (see dhcp-ddns.spec):
-    ///     -# ip_address
-    ///     -# port
-    ///     -# dns_server_timeout
-    ///     -# ncr_protocol
-    ///     -# ncr_format
-    ///     -# tsig_keys
-    ///     -# forward_ddns
-    ///     -# reverse_ddns
-    ///
-    /// @param element_id is the string name of the element as it will appear
-    /// in the configuration set.
-    /// @param pos position within the configuration text (or file) of element
-    /// to be parsed.  This is passed for error messaging.
-    ///
-    /// @return returns a ParserPtr to the parser instance.
-    /// @throw throws DCfgMgrBaseError if an error occurs.
-    virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& element_id,
-                       const isc::data::Element::Position& pos =
-                       isc::data::Element::Position());
-
     /// @brief Creates an new, blank D2CfgContext context
     ///
     /// This method is used at the beginning of configuration process to

+ 118 - 502
src/bin/d2/d2_config.cc

@@ -13,7 +13,6 @@
 #include <asiolink/io_error.h>
 
 #include <boost/foreach.hpp>
-#include <boost/lexical_cast.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 
@@ -27,12 +26,6 @@ namespace d2 {
 
 // *********************** D2Params  *************************
 
-const char *D2Params::DFT_IP_ADDRESS = "127.0.0.1";
-const size_t D2Params::DFT_PORT = 53001;
-const size_t D2Params::DFT_DNS_SERVER_TIMEOUT = 100;
-const char *D2Params::DFT_NCR_PROTOCOL = "UDP";
-const char *D2Params::DFT_NCR_FORMAT = "JSON";
-
 D2Params::D2Params(const isc::asiolink::IOAddress& ip_address,
                    const size_t port,
                    const size_t dns_server_timeout,
@@ -47,9 +40,8 @@ D2Params::D2Params(const isc::asiolink::IOAddress& ip_address,
 }
 
 D2Params::D2Params()
-    : ip_address_(isc::asiolink::IOAddress(DFT_IP_ADDRESS)),
-     port_(DFT_PORT),
-     dns_server_timeout_(DFT_DNS_SERVER_TIMEOUT),
+    : ip_address_(isc::asiolink::IOAddress("127.0.0.1")),
+     port_(53001), dns_server_timeout_(100),
      ncr_protocol_(dhcp_ddns::NCR_UDP),
      ncr_format_(dhcp_ddns::FMT_JSON) {
     validateContents();
@@ -189,9 +181,6 @@ TSIGKeyInfo::remakeKey() {
 }
 
 // *********************** DnsServerInfo  *************************
-
-const char* DnsServerInfo::EMPTY_IP_STR = "0.0.0.0";
-
 DnsServerInfo::DnsServerInfo(const std::string& hostname,
                              isc::asiolink::IOAddress ip_address, uint32_t port,
                              bool enabled)
@@ -336,120 +325,37 @@ DdnsDomainListMgr::matchDomain(const std::string& fqdn, DdnsDomainPtr& domain) {
 
 // *********************** TSIGKeyInfoParser  *************************
 
-TSIGKeyInfoParser::TSIGKeyInfoParser(const std::string& entry_name,
-                                     TSIGKeyInfoMapPtr keys)
-    : entry_name_(entry_name), keys_(keys), local_scalars_() {
-    if (!keys_) {
-        isc_throw(D2CfgError, "TSIGKeyInfoParser ctor:"
-                  " key storage cannot be null");
-    }
-}
-
-TSIGKeyInfoParser::~TSIGKeyInfoParser() {
-}
-
-void
-TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
-    isc::dhcp::ConfigPair config_pair;
-    // For each element in the key configuration:
-    // 1. Create a parser for the element.
-    // 2. Invoke the parser's build method passing in the element's
-    // configuration.
-    // 3. Invoke the parser's commit method to store the element's parsed
-    // data to the parser's local storage.
-    BOOST_FOREACH (config_pair, key_config->mapValue()) {
-        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first,
-                                                       config_pair.second->
-                                                       getPosition()));
-        parser->build(config_pair.second);
-        parser->commit();
-    }
-
-    std::string name;
-    std::string algorithm;
-    uint32_t digestbits = 0;
-    std::string secret;
-    std::map<std::string, isc::data::Element::Position> pos;
-
-    // Fetch the key's parsed scalar values from parser's local storage.
-    // Only digestbits is optional and doesn't throw when missing
-    try {
-        pos["name"] = local_scalars_.getParam("name", name);
-        pos["algorithm"] = local_scalars_.getParam("algorithm", algorithm);
-        pos["digest-bits"] = local_scalars_.getParam("digest-bits", digestbits,
-                                                     DCfgContextBase::OPTIONAL);
-        pos["secret"] = local_scalars_.getParam("secret", secret);
-    } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, "TSIG Key incomplete : " << ex.what()
-                  << " (" << key_config->getPosition() << ")");
-    }
-
-    // Name cannot be blank.
-    if (name.empty()) {
-        isc_throw(D2CfgError, "TSIG key must specify name (" << pos["name"] << ")");
-    }
-
-    // Currently, the premise is that key storage is always empty prior to
-    // parsing so we are always adding keys never replacing them. Duplicates
-    // are not allowed and should be flagged as a configuration error.
-    if (keys_->find(name) != keys_->end()) {
-        isc_throw(D2CfgError, "Duplicate TSIG key name specified : " << name
-                              << " (" << pos["name"] << ")");
-    }
+TSIGKeyInfoPtr
+TSIGKeyInfoParser::parse(data::ConstElementPtr key_config) {
+    std::string name = getString(key_config, "name");
+    std::string algorithm = getString(key_config, "algorithm");
+    uint32_t digestbits = getInteger(key_config, "digest-bits");
+    std::string secret = getString(key_config, "secret");
 
     // Algorithm must be valid.
     try {
         TSIGKeyInfo::stringToAlgorithmName(algorithm);
     } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, "TSIG key : " << ex.what() << " (" << pos["algorithm"] << ")");
-    }
-
-    // Not zero Digestbits must be an integral number of octets, greater
-    // than 80 and the half of the full length
-    if (digestbits > 0) {
-      if ((digestbits % 8) != 0) {
-          isc_throw(D2CfgError, "Invalid TSIG key digest_bits specified : " <<
-                                digestbits << " (" << pos["digest-bits"] << ")");
-      }
-      if (digestbits < 80) {
-          isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
-                                digestbits << " (" << pos["digest-bits"] << ")");
-      }
-      if (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA224_STR)) {
-          if (digestbits < 112) {
-              isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
-                                    digestbits << " (" << pos["digest-bits"]
-                                    << ")");
-          }
-      } else if (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA256_STR)) {
-          if (digestbits < 128) {
-              isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
-                                    digestbits << " (" << pos["digest-bits"]
-                                    << ")");
-          }
-      } else if (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA384_STR)) {
-          if (digestbits < 192) {
-              isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
-                                    digestbits << " (" << pos["digest-bits"]
-                                    << ")");
-          }
-      } else if (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA512_STR)) {
-          if (digestbits < 256) {
-              isc_throw(D2CfgError, "TSIG key digest_bits too small : " <<
-                                    digestbits << " (" << pos["digest-bits"]
-                                    << ")");
-          }
-      }
-    }
-
-    // Secret cannot be blank.
-    // Cryptolink lib doesn't offer any way to validate these. As long as it
-    // isn't blank we'll accept it.  If the content is bad, the call to in
-    // TSIGKeyInfo::remakeKey() made in the TSIGKeyInfo ctor will throw.
-    // We'll deal with that below.
-    if (secret.empty()) {
-        isc_throw(D2CfgError, "TSIG key must specify secret (" << pos["secret"] << ")");
-    }
+        isc_throw(D2CfgError, "tsig-key : " << ex.what()
+                  << " (" << getPosition("algorithm", key_config) << ")");
+    }
+
+    // Non-zero digest-bits must be an integral number of octets, greater
+    // than 80 and at least half of the algorithm key length. It defaults
+    // to zero and JSON parsing ensures it's a multiple of 8.
+    if ((digestbits > 0) &&
+        ((digestbits < 80) ||
+         (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA224_STR)
+          && (digestbits < 112)) ||
+         (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA256_STR)
+          && (digestbits < 128)) ||
+         (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA384_STR)
+          && (digestbits < 192)) ||
+         (boost::iequals(algorithm, TSIGKeyInfo::HMAC_SHA512_STR)
+          && (digestbits < 256)))) {
+        isc_throw(D2CfgError, "tsig-key: digest-bits too small : "
+                  << " (" << getPosition("digest-bits", key_config) << ")");
+        }
 
     // Everything should be valid, so create the key instance.
     // It is possible for the asiodns::dns::TSIGKey create to fail such as
@@ -458,134 +364,43 @@ TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
     try {
         key_info.reset(new TSIGKeyInfo(name, algorithm, secret, digestbits));
     } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, ex.what() << " (" << key_config->getPosition() << ")");
-
-    }
-
-    // Add the new TSIGKeyInfo to the key storage.
-    (*keys_)[name]=key_info;
-}
-
-isc::dhcp::ParserPtr
-TSIGKeyInfoParser::createConfigParser(const std::string& config_id,
-                                      const isc::data::Element::Position& pos) {
-    DhcpConfigParser* parser = NULL;
-    // Based on the configuration id of the element, create the appropriate
-    // parser. Scalars are set to use the parser's local scalar storage.
-    if ((config_id == "name")  ||
-        (config_id == "algorithm") ||
-        (config_id == "secret")) {
-        parser = new isc::dhcp::StringParser(config_id,
-                                             local_scalars_.getStringStorage());
-    } else if (config_id == "digest-bits") {
-        parser = new isc::dhcp::Uint32Parser(config_id,
-                                             local_scalars_.getUint32Storage());
-    } else {
-        isc_throw(NotImplemented,
-                  "parser error: TSIGKeyInfo parameter not supported: "
-                  << config_id << " (" << pos << ")");
+        isc_throw(D2CfgError, ex.what() << " ("
+                  << key_config->getPosition() << ")");
     }
 
-    // Return the new parser instance.
-    return (isc::dhcp::ParserPtr(parser));
-}
-
-void
-TSIGKeyInfoParser::commit() {
+    return (key_info);
 }
 
 // *********************** TSIGKeyInfoListParser  *************************
 
-TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
-                                       TSIGKeyInfoMapPtr keys)
-    :list_name_(list_name), keys_(keys), local_keys_(new TSIGKeyInfoMap()),
-     parsers_() {
-    if (!keys_) {
-        isc_throw(D2CfgError, "TSIGKeyInfoListParser ctor:"
-                  " key storage cannot be null");
-    }
-}
-
-TSIGKeyInfoListParser::~TSIGKeyInfoListParser() {
-}
-
-void
-TSIGKeyInfoListParser::
-build(isc::data::ConstElementPtr key_list) {
-    int i = 0;
-    isc::data::ConstElementPtr key_config;
-    // For each key element in the key list:
-    // 1. Create a parser for the key element.
-    // 2. Invoke the parser's build method passing in the key's
-    // configuration.
-    // 3. Add the parser to a local collection of parsers.
+TSIGKeyInfoMapPtr
+TSIGKeyInfoListParser::parse(data::ConstElementPtr key_list) {
+    TSIGKeyInfoMapPtr keys(new TSIGKeyInfoMap());
+    data::ConstElementPtr key_config;
+    TSIGKeyInfoParser key_parser;
     BOOST_FOREACH(key_config, key_list->listValue()) {
-        // Create a name for the parser based on its position in the list.
-        std::string entry_name = boost::lexical_cast<std::string>(i++);
-        isc::dhcp::ParserPtr parser(new TSIGKeyInfoParser(entry_name,
-                                                            local_keys_));
-        parser->build(key_config);
-        parsers_.push_back(parser);
-    }
-
-    // Now that we know we have a valid list, commit that list to the
-    // area given to us during construction (i.e. to the d2 context).
-    *keys_ = *local_keys_;
-}
-
-void
-TSIGKeyInfoListParser::commit() {
-    // Invoke commit on each server parser. This will cause each one to
-    // create it's server instance and commit it to storage.
-    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
-        parser->commit();
-    }
-}
+        TSIGKeyInfoPtr key = key_parser.parse(key_config);
 
-// *********************** DnsServerInfoParser  *************************
+        // Duplicates are not allowed and should be flagged as an error.
+        if (keys->find(key->getName()) != keys->end()) {
+            isc_throw(D2CfgError, "Duplicate TSIG key name specified : "
+                      << key->getName()
+                      << " (" << getPosition("name", key_config) << ")");
+        }
 
-DnsServerInfoParser::DnsServerInfoParser(const std::string& entry_name,
-    DnsServerInfoStoragePtr servers)
-    : entry_name_(entry_name), servers_(servers), local_scalars_() {
-    if (!servers_) {
-        isc_throw(D2CfgError, "DnsServerInfoParser ctor:"
-                  " server storage cannot be null");
+        (*keys)[key->getName()] = key;
     }
-}
 
-DnsServerInfoParser::~DnsServerInfoParser() {
+    return (keys);
 }
 
-void
-DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
-    isc::dhcp::ConfigPair config_pair;
-    // For each element in the server configuration:
-    // 1. Create a parser for the element.
-    // 2. Invoke the parser's build method passing in the element's
-    // configuration.
-    // 3. Invoke the parser's commit method to store the element's parsed
-    // data to the parser's local storage.
-    BOOST_FOREACH (config_pair, server_config->mapValue()) {
-        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first,
-                                                       config_pair.second->
-                                                       getPosition()));
-        parser->build(config_pair.second);
-        parser->commit();
-    }
-
-    std::string hostname;
-    std::string ip_address;
-    uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
-    std::map<std::string, isc::data::Element::Position> pos;
+// *********************** DnsServerInfoParser  *************************
 
-    // Fetch the server configuration's parsed scalar values from parser's
-    // local storage.  They're all optional, so no try-catch here.
-    pos["hostname"] = local_scalars_.getParam("hostname", hostname,
-                                              DCfgContextBase::OPTIONAL);
-    pos["ip-address"] = local_scalars_.getParam("ip-address", ip_address,
-                                                DCfgContextBase::OPTIONAL);
-    pos["port"] =  local_scalars_.getParam("port", port,
-                                           DCfgContextBase::OPTIONAL);
+DnsServerInfoPtr
+DnsServerInfoParser::parse(data::ConstElementPtr server_config) {
+    std::string hostname = getString(server_config, "hostname");
+    std::string ip_address = getString(server_config, "ip-address");
+    uint32_t port = getInteger(server_config, "port");
 
     // The configuration must specify one or the other.
     if (hostname.empty() == ip_address.empty()) {
@@ -594,13 +409,7 @@ DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
                   << " (" << server_config->getPosition() << ")");
     }
 
-    // Port cannot be zero.
-    if (port == 0) {
-        isc_throw(D2CfgError, "Dns Server : port cannot be 0"
-                  << " (" << pos["port"] << ")");
-    }
-
-    DnsServerInfoPtr serverInfo;
+    DnsServerInfoPtr server_info;
     if (!hostname.empty()) {
         /// @todo when resolvable hostname is supported we create the entry
         /// as follows:
@@ -608,7 +417,6 @@ DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
         /// @code
         /// // When  hostname is specified, create a valid, blank IOAddress
         /// // and then create the DnsServerInfo.
-        /// isc::asiolink::IOAddress io_addr(DnsServerInfo::EMPTY_IP_STR);
         /// serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
         ///
         /// @endcode
@@ -617,172 +425,52 @@ DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
         /// processing.
         /// Until then we'll throw unsupported.
         isc_throw(D2CfgError, "Dns Server : hostname is not yet supported"
-                  << " (" << pos["hostname"] << ")");
+                  << " (" << getPosition("hostname", server_config) << ")");
     } else {
         try {
             // Create an IOAddress from the IP address string given and then
             // create the DnsServerInfo.
             isc::asiolink::IOAddress io_addr(ip_address);
-            serverInfo.reset(new DnsServerInfo(hostname, io_addr, port));
+            server_info.reset(new DnsServerInfo(hostname, io_addr, port));
         } catch (const isc::asiolink::IOError& ex) {
             isc_throw(D2CfgError, "Dns Server : invalid IP address : "
-                      << ip_address << " (" << pos["ip-address"] << ")");
+                      << ip_address
+                      << " (" << getPosition("ip-address", server_config) << ")");
         }
     }
 
-    // Add the new DnsServerInfo to the server storage.
-    servers_->push_back(serverInfo);
-}
-
-isc::dhcp::ParserPtr
-DnsServerInfoParser::createConfigParser(const std::string& config_id,
-                                        const isc::data::Element::
-                                        Position& pos) {
-    DhcpConfigParser* parser = NULL;
-    // Based on the configuration id of the element, create the appropriate
-    // parser. Scalars are set to use the parser's local scalar storage.
-    if ((config_id == "hostname")  ||
-        (config_id == "ip-address")) {
-        parser = new isc::dhcp::StringParser(config_id,
-                                             local_scalars_.getStringStorage());
-    } else if (config_id == "port") {
-        parser = new isc::dhcp::Uint32Parser(config_id,
-                                             local_scalars_.getUint32Storage());
-    } else {
-        isc_throw(NotImplemented,
-                  "parser error: DnsServerInfo parameter not supported: "
-                  << config_id << " (" << pos << ")");
-    }
-
-    // Return the new parser instance.
-    return (isc::dhcp::ParserPtr(parser));
-}
-
-void
-DnsServerInfoParser::commit() {
+    return(server_info);
 }
 
 // *********************** DnsServerInfoListParser  *************************
 
-DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
-                                       DnsServerInfoStoragePtr servers)
-    :list_name_(list_name), servers_(servers), parsers_() {
-    if (!servers_) {
-        isc_throw(D2CfgError, "DdnsServerInfoListParser ctor:"
-                  " server storage cannot be null");
-    }
-}
-
-DnsServerInfoListParser::~DnsServerInfoListParser(){
-}
-
-void
-DnsServerInfoListParser::
-build(isc::data::ConstElementPtr server_list){
-    int i = 0;
-    isc::data::ConstElementPtr server_config;
-    // For each server element in the server list:
-    // 1. Create a parser for the server element.
-    // 2. Invoke the parser's build method passing in the server's
-    // configuration.
-    // 3. Add the parser to a local collection of parsers.
+DnsServerInfoStoragePtr
+DnsServerInfoListParser::parse(data::ConstElementPtr server_list) {
+    DnsServerInfoStoragePtr servers(new DnsServerInfoStorage());
+    data::ConstElementPtr server_config;
+    DnsServerInfoParser parser;
     BOOST_FOREACH(server_config, server_list->listValue()) {
-        // Create a name for the parser based on its position in the list.
-        std::string entry_name = boost::lexical_cast<std::string>(i++);
-        isc::dhcp::ParserPtr parser(new DnsServerInfoParser(entry_name,
-                                                            servers_));
-        parser->build(server_config);
-        parsers_.push_back(parser);
-    }
-
-    // Domains must have at least one server.
-    if (parsers_.size() == 0) {
-        isc_throw (D2CfgError, "Server List must contain at least one server"
-                   << " (" << server_list->getPosition() << ")");
+        DnsServerInfoPtr server = parser.parse(server_config);
+        servers->push_back(server);
     }
-}
 
-void
-DnsServerInfoListParser::commit() {
-    // Invoke commit on each server parser.
-    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
-        parser->commit();
-    }
+    return (servers);
 }
 
 // *********************** DdnsDomainParser  *************************
 
-DdnsDomainParser::DdnsDomainParser(const std::string& entry_name,
-                                   DdnsDomainMapPtr domains,
-                                   TSIGKeyInfoMapPtr keys)
-    : entry_name_(entry_name), domains_(domains), keys_(keys),
-    local_servers_(new DnsServerInfoStorage()), local_scalars_() {
-    if (!domains_) {
-        isc_throw(D2CfgError,
-                  "DdnsDomainParser ctor, domain storage cannot be null");
-    }
-}
-
-
-DdnsDomainParser::~DdnsDomainParser() {
-}
-
-void
-DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
-    // For each element in the domain configuration:
-    // 1. Create a parser for the element.
-    // 2. Invoke the parser's build method passing in the element's
-    // configuration.
-    // 3. Invoke the parser's commit method to store the element's parsed
-    // data to the parser's local storage.
-    isc::dhcp::ConfigPair config_pair;
-    BOOST_FOREACH(config_pair, domain_config->mapValue()) {
-        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first,
-                                                       config_pair.second->
-                                                       getPosition()));
-        parser->build(config_pair.second);
-        parser->commit();
-    }
-
-    // Now construct the domain.
-    std::string name;
-    std::string key_name;
-    std::map<std::string, isc::data::Element::Position> pos;
-
-
-    // Fetch the parsed scalar values from parser's local storage.
-    // Any required that are missing will throw.
-    try {
-        pos["name"] = local_scalars_.getParam("name", name);
-        pos["key-name"] = local_scalars_.getParam("key-name", key_name,
-                                                  DCfgContextBase::OPTIONAL);
-    } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, "DdnsDomain incomplete : " << ex.what()
-                  << " (" << domain_config->getPosition() << ")");
-    }
-
-    // Blank domain names are not allowed.
-    if (name.empty()) {
-        isc_throw(D2CfgError, "DndsDomain : name cannot be blank ("
-                   << pos["name"] << ")");
-    }
-
-    // Currently, the premise is that domain storage is always empty
-    // prior to parsing so always adding domains never replacing them.
-    // Duplicates are not allowed and should be flagged as a configuration
-    // error.
-    if (domains_->find(name) != domains_->end()) {
-        isc_throw(D2CfgError, "Duplicate domain specified:" << name
-                  << " (" << pos["name"] << ")");
-    }
+DdnsDomainPtr DdnsDomainParser::parse(data::ConstElementPtr domain_config,
+                                      const TSIGKeyInfoMapPtr keys) {
+    std::string name = getString(domain_config, "name");
+    std::string key_name = getString(domain_config, "key-name");
 
     // Key name is optional. If it is not blank, then find the key in the
-    /// list of defined keys.
+    // list of defined keys.
     TSIGKeyInfoPtr tsig_key_info;
     if (!key_name.empty()) {
-        if (keys_) {
-            TSIGKeyInfoMap::iterator kit = keys_->find(key_name);
-            if (kit != keys_->end()) {
+        if (keys) {
+            TSIGKeyInfoMap::iterator kit = keys->find(key_name);
+            if (kit != keys->end()) {
                 tsig_key_info = kit->second;
             }
         }
@@ -790,147 +478,75 @@ DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
         if (!tsig_key_info) {
             isc_throw(D2CfgError, "DdnsDomain : " << name
                       << " specifies an undefined key: " << key_name
-                      << " (" << pos["key-name"] << ")");
+                      << " (" << getPosition("key-name", domain_config) << ")");
         }
     }
 
-    // Instantiate the new domain and add it to domain storage.
-    DdnsDomainPtr domain(new DdnsDomain(name, local_servers_, tsig_key_info));
-
-    // Add the new domain to the domain storage.
-    (*domains_)[name] = domain;
-}
+    // Parse the list of DNS servers
+    data::ConstElementPtr servers_config;
+    try {
+        servers_config = domain_config->get("dns-servers");
+    } catch (const std::exception& ex) {
+        isc_throw(D2CfgError, "DdnsDomain : missing dns-server list"
+                      << " (" << servers_config->getPosition() << ")");
+    }
 
-isc::dhcp::ParserPtr
-DdnsDomainParser::createConfigParser(const std::string& config_id,
-                                     const isc::data::Element::Position& pos) {
-    DhcpConfigParser* parser = NULL;
-    // Based on the configuration id of the element, create the appropriate
-    // parser. Scalars are set to use the parser's local scalar storage.
-    if ((config_id == "name")  ||
-        (config_id == "key-name")) {
-        parser = new isc::dhcp::StringParser(config_id,
-                                             local_scalars_.getStringStorage());
-    } else if (config_id == "dns-servers") {
-       // Server list parser is given in our local server storage. It will pass
-       // this down to its server parsers and is where they will write their
-       // server instances upon commit.
-       parser = new DnsServerInfoListParser(config_id, local_servers_);
-    } else {
-       isc_throw(NotImplemented,
-                "parser error: DdnsDomain parameter not supported: "
-                << config_id << " (" << pos << ")");
+    DnsServerInfoListParser server_parser;
+    DnsServerInfoStoragePtr servers =  server_parser.parse(servers_config);
+    if (servers->size() == 0) {
+        isc_throw(D2CfgError, "DNS server list cannot be empty"
+                    << servers_config->getPosition());
     }
 
-    // Return the new domain parser instance.
-    return (isc::dhcp::ParserPtr(parser));
-}
+    // Instantiate the new domain and add it to domain storage.
+    DdnsDomainPtr domain(new DdnsDomain(name, servers, tsig_key_info));
 
-void
-DdnsDomainParser::commit() {
+    return(domain);
 }
 
 // *********************** DdnsDomainListParser  *************************
 
-DdnsDomainListParser::DdnsDomainListParser(const std::string& list_name,
-                                           DdnsDomainMapPtr domains,
-                                           TSIGKeyInfoMapPtr keys)
-    :list_name_(list_name), domains_(domains), keys_(keys), parsers_() {
-    if (!domains_) {
-        isc_throw(D2CfgError, "DdnsDomainListParser ctor:"
-                  " domain storage cannot be null");
-    }
-}
+DdnsDomainMapPtr DdnsDomainListParser::parse(data::ConstElementPtr domain_list,
+                                             const TSIGKeyInfoMapPtr keys) {
+    DdnsDomainMapPtr domains(new DdnsDomainMap());
+    DdnsDomainParser parser;
+    data::ConstElementPtr domain_config;
+    BOOST_FOREACH(domain_config, domain_list->listValue()) {
+        DdnsDomainPtr domain = parser.parse(domain_config, keys);
 
-DdnsDomainListParser::~DdnsDomainListParser(){
-}
+        // Duplicates are not allowed
+        if (domains->find(domain->getName()) != domains->end()) {
+            isc_throw(D2CfgError, "Duplicate domain specified:"
+                      << domain->getName()
+                      << " (" << getPosition("name", domain_config) << ")");
+        }
 
-void
-DdnsDomainListParser::
-build(isc::data::ConstElementPtr domain_list){
-    // For each domain element in the domain list:
-    // 1. Create a parser for the domain element.
-    // 2. Invoke the parser's build method passing in the domain's
-    // configuration.
-    // 3. Add the parser to the local collection of parsers.
-    int i = 0;
-    isc::data::ConstElementPtr domain_config;
-    BOOST_FOREACH(domain_config, domain_list->listValue()) {
-        std::string entry_name = boost::lexical_cast<std::string>(i++);
-        isc::dhcp::ParserPtr parser(new DdnsDomainParser(entry_name,
-                                                         domains_, keys_));
-        parser->build(domain_config);
-        parsers_.push_back(parser);
+        (*domains)[domain->getName()] = domain;
     }
-}
 
-void
-DdnsDomainListParser::commit() {
-    // Invoke commit on each server parser. This will cause each one to
-    // create it's server instance and commit it to storage.
-    BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
-        parser->commit();
-    }
+    return (domains);
 }
 
-
 // *********************** DdnsDomainListMgrParser  *************************
 
-DdnsDomainListMgrParser::DdnsDomainListMgrParser(const std::string& entry_name,
-                              DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys)
-    : entry_name_(entry_name), mgr_(mgr), keys_(keys),
-    local_domains_(new DdnsDomainMap()), local_scalars_() {
-}
-
-
-DdnsDomainListMgrParser::~DdnsDomainListMgrParser() {
-}
-
-void
-DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
-    // For each element in the domain manager configuration:
-    // 1. Create a parser for the element.
-    // 2. Invoke the parser's build method passing in the element's
-    // configuration.
-    // 3. Invoke the parser's commit method to store the element's parsed
-    // data to the parser's local storage.
-    isc::dhcp::ConfigPair config_pair;
-    BOOST_FOREACH(config_pair, domain_config->mapValue()) {
-        isc::dhcp::ParserPtr parser(createConfigParser(config_pair.first,
-                                                       config_pair.second->
-                                                       getPosition()));
-        parser->build(config_pair.second);
-        parser->commit();
-    }
+DdnsDomainListMgrPtr
+DdnsDomainListMgrParser::parse(data::ConstElementPtr mgr_config,
+                               const std::string& mgr_name,
+                               const TSIGKeyInfoMapPtr keys) {
+    DdnsDomainListMgrPtr mgr(new DdnsDomainListMgr(mgr_name));
 
-    // Add the new domain to the domain storage.
-    mgr_->setDomains(local_domains_);
-}
+    // Parse the list of domains
+    data::ConstElementPtr domains_config = mgr_config->get("ddns-domains");
+    if (domains_config) {
+        DdnsDomainListParser domain_parser;
+        DdnsDomainMapPtr domains =  domain_parser.parse(domains_config, keys);
 
-isc::dhcp::ParserPtr
-DdnsDomainListMgrParser::createConfigParser(const std::string& config_id,
-                                            const isc::data::Element::
-                                            Position& pos) {
-    DhcpConfigParser* parser = NULL;
-    if (config_id == "ddns-domains") {
-       // Domain list parser is given our local domain storage. It will pass
-       // this down to its domain parsers and is where they will write their
-       // domain instances upon commit.
-       parser = new DdnsDomainListParser(config_id, local_domains_, keys_);
-    } else {
-       isc_throw(NotImplemented, "parser error: "
-                 "DdnsDomainListMgr parameter not supported: " << config_id
-                 << " (" << pos << ")");
+        // Add the new domain to the domain storage.
+        mgr->setDomains(domains);
     }
 
-    // Return the new domain parser instance.
-    return (isc::dhcp::ParserPtr(parser));
+    return(mgr);
 }
 
-void
-DdnsDomainListMgrParser::commit() {
-}
-
-
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 62 - 407
src/bin/d2/d2_config.h

@@ -9,6 +9,7 @@
 
 #include <asiolink/io_service.h>
 #include <cc/data.h>
+#include <cc/simple_parser.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dns/tsig.h>
 #include <exceptions/exceptions.h>
@@ -36,10 +37,6 @@ namespace d2 {
 /// a name, the algorithm method name, optionally the minimum truncated
 /// length, and its secret key component.
 ///
-/// @todo  NOTE that TSIG configuration parsing is functional, the use of
-/// TSIG Keys during the actual DNS update transactions is not.  This will be
-/// implemented in a future release.
-///
 /// Each managed domain list consists of a list one or more domains and is
 /// represented by the class DdnsDomainListMgr.
 ///
@@ -53,9 +50,6 @@ namespace d2 {
 /// that the application can carry out DNS update exchanges with it. Servers
 /// are represented by the class, DnsServerInfo.
 ///
-/// The configuration specification for use with Kea is detailed in the file
-/// dhcp-ddns.spec.
-///
 /// The parsing class hierarchy reflects this same scheme.  Working top down:
 ///
 /// A DdnsDomainListMgrParser parses a managed domain list entry. It handles
@@ -140,17 +134,6 @@ public:
 /// @brief Acts as a storage vault for D2 global scalar parameters
 class D2Params {
 public:
-    /// @brief Default configuration constants.
-    //@{
-    /// @todo For now these are hard-coded as configuration layer cannot
-    /// readily provide them (see Trac #3358).
-    static const char *DFT_IP_ADDRESS;
-    static const size_t DFT_PORT;
-    static const size_t DFT_DNS_SERVER_TIMEOUT;
-    static const char *DFT_NCR_PROTOCOL;
-    static const char *DFT_NCR_FORMAT;
-    //@}
-
     /// @brief Constructor
     ///
     /// @param ip_address IP address at which D2 should listen for NCRs
@@ -424,14 +407,9 @@ typedef boost::shared_ptr<TSIGKeyInfoMap> TSIGKeyInfoMapPtr;
 /// updates.
 class DnsServerInfo {
 public:
-
     /// @brief defines DNS standard port value
     static const uint32_t STANDARD_DNS_PORT = 53;
 
-    /// @brief defines an "empty" string version of an ip address.
-    static const char* EMPTY_IP_STR;
-
-
     /// @brief Constructor
     ///
     /// @param hostname is the resolvable name of the server. If not blank,
@@ -732,474 +710,151 @@ private:
 /// @brief Defines a pointer for DScalarContext instances.
 typedef boost::shared_ptr<DScalarContext> DScalarContextPtr;
 
-/// @brief Parser for  TSIGKeyInfo
+/// @brief Parser for TSIGKeyInfo
 ///
-/// This class parses the configuration element "tsig-key" defined in
-/// src/bin/d2/dhcp-ddns.spec and creates an instance of a TSIGKeyInfo.
-class TSIGKeyInfoParser : public  isc::dhcp::DhcpConfigParser {
+/// This class parses the configuration element "tsig-key"
+/// and creates an instance of a TSIGKeyInfo.
+class TSIGKeyInfoParser : public  data::SimpleParser {
 public:
-    /// @brief Constructor
-    ///
-    /// @param entry_name is an arbitrary label assigned to this configuration
-    /// definition. Since servers are specified in a list this value is likely
-    /// be something akin to "key:0", set during parsing.
-    /// @param keys is a pointer to the storage area to which the parser
-    /// should commit the newly created TSIGKeyInfo instance.
-    TSIGKeyInfoParser(const std::string& entry_name, TSIGKeyInfoMapPtr keys);
-
-    /// @brief Destructor
-    virtual ~TSIGKeyInfoParser();
-
-    /// @brief Performs the actual parsing of the given  "tsig-key" element.
+    /// @brief Performs the actual parsing of the given "tsig-key" element.
     ///
     /// Parses a configuration for the elements needed to instantiate a
     /// TSIGKeyInfo, validates those entries, creates a TSIGKeyInfo instance
-    /// then attempts to add to a list of keys
     ///
     /// @param key_config is the "tsig-key" configuration to parse
-    virtual void build(isc::data::ConstElementPtr key_config);
-
-    /// @brief Creates a parser for the given "tsig-key" member element id.
-    ///
-    /// The key elements currently supported are(see dhcp-ddns.spec):
-    ///   1. name
-    ///   2. algorithm
-    ///   3. digestbits
-    ///   4. secret
     ///
-    /// @param config_id is the "item_name" for a specific member element of
-    /// the "tsig-key" specification.
-    /// @param pos position within the configuration text (or file) of element
-    /// to be parsed.  This is passed for error messaging.
-    ///
-    /// @return returns a pointer to newly created parser.
-    ///
-    /// @throw D2CfgError if configuration contains an unknown parameter
-    virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& config_id,
-                       const isc::data::Element::Position& pos =
-                       isc::data::Element::ZERO_POSITION());
+    /// @return pointer to the new TSIGKeyInfo instance
+    TSIGKeyInfoPtr parse(data::ConstElementPtr key_config);
 
-    /// @brief Commits the TSIGKeyInfo configuration
-    /// Currently this method is a NOP, as the key instance is created and
-    /// then added to a local list of keys in build().
-    virtual void commit();
-
-private:
-    /// @brief Arbitrary label assigned to this parser instance.
-    /// Since servers are specified in a list this value is likely be something
-    /// akin to "key:0", set during parsing.  Primarily here for diagnostics.
-    std::string entry_name_;
-
-    /// @brief Pointer to the storage area to which the parser should commit
-    /// the newly created TSIGKeyInfo instance. This is given to us as a
-    /// constructor argument by an upper level.
-    TSIGKeyInfoMapPtr keys_;
-
-    /// @brief Local storage area for scalar parameter values. Use to hold
-    /// data until time to commit.
-    DScalarContext local_scalars_;
 };
 
 /// @brief Parser for a list of TSIGKeyInfos
 ///
 /// This class parses a list of "tsig-key" configuration elements.
-/// (see src/bin/d2/dhcp-ddns.spec). The TSIGKeyInfo instances are added
-/// to the given storage upon commit.
-class TSIGKeyInfoListParser : public isc::dhcp::DhcpConfigParser {
+/// The TSIGKeyInfo instances are added to the given storage upon commit.
+class TSIGKeyInfoListParser : public data::SimpleParser {
 public:
-
-    /// @brief Constructor
-    ///
-    /// @param list_name is an arbitrary label assigned to this parser instance.
-    /// @param keys is a pointer to the storage area to which the parser
-    /// should commit the newly created TSIGKeyInfo instance.
-    TSIGKeyInfoListParser(const std::string& list_name, TSIGKeyInfoMapPtr keys);
-
-    /// @brief Destructor
-    virtual ~TSIGKeyInfoListParser();
-
     /// @brief Performs the parsing of the given list "tsig-key" elements.
     ///
-    /// It iterates over each key entry in the list:
-    ///   1. Instantiate a TSIGKeyInfoParser for the entry
-    ///   2. Pass the element configuration to the parser's build method
-    ///   3. Add the parser instance to local storage
+    /// Creates an empty TSIGKeyInfoMap
     ///
-    /// The net effect is to parse all of the key entries in the list
-    /// prepping them for commit.
+    /// Instantiates a TSIGKeyInfoParser
+    /// It iterates over each key entry in the list:
+    ///   2. Pass the element configuration to the parser's parse method
+    ///   3. Add the new TSIGKeyInfo instance to the key map
     ///
     /// @param key_list_config is the list of "tsig_key" elements to parse.
-    virtual void build(isc::data::ConstElementPtr key_list_config);
-
-    /// @brief Commits the list of TSIG keys
     ///
-    /// Iterates over the internal list of TSIGKeyInfoParsers, invoking
-    /// commit on each one.  Then commits the local list of keys to
-    /// storage.
-    virtual void commit();
-
-private:
-    /// @brief Arbitrary label assigned to this parser instance.
-    std::string list_name_;
-
-    /// @brief Pointer to the storage area to which the parser should commit
-    /// the list of newly created TSIGKeyInfo instances. This is given to us
-    /// as a constructor argument by an upper level.
-    TSIGKeyInfoMapPtr keys_;
-
-    /// @brief Local storage area to which individual key parsers commit.
-    TSIGKeyInfoMapPtr local_keys_;
-
-    /// @brief Local storage of TSIGKeyInfoParser instances
-    isc::dhcp::ParserCollection parsers_;
+    /// @return a map containing the TSIGKeyInfo instances
+    TSIGKeyInfoMapPtr parse(data::ConstElementPtr key_list_config);
 };
 
 /// @brief Parser for  DnsServerInfo
 ///
-/// This class parses the configuration element "dns-server" defined in
-/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DnsServerInfo.
-class DnsServerInfoParser : public  isc::dhcp::DhcpConfigParser {
+/// This class parses the configuration element "dns-server"
+/// and creates an instance of a DnsServerInfo.
+class DnsServerInfoParser : public  data::SimpleParser {
 public:
-    /// @brief Constructor
-    ///
-    /// @param entry_name is an arbitrary label assigned to this configuration
-    /// definition. Since servers are specified in a list this value is likely
-    /// be something akin to "server:0", set during parsing.
-    /// @param servers is a pointer to the storage area to which the parser
-    /// should commit the newly created DnsServerInfo instance.
-    DnsServerInfoParser(const std::string& entry_name,
-                        DnsServerInfoStoragePtr servers);
-
-    /// @brief Destructor
-    virtual ~DnsServerInfoParser();
-
     /// @brief Performs the actual parsing of the given  "dns-server" element.
     ///
     /// Parses a configuration for the elements needed to instantiate a
     /// DnsServerInfo, validates those entries, creates a DnsServerInfo instance
-    /// then attempts to add to a list of  servers.
+    /// and returns it.
     ///
     /// @param server_config is the "dns-server" configuration to parse
     ///
+    /// @return a pointer to the newly created server instance
+    ///
     /// @throw D2CfgError if:
     /// -# hostname is not blank, hostname is not yet supported
     /// -# ip_address is invalid
     /// -# port is 0
-    virtual void build(isc::data::ConstElementPtr server_config);
-
-    /// @brief Creates a parser for the given "dns-server" member element id.
-    ///
-    /// The server elements currently supported are(see dhcp-ddns.spec):
-    ///   1. hostname
-    ///   2. ip_address
-    ///   3. port
-    ///
-    /// @param config_id is the "item_name" for a specific member element of
-    /// the "dns-server" specification.
-    /// @param pos position within the configuration text (or file) of element
-    /// to be parsed.  This is passed for error messaging.
-    ///
-    /// @return returns a pointer to newly created parser.
-    ///
-    /// @throw D2CfgError if configuration contains an unknown parameter
-    virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& config_id,
-                       const isc::data::Element::Position& =
-                       isc::data::Element::ZERO_POSITION());
-
-    /// @brief Commits the configured DnsServerInfo
-    /// Currently this method is a NOP, as the server instance is created and
-    /// then added to the list of servers in build().
-    virtual void commit();
-
-private:
-    /// @brief Arbitrary label assigned to this parser instance.
-    /// Since servers are specified in a list this value is likely be something
-    /// akin to "server:0", set during parsing.  Primarily here for diagnostics.
-    std::string entry_name_;
-
-    /// @brief Pointer to the storage area to which the parser should commit
-    /// the newly created DnsServerInfo instance. This is given to us as a
-    /// constructor argument by an upper level.
-    DnsServerInfoStoragePtr servers_;
-
-    /// @brief Local storage area for scalar parameter values. Use to hold
-    /// data until time to commit.
-    DScalarContext local_scalars_;
+    DnsServerInfoPtr parse(data::ConstElementPtr server_config);
 };
 
 /// @brief Parser for a list of DnsServerInfos
 ///
 /// This class parses a list of "dns-server" configuration elements.
-/// (see src/bin/d2/dhcp-ddns.spec). The DnsServerInfo instances are added
+/// The DnsServerInfo instances are added
 /// to the given storage upon commit.
-class DnsServerInfoListParser : public isc::dhcp::DhcpConfigParser {
+class DnsServerInfoListParser : public data::SimpleParser{
 public:
-
-    /// @brief Constructor
-    ///
-    /// @param list_name is an arbitrary label assigned to this parser instance.
-    /// @param servers is a pointer to the storage area to which the parser
-    /// should commit the newly created DnsServerInfo instance.
-    DnsServerInfoListParser(const std::string& list_name,
-                            DnsServerInfoStoragePtr servers);
-
-    /// @brief Destructor
-    virtual ~DnsServerInfoListParser();
-
     /// @brief Performs the actual parsing of the given list "dns-server"
     /// elements.
-    /// It iterates over each server entry in the list:
-    ///   1. Instantiate a DnsServerInfoParser for the entry
-    ///   2. Pass the element configuration to the parser's build method
-    ///   3. Add the parser instance to local storage
     ///
-    /// The net effect is to parse all of the server entries in the list
-    /// prepping them for commit.
+    /// Creates an empty server list
+    /// It iterates over each server entry in the list:
+    ///   1. Creates a server instance by passing the entry to @c
+    ///   DnsSeverInfoParser::parse()
+    ///   2. Adds the server to the server list
     ///
     /// @param server_list_config is the list of "dns-server" elements to parse.
-    virtual void build(isc::data::ConstElementPtr server_list_config);
-
-    /// @brief Commits the list of DnsServerInfos
-    ///
-    /// Iterates over the internal list of DdnsServerInfoParsers, invoking
-    /// commit on each one.
-    virtual void commit();
-
-private:
-    /// @brief Arbitrary label assigned to this parser instance.
-    std::string list_name_;
-
-    /// @brief Pointer to the storage area to which the parser should commit
-    /// the list of newly created DnsServerInfo instances. This is given to us
-    /// as a constructor argument by an upper level.
-    DnsServerInfoStoragePtr servers_;
-
-    /// @brief Local storage of DnsServerInfoParser instances
-    isc::dhcp::ParserCollection parsers_;
+    /// @return A pointer to the new, populated server list
+    DnsServerInfoStoragePtr parse(data::ConstElementPtr server_list_config);
 };
 
 /// @brief Parser for  DdnsDomain
 ///
-/// This class parses the configuration element "ddns-domain" defined in
-/// src/bin/d2/dhcp-ddns.spec and creates an instance of a DdnsDomain.
-class DdnsDomainParser : public isc::dhcp::DhcpConfigParser {
+/// This class parses the configuration element "ddns-domain"
+/// and creates an instance of a DdnsDomain.
+class DdnsDomainParser : public data::SimpleParser {
 public:
-    /// @brief Constructor
-    ///
-    /// @param entry_name is an arbitrary label assigned to this configuration
-    /// definition. Since domains are specified in a list this value is likely
-    /// be something akin to "forward-ddns:0", set during parsing.
-    /// @param domains is a pointer to the storage area to which the parser
-    /// @param keys is a pointer to a map of the defined TSIG keys.
-    /// should commit the newly created DdnsDomain instance.
-    DdnsDomainParser(const std::string& entry_name, DdnsDomainMapPtr domains,
-                     TSIGKeyInfoMapPtr keys);
-
-    /// @brief Destructor
-    virtual ~DdnsDomainParser();
-
     /// @brief Performs the actual parsing of the given  "ddns-domain" element.
     ///
     /// Parses a configuration for the elements needed to instantiate a
-    /// DdnsDomain, validates those entries, creates a DdnsDomain instance
-    /// then attempts to add it to a list of domains.
+    /// DdnsDomain, validates those entries, and creates a DdnsDomain instance.
     ///
     /// @param domain_config is the "ddns-domain" configuration to parse
-    virtual void build(isc::data::ConstElementPtr domain_config);
-
-    /// @brief Creates a parser for the given "ddns-domain" member element id.
-    ///
-    /// The domain elements currently supported are(see dhcp-ddns.spec):
-    ///   1. name
-    ///   2. key_name
-    ///   3. dns_servers
-    ///
-    /// @param config_id is the "item_name" for a specific member element of
-    /// the "ddns-domain" specification.
-    /// @param pos position within the configuration text (or file) of element
-    /// to be parsed.  This is passed for error messaging.
+    /// @param keys map of defined TSIG keys
     ///
-    /// @return returns a pointer to newly created parser.
-    ///
-    /// @throw D2CfgError if configuration contains an unknown parameter
-    virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& config_id,
-                       const isc::data::Element::Position& pos =
-                       isc::data::Element::ZERO_POSITION());
-
-    /// @brief Commits the configured DdnsDomain
-    /// Currently this method is a NOP, as the domain instance is created and
-    /// then added to the list of domains in build().
-    virtual void commit();
-
-private:
-
-    /// @brief Arbitrary label assigned to this parser instance.
-    std::string entry_name_;
-
-    /// @brief Pointer to the storage area to which the parser should commit
-    /// the newly created DdnsDomain instance. This is given to us as a
-    /// constructor argument by an upper level.
-    DdnsDomainMapPtr domains_;
-
-    /// @brief Pointer to the map of defined TSIG keys.
-    /// This map is passed into us and contains all of the TSIG keys defined
-    /// for this configuration.  It is used to validate the key name entry of
-    /// DdnsDomains that specify one.
-    TSIGKeyInfoMapPtr keys_;
-
-    /// @brief Local storage for DnsServerInfo instances. This is passed into
-    /// DnsServerInfoListParser(s), which in turn passes it into each
-    /// DnsServerInfoParser.  When the DnsServerInfoParsers "commit" they add
-    /// their server instance to this storage.
-    DnsServerInfoStoragePtr local_servers_;
-
-    /// @brief Local storage area for scalar parameter values. Use to hold
-    /// data until time to commit.
-    DScalarContext local_scalars_;
+    /// @return a pointer to the new domain instance
+    DdnsDomainPtr parse(data::ConstElementPtr domain_config,
+                        const TSIGKeyInfoMapPtr keys);
 };
 
 /// @brief Parser for a list of DdnsDomains
 ///
-/// This class parses a list of "ddns-domain" configuration elements.
-/// (see src/bin/d2/dhcp-ddns.spec). The DdnsDomain instances are added
-/// to the given storage upon commit.
-class DdnsDomainListParser : public isc::dhcp::DhcpConfigParser {
+/// This class parses a list of "ddns-domain" configuration elements
+/// into a map of DdnsDomains.
+class DdnsDomainListParser : public data::SimpleParser {
 public:
-
-    /// @brief Constructor
-    ///
-    /// @param list_name is an arbitrary label assigned to this parser instance.
-    /// @param domains is a pointer to the storage area to which the parser
-    /// @param keys is a pointer to a map of the defined TSIG keys.
-    /// should commit the newly created DdnsDomain instance.
-    DdnsDomainListParser(const std::string& list_name,
-                         DdnsDomainMapPtr domains, TSIGKeyInfoMapPtr keys);
-
-    /// @brief Destructor
-    virtual ~DdnsDomainListParser();
-
     /// @brief Performs the actual parsing of the given list "ddns-domain"
     /// elements.
+    /// Creates a new DdnsDomain map
     /// It iterates over each domain entry in the list:
-    ///   1. Instantiate a DdnsDomainParser for the entry
-    ///   2. Pass the element configuration to the parser's build method
-    ///   3. Add the parser instance to local storage
-    ///
-    /// The net effect is to parse all of the domain entries in the list
-    /// prepping them for commit.
+    ///   1. Creates a DdnsDomain instance by passing the entry into @c
+    ///   DdnsDomainParser::parser()
+    ///   2. Adds the DdnsDomain instance to the domain map
     ///
     /// @param domain_list_config is the list of "ddns-domain" elements to
     /// parse.
-    virtual void build(isc::data::ConstElementPtr domain_list_config);
-
-    /// @brief Commits the list of DdnsDomains
-    ///
-    /// Iterates over the internal list of DdnsDomainParsers, invoking
-    /// commit on each one.
-    virtual void commit();
-
-private:
-    /// @brief Arbitrary label assigned to this parser instance.
-    std::string list_name_;
-
-    /// @brief Pointer to the storage area to which the parser should commit
-    /// the list of newly created DdnsDomain instances. This is given to us
-    /// as a constructor argument by an upper level.
-    DdnsDomainMapPtr domains_;
-
-    /// @brief Pointer to the map of defined TSIG keys.
-    /// This map is passed into us and contains all of the TSIG keys defined
-    /// for this configuration.  It is used to validate the key name entry of
-    /// DdnsDomains that specify one.
-    TSIGKeyInfoMapPtr keys_;
-
-    /// @brief Local storage of DdnsDomainParser instances
-    isc::dhcp::ParserCollection parsers_;
+    /// @param keys map of defined TSIG keys
+    /// @return a pointer to the newly populated domain map
+    DdnsDomainMapPtr parse(data::ConstElementPtr domain_list_config,
+                           const TSIGKeyInfoMapPtr keys);
 };
 
 /// @brief Parser for DdnsDomainListMgr
 ///
 /// This class parses the configuration elements "forward-ddns" and
-/// "reverse-ddns" as defined in src/bin/d2/dhcp-ddns.spec.  It populates the
-/// given DdnsDomainListMgr with parsed information upon commit.  Note that
-/// unlike other parsers, this parser does NOT instantiate the final object
-/// during the commit phase, it populates it.  It must pre-exist.
-class DdnsDomainListMgrParser : public isc::dhcp::DhcpConfigParser {
+/// "reverse-ddns".  It populates the given DdnsDomainListMgr with parsed
+/// information.
+class DdnsDomainListMgrParser : public data::SimpleParser {
 public:
-    /// @brief Constructor
-    ///
-    /// @param entry_name is an arbitrary label assigned to this configuration
-    /// definition.
-    /// @param mgr is a pointer to the DdnsDomainListMgr to populate.
-    /// @param keys is a pointer to a map of the defined TSIG keys.
-    /// @throw throws D2CfgError if mgr pointer is empty.
-    DdnsDomainListMgrParser(const std::string& entry_name,
-                     DdnsDomainListMgrPtr mgr, TSIGKeyInfoMapPtr keys);
-
-    /// @brief Destructor
-    virtual ~DdnsDomainListMgrParser();
-
     /// @brief Performs the actual parsing of the given manager element.
     ///
     /// Parses a configuration for the elements needed to instantiate a
     /// DdnsDomainListMgr, validates those entries, then creates a
     /// DdnsDomainListMgr.
     ///
-    /// @param mgr_config is the manager configuration to parse
-    virtual void build(isc::data::ConstElementPtr mgr_config);
-
-    /// @brief Creates a parser for the given manager member element id.
+    /// @param mgr_config manager configuration to parse
+    /// @param mgr_name convenience label for the manager instance
+    /// @param keys map of defined TSIG keys
     ///
-    /// The manager elements currently supported are (see dhcp-ddns.spec):
-    ///     1. ddns_domains
-    ///
-    /// @param config_id is the "item_name" for a specific member element of
-    /// the manager specification.
-    /// @param pos position within the configuration text (or file) of element
-    /// to be parsed.  This is passed for error messaging.
-    ///
-    /// @return returns a pointer to newly created parser.
-    ///
-    /// @throw D2CfgError if configuration contains an unknown parameter
-    virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& config_id,
-                       const isc::data::Element::Position& pos =
-                       isc::data::Element::ZERO_POSITION());
-
-    /// @brief Commits the configured DdsnDomainListMgr
-    /// Currently this method is a NOP, as the manager instance is created
-    /// in build().
-    virtual void commit();
-
-private:
-    /// @brief Arbitrary label assigned to this parser instance.
-    std::string entry_name_;
-
-    /// @brief Pointer to manager instance to which the parser should commit
-    /// the parsed data. This is given to us as a constructor argument by an
-    /// upper level.
-    DdnsDomainListMgrPtr mgr_;
-
-    /// @brief Pointer to the map of defined TSIG keys.
-    /// This map is passed into us and contains all of the TSIG keys defined
-    /// for this configuration.  It is used to validate the key name entry of
-    /// DdnsDomains that specify one.
-    TSIGKeyInfoMapPtr keys_;
-
-    /// @brief Local storage for DdnsDomain instances. This is passed into a
-    /// DdnsDomainListParser(s), which in turn passes it into each
-    /// DdnsDomainParser.  When the DdnsDomainParsers "commit" they add their
-    /// domain instance to this storage.
-    DdnsDomainMapPtr local_domains_;
-
-    /// @brief Local storage area for scalar parameter values. Use to hold
-    /// data until time to commit.
-    /// @todo Currently, the manager has no scalars but this is likely to
-    /// change as matching capabilities expand.
-    DScalarContext local_scalars_;
+    /// @return a pointer to the new manager instance
+    DdnsDomainListMgrPtr parse(data::ConstElementPtr mgr_config,
+                               const std::string& mgr_name,
+                               const TSIGKeyInfoMapPtr keys);
 };
 
 

+ 16 - 1
src/bin/d2/d2_controller.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
@@ -8,6 +8,7 @@
 
 #include <d2/d2_controller.h>
 #include <d2/d2_process.h>
+#include <d2/parser_context.h>
 #include <process/spec_config.h>
 
 #include <stdlib.h>
@@ -54,6 +55,20 @@ D2Controller::D2Controller()
     }
 }
 
+isc::data::ConstElementPtr 
+D2Controller::parseFile(const std::string& file_name) {
+    isc::data::ConstElementPtr elements;
+
+    // Read contents of the file and parse it as JSON
+    D2ParserContext parser;
+    elements = parser.parseFile(file_name, D2ParserContext::PARSER_DHCPDDNS);
+    if (!elements) {
+        isc_throw(isc::BadValue, "no configuration found in file");
+    }
+
+    return (elements);
+}
+
 D2Controller::~D2Controller() {
 }
 

+ 12 - 1
src/bin/d2/d2_controller.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
@@ -53,6 +53,17 @@ private:
     /// pointer.
     virtual process::DProcessBase* createProcess();
 
+    ///@brief Parse a given file into Elements
+    ///
+    /// Uses bison parsing to parse a JSON configuration file into an
+    /// a element map.
+    ///
+    /// @param file_name pathname of the file to parse
+    ///
+    /// @return pointer to the map of elements created
+    /// @throw BadValue if the file is empty
+    virtual isc::data::ConstElementPtr parseFile(const std::string& file_name);
+
     /// @brief Constructor is declared private to maintain the integrity of
     /// the singleton instance.
     D2Controller();

File diff suppressed because it is too large
+ 3607 - 0
src/bin/d2/d2_lexer.cc


+ 733 - 0
src/bin/d2/d2_lexer.ll

@@ -0,0 +1,733 @@
+/* Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+
+   This Source Code Form is subject to the terms of the Mozilla Public
+   License, v. 2.0. If a copy of the MPL was not distributed with this
+   file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%{ /* -*- C++ -*- */
+#include <cerrno>
+#include <climits>
+#include <cstdlib>
+#include <string>
+#include <d2/parser_context.h>
+#include <asiolink/io_address.h>
+#include <boost/lexical_cast.hpp>
+#include <exceptions/exceptions.h>
+
+// Work around an incompatibility in flex (at least versions
+// 2.5.31 through 2.5.33): it generates code that does
+// not conform to C89.  See Debian bug 333231
+// <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=333231>.
+# undef yywrap
+# define yywrap() 1
+
+namespace {
+
+bool start_token_flag = false;
+
+isc::d2::D2ParserContext::ParserType start_token_value;
+unsigned int comment_start_line = 0;
+
+};
+
+// To avoid the call to exit... oops!
+#define YY_FATAL_ERROR(msg) isc::d2::D2ParserContext::fatal(msg)
+%}
+
+/* noyywrap disables automatic rewinding for the next file to parse. Since we
+   always parse only a single string, there's no need to do any wraps. And
+   using yywrap requires linking with -lfl, which provides the default yywrap
+   implementation that always returns 1 anyway. */
+%option noyywrap
+
+/* nounput simplifies the lexer, by removing support for putting a character
+   back into the input stream. We never use such capability anyway. */
+%option nounput
+
+/* batch means that we'll never use the generated lexer interactively. */
+%option batch
+
+/* avoid to get static global variables to remain with C++. */
+/* in last resort %option reentrant */
+
+/* Enables debug mode. To see the debug messages, one needs to also set
+   yy_flex_debug to 1, then the debug messages will be printed on stderr. */
+%option debug
+
+/* I have no idea what this option does, except it was specified in the bison
+   examples and Postgres folks added it to remove gcc 4.3 warnings. Let's
+   be on the safe side and keep it. */
+%option noinput
+
+%x COMMENT
+%x DIR_ENTER DIR_INCLUDE DIR_EXIT
+
+/* These are not token expressions yet, just convenience expressions that
+   can be used during actual token definitions. Note some can match
+   incorrect inputs (e.g., IP addresses) which must be checked. */
+int   \-?[0-9]+
+blank [ \t\r]
+
+UnicodeEscapeSequence           u[0-9A-Fa-f]{4}
+JSONEscapeCharacter             ["\\/bfnrt]
+JSONEscapeSequence              {JSONEscapeCharacter}|{UnicodeEscapeSequence}
+JSONStandardCharacter           [^\x00-\x1f"\\]
+JSONStringCharacter             {JSONStandardCharacter}|\\{JSONEscapeSequence}
+JSONString                      \"{JSONStringCharacter}*\"
+
+/* for errors */
+
+BadUnicodeEscapeSequence        u[0-9A-Fa-f]{0,3}[^0-9A-Fa-f]
+BadJSONEscapeSequence           [^"\\/bfnrtu]|{BadUnicodeEscapeSequence}
+ControlCharacter                [\x00-\x1f]
+ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
+
+%{
+// This code run each time a pattern is matched. It updates the location
+// by moving it ahead by yyleng bytes. yyleng specifies the length of the
+// currently matched token.
+#define YY_USER_ACTION  driver.loc_.columns(yyleng);
+%}
+
+%%
+
+%{
+    // This part of the code is copied over to the verbatim to the top
+    // of the generated yylex function. Explanation:
+    // http://www.gnu.org/software/bison/manual/html_node/Multiple-start_002dsymbols.html
+
+    // Code run each time yylex is called.
+    driver.loc_.step();
+
+    if (start_token_flag) {
+        start_token_flag = false;
+        switch (start_token_value) {
+        case D2ParserContext::PARSER_JSON:
+        default:
+            return isc::d2::D2Parser::make_TOPLEVEL_JSON(driver.loc_);
+        case D2ParserContext::PARSER_DHCPDDNS:
+            return isc::d2::D2Parser::make_TOPLEVEL_DHCPDDNS(driver.loc_);
+        case D2ParserContext::PARSER_SUB_DHCPDDNS:
+            return isc::d2::D2Parser::make_SUB_DHCPDDNS(driver.loc_);
+        case D2ParserContext::PARSER_TSIG_KEY:
+            return isc::d2::D2Parser::make_SUB_TSIG_KEY(driver.loc_);
+        case D2ParserContext::PARSER_TSIG_KEYS:
+            return isc::d2::D2Parser::make_SUB_TSIG_KEYS(driver.loc_);
+        case D2ParserContext::PARSER_DDNS_DOMAIN:
+            return isc::d2::D2Parser::make_SUB_DDNS_DOMAIN(driver.loc_);
+        case D2ParserContext::PARSER_DDNS_DOMAINS:
+            return isc::d2::D2Parser::make_SUB_DDNS_DOMAINS(driver.loc_);
+        case D2ParserContext::PARSER_DNS_SERVER:
+            return isc::d2::D2Parser::make_SUB_DNS_SERVER(driver.loc_);
+        }
+    }
+%}
+
+#.* ;
+
+"//"(.*) ;
+
+"/*" {
+  BEGIN(COMMENT);
+  comment_start_line = driver.loc_.end.line;;
+}
+
+<COMMENT>"*/" BEGIN(INITIAL);
+<COMMENT>. ;
+<COMMENT><<EOF>> {
+    isc_throw(D2ParseError, "Comment not closed. (/* in line " << comment_start_line);
+}
+
+"<?" BEGIN(DIR_ENTER);
+<DIR_ENTER>"include" BEGIN(DIR_INCLUDE);
+<DIR_INCLUDE>\"([^\"\n])+\" {
+    // Include directive.
+
+    // Extract the filename.
+    std::string tmp(yytext+1);
+    tmp.resize(tmp.size() - 1);
+
+    driver.includeFile(tmp);
+}
+<DIR_ENTER,DIR_INCLUDE,DIR_EXIT><<EOF>> {
+    isc_throw(D2ParseError, "Directive not closed.");
+}
+<DIR_EXIT>"?>" BEGIN(INITIAL);
+
+
+<*>{blank}+   {
+    // Ok, we found a with space. Let's ignore it and update loc variable.
+    driver.loc_.step();
+}
+
+<*>[\n]+      {
+    // Newline found. Let's update the location and continue.
+    driver.loc_.lines(yyleng);
+    driver.loc_.step();
+}
+
+\"DhcpDdns\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONFIG:
+        return isc::d2::D2Parser::make_DHCPDDNS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("DhcpDdns", driver.loc_);
+    }
+}
+
+\"ip-address\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+    case isc::d2::D2ParserContext::DNS_SERVER:
+    case isc::d2::D2ParserContext::DNS_SERVERS:
+        return isc::d2::D2Parser::make_IP_ADDRESS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("ip-address", driver.loc_);
+    }
+}
+
+\"port\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+    case isc::d2::D2ParserContext::DNS_SERVER:
+    case isc::d2::D2ParserContext::DNS_SERVERS:
+        return isc::d2::D2Parser::make_PORT(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("port", driver.loc_);
+    }
+}
+
+\"dns-server-timeout\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_DNS_SERVER_TIMEOUT(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("dns-server-timeout", driver.loc_);
+    }
+}
+
+\"ncr-protocol\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_NCR_PROTOCOL(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("ncr-protocol", driver.loc_);
+    }
+}
+
+\"ncr-format\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_NCR_FORMAT(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("ncr-format", driver.loc_);
+    }
+}
+
+(?i:\"UDP\") {
+    /* dhcp-ddns value keywords are case insensitive */
+    if (driver.ctx_ == isc::d2::D2ParserContext::NCR_PROTOCOL) {
+        return isc::d2::D2Parser::make_UDP(driver.loc_);
+    }
+    std::string tmp(yytext+1);
+    tmp.resize(tmp.size() - 1);
+    return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"TCP\") {
+    /* dhcp-ddns value keywords are case insensitive */
+    if (driver.ctx_ == isc::d2::D2ParserContext::NCR_PROTOCOL) {
+        return isc::d2::D2Parser::make_TCP(driver.loc_);
+    }
+    std::string tmp(yytext+1);
+    tmp.resize(tmp.size() - 1);
+    return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+(?i:\"JSON\") {
+    /* dhcp-ddns value keywords are case insensitive */
+    if (driver.ctx_ == isc::d2::D2ParserContext::NCR_FORMAT) {
+        return isc::d2::D2Parser::make_JSON(driver.loc_);
+    }
+    std::string tmp(yytext+1);
+    tmp.resize(tmp.size() - 1);
+    return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
+}
+
+\"forward-ddns\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_FORWARD_DDNS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("forward-ddns", driver.loc_);
+    }
+}
+
+\"reverse-ddns\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_REVERSE_DDNS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("reverse-ddns", driver.loc_);
+    }
+}
+
+\"ddns-domains\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::FORWARD_DDNS:
+    case isc::d2::D2ParserContext::REVERSE_DDNS:
+        return isc::d2::D2Parser::make_DDNS_DOMAINS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("ddns-domains", driver.loc_);
+    }
+}
+
+\"key-name\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DDNS_DOMAIN:
+    case isc::d2::D2ParserContext::DDNS_DOMAINS:
+        return isc::d2::D2Parser::make_KEY_NAME(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("key-name", driver.loc_);
+    }
+}
+
+\"dns-servers\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DDNS_DOMAIN:
+    case isc::d2::D2ParserContext::DDNS_DOMAINS:
+        return isc::d2::D2Parser::make_DNS_SERVERS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("dns-servers", driver.loc_);
+    }
+}
+
+\"hostname\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DNS_SERVER:
+    case isc::d2::D2ParserContext::DNS_SERVERS:
+        return isc::d2::D2Parser::make_HOSTNAME(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("hostname", driver.loc_);
+    }
+}
+
+
+\"tsig-keys\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_TSIG_KEYS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("tsig-keys", driver.loc_);
+    }
+}
+
+\"algorithm\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::TSIG_KEY:
+    case isc::d2::D2ParserContext::TSIG_KEYS:
+        return isc::d2::D2Parser::make_ALGORITHM(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("algorithm", driver.loc_);
+    }
+}
+
+\"digest-bits\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::TSIG_KEY:
+    case isc::d2::D2ParserContext::TSIG_KEYS:
+        return isc::d2::D2Parser::make_DIGEST_BITS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("digest-bits", driver.loc_);
+    }
+}
+
+\"secret\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::TSIG_KEY:
+    case isc::d2::D2ParserContext::TSIG_KEYS:
+        return isc::d2::D2Parser::make_SECRET(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("secret", driver.loc_);
+    }
+}
+
+
+\"Logging\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONFIG:
+        return isc::d2::D2Parser::make_LOGGING(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("Logging", driver.loc_);
+    }
+}
+
+\"loggers\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::LOGGING:
+        return isc::d2::D2Parser::make_LOGGERS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("loggers", driver.loc_);
+    }
+}
+
+\"output_options\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::LOGGERS:
+        return isc::d2::D2Parser::make_OUTPUT_OPTIONS(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("output_options", driver.loc_);
+    }
+}
+
+\"output\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+        return isc::d2::D2Parser::make_OUTPUT(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("output", driver.loc_);
+    }
+}
+
+\"name\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::LOGGERS:
+    case isc::d2::D2ParserContext::TSIG_KEY:
+    case isc::d2::D2ParserContext::TSIG_KEYS:
+    case isc::d2::D2ParserContext::DDNS_DOMAIN:
+    case isc::d2::D2ParserContext::DDNS_DOMAINS:
+        return isc::d2::D2Parser::make_NAME(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("name", driver.loc_);
+    }
+}
+
+\"debuglevel\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::LOGGERS:
+        return isc::d2::D2Parser::make_DEBUGLEVEL(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("debuglevel", driver.loc_);
+    }
+}
+
+\"severity\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::LOGGERS:
+        return isc::d2::D2Parser::make_SEVERITY(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("severity", driver.loc_);
+    }
+}
+
+\"Dhcp4\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONFIG:
+        return isc::d2::D2Parser::make_DHCP4(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("Dhcp4", driver.loc_);
+    }
+}
+
+\"Dhcp6\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::CONFIG:
+        return isc::d2::D2Parser::make_DHCP6(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("Dhcp6", driver.loc_);
+    }
+}
+
+
+{JSONString} {
+    // A string has been matched. It contains the actual string and single quotes.
+    // We need to get those quotes out of the way and just use its content, e.g.
+    // for 'foo' we should get foo
+    std::string raw(yytext+1);
+    size_t len = raw.size() - 1;
+    raw.resize(len);
+    std::string decoded;
+    decoded.reserve(len);
+    for (size_t pos = 0; pos < len; ++pos) {
+        int b = 0;
+        char c = raw[pos];
+        switch (c) {
+        case '"':
+            // impossible condition
+            driver.error(driver.loc_, "Bad quote in \"" + raw + "\"");
+        case '\\':
+            ++pos;
+            if (pos >= len) {
+                // impossible condition
+                driver.error(driver.loc_, "Overflow escape in \"" + raw + "\"");
+            }
+            c = raw[pos];
+            switch (c) {
+            case '"':
+            case '\\':
+            case '/':
+                decoded.push_back(c);
+                break;
+            case 'b':
+                decoded.push_back('\b');
+                break;
+            case 'f':
+                decoded.push_back('\f');
+                break;
+            case 'n':
+                decoded.push_back('\n');
+                break;
+            case 'r':
+                decoded.push_back('\r');
+                break;
+            case 't':
+                decoded.push_back('\t');
+                break;
+            case 'u':
+                // support only \u0000 to \u00ff
+                ++pos;
+                if (pos + 4 > len) {
+                    // impossible condition
+                    driver.error(driver.loc_,
+                                 "Overflow unicode escape in \"" + raw + "\"");
+                }
+                if ((raw[pos] != '0') || (raw[pos + 1] != '0')) {
+                    driver.error(driver.loc_, "Unsupported unicode escape in \"" + raw + "\"");
+                }
+                pos += 2;
+                c = raw[pos];
+                if ((c >= '0') && (c <= '9')) {
+                    b = (c - '0') << 4;
+                } else if ((c >= 'A') && (c <= 'F')) {
+                    b = (c - 'A' + 10) << 4;
+                } else if ((c >= 'a') && (c <= 'f')) {
+                    b = (c - 'a' + 10) << 4;
+                } else {
+                    // impossible condition
+                    driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+                }
+                pos++;
+                c = raw[pos];
+                if ((c >= '0') && (c <= '9')) {
+                    b |= c - '0';
+                } else if ((c >= 'A') && (c <= 'F')) {
+                    b |= c - 'A' + 10;
+                } else if ((c >= 'a') && (c <= 'f')) {
+                    b |= c - 'a' + 10;
+                } else {
+                    // impossible condition
+                    driver.error(driver.loc_, "Not hexadecimal in unicode escape in \"" + raw + "\"");
+                }
+                decoded.push_back(static_cast<char>(b & 0xff));
+                break;
+            default:
+                // impossible condition
+                driver.error(driver.loc_, "Bad escape in \"" + raw + "\"");
+            }
+            break;
+        default:
+            if ((c >= 0) && (c < 0x20)) {
+                // impossible condition
+                driver.error(driver.loc_, "Invalid control in \"" + raw + "\"");
+            }
+            decoded.push_back(c);
+        }
+    }
+
+    return isc::d2::D2Parser::make_STRING(decoded, driver.loc_);
+}
+
+\"{JSONStringCharacter}*{ControlCharacter}{ControlCharacterFill}*\" {
+    // Bad string with a forbidden control character inside
+    driver.error(driver.loc_, "Invalid control in " + std::string(yytext));
+}
+
+\"{JSONStringCharacter}*\\{BadJSONEscapeSequence}[^\x00-\x1f"]*\" {
+    // Bad string with a bad escape inside
+    driver.error(driver.loc_, "Bad escape in " + std::string(yytext));
+}
+
+\"{JSONStringCharacter}*\\\" {
+    // Bad string with an open escape at the end
+    driver.error(driver.loc_, "Overflow escape in " + std::string(yytext));
+}
+
+"["    { return isc::d2::D2Parser::make_LSQUARE_BRACKET(driver.loc_); }
+"]"    { return isc::d2::D2Parser::make_RSQUARE_BRACKET(driver.loc_); }
+"{"    { return isc::d2::D2Parser::make_LCURLY_BRACKET(driver.loc_); }
+"}"    { return isc::d2::D2Parser::make_RCURLY_BRACKET(driver.loc_); }
+","    { return isc::d2::D2Parser::make_COMMA(driver.loc_); }
+":"    { return isc::d2::D2Parser::make_COLON(driver.loc_); }
+
+{int} {
+    // An integer was found.
+    std::string tmp(yytext);
+    int64_t integer = 0;
+    try {
+        // In substring we want to use negative values (e.g. -1).
+        // In enterprise-id we need to use values up to 0xffffffff.
+        // To cover both of those use cases, we need at least
+        // int64_t.
+        integer = boost::lexical_cast<int64_t>(tmp);
+    } catch (const boost::bad_lexical_cast &) {
+        driver.error(driver.loc_, "Failed to convert " + tmp + " to an integer.");
+    }
+
+    // The parser needs the string form as double conversion is no lossless
+    return isc::d2::D2Parser::make_INTEGER(integer, driver.loc_);
+}
+
+[-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)? {
+    // A floating point was found.
+    std::string tmp(yytext);
+    double fp = 0.0;
+    try {
+        fp = boost::lexical_cast<double>(tmp);
+    } catch (const boost::bad_lexical_cast &) {
+        driver.error(driver.loc_, "Failed to convert " + tmp + " to a floating point.");
+    }
+
+    return isc::d2::D2Parser::make_FLOAT(fp, driver.loc_);
+}
+
+true|false {
+    string tmp(yytext);
+    return isc::d2::D2Parser::make_BOOLEAN(tmp == "true", driver.loc_);
+}
+
+null {
+   return isc::d2::D2Parser::make_NULL_TYPE(driver.loc_);
+}
+
+(?i:true) driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
+
+(?i:false) driver.error (driver.loc_, "JSON false reserved keyword is lower case only");
+
+(?i:null) driver.error (driver.loc_, "JSON null reserved keyword is lower case only");
+
+<*>.   driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
+
+<<EOF>> {
+    if (driver.states_.empty()) {
+        return isc::d2::D2Parser::make_END(driver.loc_);
+    }
+    driver.loc_ = driver.locs_.back();
+    driver.locs_.pop_back();
+    driver.file_ = driver.files_.back();
+    driver.files_.pop_back();
+    if (driver.sfile_) {
+        fclose(driver.sfile_);
+        driver.sfile_ = 0;
+    }
+    if (!driver.sfiles_.empty()) {
+        driver.sfile_ = driver.sfiles_.back();
+        driver.sfiles_.pop_back();
+    }
+    d2_parser__delete_buffer(YY_CURRENT_BUFFER);
+    d2_parser__switch_to_buffer(driver.states_.back());
+    driver.states_.pop_back();
+
+    BEGIN(DIR_EXIT);
+}
+
+%%
+
+using namespace isc::dhcp;
+
+void
+D2ParserContext::scanStringBegin(const std::string& str, ParserType parser_type)
+{
+    start_token_flag = true;
+    start_token_value = parser_type;
+
+    file_ = "<string>";
+    sfile_ = 0;
+    loc_.initialize(&file_);
+    yy_flex_debug = trace_scanning_;
+    YY_BUFFER_STATE buffer;
+    buffer = d2_parser__scan_bytes(str.c_str(), str.size());
+    if (!buffer) {
+        fatal("cannot scan string");
+        // fatal() throws an exception so this can't be reached
+    }
+}
+
+void
+D2ParserContext::scanFileBegin(FILE * f,
+                              const std::string& filename,
+                              ParserType parser_type)
+{
+    start_token_flag = true;
+    start_token_value = parser_type;
+
+    file_ = filename;
+    sfile_ = f;
+    loc_.initialize(&file_);
+    yy_flex_debug = trace_scanning_;
+    YY_BUFFER_STATE buffer;
+
+    // See d2_lexer.cc header for available definitions
+    buffer = d2_parser__create_buffer(f, 65536 /*buffer size*/);
+    if (!buffer) {
+        fatal("cannot scan file " + filename);
+    }
+    d2_parser__switch_to_buffer(buffer);
+}
+
+void
+D2ParserContext::scanEnd() {
+    if (sfile_)
+        fclose(sfile_);
+    sfile_ = 0;
+    static_cast<void>(d2_parser_lex_destroy());
+    // Close files
+    while (!sfiles_.empty()) {
+        FILE* f = sfiles_.back();
+        if (f) {
+            fclose(f);
+        }
+        sfiles_.pop_back();
+    }
+    // Delete states
+    while (!states_.empty()) {
+        d2_parser__delete_buffer(states_.back());
+        states_.pop_back();
+    }
+}
+
+void
+D2ParserContext::includeFile(const std::string& filename) {
+    if (states_.size() > 10) {
+        fatal("Too many nested include.");
+    }
+
+    FILE* f = fopen(filename.c_str(), "r");
+    if (!f) {
+        fatal("Can't open include file " + filename);
+    }
+    if (sfile_) {
+        sfiles_.push_back(sfile_);
+    }
+    sfile_ = f;
+    states_.push_back(YY_CURRENT_BUFFER);
+    YY_BUFFER_STATE buffer;
+    buffer = d2_parser__create_buffer(f, 65536 /*buffer size*/);
+    if (!buffer) {
+        fatal( "Can't scan include file " + filename);
+    }
+    d2_parser__switch_to_buffer(buffer);
+    files_.push_back(file_);
+    file_ = filename;
+    locs_.push_back(loc_);
+    loc_.initialize(&file_);
+
+    BEGIN(INITIAL);
+}
+
+namespace {
+/// To avoid unused function error
+class Dummy {
+    // cppcheck-suppress unusedPrivateFunction
+    void dummy() { yy_fatal_error("Fix me: how to disable its definition?"); }
+};
+}

File diff suppressed because it is too large
+ 2221 - 0
src/bin/d2/d2_parser.cc


File diff suppressed because it is too large
+ 1562 - 0
src/bin/d2/d2_parser.h


+ 737 - 0
src/bin/d2/d2_parser.yy

@@ -0,0 +1,737 @@
+/* Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+
+   This Source Code Form is subject to the terms of the Mozilla Public
+   License, v. 2.0. If a copy of the MPL was not distributed with this
+   file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+%skeleton "lalr1.cc" /* -*- C++ -*- */
+%require "3.0.0"
+%defines
+%define parser_class_name {D2Parser}
+%define api.prefix {d2_parser_}
+%define api.token.constructor
+%define api.value.type variant
+%define api.namespace {isc::d2}
+%define parse.assert
+%code requires
+{
+#include <string>
+#include <cc/data.h>
+#include <d2/d2_config.h>
+#include <boost/lexical_cast.hpp>
+#include <d2/parser_context_decl.h>
+
+using namespace isc::d2;
+using namespace isc::data;
+using namespace std;
+}
+// The parsing context.
+%param { isc::d2::D2ParserContext& ctx }
+%locations
+%define parse.trace
+%define parse.error verbose
+%code
+{
+#include <d2/parser_context.h>
+}
+
+
+%define api.token.prefix {TOKEN_}
+// Tokens in an order which makes sense and related to the intented use.
+// Actual regexps for tokens are defined in d2_lexer.ll.
+%token
+  END  0  "end of file"
+  COMMA ","
+  COLON ":"
+  LSQUARE_BRACKET "["
+  RSQUARE_BRACKET "]"
+  LCURLY_BRACKET "{"
+  RCURLY_BRACKET "}"
+  NULL_TYPE "null"
+
+  DHCP6 "Dhcp6"
+  DHCP4 "Dhcp4"
+
+  DHCPDDNS "DhcpDdns"
+  IP_ADDRESS "ip-address"
+  PORT "port"
+  DNS_SERVER_TIMEOUT "dns-server-timeout"
+  NCR_PROTOCOL "ncr-protocol"
+  UDP "UDP"
+  TCP "TCP"
+  NCR_FORMAT "ncr-format"
+  JSON "JSON"
+  FORWARD_DDNS "forward-ddns"
+  REVERSE_DDNS "reverse-ddns"
+  DDNS_DOMAINS "ddns-domains"
+  KEY_NAME "key-name"
+  DNS_SERVERS "dns-servers"
+  HOSTNAME "hostname"
+  TSIG_KEYS "tsig-keys"
+  ALGORITHM "algorithm"
+  DIGEST_BITS "digest-bits"
+  SECRET "secret"
+
+  LOGGING "Logging"
+  LOGGERS "loggers"
+  NAME "name"
+  OUTPUT_OPTIONS "output_options"
+  OUTPUT "output"
+  DEBUGLEVEL "debuglevel"
+  SEVERITY "severity"
+
+  // Not real tokens, just a way to signal what the parser is expected to
+  // parse.
+  TOPLEVEL_JSON
+  TOPLEVEL_DHCPDDNS
+  SUB_DHCPDDNS
+  SUB_TSIG_KEY
+  SUB_TSIG_KEYS
+  SUB_DDNS_DOMAIN
+  SUB_DDNS_DOMAINS
+  SUB_DNS_SERVER
+  SUB_DNS_SERVERS
+;
+
+%token <std::string> STRING "constant string"
+%token <int64_t> INTEGER "integer"
+%token <double> FLOAT "floating point"
+%token <bool> BOOLEAN "boolean"
+
+%type <ElementPtr> value
+%type <ElementPtr> ncr_protocol_value
+
+%printer { yyoutput << $$; } <*>;
+
+%%
+
+// The whole grammar starts with a map, because the config file
+// constists of Dhcp, Logger and DhcpDdns entries in one big { }.
+// We made the same for subparsers at the exception of the JSON value.
+%start start;
+
+start: TOPLEVEL_JSON { ctx.ctx_ = ctx.NO_KEYWORD; } sub_json
+     | TOPLEVEL_DHCPDDNS { ctx.ctx_ = ctx.CONFIG; } syntax_map
+     | SUB_DHCPDDNS { ctx.ctx_ = ctx.DHCPDDNS; } sub_dhcpddns
+     | SUB_TSIG_KEY { ctx.ctx_ = ctx.TSIG_KEY; } sub_tsig_key
+     | SUB_TSIG_KEYS { ctx.ctx_ = ctx.TSIG_KEYS; } sub_tsig_keys
+     | SUB_DDNS_DOMAIN { ctx.ctx_ = ctx.DDNS_DOMAIN; } sub_ddns_domain
+     | SUB_DDNS_DOMAINS { ctx.ctx_ = ctx.DDNS_DOMAINS; } sub_ddns_domains
+     | SUB_DNS_SERVER { ctx.ctx_ = ctx.DNS_SERVERS; } sub_dns_server
+     | SUB_DNS_SERVERS { ctx.ctx_ = ctx.DNS_SERVERS; } sub_dns_servers
+     ;
+
+// ---- generic JSON parser ---------------------------------
+
+// Note that ctx_ is NO_KEYWORD here
+
+// Values rule
+value: INTEGER { $$ = ElementPtr(new IntElement($1, ctx.loc2pos(@1))); }
+     | FLOAT { $$ = ElementPtr(new DoubleElement($1, ctx.loc2pos(@1))); }
+     | BOOLEAN { $$ = ElementPtr(new BoolElement($1, ctx.loc2pos(@1))); }
+     | STRING { $$ = ElementPtr(new StringElement($1, ctx.loc2pos(@1))); }
+     | NULL_TYPE { $$ = ElementPtr(new NullElement(ctx.loc2pos(@1))); }
+     | map2 { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+     | list_generic { $$ = ctx.stack_.back(); ctx.stack_.pop_back(); }
+     ;
+
+sub_json: value {
+    // Push back the JSON value on the stack
+    ctx.stack_.push_back($1);
+};
+
+map2: LCURLY_BRACKET {
+    // This code is executed when we're about to start parsing
+    // the content of the map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} map_content RCURLY_BRACKET {
+    // map parsing completed. If we ever want to do any wrap up
+    // (maybe some sanity checking), this would be the best place
+    // for it.
+};
+
+// Assignments rule
+map_content: %empty // empty map
+           | not_empty_map
+           ;
+
+not_empty_map: STRING COLON value {
+                  // map containing a single entry
+                  ctx.stack_.back()->set($1, $3);
+                  }
+             | not_empty_map COMMA STRING COLON value {
+                  // map consisting of a shorter map followed by
+                  // comma and string:value
+                  ctx.stack_.back()->set($3, $5);
+                  }
+             ;
+
+list_generic: LSQUARE_BRACKET {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(l);
+} list_content RSQUARE_BRACKET {
+    // list parsing complete. Put any sanity checking here
+};
+
+list_content: %empty // Empty list
+            | not_empty_list
+            ;
+
+not_empty_list: value {
+                  // List consisting of a single element.
+                  ctx.stack_.back()->add($1);
+                  }
+              | not_empty_list COMMA value {
+                  // List ending with , and a value.
+                  ctx.stack_.back()->add($3);
+                  }
+              ;
+
+// ---- generic JSON parser ends here ----------------------------------
+
+// ---- syntax checking parser starts here -----------------------------
+
+// Unknown keyword in a map
+unknown_map_entry: STRING COLON {
+    const std::string& where = ctx.contextName();
+    const std::string& keyword = $1;
+    error(@1,
+          "got unexpected keyword \"" + keyword + "\" in " + where + " map.");
+};
+
+
+// This defines the top-level { } that holds Dhcp6, Dhcp4, DhcpDdns or Logging
+// objects.
+syntax_map: LCURLY_BRACKET {
+    // This code is executed when we're about to start parsing
+    // the content of the map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} global_objects RCURLY_BRACKET {
+    // map parsing completed. If we ever want to do any wrap up
+    // (maybe some sanity checking), this would be the best place
+    // for it.
+};
+
+// This represents top-level entries: Dhcp6, Dhcp4, DhcpDdns, Logging
+global_objects: global_object
+              | global_objects COMMA global_object
+              ;
+
+// This represents a single top level entry, e.g. Dhcp6 or DhcpDdns.
+global_object: dhcp6_json_object
+             | logging_object
+             | dhcp4_json_object
+             | dhcpddns_object
+             | unknown_map_entry
+             ;
+
+// --- dhcp ddns ---------------------------------------------
+
+dhcpddns_object: DHCPDDNS {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("DhcpDdns", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.DHCPDDNS);
+} COLON LCURLY_BRACKET dhcpddns_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+sub_dhcpddns: LCURLY_BRACKET {
+    // Parse the dhcpddns map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} dhcpddns_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+dhcpddns_params: dhcpddns_param
+               | dhcpddns_params COMMA dhcpddns_param
+               ;
+
+// These are the top-level parameters allowed for DhcpDdns
+dhcpddns_param: ip_address
+              | port
+              | dns_server_timeout
+              | ncr_protocol
+              | ncr_format
+              | forward_ddns
+              | reverse_ddns
+              | tsig_keys
+              | unknown_map_entry
+              ;
+
+ip_address: IP_ADDRESS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("ip-address", s);
+    ctx.leave();
+};
+
+port: PORT COLON INTEGER {
+    if ($3 <= 0 || $3 >= 65536 ) {
+        error(@3, "port must be greater than zero but less than 65536");
+    }
+    ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("port", i);
+};
+
+dns_server_timeout: DNS_SERVER_TIMEOUT COLON INTEGER {
+    if ($3 <= 0) {
+        error(@3, "dns-server-timeout must be greater than zero");
+    } else {
+        ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+        ctx.stack_.back()->set("dns-server-timeout", i);
+    }
+};
+
+ncr_protocol: NCR_PROTOCOL {
+    ctx.enter(ctx.NCR_PROTOCOL);
+} COLON ncr_protocol_value {
+    ctx.stack_.back()->set("ncr-protocol", $4);
+    ctx.leave();
+};
+
+ncr_protocol_value:
+    UDP { $$ = ElementPtr(new StringElement("UDP", ctx.loc2pos(@1))); }
+  | TCP { $$ = ElementPtr(new StringElement("TCP", ctx.loc2pos(@1))); }
+  ;
+
+ncr_format: NCR_FORMAT {
+    ctx.enter(ctx.NCR_FORMAT);
+} COLON JSON {
+    ElementPtr json(new StringElement("JSON", ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("ncr-format", json);
+    ctx.leave();
+};
+
+forward_ddns : FORWARD_DDNS {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("forward-ddns", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.FORWARD_DDNS);
+} COLON LCURLY_BRACKET ddns_mgr_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+reverse_ddns : REVERSE_DDNS {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("reverse-ddns", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.REVERSE_DDNS);
+} COLON LCURLY_BRACKET ddns_mgr_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+ddns_mgr_params: %empty
+               | not_empty_ddns_mgr_params
+               ;
+
+not_empty_ddns_mgr_params: ddns_mgr_param
+                         | ddns_mgr_params COMMA ddns_mgr_param
+                         ;
+
+ddns_mgr_param: ddns_domains
+              | unknown_map_entry
+              ;
+
+
+// --- ddns-domains ----------------------------------------
+ddns_domains: DDNS_DOMAINS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("ddns-domains", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.DDNS_DOMAINS);
+} COLON LSQUARE_BRACKET ddns_domain_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+sub_ddns_domains: LSQUARE_BRACKET {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(l);
+} ddns_domain_list RSQUARE_BRACKET {
+    // parsing completed
+}
+
+ddns_domain_list: %empty
+              | not_empty_ddns_domain_list
+              ;
+
+not_empty_ddns_domain_list: ddns_domain
+                        | not_empty_ddns_domain_list COMMA ddns_domain
+                        ;
+
+ddns_domain: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} ddns_domain_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+sub_ddns_domain: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} ddns_domain_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+ddns_domain_params: ddns_domain_param
+                  | ddns_domain_params COMMA ddns_domain_param
+                  ;
+
+ddns_domain_param: ddns_domain_name
+                 | ddns_domain_key_name
+                 | dns_servers
+                 | unknown_map_entry
+                 ;
+
+//  @todo NAME needs to be an FQDN sort of thing
+ddns_domain_name: NAME {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    if ($4 == "") {
+        error(@3, "Ddns domain name cannot be blank");
+    }
+    ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+    ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("name", name);
+    ctx.leave();
+};
+
+ddns_domain_key_name: KEY_NAME {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+    ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("key-name", name);
+    ctx.leave();
+};
+
+// --- end ddns-domains ----------------------------------------
+
+// --- dns-servers ----------------------------------------
+dns_servers: DNS_SERVERS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("dns-servers", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.DNS_SERVERS);
+} COLON LSQUARE_BRACKET dns_server_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+sub_dns_servers: LSQUARE_BRACKET {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(l);
+} dns_server_list RSQUARE_BRACKET {
+    // parsing completed
+}
+
+dns_server_list: dns_server
+               | dns_server_list COMMA dns_server
+               ;
+
+dns_server: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} dns_server_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+sub_dns_server: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} dns_server_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+dns_server_params: dns_server_param
+               | dns_server_params COMMA dns_server_param
+               ;
+
+dns_server_param: dns_server_hostname
+              | dns_server_ip_address
+              | dns_server_port
+              | unknown_map_entry
+              ;
+
+dns_server_hostname: HOSTNAME {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    if ($4 != "") {
+        error(@3, "hostname is not yet supported");
+    }
+    ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+    ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("hostname", name);
+    ctx.leave();
+};
+
+dns_server_ip_address: IP_ADDRESS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr s(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("ip-address", s);
+    ctx.leave();
+};
+
+dns_server_port: PORT COLON INTEGER {
+    if ($3 <= 0 || $3 >= 65536 ) {
+        error(@3, "port must be greater than zero but less than 65536");
+    }
+    ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("port", i);
+};
+
+// --- end of dns-servers ---------------------------------
+
+
+
+// --- tsig-keys ----------------------------------------
+// "tsig-keys" : [ ... ]
+tsig_keys: TSIG_KEYS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("tsig-keys", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.TSIG_KEYS);
+} COLON LSQUARE_BRACKET tsig_keys_list RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+sub_tsig_keys: LSQUARE_BRACKET {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(l);
+} tsig_keys_list RSQUARE_BRACKET {
+    // parsing completed
+}
+
+tsig_keys_list: %empty
+              | not_empty_tsig_keys_list
+              ;
+
+not_empty_tsig_keys_list: tsig_key
+                        | not_empty_tsig_keys_list COMMA tsig_key
+                        ;
+
+tsig_key: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} tsig_key_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+sub_tsig_key: LCURLY_BRACKET {
+    // Parse tsig key list entry map
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.push_back(m);
+} tsig_key_params RCURLY_BRACKET {
+    // parsing completed
+};
+
+
+tsig_key_params: tsig_key_param
+               | tsig_key_params COMMA tsig_key_param
+               ;
+
+tsig_key_param: tsig_key_name
+              | tsig_key_algorithm
+              | tsig_key_digest_bits
+              | tsig_key_secret
+              | unknown_map_entry
+              ;
+
+tsig_key_name: NAME {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    if ($4 == "") {
+        error(@3, "TSIG key name cannot be blank");
+    }
+    ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+    ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("name", name);
+    ctx.leave();
+};
+
+tsig_key_algorithm: ALGORITHM {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    if ($4 == "") {
+        error(@3, "TSIG key algorithm cannot be blank");
+    }
+    ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("algorithm", elem);
+    ctx.leave();
+};
+
+tsig_key_digest_bits: DIGEST_BITS COLON INTEGER {
+    if ($3 < 0 || ($3 > 0  && ($3 % 8 != 0))) {
+        error(@3, "TSIG key digest-bits must either be zero or a positive, multiple of eight");
+    }
+    ElementPtr elem(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("digest-bits", elem);
+};
+
+tsig_key_secret: SECRET {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    if ($4 == "") {
+        error(@3, "TSIG key secret cannot be blank");
+    }
+    ElementPtr elem(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("secret", elem);
+    ctx.leave();
+};
+
+
+// --- end of tsig-keys ---------------------------------
+
+
+dhcp6_json_object: DHCP6 {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("Dhcp6", $4);
+    ctx.leave();
+};
+
+dhcp4_json_object: DHCP4 {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("Dhcp4", $4);
+    ctx.leave();
+};
+
+// --- logging entry -----------------------------------------
+
+// This defines the top level "Logging" object. It parses
+// the following "Logging": { ... }. The ... is defined
+// by logging_params
+logging_object: LOGGING {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("Logging", m);
+    ctx.stack_.push_back(m);
+    ctx.enter(ctx.LOGGING);
+} COLON LCURLY_BRACKET logging_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// This defines the list of allowed parameters that may appear
+// in the top-level Logging object. It can either be a single
+// parameter or several parameters separated by commas.
+logging_params: logging_param
+              | logging_params COMMA logging_param
+              ;
+
+// There's currently only one parameter defined, which is "loggers".
+logging_param: loggers;
+
+// "loggers", the only parameter currently defined in "Logging" object,
+// is "Loggers": [ ... ].
+loggers: LOGGERS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("loggers", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.LOGGERS);
+}  COLON LSQUARE_BRACKET loggers_entries RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// These are the parameters allowed in loggers: either one logger
+// entry or multiple entries separate by commas.
+loggers_entries: logger_entry
+               | loggers_entries COMMA logger_entry
+               ;
+
+// This defines a single entry defined in loggers in Logging.
+logger_entry: LCURLY_BRACKET {
+    ElementPtr l(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(l);
+    ctx.stack_.push_back(l);
+} logger_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+logger_params: logger_param
+             | logger_params COMMA logger_param
+             ;
+
+logger_param: name
+            | output_options_list
+            | debuglevel
+            | severity
+            | unknown_map_entry
+            ;
+
+name: NAME {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr name(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("name", name);
+    ctx.leave();
+};
+
+debuglevel: DEBUGLEVEL COLON INTEGER {
+    ElementPtr dl(new IntElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("debuglevel", dl);
+};
+severity: SEVERITY {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("severity", sev);
+    ctx.leave();
+};
+
+output_options_list: OUTPUT_OPTIONS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("output_options", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.OUTPUT_OPTIONS);
+} COLON LSQUARE_BRACKET output_options_list_content RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+output_options_list_content: output_entry
+                           | output_options_list_content COMMA output_entry
+                           ;
+
+output_entry: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} output_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+};
+
+output_params: output_param
+             | output_params COMMA output_param
+             ;
+
+output_param: OUTPUT {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON STRING {
+    ElementPtr sev(new StringElement($4, ctx.loc2pos(@4)));
+    ctx.stack_.back()->set("output", sev);
+    ctx.leave();
+};
+
+%%
+
+void
+isc::d2::D2Parser::error(const location_type& loc,
+                              const std::string& what)
+{
+    ctx.error(loc, what);
+}

+ 154 - 0
src/bin/d2/d2_simple_parser.cc

@@ -0,0 +1,154 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <d2/d2_config.h>
+#include <d2/d2_simple_parser.h>
+#include <cc/data.h>
+#include <boost/foreach.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace d2 {
+/// @brief This sets of arrays define the default values and
+///        values inherited (derived) between various scopes.
+///
+/// Each of those is documented in @file d2_simple_parser.cc. This
+/// is different than most other comments in Kea code. The reason
+/// for placing those in .cc rather than .h file is that it
+/// is expected to be one centralized place to look at for
+/// the default values. This is expected to be looked at also by
+/// people who are not skilled in C or C++, so they may be
+/// confused with the differences between declaration and definition.
+/// As such, there's one file to look at that hopefully is readable
+/// without any C or C++ skills.
+///
+/// @{
+
+/// @brief This table defines default global values for D2
+///
+/// Some of the global parameters defined in the global scope (i.e. directly
+/// in DhcpDdns) are optional. If not defined, the following values will be
+/// used.
+const SimpleDefaults D2SimpleParser::D2_GLOBAL_DEFAULTS = {
+    { "ip-address",         Element::string, "127.0.0.1" },
+    { "port",               Element::integer, "53001" },
+    { "dns-server-timeout", Element::integer, "100" }, // in seconds
+    { "ncr-protocol",       Element::string, "UDP" },
+    { "ncr-format",         Element::string, "JSON" }
+};
+
+/// Supplies defaults for ddns-domoains list elements (i.e. DdnsDomains)
+const SimpleDefaults D2SimpleParser::TSIG_KEY_DEFAULTS = {
+    { "digest-bits", Element::integer, "0" }
+};
+
+/// Supplies defaults for optional values in DDNS domain managers
+/// (e.g. "forward-ddns" and "reverse-ddns").
+/// @note  While there are none yet defined, it is highly likely
+/// there will be domain manager defaults added in the future.
+/// This code to set defaults already uses this list, so supporting
+/// values will simply require adding them to this list.
+const SimpleDefaults D2SimpleParser::DDNS_DOMAIN_MGR_DEFAULTS = {
+};
+
+/// Supplies defaults for ddns-domoains list elements (i.e. DdnsDomains)
+const SimpleDefaults D2SimpleParser::DDNS_DOMAIN_DEFAULTS = {
+    { "key-name", Element::string, "" }
+};
+
+/// Supplies defaults for optional values DdnsDomain entries.
+const SimpleDefaults D2SimpleParser::DNS_SERVER_DEFAULTS = {
+    { "hostname", Element::string, "" },
+    { "port",     Element::integer, "53" },
+};
+
+/// @}
+
+/// ---------------------------------------------------------------------------
+/// --- end of default values -------------------------------------------------
+/// ---------------------------------------------------------------------------
+
+size_t
+D2SimpleParser::setAllDefaults(isc::data::ElementPtr global) {
+    size_t cnt = 0;
+    // Set global defaults first.
+    cnt = setDefaults(global, D2_GLOBAL_DEFAULTS);
+
+    // If the key list is present, set its members' defaults
+    if (global->find("tsig-keys")) {
+        ConstElementPtr keys = global->get("tsig-keys");
+        cnt += setListDefaults(keys, TSIG_KEY_DEFAULTS);
+    } else {
+        // Not present, so add an empty list.
+        ConstElementPtr list(new ListElement());
+        global->set("tsig-keys", list);
+        cnt++;
+    }
+
+    // Set the forward domain manager defaults.
+    cnt += setManagerDefaults(global, "forward-ddns", DDNS_DOMAIN_MGR_DEFAULTS);
+
+    // Set the reverse domain manager defaults.
+    cnt += setManagerDefaults(global, "reverse-ddns", DDNS_DOMAIN_MGR_DEFAULTS);
+    return (cnt);
+}
+
+size_t
+D2SimpleParser::setDdnsDomainDefaults(ElementPtr domain,
+                                      const SimpleDefaults& domain_defaults) {
+    size_t cnt = 0;
+
+    // Set the domain's scalar defaults
+    cnt += setDefaults(domain, domain_defaults);
+    if (domain->find("dns-servers")) {
+        // Now add the defaults to its server list.
+        ConstElementPtr servers = domain->get("dns-servers");
+        cnt += setListDefaults(servers, DNS_SERVER_DEFAULTS);
+    }
+
+    return (cnt);
+}
+
+
+size_t
+D2SimpleParser::setManagerDefaults(ElementPtr global,
+                                   const std::string& mgr_name,
+                                   const SimpleDefaults& mgr_defaults) {
+    size_t cnt = 0;
+
+    if (!global->find(mgr_name)) {
+        // If it's not present, then default is an empty map
+        ConstElementPtr map(new MapElement());
+        global->set(mgr_name, map);
+        ++cnt;
+    } else {
+        // Get a writable copy of the manager element map
+        ElementPtr mgr =
+            boost::const_pointer_cast<Element>(global->get(mgr_name));
+
+        // Set the manager's scalar defaults first
+        cnt += setDefaults(mgr, mgr_defaults);
+
+        // Get the domain list and set defaults for them.
+        // The domain list may not be present ddns for this
+        // manager is disabled.
+        if (mgr->find("ddns-domains")) {
+            ConstElementPtr domains = mgr->get("ddns-domains");
+            BOOST_FOREACH(ElementPtr domain, domains->listValue()) {
+                // Set the domain's defaults.  We can't use setListDefaults()
+                // as this does not handle sub-lists or maps, like server list.
+                cnt += setDdnsDomainDefaults(domain, DDNS_DOMAIN_DEFAULTS);
+            }
+        }
+
+    }
+
+    return (cnt);
+}
+
+};
+};

+ 84 - 0
src/bin/d2/d2_simple_parser.h

@@ -0,0 +1,84 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_SIMPLE_PARSER_H
+#define D2_SIMPLE_PARSER_H
+
+#include <cc/simple_parser.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief SimpleParser specialized for D2
+///
+/// This class is a @ref isc::data::SimpleParser dedicated to D2.
+/// In particular, it contains all the default values and names of the
+/// parameters that are to be derived (inherited) between scopes.
+/// For the actual values, see @file d2_simple_parser.cc
+class D2SimpleParser : public data::SimpleParser {
+public:
+
+    /// @brief Sets all defaults for D2 configuration
+    ///
+    /// This method sets global and element defaults.
+    ///
+    /// @param global scope to be filled in with defaults.
+    /// @return number of default values added
+    static size_t setAllDefaults(data::ElementPtr global);
+
+    // see d2_simple_parser.cc for comments for those parameters
+    static const data::SimpleDefaults D2_GLOBAL_DEFAULTS;
+
+    // Defaults for tsig-keys list elements, TSIGKeyInfos
+    static const data::SimpleDefaults TSIG_KEY_DEFAULTS;
+
+    // Defaults for <forward|reverse>-ddns elements, DdnsDomainListMgrs
+    static const data::SimpleDefaults DDNS_DOMAIN_MGR_DEFAULTS;
+
+    // Defaults for ddns-domains list elements, DdnsDomains
+    static const data::SimpleDefaults DDNS_DOMAIN_DEFAULTS;
+
+    // Defaults for dns-servers list elements, DnsServerInfos
+    static const data::SimpleDefaults DNS_SERVER_DEFAULTS;
+
+    /// @brief Adds default values to a DDNS Domain element
+    ///
+    /// Adds the scalar default values to the given DDNS domain
+    /// element, and then adds the DNS Server defaults to the domain's
+    /// server list, "dns-servers".
+    ///
+    /// @param domain DDNS domain element to which defaults should be added
+    /// @param domain_defaults list of default values from which to add
+    /// @return returns the number of default values added
+    static size_t setDdnsDomainDefaults(data::ElementPtr domain,
+                                        const data::SimpleDefaults&
+                                        domain_defaults);
+
+    /// @brief Adds default values to a DDNS Domain List Manager
+    ///
+    /// This function looks for the named DDNS domain manager element within
+    /// the given element tree.  If it is found, it adds the scalar default
+    /// values to the manager element and then adds the DDNS Domain defaults
+    /// to its domain list, "ddns-domains".  If the manager element is not
+    /// found, then an empty map entry is added for it, thus defaulting the
+    /// manager to "disabled".
+    ///
+    /// @param global element tree containgin the DDNS domain manager element
+    /// to which defaults should be
+    /// added
+    /// @param mgr_name name of the manager element within the element tree
+    /// (e.g. "forward-ddns", "reverse-ddns")
+    /// @param mgr_defaults list of default values from which to add
+    /// @return returns the number of default values added
+    static size_t setManagerDefaults(data::ElementPtr global,
+                                     const std::string& mgr_name,
+                                     const data::SimpleDefaults& mgr_defaults);
+};
+
+};
+};
+
+#endif

+ 193 - 0
src/bin/d2/location.hh

@@ -0,0 +1,193 @@
+// Generated 201702171216
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Locations for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file location.hh
+ ** Define the isc::d2::location class.
+ */
+
+#ifndef YY_D2_PARSER_LOCATION_HH_INCLUDED
+# define YY_D2_PARSER_LOCATION_HH_INCLUDED
+
+# include "position.hh"
+
+#line 14 "d2_parser.yy" // location.cc:296
+namespace isc { namespace d2 {
+#line 46 "location.hh" // location.cc:296
+  /// Abstract a location.
+  class location
+  {
+  public:
+
+    /// Construct a location from \a b to \a e.
+    location (const position& b, const position& e)
+      : begin (b)
+      , end (e)
+    {
+    }
+
+    /// Construct a 0-width location in \a p.
+    explicit location (const position& p = position ())
+      : begin (p)
+      , end (p)
+    {
+    }
+
+    /// Construct a 0-width location in \a f, \a l, \a c.
+    explicit location (std::string* f,
+                       unsigned int l = 1u,
+                       unsigned int c = 1u)
+      : begin (f, l, c)
+      , end (f, l, c)
+    {
+    }
+
+
+    /// Initialization.
+    void initialize (std::string* f = YY_NULLPTR,
+                     unsigned int l = 1u,
+                     unsigned int c = 1u)
+    {
+      begin.initialize (f, l, c);
+      end = begin;
+    }
+
+    /** \name Line and Column related manipulators
+     ** \{ */
+  public:
+    /// Reset initial location to final location.
+    void step ()
+    {
+      begin = end;
+    }
+
+    /// Extend the current location to the COUNT next columns.
+    void columns (int count = 1)
+    {
+      end += count;
+    }
+
+    /// Extend the current location to the COUNT next lines.
+    void lines (int count = 1)
+    {
+      end.lines (count);
+    }
+    /** \} */
+
+
+  public:
+    /// Beginning of the located region.
+    position begin;
+    /// End of the located region.
+    position end;
+  };
+
+  /// Join two locations, in place.
+  inline location& operator+= (location& res, const location& end)
+  {
+    res.end = end.end;
+    return res;
+  }
+
+  /// Join two locations.
+  inline location operator+ (location res, const location& end)
+  {
+    return res += end;
+  }
+
+  /// Add \a width columns to the end position, in place.
+  inline location& operator+= (location& res, int width)
+  {
+    res.columns (width);
+    return res;
+  }
+
+  /// Add \a width columns to the end position.
+  inline location operator+ (location res, int width)
+  {
+    return res += width;
+  }
+
+  /// Subtract \a width columns to the end position, in place.
+  inline location& operator-= (location& res, int width)
+  {
+    return res += -width;
+  }
+
+  /// Subtract \a width columns to the end position.
+  inline location operator- (location res, int width)
+  {
+    return res -= width;
+  }
+
+  /// Compare two location objects.
+  inline bool
+  operator== (const location& loc1, const location& loc2)
+  {
+    return loc1.begin == loc2.begin && loc1.end == loc2.end;
+  }
+
+  /// Compare two location objects.
+  inline bool
+  operator!= (const location& loc1, const location& loc2)
+  {
+    return !(loc1 == loc2);
+  }
+
+  /** \brief Intercept output stream redirection.
+   ** \param ostr the destination output stream
+   ** \param loc a reference to the location to redirect
+   **
+   ** Avoid duplicate information.
+   */
+  template <typename YYChar>
+  inline std::basic_ostream<YYChar>&
+  operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
+  {
+    unsigned int end_col = 0 < loc.end.column ? loc.end.column - 1 : 0;
+    ostr << loc.begin;
+    if (loc.end.filename
+        && (!loc.begin.filename
+            || *loc.begin.filename != *loc.end.filename))
+      ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col;
+    else if (loc.begin.line < loc.end.line)
+      ostr << '-' << loc.end.line << '.' << end_col;
+    else if (loc.begin.column < end_col)
+      ostr << '-' << end_col;
+    return ostr;
+  }
+
+#line 14 "d2_parser.yy" // location.cc:296
+} } // isc::d2
+#line 192 "location.hh" // location.cc:296
+#endif // !YY_D2_PARSER_LOCATION_HH_INCLUDED

+ 159 - 0
src/bin/d2/parser_context.cc

@@ -0,0 +1,159 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <d2/d2_parser.h>
+#include <d2/parser_context.h>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <boost/lexical_cast.hpp>
+#include <fstream>
+#include <limits>
+
+namespace isc {
+namespace d2 {
+
+D2ParserContext::D2ParserContext()
+  : ctx_(NO_KEYWORD), trace_scanning_(false), trace_parsing_(false)
+{
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseString(const std::string& str, ParserType parser_type)
+{
+    scanStringBegin(str, parser_type);
+    return (parseCommon());
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseFile(const std::string& filename, ParserType parser_type) {
+    FILE* f = fopen(filename.c_str(), "r");
+    if (!f) {
+        isc_throw(D2ParseError, "Unable to open file " << filename);
+    }
+    scanFileBegin(f, filename, parser_type);
+    return (parseCommon());
+}
+
+isc::data::ElementPtr
+D2ParserContext::parseCommon() {
+    isc::d2::D2Parser parser(*this);
+    // Uncomment this to get detailed parser logs.
+    // trace_parsing_ = true;
+    parser.set_debug_level(trace_parsing_);
+    try {
+        int res = parser.parse();
+        if (res != 0) {
+            isc_throw(D2ParseError, "Parser abort");
+        }
+        scanEnd();
+    }
+    catch (...) {
+        scanEnd();
+        throw;
+    }
+    if (stack_.size() == 1) {
+        return (stack_[0]);
+    } else {
+        isc_throw(D2ParseError, "Expected exactly one terminal Element expected, found "
+                  << stack_.size());
+    }
+}
+
+
+void
+D2ParserContext::error(const isc::d2::location& loc, const std::string& what)
+{
+    isc_throw(D2ParseError, loc << ": " << what);
+}
+
+void
+D2ParserContext::error (const std::string& what)
+{
+    isc_throw(D2ParseError, what);
+}
+
+void
+D2ParserContext::fatal (const std::string& what)
+{
+    isc_throw(D2ParseError, what);
+}
+
+isc::data::Element::Position
+D2ParserContext::loc2pos(isc::d2::location& loc)
+{
+    const std::string& file = *loc.begin.filename;
+    const uint32_t line = loc.begin.line;
+    const uint32_t pos = loc.begin.column;
+    return (isc::data::Element::Position(file, line, pos));
+}
+
+void
+D2ParserContext::enter(const ParserContext& ctx)
+{
+    cstack_.push_back(ctx_);
+    ctx_ = ctx;
+}
+
+void
+D2ParserContext::leave()
+{
+    if (cstack_.empty()) {
+        fatal("unbalanced syntactic context");
+    }
+
+    ctx_ = cstack_.back();
+    cstack_.pop_back();
+}
+
+const std::string
+D2ParserContext::contextName()
+{
+    switch (ctx_) {
+    case NO_KEYWORD:
+        return ("__no keyword__");
+    case CONFIG:
+        return ("toplevel");
+    case DHCPDDNS:
+        return ("DhcpDdns");
+    case TSIG_KEY:
+        return ("tsig-key");
+    case TSIG_KEYS:
+        return ("tsig-keys");
+    case ALGORITHM:
+        return("algorithm");
+    case DIGEST_BITS:
+        return("digest-bits");
+    case SECRET:
+        return("secret");
+    case FORWARD_DDNS:
+        return("forward-ddns");
+    case REVERSE_DDNS:
+        return("reverse-ddns");
+    case DDNS_DOMAIN:
+        return("ddns-domain");
+    case DDNS_DOMAINS:
+        return("ddns-domains");
+    case DNS_SERVER:
+        return("dns-server");
+    case DNS_SERVERS:
+        return("dns-servers");
+    case LOGGING:
+        return ("Logging");
+    case LOGGERS:
+        return ("loggers");
+    case OUTPUT_OPTIONS:
+        return ("output-options");
+    case NCR_PROTOCOL:
+        return ("ncr-protocol");
+    case NCR_FORMAT:
+        return ("ncr-format");
+    default:
+        return ("__unknown__");
+    }
+}
+
+};
+};

+ 298 - 0
src/bin/d2/parser_context.h

@@ -0,0 +1,298 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PARSER_CONTEXT_H
+#define PARSER_CONTEXT_H
+#include <string>
+#include <map>
+#include <vector>
+#include <d2/d2_parser.h>
+#include <d2/parser_context_decl.h>
+#include <exceptions/exceptions.h>
+
+// Tell Flex the lexer's prototype ...
+#define YY_DECL isc::d2::D2Parser::symbol_type d2_parser_lex (D2ParserContext& driver)
+
+// ... and declare it for the parser's sake.
+YY_DECL;
+
+namespace isc {
+namespace d2 {
+
+/// @brief Evaluation error exception raised when trying to parse.
+///
+/// @todo: This probably should be common for Dhcp4 and Dhcp6.
+class D2ParseError : public isc::Exception {
+public:
+    D2ParseError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Evaluation context, an interface to the expression evaluation.
+class D2ParserContext
+{
+public:
+
+    /// @brief Defines currently supported scopes
+    ///
+    /// D2Parser may eventually support multiple levels of parsing scope. 
+    /// Currently it supports only the D2 module scope which expects the data
+    /// to be parsed to be a map containing the DhcpDdns element and its
+    /// constituents.
+    ///
+    typedef enum {
+        /// This parser will parse the content as generic JSON.
+        PARSER_JSON,
+
+        ///< Used for parsing top level (contains DhcpDdns, Logging, others)
+        PARSER_DHCPDDNS,
+
+        ///< Used for parsing content of DhcpDdns.
+        PARSER_SUB_DHCPDDNS,
+
+        ///< Used for parsing content of a TSIG key.
+        PARSER_TSIG_KEY,
+
+        ///< Used for pasing a list of TSIG Keys.
+        PARSER_TSIG_KEYS,
+
+        ///< Used for parsing content of a DDNS Domain.
+        PARSER_DDNS_DOMAIN,
+
+        ///< Used for parsing a list a DDNS Domains.
+        PARSER_DDNS_DOMAINS,
+
+        ///< Used for parsing content of a DNS Server.
+        PARSER_DNS_SERVER,
+
+        ///< Used for pasing a list of DNS servers.
+        PARSER_DNS_SERVERS
+    } ParserType;
+
+    /// @brief Default constructor.
+    D2ParserContext();
+
+    /// @brief JSON elements being parsed.
+    std::vector<isc::data::ElementPtr> stack_;
+
+    /// @brief Method called before scanning starts on a string.
+    ///
+    /// @param str string to be parsed
+    /// @param type specifies expected content
+    void scanStringBegin(const std::string& str, ParserType type);
+
+    /// @brief Method called before scanning starts on a file.
+    ///
+    /// @param f stdio FILE pointer
+    /// @param filename file to be parsed
+    /// @param type specifies expected content
+    void scanFileBegin(FILE* f, const std::string& filename, ParserType type);
+
+    /// @brief Method called after the last tokens are scanned.
+    void scanEnd();
+
+    /// @brief Divert input to an include file.
+    ///
+    /// @param filename file to be included
+    void includeFile(const std::string& filename);
+
+    /// @brief Run the parser on the string specified.
+    ///
+    /// This method parses specified string. Depending on the value of
+    /// parser_type, parser may either check only that the input is valid
+    /// JSON, or may do more specific syntax checking. See @ref ParserType
+    /// for supported syntax checkers.
+    ///
+    /// @param str string to be parsed
+    /// @param parser_type specifies expected content
+    /// @return Element structure representing parsed text.
+    isc::data::ElementPtr parseString(const std::string& str,
+                                      ParserType parser_type);
+
+    /// @brief Run the parser on the file specified.
+    ///
+    /// This method parses specified file. Depending on the value of
+    /// parser_type, parser may either check only that the input is valid
+    /// JSON, or may do more specific syntax checking. See @ref ParserType
+    /// for supported syntax checkers.
+    ///
+    /// @param filename file to be parsed
+    /// @param parser_type specifies expected content
+    /// @return Element structure representing parsed text.
+    isc::data::ElementPtr parseFile(const std::string& filename,
+                                    ParserType parser_type);
+
+    /// @brief Error handler
+    ///
+    /// @param loc location within the parsed file when experienced a problem.
+    /// @param what string explaining the nature of the error.
+    /// @throw D2ParseError
+    void error(const isc::d2::location& loc, const std::string& what);
+
+    /// @brief Error handler
+    ///
+    /// This is a simplified error reporting tool for reporting
+    /// parsing errors.
+    ///
+    /// @param what string explaining the nature of the error.
+    /// @throw D2ParseError
+    void error(const std::string& what);
+
+    /// @brief Fatal error handler
+    ///
+    /// This is for should not happen but fatal errors.
+    /// Used by YY_FATAL_ERROR macro so required to be static.
+    ///
+    /// @param what string explaining the nature of the error.
+    /// @throw D2ParseError
+    static void fatal(const std::string& what);
+
+    /// @brief Converts bison's position to one understood by isc::data::Element
+    ///
+    /// Convert a bison location into an element position
+    /// (take the begin, the end is lost)
+    ///
+    /// @param loc location in bison format
+    /// @return Position in format accepted by Element
+    isc::data::Element::Position loc2pos(isc::d2::location& loc);
+
+    /// @brief Defines syntactic contexts for lexical tie-ins
+    typedef enum {
+        ///< This one is used in pure JSON mode.
+        NO_KEYWORD,
+
+        ///< Used while parsing top level (contains DhcpDdns, Logging, ...)
+        CONFIG,
+
+        ///< Used while parsing content of DhcpDdns.
+        DHCPDDNS,
+
+        ///< Used while parsing content of a tsig-key
+        TSIG_KEY,
+
+        ///< Used while parsing a list of tsig-keys
+        TSIG_KEYS,
+
+        ///< Used while parsing content of DhcpDdns/tsig-keys/algorithm
+        ALGORITHM,
+
+        ///< Used while parsing content of DhcpDdns/tsig-keys/digest-bits
+        DIGEST_BITS,
+
+        ///< Used while parsing content of DhcpDdns/tsig-keys/secret
+        SECRET,
+
+        ///< Used while parsing content of DhcpDdns/forward-ddns
+        FORWARD_DDNS,
+
+        ///< Used while parsing content of DhcpDdns/reverse-ddns
+        REVERSE_DDNS,
+
+        ///< Used while parsing content of a ddns-domain
+        DDNS_DOMAIN,
+
+        ///< Used while parsing a list of ddns-domains
+        DDNS_DOMAINS,
+
+        ///< Used while parsing content of a dns-server
+        DNS_SERVER,
+
+        ///< Used while parsing content of list of dns-servers
+        DNS_SERVERS,
+
+        ///< Used while parsing content of Logging
+        LOGGING,
+
+        /// Used while parsing Logging/loggers structures.
+        LOGGERS,
+
+        /// Used while parsing Logging/loggers/output_options structures.
+        OUTPUT_OPTIONS,
+
+        /// Used while parsing DhcpDdns/ncr-protocol
+        NCR_PROTOCOL,
+
+        /// Used while parsing DhcpDdns/ncr-format
+        NCR_FORMAT
+
+    } ParserContext;
+
+    /// @brief File name
+    std::string file_;
+
+    /// @brief File name stack
+    std::vector<std::string> files_;
+
+    /// @brief Location of the current token
+    ///
+    /// The lexer will keep updating it. This variable will be useful
+    /// for logging errors.
+    isc::d2::location loc_;
+
+    /// @brief Location stack
+    std::vector<isc::d2::location> locs_;
+
+    /// @brief Lexer state stack
+    std::vector<struct yy_buffer_state*> states_;
+
+    /// @brief sFile (aka FILE)
+    FILE* sfile_;
+
+    /// @brief sFile (aka FILE) stack
+    ///
+    /// This is a stack of files. Typically there's only one file (the
+    /// one being currently parsed), but there may be more if one
+    /// file includes another.
+    std::vector<FILE*> sfiles_;
+
+    /// @brief Current syntactic context
+    ParserContext ctx_;
+
+    /// @brief Enter a new syntactic context
+    ///
+    /// Entering a new syntactic context is useful in several ways.
+    /// First, it allows the parser to avoid conflicts. Second, it
+    /// allows the lexer to return different tokens depending on
+    /// context (e.g. if "name" string is detected, the lexer
+    /// will return STRING token if in JSON mode or NAME if
+    /// in TSIG_KEY mode. Finally, the syntactic context allows the
+    /// error message to be more descriptive if the input string
+    /// does not parse properly.
+    ///
+    /// @param ctx the syntactic context to enter into
+    void enter(const ParserContext& ctx);
+
+    /// @brief Leave a syntactic context
+    ///
+    /// @throw isc::Unexpected if unbalanced
+    void leave();
+
+    /// @brief Get the syntax context name
+    ///
+    /// @return printable name of the context.
+    const std::string contextName();
+
+ private:
+    /// @brief Flag determining scanner debugging.
+    bool trace_scanning_;
+
+    /// @brief Flag determining parser debugging.
+    bool trace_parsing_;
+
+    /// @brief Syntactic context stack
+    std::vector<ParserContext> cstack_;
+
+    /// @brief Common part of parseXXX
+    ///
+    /// @return Element structure representing parsed text.
+    isc::data::ElementPtr parseCommon();
+};
+
+}; // end of isc::eval namespace
+}; // end of isc namespace
+
+#endif

+ 20 - 0
src/bin/d2/parser_context_decl.h

@@ -0,0 +1,20 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef D2_PARSER_CONTEXT_DECL_H
+#define D2_PARSER_CONTEXT_DECL_H
+
+/// @file d2/parser_context_decl.h Forward declaration of the ParserContext class
+
+namespace isc {
+namespace d2 {
+
+class D2ParserContext;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif

+ 181 - 0
src/bin/d2/position.hh

@@ -0,0 +1,181 @@
+// Generated 201702171216
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Positions for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file position.hh
+ ** Define the isc::d2::position class.
+ */
+
+#ifndef YY_D2_PARSER_POSITION_HH_INCLUDED
+# define YY_D2_PARSER_POSITION_HH_INCLUDED
+
+# include <algorithm> // std::max
+# include <iostream>
+# include <string>
+
+# ifndef YY_NULLPTR
+#  if defined __cplusplus && 201103L <= __cplusplus
+#   define YY_NULLPTR nullptr
+#  else
+#   define YY_NULLPTR 0
+#  endif
+# endif
+
+#line 14 "d2_parser.yy" // location.cc:296
+namespace isc { namespace d2 {
+#line 56 "position.hh" // location.cc:296
+  /// Abstract a position.
+  class position
+  {
+  public:
+    /// Construct a position.
+    explicit position (std::string* f = YY_NULLPTR,
+                       unsigned int l = 1u,
+                       unsigned int c = 1u)
+      : filename (f)
+      , line (l)
+      , column (c)
+    {
+    }
+
+
+    /// Initialization.
+    void initialize (std::string* fn = YY_NULLPTR,
+                     unsigned int l = 1u,
+                     unsigned int c = 1u)
+    {
+      filename = fn;
+      line = l;
+      column = c;
+    }
+
+    /** \name Line and Column related manipulators
+     ** \{ */
+    /// (line related) Advance to the COUNT next lines.
+    void lines (int count = 1)
+    {
+      if (count)
+        {
+          column = 1u;
+          line = add_ (line, count, 1);
+        }
+    }
+
+    /// (column related) Advance to the COUNT next columns.
+    void columns (int count = 1)
+    {
+      column = add_ (column, count, 1);
+    }
+    /** \} */
+
+    /// File name to which this position refers.
+    std::string* filename;
+    /// Current line number.
+    unsigned int line;
+    /// Current column number.
+    unsigned int column;
+
+  private:
+    /// Compute max(min, lhs+rhs) (provided min <= lhs).
+    static unsigned int add_ (unsigned int lhs, int rhs, unsigned int min)
+    {
+      return (0 < rhs || -static_cast<unsigned int>(rhs) < lhs
+              ? rhs + lhs
+              : min);
+    }
+  };
+
+  /// Add \a width columns, in place.
+  inline position&
+  operator+= (position& res, int width)
+  {
+    res.columns (width);
+    return res;
+  }
+
+  /// Add \a width columns.
+  inline position
+  operator+ (position res, int width)
+  {
+    return res += width;
+  }
+
+  /// Subtract \a width columns, in place.
+  inline position&
+  operator-= (position& res, int width)
+  {
+    return res += -width;
+  }
+
+  /// Subtract \a width columns.
+  inline position
+  operator- (position res, int width)
+  {
+    return res -= width;
+  }
+
+  /// Compare two position objects.
+  inline bool
+  operator== (const position& pos1, const position& pos2)
+  {
+    return (pos1.line == pos2.line
+            && pos1.column == pos2.column
+            && (pos1.filename == pos2.filename
+                || (pos1.filename && pos2.filename
+                    && *pos1.filename == *pos2.filename)));
+  }
+
+  /// Compare two position objects.
+  inline bool
+  operator!= (const position& pos1, const position& pos2)
+  {
+    return !(pos1 == pos2);
+  }
+
+  /** \brief Intercept output stream redirection.
+   ** \param ostr the destination output stream
+   ** \param pos a reference to the position to redirect
+   */
+  template <typename YYChar>
+  inline std::basic_ostream<YYChar>&
+  operator<< (std::basic_ostream<YYChar>& ostr, const position& pos)
+  {
+    if (pos.filename)
+      ostr << *pos.filename << ':';
+    return ostr << pos.line << '.' << pos.column;
+  }
+
+#line 14 "d2_parser.yy" // location.cc:296
+} } // isc::d2
+#line 180 "position.hh" // location.cc:296
+#endif // !YY_D2_PARSER_POSITION_HH_INCLUDED

+ 158 - 0
src/bin/d2/stack.hh

@@ -0,0 +1,158 @@
+// Generated 201702171216
+// A Bison parser, made by GNU Bison 3.0.4.
+
+// Stack handling for Bison parsers in C++
+
+// Copyright (C) 2002-2015 Free Software Foundation, Inc.
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+// As a special exception, you may create a larger work that contains
+// part or all of the Bison parser skeleton and distribute that work
+// under terms of your choice, so long as that work isn't itself a
+// parser generator using the skeleton or a modified version thereof
+// as a parser skeleton.  Alternatively, if you modify or redistribute
+// the parser skeleton itself, you may (at your option) remove this
+// special exception, which will cause the skeleton and the resulting
+// Bison output files to be licensed under the GNU General Public
+// License without this special exception.
+
+// This special exception was added by the Free Software Foundation in
+// version 2.2 of Bison.
+
+/**
+ ** \file stack.hh
+ ** Define the isc::d2::stack class.
+ */
+
+#ifndef YY_D2_PARSER_STACK_HH_INCLUDED
+# define YY_D2_PARSER_STACK_HH_INCLUDED
+
+# include <vector>
+
+#line 14 "d2_parser.yy" // stack.hh:132
+namespace isc { namespace d2 {
+#line 46 "stack.hh" // stack.hh:132
+  template <class T, class S = std::vector<T> >
+  class stack
+  {
+  public:
+    // Hide our reversed order.
+    typedef typename S::reverse_iterator iterator;
+    typedef typename S::const_reverse_iterator const_iterator;
+
+    stack ()
+      : seq_ ()
+    {
+      seq_.reserve (200);
+    }
+
+    stack (unsigned int n)
+      : seq_ (n)
+    {}
+
+    inline
+    T&
+    operator[] (unsigned int i)
+    {
+      return seq_[seq_.size () - 1 - i];
+    }
+
+    inline
+    const T&
+    operator[] (unsigned int i) const
+    {
+      return seq_[seq_.size () - 1 - i];
+    }
+
+    /// Steal the contents of \a t.
+    ///
+    /// Close to move-semantics.
+    inline
+    void
+    push (T& t)
+    {
+      seq_.push_back (T());
+      operator[](0).move (t);
+    }
+
+    inline
+    void
+    pop (unsigned int n = 1)
+    {
+      for (; n; --n)
+        seq_.pop_back ();
+    }
+
+    void
+    clear ()
+    {
+      seq_.clear ();
+    }
+
+    inline
+    typename S::size_type
+    size () const
+    {
+      return seq_.size ();
+    }
+
+    inline
+    const_iterator
+    begin () const
+    {
+      return seq_.rbegin ();
+    }
+
+    inline
+    const_iterator
+    end () const
+    {
+      return seq_.rend ();
+    }
+
+  private:
+    stack (const stack&);
+    stack& operator= (const stack&);
+    /// The wrapped container.
+    S seq_;
+  };
+
+  /// Present a slice of the top of a stack.
+  template <class T, class S = stack<T> >
+  class slice
+  {
+  public:
+    slice (const S& stack, unsigned int range)
+      : stack_ (stack)
+      , range_ (range)
+    {}
+
+    inline
+    const T&
+    operator [] (unsigned int i) const
+    {
+      return stack_[range_ - i];
+    }
+
+  private:
+    const S& stack_;
+    unsigned int range_;
+  };
+
+#line 14 "d2_parser.yy" // stack.hh:132
+} } // isc::d2
+#line 156 "stack.hh" // stack.hh:132
+
+#endif // !YY_D2_PARSER_STACK_HH_INCLUDED

+ 4 - 0
src/bin/d2/tests/Makefile.am

@@ -23,6 +23,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/d2/tests\"
 AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+AM_CPPFLAGS += -DCFG_EXAMPLES=\"$(abs_top_srcdir)/doc/examples/ddns\"
 
 CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 
@@ -55,6 +56,8 @@ d2_unittests_SOURCES += nc_remove_unittests.cc
 d2_unittests_SOURCES += nc_test_utils.cc nc_test_utils.h
 d2_unittests_SOURCES += nc_trans_unittests.cc
 d2_unittests_SOURCES += d2_controller_unittests.cc
+d2_unittests_SOURCES += d2_simple_parser_unittest.cc
+d2_unittests_SOURCES += parser_unittest.cc parser_unittest.h
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS)
@@ -76,6 +79,7 @@ d2_unittests_LDADD += $(top_builddir)/src/lib/process/libkea-process.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
+d2_unittests_LDADD += $(top_builddir)/src/lib/testutils/libkea-testutils.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/stats/libkea-stats.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la

+ 211 - 314
src/bin/d2/tests/d2_cfg_mgr_unittests.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
@@ -9,6 +9,9 @@
 #include <config/module_spec.h>
 #include <d2/d2_config.h>
 #include <d2/d2_cfg_mgr.h>
+#include <d2/d2_simple_parser.h>
+#include <d2/parser_context.h>
+#include <d2/tests/parser_unittest.h>
 #include <dhcpsrv/testutils/config_result_check.h>
 #include <process/testutils/d_test_stubs.h>
 #include <test_data_files_config.h>
@@ -95,270 +98,150 @@ public:
 
         return (config.str());
     }
+
     /// @brief Enumeration to select between expected configuration outcomes
     enum RunConfigMode {
-       SHOULD_PASS,
-       SHOULD_FAIL
+        NO_ERROR,
+        SYNTAX_ERROR,
+        LOGIC_ERROR
     };
 
     /// @brief Parses a configuration string and tests against a given outcome
     ///
     /// Convenience method which accepts JSON text and an expected pass or fail
-    /// outcome. It converts the text into an ElementPtr and passes that to
-    /// configuration manager's parseConfig method.  It then tests the
-    /// parse result against the expected outcome  If they do not match it
-    /// the method asserts a failure.   If they do match, it refreshes the
-    /// the D2Params pointer with the newly parsed instance.
+    /// outcome.  It uses the D2ParserContext to parse the text under the
+    /// PARSE_SUB_DHCPDDNS context, then adds the D2 defaults to the resultant
+    /// element tree. Assuming that's successful the element tree is passed
+    /// to D2CfgMgr::parseConfig() method.
     ///
     /// @param config_str the JSON configuration text to parse
-    /// @param mode indicator if the parsing should fail or not.  It defaults
+    /// @param error_type  indicates the type error expected, NONE, SYNTAX,
+    /// or LOGIC. SYNTAX errors are emitted by JSON parser, logic errors
+    /// are emitted by element parser(s).
+    /// @param exp_error exact text of the error message expected
     /// defaults to SHOULD_PASS.
     ///
-    void runConfig(std::string config_str, RunConfigMode mode=SHOULD_PASS) {
-        // We assume the config string is valid JSON.
-        ASSERT_TRUE(fromJSON(config_str));
+    /// @return AssertionSuccess if test passes, AssertionFailure otherwise
+    ::testing::AssertionResult runConfigOrFail(const std::string& json,
+                                               const RunConfigMode mode,
+                                               const std::string& exp_error) {
+
+        try {
+            // Invoke the JSON parser, casting the returned element tree
+            // into mutable form.
+            D2ParserContext parser_context;
+            data::ElementPtr elem =
+                boost::const_pointer_cast<Element>
+                (parser_context.parseString(json, D2ParserContext::
+                                                  PARSER_SUB_DHCPDDNS));
+
+            // If parsing succeeded when we expected a syntax error, then fail.
+            if (mode == SYNTAX_ERROR) {
+               return ::testing::AssertionFailure()
+                             << "Unexpected  JSON parsing success"
+                             << "\njson: [" << json << " ]";
+            }
 
-        // Parse the configuration and verify we got the expected outcome.
-        answer_ = cfg_mgr_->parseConfig(config_set_);
-        ASSERT_TRUE(checkAnswer(mode == SHOULD_FAIL));
+            // JSON parsed ok, so add the defaults to the element tree it produced.
+            D2SimpleParser::setAllDefaults(elem);
+            config_set_ = elem;
+        } catch (const std::exception& ex) {
+            // JSON Parsing failed
+            if (exp_error.empty()) {
+                // We did not expect an error, so fail.
+                return ::testing::AssertionFailure()
+                          << "Unexpected syntax error:" << ex.what()
+                          << "\njson: [" << json << " ]";
+            }
 
-        // Verify that the D2 context can be retrieved and is not null.
-        D2CfgContextPtr context;
-        ASSERT_NO_THROW(context = cfg_mgr_->getD2CfgContext());
+            if (ex.what() != exp_error) {
+                // Expected an error not the one we got, so fail
+                return ::testing::AssertionFailure()
+                          << "Wrong syntax error detected, expected: "
+                          << exp_error << ", got: " << ex.what()
+                          << "\njson: [" << json << " ]";
+            }
 
-        // Verify that the global scalars have the proper values.
-        d2_params_ = context->getD2Params();
-        ASSERT_TRUE(d2_params_);
-    }
+            // We go the syntax error we expected, so return success
+            return ::testing::AssertionSuccess();
+        }
 
-    /// @brief Check parse result against expected outcome and position info
-    ///
-    /// This method analyzes the given parsing result against an expected outcome
-    /// of SHOULD_PASS or SHOULD_FAIL.  If it is expected to fail, the comment
-    /// contained within the result is searched for Element::Position information
-    /// which should contain the given file name.  It does not attempt to verify
-    /// the numerical values for line number and col.
-    ///
-    /// @param answer Element set containing an integer result code and string
-    /// comment.
-    /// @param mode indicator if the parsing should fail or not.
-    /// @param file_name name of the file containing the configuration text
-    /// parsed. It defaults to "<string>" which is the value present if the
-    /// configuration text did not originate from a file. (i.e. one did not use
-    /// isc::data::Element::fromJSONFile() to read the JSON text).
-    void
-    checkAnswerWithError(isc::data::ConstElementPtr answer,
-                         RunConfigMode mode, std::string file_name="<string>") {
+        // The JSON parsed ok and we've added the defaults, pass the config
+        // into the Element parser and check for the expected outcome.
+        data::ConstElementPtr answer = cfg_mgr_->parseConfig(config_set_);
+
+        // Extract the result and error text from the answer.
         int rcode = 0;
         isc::data::ConstElementPtr comment;
         comment = isc::config::parseAnswer(rcode, answer);
 
-        if (mode == SHOULD_PASS) {
-            if (rcode == 0) {
-                return;
+        if (rcode != 0) {
+            // Element Parsing failed.
+            if (exp_error.empty()) {
+                // We didn't expect it to, fail the test.
+                return ::testing::AssertionFailure()
+                              << "Unexpected logic error: " << *comment
+                              << "\njson: [" << json << " ]";
             }
 
-            FAIL() << "Parsing was expected to pass but failed : " << rcode
-                   << " comment: " << *comment;
+            if (comment->stringValue() != exp_error) {
+                // We 't expect a different error, fail the test.
+                return ::testing::AssertionFailure()
+                              << "Wrong logic error detected, expected: "
+                              << exp_error << ", got: " << *comment
+                              << "\njson: [" << json << " ]";
+            }
+        } else {
+            // Element parsing succeeded.
+            if (!exp_error.empty()) {
+                // It was supposed to fail, so fail the test.
+                return ::testing::AssertionFailure()
+                              << "Unexpected logic success, expected error:"
+                              << exp_error
+                              << "\njson: [" << json << " ]";
+            }
         }
 
-        if (rcode == 0) {
-            FAIL() << "Parsing was expected to fail but passed : "
-                   << " comment: " << *comment;
+        // Verify that the D2 context can be retrieved and is not null.
+        D2CfgContextPtr context;
+        context = cfg_mgr_->getD2CfgContext();
+        if (!context) {
+            return ::testing::AssertionFailure() << "D2CfgContext is null";
         }
 
-        // Parsing was expected to fail, test for position info.
-        if (isc::dhcp::test::errorContainsPosition(answer, file_name)) {
-            return;
+        // Verify that the global scalar container has been created.
+        d2_params_ = context->getD2Params();
+        if (!d2_params_) {
+            return ::testing::AssertionFailure() << "D2Params is null";
         }
 
-        FAIL() << "Parsing failed as expected but lacks position : " << *comment;
+        return ::testing::AssertionSuccess();
     }
 
+
     /// @brief Pointer the D2Params most recently parsed.
     D2ParamsPtr d2_params_;
 };
 
+/// @brief Convenience macros for invoking runOrConfig()
+#define RUN_CONFIG_OK(a) (runConfigOrFail(a, NO_ERROR, ""))
+#define SYNTAX_ERROR(a,b) ASSERT_TRUE(runConfigOrFail(a,SYNTAX_ERROR,b))
+#define LOGIC_ERROR(a,b) ASSERT_TRUE(runConfigOrFail(a,LOGIC_ERROR,b))
+
 /// @brief Tests that the spec file is valid.
 /// Verifies that the DHCP-DDNS configuration specification file
-//  is valid.
+///  is valid.
 TEST(D2SpecTest, basicSpec) {
     ASSERT_NO_THROW(isc::config::
                     moduleSpecFromFile(specfile("dhcp-ddns.spec")));
 }
 
-/// @brief Convenience function which compares the contents of the given
-/// DnsServerInfo against the given set of values.
-///
-/// It is structured in such a way that each value is checked, and output
-/// is generate for all that do not match.
-///
-/// @param server is a pointer to the server to check against.
-/// @param hostname is the value to compare against server's hostname_.
-/// @param ip_address is the string value to compare against server's
-/// ip_address_.
-/// @param port is the value to compare against server's port.
-///
-/// @return returns true if there is a match across the board, otherwise it
-/// returns false.
-bool checkServer(DnsServerInfoPtr server, const char* hostname,
-                 const char *ip_address, uint32_t port)
-{
-    // Return value, assume its a match.
-    bool result = true;
-
-    if (!server) {
-        EXPECT_TRUE(server);
-        return false;
-    }
-
-    // Check hostname.
-    if (server->getHostname() != hostname) {
-        EXPECT_EQ(hostname, server->getHostname());
-        result = false;
-    }
-
-    // Check IP address.
-    if (server->getIpAddress().toText() != ip_address) {
-        EXPECT_EQ(ip_address, server->getIpAddress().toText());
-        result = false;
-    }
-
-    // Check port.
-    if (server->getPort() !=  port) {
-        EXPECT_EQ (port, server->getPort());
-        result = false;
-    }
-
-    return (result);
-}
-
-/// @brief Convenience function which compares the contents of the given
-/// TSIGKeyInfo against the given set of values, and that the TSIGKey
-/// member points to a key.
-///
-/// @param key is a pointer to the TSIGKeyInfo instance to verify
-/// @param name is the value to compare against key's name_.
-/// @param algorithm is the string value to compare against key's algorithm.
-/// @param secret is the value to compare against key's secret.
-///
-/// @return returns true if there is a match across the board, otherwise it
-/// returns false.
-bool checkKey(TSIGKeyInfoPtr key, const std::string& name,
-              const std::string& algorithm, const std::string& secret,
-              uint32_t digestbits = 0) {
-    // Return value, assume its a match.
-    return (((key) &&
-        (key->getName() == name) &&
-        (key->getAlgorithm() == algorithm)  &&
-        (key->getDigestbits() == digestbits) &&
-        (key->getSecret() ==  secret)  &&
-        (key->getTSIGKey())));
-}
-
-/// @brief Test fixture class for testing DnsServerInfo parsing.
-class TSIGKeyInfoTest : public ConfigParseTest {
-public:
-
-    /// @brief Constructor
-    TSIGKeyInfoTest() {
-        reset();
-    }
-
-    /// @brief Destructor
-    ~TSIGKeyInfoTest() {
-    }
-
-    /// @brief Wipe out the current storage and parser and replace
-    /// them with new ones.
-    void reset() {
-        keys_.reset(new TSIGKeyInfoMap());
-        parser_.reset(new TSIGKeyInfoParser("test", keys_));
-    }
-
-    /// @brief Storage for "committing" keys.
-    TSIGKeyInfoMapPtr keys_;
-
-    /// @brief Pointer to the current parser instance.
-    isc::dhcp::ParserPtr parser_;
-};
-
-/// @brief Test fixture class for testing DnsServerInfo parsing.
-class DnsServerInfoTest : public ConfigParseTest {
-public:
-
-    /// @brief Constructor
-    DnsServerInfoTest() {
-        reset();
-    }
-
-    /// @brief Destructor
-    ~DnsServerInfoTest() {
-    }
-
-    /// @brief Wipe out the current storage and parser and replace
-    /// them with new ones.
-    void reset() {
-        servers_.reset(new DnsServerInfoStorage());
-        parser_.reset(new DnsServerInfoParser("test", servers_));
-    }
-
-    /// @brief Storage for "committing" servers.
-    DnsServerInfoStoragePtr servers_;
-
-    /// @brief Pointer to the current parser instance.
-    isc::dhcp::ParserPtr parser_;
-};
-
-
-/// @brief Test fixture class for testing DDnsDomain parsing.
-class DdnsDomainTest : public ConfigParseTest {
-public:
-
-    /// @brief Constructor
-    DdnsDomainTest() {
-        reset();
-    }
-
-    /// @brief Destructor
-    ~DdnsDomainTest() {
-    }
-
-    /// @brief Wipe out the current storage and parser and replace
-    /// them with new ones.
-    void reset() {
-        keys_.reset(new TSIGKeyInfoMap());
-        domains_.reset(new DdnsDomainMap());
-        parser_.reset(new DdnsDomainParser("test", domains_, keys_));
-    }
-
-    /// @brief Add TSIGKeyInfos to the key map
-    ///
-    /// @param name the name of the key
-    /// @param algorithm the algorithm of the key
-    /// @param secret the secret value of the key
-    void addKey(const std::string& name, const std::string& algorithm,
-                const std::string& secret) {
-        TSIGKeyInfoPtr key_info(new TSIGKeyInfo(name, algorithm, secret));
-        (*keys_)[name]=key_info;
-    }
-
-    /// @brief Storage for "committing" domains.
-    DdnsDomainMapPtr domains_;
-
-    /// @brief Storage for TSIGKeys
-    TSIGKeyInfoMapPtr keys_;
-
-    /// @brief Pointer to the current parser instance.
-    isc::dhcp::ParserPtr parser_;
-};
-
 /// @brief Tests a basic valid configuration for D2Param.
 TEST_F(D2CfgMgrTest, validParamsEntry) {
     // Verify that ip_address can be valid v4 address.
     std::string config = makeParamsConfigString ("192.0.0.1", 777, 333,
                                            "UDP", "JSON");
-    runConfig(config);
+    RUN_CONFIG_OK(config);
 
     EXPECT_EQ(isc::asiolink::IOAddress("192.0.0.1"),
               d2_params_->getIpAddress());
@@ -369,7 +252,7 @@ TEST_F(D2CfgMgrTest, validParamsEntry) {
 
     // Verify that ip_address can be valid v6 address.
     config = makeParamsConfigString ("3001::5", 777, 333, "UDP", "JSON");
-    runConfig(config);
+    RUN_CONFIG_OK(config);
 
     // Verify that the global scalars have the proper values.
     EXPECT_EQ(isc::asiolink::IOAddress("3001::5"),
@@ -386,6 +269,9 @@ TEST_F(D2CfgMgrTest, validParamsEntry) {
 /// Currently they are all optional.
 TEST_F(D2CfgMgrTest, defaultValues) {
 
+    ElementPtr defaults = isc::d2::test::parseJSON("{ }");
+    ASSERT_NO_THROW(D2SimpleParser::setAllDefaults(defaults));
+
     // Check that omitting ip_address gets you its default
     std::string config =
             "{"
@@ -398,9 +284,11 @@ TEST_F(D2CfgMgrTest, defaultValues) {
             "\"reverse-ddns\" : {} "
             "}";
 
-    runConfig(config);
-    EXPECT_EQ(isc::asiolink::IOAddress(D2Params::DFT_IP_ADDRESS),
-              d2_params_->getIpAddress());
+    RUN_CONFIG_OK(config);
+    ConstElementPtr deflt;
+    ASSERT_NO_THROW(deflt = defaults->get("ip-address"));
+    ASSERT_TRUE(deflt);
+    EXPECT_EQ(deflt->stringValue(), d2_params_->getIpAddress().toText());
 
     // Check that omitting port gets you its default
     config =
@@ -414,8 +302,10 @@ TEST_F(D2CfgMgrTest, defaultValues) {
             "\"reverse-ddns\" : {} "
             "}";
 
-    runConfig(config);
-    EXPECT_EQ(D2Params::DFT_PORT, d2_params_->getPort());
+    RUN_CONFIG_OK(config);
+    ASSERT_NO_THROW(deflt = defaults->get("port"));
+    ASSERT_TRUE(deflt);
+    EXPECT_EQ(deflt->intValue(), d2_params_->getPort());
 
     // Check that omitting timeout gets you its default
     config =
@@ -429,11 +319,12 @@ TEST_F(D2CfgMgrTest, defaultValues) {
             "\"reverse-ddns\" : {} "
             "}";
 
-    runConfig(config);
-    EXPECT_EQ(D2Params::DFT_DNS_SERVER_TIMEOUT,
-              d2_params_->getDnsServerTimeout());
+    RUN_CONFIG_OK(config);
+    ASSERT_NO_THROW(deflt = defaults->get("dns-server-timeout"));
+    ASSERT_TRUE(deflt);
+    EXPECT_EQ(deflt->intValue(), d2_params_->getDnsServerTimeout());
 
-    // Check that protocol timeout gets you its default
+    // Check that omitting protocol gets you its default
     config =
             "{"
             " \"ip-address\": \"192.0.0.1\" , "
@@ -445,11 +336,13 @@ TEST_F(D2CfgMgrTest, defaultValues) {
             "\"reverse-ddns\" : {} "
             "}";
 
-    runConfig(config);
-    EXPECT_EQ(dhcp_ddns::stringToNcrProtocol(D2Params::DFT_NCR_PROTOCOL),
+    RUN_CONFIG_OK(config);
+    ASSERT_NO_THROW(deflt = defaults->get("ncr-protocol"));
+    ASSERT_TRUE(deflt);
+    EXPECT_EQ(dhcp_ddns::stringToNcrProtocol(deflt->stringValue()),
               d2_params_->getNcrProtocol());
 
-    // Check that format timeout gets you its default
+    // Check that omitting format gets you its default
     config =
             "{"
             " \"ip-address\": \"192.0.0.1\" , "
@@ -461,8 +354,10 @@ TEST_F(D2CfgMgrTest, defaultValues) {
             "\"reverse-ddns\" : {} "
             "}";
 
-    runConfig(config);
-    EXPECT_EQ(dhcp_ddns::stringToNcrFormat(D2Params::DFT_NCR_FORMAT),
+    RUN_CONFIG_OK(config);
+    ASSERT_NO_THROW(deflt = defaults->get("ncr-format"));
+    ASSERT_TRUE(deflt);
+    EXPECT_EQ(dhcp_ddns::stringToNcrFormat(deflt->stringValue()),
               d2_params_->getNcrFormat());
 }
 
@@ -482,7 +377,8 @@ TEST_F(D2CfgMgrTest, unsupportedTopLevelItems) {
             "\"bogus-param\" : true "
             "}";
 
-    runConfig(config, SHOULD_FAIL);
+    SYNTAX_ERROR(config, "<string>:1.181-193: got unexpected "
+                         "keyword \"bogus-param\" in DhcpDdns map.");
 
     // Check that unsupported top level objects fails.  For
     // D2 these fail as they are not in the parse order.
@@ -500,7 +396,8 @@ TEST_F(D2CfgMgrTest, unsupportedTopLevelItems) {
             "\"bogus-object-two\" : {} "
             "}";
 
-    runConfig(config, SHOULD_FAIL);
+    SYNTAX_ERROR(config, "<string>:1.139-156: got unexpected"
+                         " keyword \"bogus-object-one\" in DhcpDdns map.");
 }
 
 
@@ -516,31 +413,35 @@ TEST_F(D2CfgMgrTest, invalidEntry) {
     // Cannot use IPv4 ANY address
     std::string config = makeParamsConfigString ("0.0.0.0", 777, 333,
                                            "UDP", "JSON");
-    runConfig(config, SHOULD_FAIL);
+    LOGIC_ERROR(config, "IP address cannot be \"0.0.0.0\" (<string>:1:17)");
 
     // Cannot use IPv6 ANY address
     config = makeParamsConfigString ("::", 777, 333, "UDP", "JSON");
-    runConfig(config, SHOULD_FAIL);
+    LOGIC_ERROR(config, "IP address cannot be \"::\" (<string>:1:17)");
 
     // Cannot use port  0
     config = makeParamsConfigString ("127.0.0.1", 0, 333, "UDP", "JSON");
-    runConfig(config, SHOULD_FAIL);
+    SYNTAX_ERROR(config, "<string>:1.40: port must be greater than zero but less than 65536");
 
     // Cannot use dns server timeout of 0
     config = makeParamsConfigString ("127.0.0.1", 777, 0, "UDP", "JSON");
-    runConfig(config, SHOULD_FAIL);
+    SYNTAX_ERROR(config, "<string>:1.69: dns-server-timeout"
+                         " must be greater than zero");
 
     // Invalid protocol
     config = makeParamsConfigString ("127.0.0.1", 777, 333, "BOGUS", "JSON");
-    runConfig(config, SHOULD_FAIL);
+    SYNTAX_ERROR(config, "<string>:1.92-98: syntax error,"
+                         " unexpected constant string, expecting UDP or TCP");
 
     // Unsupported protocol
     config = makeParamsConfigString ("127.0.0.1", 777, 333, "TCP", "JSON");
-    runConfig(config, SHOULD_FAIL);
+    LOGIC_ERROR(config, "ncr-protocol : TCP is not yet supported"
+                        "  (<string>:1:92)");
 
     // Invalid format
     config = makeParamsConfigString ("127.0.0.1", 777, 333, "UDP", "BOGUS");
-    runConfig(config, SHOULD_FAIL);
+    SYNTAX_ERROR(config, "<string>:1.115-121: syntax error,"
+                         " unexpected constant string, expecting JSON");
 }
 
 /// @brief Tests the enforcement of data validation when parsing TSIGKeyInfos.
@@ -1265,7 +1166,7 @@ TEST_F(D2CfgMgrTest, fullConfig) {
                         " \"ncr-format\": \"JSON\", "
                         "\"tsig-keys\": ["
                         "{"
-                        "  \"name\": \"d2_key.tmark.org\" , "
+                        "  \"name\": \"d2_key.example.com\" , "
                         "  \"algorithm\": \"hmac-md5\" , "
                         "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
                         "},"
@@ -1278,8 +1179,8 @@ TEST_F(D2CfgMgrTest, fullConfig) {
                         "],"
                         "\"forward-ddns\" : {"
                         "\"ddns-domains\": [ "
-                        "{ \"name\": \"tmark.org\" , "
-                        "  \"key-name\": \"d2_key.tmark.org\" , "
+                        "{ \"name\": \"example.com\" , "
+                        "  \"key-name\": \"d2_key.example.com\" , "
                         "  \"dns-servers\" : [ "
                         "  { \"ip-address\": \"127.0.0.1\" } , "
                         "  { \"ip-address\": \"127.0.0.2\" } , "
@@ -1297,7 +1198,7 @@ TEST_F(D2CfgMgrTest, fullConfig) {
                         "\"reverse-ddns\" : {"
                         "\"ddns-domains\": [ "
                         "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
-                        "  \"key-name\": \"d2_key.tmark.org\" , "
+                        "  \"key-name\": \"d2_key.example.com\" , "
                         "  \"dns-servers\" : [ "
                         "  { \"ip-address\": \"127.0.1.1\" } , "
                         "  { \"ip-address\": \"127.0.2.1\" } , "
@@ -1313,11 +1214,8 @@ TEST_F(D2CfgMgrTest, fullConfig) {
                         "  ] } "
                         "] } }";
 
-    ASSERT_TRUE(fromJSON(config));
-
-    // Verify that we can parse the configuration.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
-    ASSERT_TRUE(checkAnswer(0));
+    // Should parse without error.
+    RUN_CONFIG_OK(config);
 
     // Verify that the D2 context can be retrieved and is not null.
     D2CfgContextPtr context;
@@ -1406,12 +1304,12 @@ TEST_F(D2CfgMgrTest, forwardMatch) {
                         "\"tsig-keys\": [] ,"
                         "\"forward-ddns\" : {"
                         "\"ddns-domains\": [ "
-                        "{ \"name\": \"tmark.org\" , "
+                        "{ \"name\": \"example.com\" , "
                         "  \"dns-servers\" : [ "
                         "  { \"ip-address\": \"127.0.0.1\" } "
                         "  ] } "
                         ", "
-                        "{ \"name\": \"one.tmark.org\" , "
+                        "{ \"name\": \"one.example.com\" , "
                         "  \"dns-servers\" : [ "
                         "  { \"ip-address\": \"127.0.0.2\" } "
                         "  ] } "
@@ -1424,11 +1322,8 @@ TEST_F(D2CfgMgrTest, forwardMatch) {
                         "\"reverse-ddns\" : {} "
                         "}";
 
-
-    ASSERT_TRUE(fromJSON(config));
     // Verify that we can parse the configuration.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
-    ASSERT_TRUE(checkAnswer(0));
+    RUN_CONFIG_OK(config);
 
     // Verify that the D2 context can be retrieved and is not null.
     D2CfgContextPtr context;
@@ -1440,24 +1335,24 @@ TEST_F(D2CfgMgrTest, forwardMatch) {
 
     DdnsDomainPtr match;
     // Verify that an exact match works.
-    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
-    EXPECT_EQ("tmark.org", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
+    EXPECT_EQ("example.com", match->getName());
 
-    // Verify that search is case-insensitive.
-    EXPECT_TRUE(cfg_mgr_->matchForward("TMARK.ORG", match));
-    EXPECT_EQ("tmark.org", match->getName());
+    // Verify that search is case insensitive.
+    EXPECT_TRUE(cfg_mgr_->matchForward("EXAMPLE.COM", match));
+    EXPECT_EQ("example.com", match->getName());
 
     // Verify that an exact match works.
-    EXPECT_TRUE(cfg_mgr_->matchForward("one.tmark.org", match));
-    EXPECT_EQ("one.tmark.org", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchForward("one.example.com", match));
+    EXPECT_EQ("one.example.com", match->getName());
 
     // Verify that a FQDN for sub-domain matches.
-    EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
-    EXPECT_EQ("tmark.org", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchForward("blue.example.com", match));
+    EXPECT_EQ("example.com", match->getName());
 
     // Verify that a FQDN for sub-domain matches.
-    EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
-    EXPECT_EQ("one.tmark.org", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchForward("red.one.example.com", match));
+    EXPECT_EQ("one.example.com", match->getName());
 
     // Verify that an FQDN with no match, returns the wild card domain.
     EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
@@ -1479,12 +1374,12 @@ TEST_F(D2CfgMgrTest, matchNoWildcard) {
                         "\"tsig-keys\": [] ,"
                         "\"forward-ddns\" : {"
                         "\"ddns-domains\": [ "
-                        "{ \"name\": \"tmark.org\" , "
+                        "{ \"name\": \"example.com\" , "
                         "  \"dns-servers\" : [ "
                         "  { \"ip-address\": \"127.0.0.1\" } "
                         "  ] } "
                         ", "
-                        "{ \"name\": \"one.tmark.org\" , "
+                        "{ \"name\": \"one.example.com\" , "
                         "  \"dns-servers\" : [ "
                         "  { \"ip-address\": \"127.0.0.2\" } "
                         "  ] } "
@@ -1492,11 +1387,8 @@ TEST_F(D2CfgMgrTest, matchNoWildcard) {
                         "\"reverse-ddns\" : {} "
                         " }";
 
-    ASSERT_TRUE(fromJSON(config));
-
     // Verify that we can parse the configuration.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
-    ASSERT_TRUE(checkAnswer(0));
+    RUN_CONFIG_OK(config);
 
     // Verify that the D2 context can be retrieved and is not null.
     D2CfgContextPtr context;
@@ -1504,14 +1396,14 @@ TEST_F(D2CfgMgrTest, matchNoWildcard) {
 
     DdnsDomainPtr match;
     // Verify that full or partial matches, still match.
-    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
-    EXPECT_EQ("tmark.org", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
+    EXPECT_EQ("example.com", match->getName());
 
-    EXPECT_TRUE(cfg_mgr_->matchForward("blue.tmark.org", match));
-    EXPECT_EQ("tmark.org", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchForward("blue.example.com", match));
+    EXPECT_EQ("example.com", match->getName());
 
-    EXPECT_TRUE(cfg_mgr_->matchForward("red.one.tmark.org", match));
-    EXPECT_EQ("one.tmark.org", match->getName());
+    EXPECT_TRUE(cfg_mgr_->matchForward("red.one.example.com", match));
+    EXPECT_EQ("one.example.com", match->getName());
 
     // Verify that a FQDN with no match, fails to match.
     EXPECT_FALSE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
@@ -1534,11 +1426,8 @@ TEST_F(D2CfgMgrTest, matchAll) {
                         "\"reverse-ddns\" : {} "
                         "}";
 
-    ASSERT_TRUE(fromJSON(config));
-
     // Verify that we can parse the configuration.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
-    ASSERT_TRUE(checkAnswer(0));
+    RUN_CONFIG_OK(config);
 
     // Verify that the D2 context can be retrieved and is not null.
     D2CfgContextPtr context;
@@ -1546,7 +1435,7 @@ TEST_F(D2CfgMgrTest, matchAll) {
 
     // Verify that wild card domain is returned for any FQDN.
     DdnsDomainPtr match;
-    EXPECT_TRUE(cfg_mgr_->matchForward("tmark.org", match));
+    EXPECT_TRUE(cfg_mgr_->matchForward("example.com", match));
     EXPECT_EQ("*", match->getName());
     EXPECT_TRUE(cfg_mgr_->matchForward("shouldbe.wildcard", match));
     EXPECT_EQ("*", match->getName());
@@ -1596,11 +1485,8 @@ TEST_F(D2CfgMgrTest, matchReverse) {
                         "  ] } "
                         "] } }";
 
-    ASSERT_TRUE(fromJSON(config));
-
     // Verify that we can parse the configuration.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
-    ASSERT_TRUE(checkAnswer(0));
+    RUN_CONFIG_OK(config);
 
     // Verify that the D2 context can be retrieved and is not null.
     D2CfgContextPtr context;
@@ -1641,6 +1527,12 @@ TEST_F(D2CfgMgrTest, matchReverse) {
 }
 
 /// @brief Tests D2 config parsing against a wide range of config permutations.
+///
+/// It tests for both syntax errors that the JSON parsing (D2ParserContext)
+/// should detect as well as post-JSON parsing logic errors generated by
+/// the Element parsers (i.e...SimpleParser/DhcpParser derivations)
+///
+///
 /// It iterates over all of the test configurations described in given file.
 /// The file content is JSON specialized to this test. The format of the file
 /// is:
@@ -1656,12 +1548,13 @@ TEST_F(D2CfgMgrTest, matchReverse) {
 ///
 /// #    Each test has:
 /// #      1. description - optional text description
-/// #      2. should-fail - bool indicator if parsing is expected to file
-/// #         (defaults to false)
-/// #       3. data - configuration text to parse
+/// #      2. syntax-error - error JSON parser should emit (omit if none)
+/// #      3. logic-error - error element parser(s) should emit (omit if none)
+/// #      4. data - configuration text to parse
 /// #
 ///      "description" : "<text describing test>",
-///      "should_fail" : <true|false> ,
+///      "syntax-error" : "<exact text from JSON parser including position>" ,
+///      "logic-error" : "<exact text from element parser including position>" ,
 ///      "data" :
 ///          {
 /// #        configuration elements here
@@ -1695,18 +1588,15 @@ TEST_F(D2CfgMgrTest, configPermutations) {
     }
 
     // Read in each test For each test, read:
+    //
     //  1. description - optional text description
-    //  2. should-fail - bool indicator if parsing is expected to file (defaults
-    //     to false
+    //  2. syntax-error or logic-error or neither
     //  3. data - configuration text to parse
-    //
-    // Next attempt to parse the configuration by passing it into
-    // D2CfgMgr::parseConfig().  Then check the parsing outcome against the
-    // expected outcome as given by should-fail.
+    //  4. convert data into JSON text
+    //  5. submit JSON for parsing
     isc::data::ConstElementPtr test;
     ASSERT_TRUE(tests->get("test-list"));
     BOOST_FOREACH(test, tests->get("test-list")->listValue()) {
-
         // Grab the description.
         std::string description = "<no desc>";
         isc::data::ConstElementPtr elem = test->get("description");
@@ -1714,12 +1604,19 @@ TEST_F(D2CfgMgrTest, configPermutations) {
             elem->getValue(description);
         }
 
-        // Grab the outcome flag, should-fail, defaults to false if it's
-        // not specified.
-        bool should_fail = false;
-        elem = test->get("should-fail");
-        if (elem)  {
-            elem->getValue(should_fail);
+        // Grab the expected error message, if there is one.
+        std::string expected_error = "";
+        RunConfigMode mode = NO_ERROR;
+        elem = test->get("syntax-error");
+        if (elem) {
+            elem->getValue(expected_error);
+            mode = SYNTAX_ERROR;
+        } else {
+            elem = test->get("logic-error");
+            if (elem) {
+                elem->getValue(expected_error);
+                mode = LOGIC_ERROR;
+            }
         }
 
         // Grab the test's configuration data.
@@ -1727,13 +1624,13 @@ TEST_F(D2CfgMgrTest, configPermutations) {
         ASSERT_TRUE(data) << "No data for test: "
                           << " : " << test->getPosition();
 
-        // Attempt to parse the configuration. We verify that we get the expected
-        // outcome, and if it was supposed to fail if the explanation contains
-        // position information.
-        checkAnswerWithError(cfg_mgr_->parseConfig(data),
-                             (should_fail ? SHOULD_FAIL : SHOULD_PASS),
-                             test_file);
+        // Convert the test data back to JSON text, then submit it for parsing.
+        stringstream os;
+        data->toJSON(os);
+        EXPECT_TRUE(runConfigOrFail(os.str(), mode, expected_error))
+            << " failed for test: " << test->getPosition() << std::endl;
     }
 }
 
+
 } // end of anonymous namespace

+ 2 - 1
src/bin/d2/tests/d2_controller_unittests.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
@@ -10,6 +10,7 @@
 #include <d2/d2_controller.h>
 #include <d2/d2_process.h>
 #include <process/spec_config.h>
+#include <d2/tests/nc_test_utils.h>
 #include <process/testutils/d_test_stubs.h>
 
 #include <boost/pointer_cast.hpp>

+ 7 - 6
src/bin/d2/tests/d2_process_unittests.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
@@ -11,6 +11,7 @@
 #include <d2/d2_process.h>
 #include <dhcp_ddns/ncr_io.h>
 #include <process/testutils/d_test_stubs.h>
+#include <d2/tests/nc_test_utils.h>
 
 #include <boost/bind.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
@@ -32,21 +33,21 @@ const char* bad_ip_d2_config = "{ "
                         "\"ip-address\" : \"1.1.1.1\" , "
                         "\"port\" : 5031, "
                         "\"tsig-keys\": ["
-                        "{ \"name\": \"d2_key.tmark.org\" , "
+                        "{ \"name\": \"d2_key.example.com\" , "
                         "   \"algorithm\": \"HMAC-MD5\" ,"
                         "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
                         "} ],"
                         "\"forward-ddns\" : {"
                         "\"ddns-domains\": [ "
-                        "{ \"name\": \"tmark.org\" , "
-                        "  \"key-name\": \"d2_key.tmark.org\" , "
+                        "{ \"name\": \"example.com\" , "
+                        "  \"key-name\": \"d2_key.example.com\" , "
                         "  \"dns-servers\" : [ "
                         "  { \"ip-address\": \"127.0.0.101\" } "
                         "] } ] }, "
                         "\"reverse-ddns\" : {"
                         "\"ddns-domains\": [ "
                         "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
-                        "  \"key-name\": \"d2_key.tmark.org\" , "
+                        "  \"key-name\": \"d2_key.example.com\" , "
                         "  \"dns-servers\" : [ "
                         "  { \"ip-address\": \"127.0.0.101\" , "
                         "    \"port\": 100 } ] } "
@@ -511,7 +512,7 @@ TEST_F(D2ProcessTest, canShutdown) {
         " \"change-type\" : 0 , "
         " \"forward-change\" : true , "
         " \"reverse-change\" : false , "
-        " \"fqdn\" : \"fish.tmark.org\" , "
+        " \"fqdn\" : \"fish.example.com\" , "
         " \"ip-address\" : \"192.168.2.1\" , "
         " \"dhcid\" : \"010203040A7F8E3D\" , "
         " \"lease-expires-on\" : \"20130121132405\" , "

File diff suppressed because it is too large
+ 1149 - 0
src/bin/d2/tests/d2_simple_parser_unittest.cc


+ 25 - 0
src/bin/d2/tests/nc_test_utils.cc

@@ -22,6 +22,31 @@ using namespace isc::d2;
 namespace isc {
 namespace d2 {
 
+const char* valid_d2_config = "{ "
+                        "\"ip-address\" : \"127.0.0.1\" , "
+                        "\"port\" : 5031, "
+                        "\"tsig-keys\": ["
+                        "{ \"name\": \"d2_key.example.com\" , "
+                        "   \"algorithm\": \"HMAC-MD5\" ,"
+                        "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
+                        "} ],"
+                        "\"forward-ddns\" : {"
+                        "\"ddns-domains\": [ "
+                        "{ \"name\": \"example.com.\" , "
+                        "  \"key-name\": \"d2_key.example.com\" , "
+                        "  \"dns-servers\" : [ "
+                        "  { \"ip-address\": \"127.0.0.101\" } "
+                        "] } ] }, "
+                        "\"reverse-ddns\" : {"
+                        "\"ddns-domains\": [ "
+                        "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
+                        "  \"key-name\": \"d2_key.example.com\" , "
+                        "  \"dns-servers\" : [ "
+                        "  { \"ip-address\": \"127.0.0.101\" , "
+                        "    \"port\": 100 } ] } "
+                        "] } }";
+
+
 const char* TEST_DNS_SERVER_IP = "127.0.0.1";
 size_t TEST_DNS_SERVER_PORT = 5301;
 

+ 1 - 0
src/bin/d2/tests/nc_test_utils.h

@@ -20,6 +20,7 @@
 namespace isc {
 namespace d2 {
 
+extern const char* valid_d2_config;
 extern const char* TEST_DNS_SERVER_IP;
 extern size_t TEST_DNS_SERVER_PORT;
 

+ 586 - 0
src/bin/d2/tests/parser_unittest.cc

@@ -0,0 +1,586 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <d2/parser_context.h>
+#include <d2/tests/parser_unittest.h>
+#include <testutils/io_utils.h>
+
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+/// @brief compares two JSON trees
+///
+/// If differences are discovered, gtest failure is reported (using EXPECT_EQ)
+///
+/// @param a first to be compared
+/// @param b second to be compared
+void compareJSON(ConstElementPtr a, ConstElementPtr b) {
+    ASSERT_TRUE(a);
+    ASSERT_TRUE(b);
+    EXPECT_EQ(a->str(), b->str());
+}
+
+/// @brief Tests if the input string can be parsed with specific parser
+///
+/// The input text will be passed to bison parser of specified type.
+/// Then the same input text is passed to legacy JSON parser and outputs
+/// from both parsers are compared. The legacy comparison can be disabled,
+/// if the feature tested is not supported by the old parser (e.g.
+/// new comment styles)
+///
+/// @param txt text to be compared
+/// @param parser_type bison parser type to be instantiated
+/// @param compare whether to compare the output with legacy JSON parser
+void testParser(const std::string& txt, D2ParserContext::ParserType parser_type,
+    bool compare = true) {
+    ConstElementPtr test_json;
+
+    ASSERT_NO_THROW({
+            try {
+                D2ParserContext ctx;
+                test_json = ctx.parseString(txt, parser_type);
+            } catch (const std::exception &e) {
+                cout << "EXCEPTION: " << e.what() << endl;
+                throw;
+            }
+
+    });
+
+    if (!compare) {
+        return;
+    }
+
+    // Now compare if both representations are the same.
+    ElementPtr reference_json;
+    ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
+    compareJSON(reference_json, test_json);
+}
+
+// Generic JSON parsing tests
+TEST(ParserTest, mapInMap) {
+    string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
+    testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, listInList) {
+    string txt = "[ [ \"Britain\", \"Wales\", \"Scotland\" ], "
+                 "[ \"Pomorze\", \"Wielkopolska\", \"Tatry\"] ]";
+    testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedMaps) {
+    string txt = "{ \"europe\": { \"UK\": { \"London\": { \"street\": \"221B Baker\" }}}}";
+    testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, nestedLists) {
+    string txt = "[ \"half\", [ \"quarter\", [ \"eighth\", [ \"sixteenth\" ]]]]";
+    testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, listsInMaps) {
+    string txt = "{ \"constellations\": { \"orion\": [ \"rigel\", \"betelgeuse\" ], "
+                    "\"cygnus\": [ \"deneb\", \"albireo\"] } }";
+    testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, mapsInLists) {
+    string txt = "[ { \"body\": \"earth\", \"gravity\": 1.0 },"
+                 " { \"body\": \"mars\", \"gravity\": 0.376 } ]";
+    testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, types) {
+    string txt = "{ \"string\": \"foo\","
+                   "\"integer\": 42,"
+                   "\"boolean\": true,"
+                   "\"map\": { \"foo\": \"bar\" },"
+                   "\"list\": [ 1, 2, 3 ],"
+                   "\"null\": null }";
+    testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+TEST(ParserTest, keywordJSON) {
+    string txt = "{ \"name\": \"user\","
+                   "\"type\": \"password\","
+                   "\"user\": \"name\","
+                   "\"password\": \"type\" }";
+    testParser(txt, D2ParserContext::PARSER_JSON);
+}
+
+// PARSER_DHCPDDNS parser tests
+TEST(ParserTest, keywordDhcpDdns) {
+    string txt =
+        "{ \"DhcpDdns\" : \n"
+           "{ \n"
+            " \"ip-address\": \"192.168.77.1\", \n"
+            " \"port\": 777 , \n "
+            " \"ncr-protocol\": \"UDP\", \n"
+            "\"tsig-keys\": [], \n"
+            "\"forward-ddns\" : {}, \n"
+            "\"reverse-ddns\" : {} \n"
+            "} \n"
+         "} \n";
+     testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+TEST(ParserTest, keywordDhcp6) {
+     string txt = "{ \"Dhcp6\": { \"interfaces-config\": {"
+                  " \"interfaces\": [ \"type\", \"htype\" ] },\n"
+                  "\"preferred-lifetime\": 3000,\n"
+                  "\"rebind-timer\": 2000, \n"
+                  "\"renew-timer\": 1000, \n"
+                  "\"subnet6\": [ { "
+                  "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                  "    \"subnet\": \"2001:db8:1::/48\", "
+                  "    \"interface\": \"test\" } ],\n"
+                   "\"valid-lifetime\": 4000 } }";
+     testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+TEST(ParserTest, keywordDhcp4) {
+    string txt = "{ \"Dhcp4\": { \"interfaces-config\": {"
+                  " \"interfaces\": [ \"type\", \"htype\" ] },\n"
+                  "\"rebind-timer\": 2000, \n"
+                  "\"renew-timer\": 1000, \n"
+                  "\"subnet4\": [ { "
+                  "  \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+                  "  \"subnet\": \"192.0.2.0/24\", "
+                  "  \"interface\": \"test\" } ],\n"
+                   "\"valid-lifetime\": 4000 } }";
+     testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+TEST(ParserTest, Logging) {
+    string txt = "{ \"Logging\": { \n"
+                 "    \"loggers\": [ \n"
+                 "        { \n"
+                 "            \"name\": \"kea-dhcp6\", \n"
+                 "            \"output_options\": [ \n"
+                 "                { \n"
+                 "                    \"output\": \"stdout\" \n"
+                 "                } \n"
+                 "            ], \n"
+                 "            \"debuglevel\": 0, \n"
+                 "            \"severity\": \"INFO\" \n"
+                 "        } \n"
+                 "    ] }\n"
+                 "} \n";
+     testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+
+// Tests if bash (#) comments are supported. That's the only comment type that
+// was supported by the old parser.
+TEST(ParserTest, bashComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000,\n"
+                "# this is a comment\n"
+                "\"rebind-timer\": 2000, \n"
+                "# lots of comments here\n"
+                "# and here\n"
+                "\"renew-timer\": 1000, \n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser(txt, D2ParserContext::PARSER_DHCPDDNS);
+}
+
+// Tests if C++ (//) comments can start anywhere, not just in the first line.
+TEST(ParserTest, cppComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, // this is a comment \n"
+                "\"rebind-timer\": 2000, // everything after // is ignored\n"
+                "\"renew-timer\": 1000, // this will be ignored, too\n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+// Tests if bash (#) comments can start anywhere, not just in the first line.
+TEST(ParserTest, bashCommentsInline) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, # this is a comment \n"
+                "\"rebind-timer\": 2000, # everything after # is ignored\n"
+                "\"renew-timer\": 1000, # this will be ignored, too\n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+// Tests if multi-line C style comments are handled correctly.
+TEST(ParserTest, multilineComments) {
+    string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
+                "  \"interfaces\": [ \"*\" ]"
+                "},\n"
+                "\"preferred-lifetime\": 3000, /* this is a C style comment\n"
+                "that\n can \n span \n multiple \n lines */ \n"
+                "\"rebind-timer\": 2000,\n"
+                "\"renew-timer\": 1000, \n"
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+                "    \"subnet\": \"2001:db8:1::/48\", "
+                "    \"interface\": \"eth0\""
+                " } ],"
+                "\"valid-lifetime\": 4000 } }";
+    testParser(txt, D2ParserContext::PARSER_DHCPDDNS, false);
+}
+
+/// @brief Loads specified example config file
+///
+/// This test loads specified example file twice: first, using the legacy
+/// JSON file and then second time using bison parser. Two created Element
+/// trees are then compared. The input is decommented before it is passed
+/// to legacy parser (as legacy support for comments is very limited).
+///
+/// @param fname name of the file to be loaded
+void testFile(const std::string& fname) {
+    ElementPtr reference_json;
+    ConstElementPtr test_json;
+
+    string decommented = dhcp::test::decommentJSONfile(fname);
+    EXPECT_NO_THROW(reference_json = Element::fromJSONFile(decommented, true));
+
+    // remove the temporary file
+    EXPECT_NO_THROW(::remove(decommented.c_str()));
+
+    EXPECT_NO_THROW(
+    try {
+        D2ParserContext ctx;
+        test_json = ctx.parseFile(fname, D2ParserContext::PARSER_DHCPDDNS);
+    } catch (const std::exception &x) {
+        cout << "EXCEPTION: " << x.what() << endl;
+        throw;
+    });
+
+    ASSERT_TRUE(reference_json);
+    ASSERT_TRUE(test_json);
+
+    compareJSON(reference_json, test_json);
+}
+
+// This test loads all available existing files. Each config is loaded
+// twice: first with the existing Element::fromJSONFile() and then
+// the second time with D2Parser. Both JSON trees are then compared.
+TEST(ParserTest, file) {
+    vector<string> configs;
+    configs.push_back("sample1.json");
+    configs.push_back("template.json");
+
+    for (int i = 0; i<configs.size(); i++) {
+        testFile(string(CFG_EXAMPLES) + "/" + configs[i]);
+    }
+}
+
+/// @brief Tests error conditions in D2Parser
+///
+/// @param txt text to be parsed
+/// @param parser_type type of the parser to be used in the test
+/// @param msg expected content of the exception
+void testError(const std::string& txt,
+               D2ParserContext::ParserType parser_type,
+               const std::string& msg)
+{
+    try {
+        D2ParserContext ctx;
+        ConstElementPtr parsed = ctx.parseString(txt, parser_type);
+        FAIL() << "Expected D2ParseError but nothing was raised (expected: "
+               << msg << ")";
+    }
+    catch (const D2ParseError& ex) {
+        EXPECT_EQ(msg, ex.what());
+    }
+    catch (...) {
+        FAIL() << "Expected D2ParseError but something else was raised";
+    }
+}
+
+// Verify that error conditions are handled correctly.
+TEST(ParserTest, errors) {
+    // no input
+    testError("", D2ParserContext::PARSER_JSON,
+              "<string>:1.1: syntax error, unexpected end of file");
+    testError(" ", D2ParserContext::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+    testError("\n", D2ParserContext::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("\t", D2ParserContext::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+    testError("\r", D2ParserContext::PARSER_JSON,
+              "<string>:1.2: syntax error, unexpected end of file");
+
+    // comments
+    testError("# nothing\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError(" #\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("// nothing\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("/* nothing */\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file");
+    testError("/* no\nthing */\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:3.1: syntax error, unexpected end of file");
+    testError("/* no\nthing */\n\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:4.1: syntax error, unexpected end of file");
+    testError("/* nothing\n",
+              D2ParserContext::PARSER_JSON,
+              "Comment not closed. (/* in line 1");
+    testError("\n\n\n/* nothing\n",
+              D2ParserContext::PARSER_JSON,
+              "Comment not closed. (/* in line 4");
+    testError("{ /* */*/ }\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.3-8: Invalid character: *");
+    testError("{ /* // *// }\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.3-11: Invalid character: /");
+    testError("{ /* // *///  }\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:2.1: syntax error, unexpected end of file, "
+              "expecting }");
+
+    // includes
+    testError("<?\n",
+              D2ParserContext::PARSER_JSON,
+              "Directive not closed.");
+    testError("<?include\n",
+              D2ParserContext::PARSER_JSON,
+              "Directive not closed.");
+    string file = string(CFG_EXAMPLES) + "/" + "sample1.json";
+    testError("<?include \"" + file + "\"\n",
+              D2ParserContext::PARSER_JSON,
+              "Directive not closed.");
+    testError("<?include \"/foo/bar\" ?>/n",
+              D2ParserContext::PARSER_JSON,
+              "Can't open include file /foo/bar");
+
+    // JSON keywords
+    testError("{ \"foo\": True }",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.10-13: JSON true reserved keyword is lower case only");
+    testError("{ \"foo\": False }",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.10-14: JSON false reserved keyword is lower case only");
+    testError("{ \"foo\": NULL }",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.10-13: JSON null reserved keyword is lower case only");
+    testError("{ \"foo\": Tru }",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.10: Invalid character: T");
+    testError("{ \"foo\": nul }",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.10: Invalid character: n");
+
+    // numbers
+    testError("123",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.1-3: syntax error, unexpected integer, "
+              "expecting {");
+    testError("-456",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.1-4: syntax error, unexpected integer, "
+              "expecting {");
+    testError("-0001",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.1-5: syntax error, unexpected integer, "
+              "expecting {");
+    testError("1234567890123456789012345678901234567890",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1-40: Failed to convert "
+              "1234567890123456789012345678901234567890"
+              " to an integer.");
+    testError("-3.14e+0",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.1-8: syntax error, unexpected floating point, "
+              "expecting {");
+    testError("1e50000",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1-7: Failed to convert 1e50000 "
+              "to a floating point.");
+
+    // strings
+    testError("\"aabb\"",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.1-6: syntax error, unexpected constant string, "
+              "expecting {");
+    testError("{ \"aabb\"err",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.9: Invalid character: e");
+    testError("{ err\"aabb\"",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.3: Invalid character: e");
+    testError("\"a\n\tb\"",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1-6: Invalid control in \"a\n\tb\"");
+    testError("\"a\\n\\tb\"",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.1-8: syntax error, unexpected constant string, "
+              "expecting {");
+    testError("\"a\\x01b\"",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1-8: Bad escape in \"a\\x01b\"");
+    testError("\"a\\u0162\"",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1-9: Unsupported unicode escape in \"a\\u0162\"");
+    testError("\"a\\u062z\"",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1-9: Bad escape in \"a\\u062z\"");
+    testError("\"abc\\\"",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1-6: Overflow escape in \"abc\\\"");
+
+    // from data_unittest.c
+    testError("\\a",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+    testError("\\",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+    testError("\\\"\\\"",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.1: Invalid character: \\");
+
+    // want a map
+    testError("[]\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.1: syntax error, unexpected [, "
+              "expecting {");
+    testError("[]\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.1: syntax error, unexpected [, "
+              "expecting {");
+    testError("{ 123 }\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.3-5: syntax error, unexpected integer, "
+              "expecting }");
+    testError("{ 123 }\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.3-5: syntax error, unexpected integer");
+    testError("{ \"foo\" }\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.9: syntax error, unexpected }, "
+              "expecting :");
+    testError("{ \"foo\" }\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.9: syntax error, unexpected }, expecting :");
+    testError("{ \"foo\":null }\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.3-7: got unexpected keyword "
+              "\"foo\" in toplevel map.");
+    testError("{ \"Dhcp6\" }\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:1.11: syntax error, unexpected }, "
+              "expecting :");
+    testError("{ \"Dhcp4\":[]\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:2.1: syntax error, unexpected end of file, "
+              "expecting \",\" or }");
+    testError("{}{}\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected {, "
+              "expecting end of file");
+
+    // bad commas
+    testError("{ , }\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected \",\", "
+              "expecting }");
+    testError("{ , \"foo\":true }\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.3: syntax error, unexpected \",\", "
+              "expecting }");
+    testError("{ \"foo\":true, }\n",
+              D2ParserContext::PARSER_JSON,
+              "<string>:1.15: syntax error, unexpected }, "
+              "expecting constant string");
+
+    // bad type
+    testError("{ \"DhcpDdns\":{\n"
+              "  \"dns-server-timeout\":false }}\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:2.24-28: syntax error, unexpected boolean, "
+              "expecting integer");
+
+    // unknown keyword
+    testError("{ \"DhcpDdns\":{\n"
+              " \"totally-bogus\":600 }}\n",
+              D2ParserContext::PARSER_DHCPDDNS,
+              "<string>:2.2-16: got unexpected keyword "
+              "\"totally-bogus\" in DhcpDdns map.");
+}
+
+// Check unicode escapes
+TEST(ParserTest, unicodeEscapes) {
+    ConstElementPtr result;
+    string json;
+
+    // check we can reread output
+    for (char c = -128; c < 127; ++c) {
+        string ins(" ");
+        ins[1] = c;
+        ConstElementPtr e(new StringElement(ins));
+        json = e->str();
+        ASSERT_NO_THROW(
+        try {
+            D2ParserContext ctx;
+            result = ctx.parseString(json, D2ParserContext::PARSER_JSON);
+        } catch (const std::exception &x) {
+            cout << "EXCEPTION: " << x.what() << endl;
+            throw;
+        });
+        ASSERT_EQ(Element::string, result->getType());
+        EXPECT_EQ(ins, result->stringValue());
+    }
+}
+
+// This test checks that all representations of a slash is recognized properly.
+TEST(ParserTest, unicodeSlash) {
+    // check the 4 possible encodings of solidus '/'
+    ConstElementPtr result;
+    string json = "\"/\\/\\u002f\\u002F\"";
+    ASSERT_NO_THROW(
+    try {
+        D2ParserContext ctx;
+        result = ctx.parseString(json, D2ParserContext::PARSER_JSON);
+    } catch (const std::exception &x) {
+        cout << "EXCEPTION: " << x.what() << endl;
+        throw;
+    });
+    ASSERT_EQ(Element::string, result->getType());
+    EXPECT_EQ("////", result->stringValue());
+}
+
+};
+};
+};

+ 36 - 0
src/bin/d2/tests/parser_unittest.h

@@ -0,0 +1,36 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef PARSER_UNITTEST_H
+#define PARSER_UNITTEST_H
+
+#include <gtest/gtest.h>
+#include <cc/data.h>
+#include <d2/parser_context.h>
+
+using namespace isc::data;
+using namespace std;
+
+namespace isc {
+namespace d2 {
+namespace test {
+
+/// @brief Runs parser in JSON mode, useful for parser testing
+///
+/// @param in string to be parsed
+/// @return ElementPtr structure representing parsed JSON
+inline isc::data::ElementPtr
+parseJSON(const std::string& in)
+{
+    isc::d2::D2ParserContext ctx;
+    return (ctx.parseString(in, isc::d2::D2ParserContext::PARSER_JSON));
+}
+
+};
+};
+};
+
+#endif // PARSER_UNITTEST_H

+ 64 - 62
src/bin/d2/tests/testdata/d2_cfg_tests.json

@@ -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,13 +9,16 @@
 # Each test entry consists of:
 #
 # description - text describing the test (optional)
-# should-fail - indicates whether parsing is expected to fail, defaults to
-#               false
+# syntax-error - syntax error the JSON parsing should emit for this test
+#               defaults to "" = no error
+# logic-error - indicates whether a post-JSON parsing logic error should occur
+#               defaults to false
 # data {} - Configuration text to submit for parsing.
 #
 # The vast majority of the tests in this file are invalid and are expected
-# to fail.  There are some that should succeed and are used more or less
-# as sanity checks.
+# to fail either as a syntax error caught by the JSON parser or a logic error
+# caught during element processing. There are some that should succeed and are
+# used more or less as sanity checks.
 
 { "test-list" : [
 #-----
@@ -24,7 +27,6 @@
 # as well as validating this as the smallest config which makes writing
 # permutations easier.
 "description" : "D2 smallest, valid config",
-"should-fail" : false,
 "data" :
     {
     "forward-ddns" : {},
@@ -34,9 +36,9 @@
 }
 
 #-----
+# Map should be supplied through setDefaults
 ,{
 "description" : "D2 missing forward-ddns map",
-"should-fail" : true,
 "data" :
     {
     "reverse-ddns" : {},
@@ -45,9 +47,9 @@
 }
 
 #-----
+# Map should be supplied through setDefaults
 ,{
 "description" : "D2 missing reverse-ddns map",
-"should-fail" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -57,9 +59,9 @@
 
 
 #-----
+# Map should be supplied through setDefaults
 ,{
 "description" : "D2 missing tsig-keys list",
-"should-fail" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -70,7 +72,7 @@
 #-----
 ,{
 "description" : "D2 unknown scalar",
-"should-fail" : true,
+"syntax-error" : "<string>:1.3-16: got unexpected keyword \"bogus-scalar\" in DhcpDdns map.",
 "data" :
     {
     "bogus-scalar" : true,
@@ -83,7 +85,7 @@
 #-----
 ,{
 "description" : "D2 unknown map",
-"should-fail" : true,
+"syntax-error" : "<string>:1.3-13: got unexpected keyword \"bogus-map\" in DhcpDdns map.",
 "data" :
     {
     "bogus-map" : {},
@@ -96,7 +98,7 @@
 #-----
 ,{
 "description" : "D2 unknown list",
-"should-fail" : true,
+"syntax-error" : "<string>:1.3-14: got unexpected keyword \"bogus-list\" in DhcpDdns map.",
 "data" :
     {
     "bogus-list" : [],
@@ -135,7 +137,7 @@
 #-----
 ,{
 "description" : "D2Params.ip-address invalid value",
-"should-fail" : true,
+"logic-error" : "invalid address (bogus) specified for parameter 'ip-address' (<string>:1:39)",
 "data" :
     {
     "ip-address" : "bogus",
@@ -161,7 +163,7 @@
 #-----
 ,{
 "description" : "D2Params.port can't be 0",
-"should-fail" : true,
+"syntax-error" : "<string>:1.33: port must be greater than zero but less than 65536",
 "data" :
     {
     "port" : 0,
@@ -174,7 +176,7 @@
 #-----
 ,{
 "description" : "D2Params.port, non numeric",
-"should-fail" : true,
+"syntax-error" : "<string>:1.33-39: syntax error, unexpected constant string, expecting integer",
 "data" :
     {
     "port" : "bogus",
@@ -199,7 +201,7 @@
 #-----
 ,{
 "description" : "D2Params.dns-server-timeout can't be 0",
-"should-fail" : true,
+"syntax-error" : "<string>:1.25: dns-server-timeout must be greater than zero",
 "data" :
     {
     "dns-server-timeout" : 0,
@@ -212,7 +214,7 @@
 #-----
 ,{
 "description" : "D2Params.dns-server-timeout, non numeric",
-"should-fail" : true,
+"syntax-error" : "<string>:1.25-31: syntax error, unexpected constant string, expecting integer",
 "data" :
     {
     "dns-server-timeout" : "bogus",
@@ -238,7 +240,7 @@
 #-----
 ,{
 "description" : "D2Params.ncr-protocol, unsupported TCP",
-"should-fail" : true,
+"logic-error" : "ncr-protocol : TCP is not yet supported  (<string>:1:41)",
 "data" :
     {
     "ncr-protocol" : "TCP",
@@ -252,7 +254,7 @@
 #-----
 ,{
 "description" : "D2Params.ncr-protocol, invalid value",
-"should-fail" : true,
+"syntax-error" : "<string>:1.41-47: syntax error, unexpected constant string, expecting UDP or TCP",
 "data" :
     {
     "ncr-protocol" : "bogus",
@@ -279,7 +281,7 @@
 #-----
 ,{
 "description" : "D2Params.ncr-format, invalid value",
-"should-fail" : true,
+"syntax-error" : "<string>:1.39-45: syntax error, unexpected constant string, expecting JSON",
 "data" :
     {
     "ncr-format" : "bogus",
@@ -313,7 +315,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, missing key name",
-"should-fail" : true,
+"logic-error" : "element: tsig-keys : String parameter name not found(<string>:1:62)<string>:1:47",
 "data" :
     {
     "forward-ddns" : {},
@@ -331,7 +333,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, blank key name",
-"should-fail" : true,
+"syntax-error" : "<string>:1.95: TSIG key name cannot be blank",
 "data" :
     {
     "forward-ddns" : {},
@@ -350,7 +352,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, duplicate key name",
-"should-fail" : true,
+"logic-error" : "Duplicate TSIG key name specified : first.key (<string>:1:185)",
 "data" :
     {
     "forward-ddns" : {},
@@ -374,7 +376,7 @@
 #----- D2.tsig-keys, algorithm tests
 
 ,{
-"description" : "D2.tsig-keys, all valid algorthms",
+"description" : "D2.tsig-keys, all valid algorithms",
 "data" :
     {
     "forward-ddns" : {},
@@ -418,7 +420,7 @@
 #----- D2.tsig-keys, algorithm tests
 ,{
 "description" : "D2.tsig-keys, missing algorithm",
-"should-fail" : true,
+"logic-error" : "element: tsig-keys : String parameter algorithm not found(<string>:1:62)<string>:1:47",
 "data" :
     {
     "forward-ddns" : {},
@@ -436,7 +438,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, blank algorithm",
-"should-fail" : true,
+"syntax-error" : "<string>:1.75: TSIG key algorithm cannot be blank",
 "data" :
     {
     "forward-ddns" : {},
@@ -455,7 +457,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, invalid algorithm",
-"should-fail" : true,
+"logic-error" : "tsig-key : Unknown TSIG Key algorithm: bogus (<string>:1:77)",
 "data" :
     {
     "forward-ddns" : {},
@@ -473,7 +475,7 @@
 
 #----- D2.tsig-keys, digest-bits tests
 ,{
-"description" : "D2.tsig-keys, all valid algorthms",
+"description" : "D2.tsig-keys, all valid algorthims",
 "data" :
     {
     "forward-ddns" : {},
@@ -523,7 +525,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, invalid digest-bits",
-"should-fail" : true,
+"syntax-error" : "<string>:1.104-105: TSIG key digest-bits must either be zero or a positive, multiple of eight",
 "data" :
     {
     "forward-ddns" : {},
@@ -535,7 +537,7 @@
             "algorithm" : "HMAC-MD5",
             "digest-bits" : 84,
             "secret" : "LSWXnfkKZjdPJI5QxlpnfQ=="
-            },
+            }
         ]
     }
 }
@@ -543,7 +545,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-MD5",
-"should-fail" : true,
+"logic-error" : "tsig-key: digest-bits too small :  (<string>:1:104)",
 "data" :
     {
     "forward-ddns" : {},
@@ -563,7 +565,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA1",
-"should-fail" : true,
+"logic-error" : "tsig-key: digest-bits too small :  (<string>:1:105)",
 "data" :
     {
     "forward-ddns" : {},
@@ -583,7 +585,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA224",
-"should-fail" : true,
+"logic-error" : "tsig-key: digest-bits too small :  (<string>:1:107)",
 "data" :
     {
     "forward-ddns" : {},
@@ -603,7 +605,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA256",
-"should-fail" : true,
+"logic-error" : "tsig-key: digest-bits too small :  (<string>:1:107)",
 "data" :
     {
     "forward-ddns" : {},
@@ -623,7 +625,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA384",
-"should-fail" : true,
+"logic-error" : "tsig-key: digest-bits too small :  (<string>:1:107)",
 "data" :
     {
     "forward-ddns" : {},
@@ -643,7 +645,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA512",
-"should-fail" : true,
+"logic-error" : "tsig-key: digest-bits too small :  (<string>:1:107)",
 "data" :
     {
     "forward-ddns" : {},
@@ -663,7 +665,7 @@
 #----- D2.tsig-keys, secret tests
 ,{
 "description" : "D2.tsig-keys, missing secret",
-"should-fail" : true,
+"logic-error" : "element: tsig-keys : String parameter secret not found(<string>:1:62)<string>:1:47",
 "data" :
     {
     "forward-ddns" : {},
@@ -681,7 +683,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, blank secret",
-"should-fail" : true,
+"syntax-error" : "<string>:1.118: TSIG key secret cannot be blank",
 "data" :
     {
     "forward-ddns" : {},
@@ -700,7 +702,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, invalid secret",
-"should-fail" : true,
+"logic-error" : "Cannot make TSIGKey: Incomplete input for base64: bogus (<string>:1:62)",
 "data" :
     {
     "forward-ddns" : {},
@@ -733,7 +735,7 @@
 #------
 ,{
 "description" : "D2.forward-ddns, unknown parameter",
-"should-fail" : true,
+"syntax-error" : "<string>:1.21-27: got unexpected keyword \"bogus\" in forward-ddns map.",
 "data" :
     {
     "forward-ddns" :
@@ -782,7 +784,7 @@
 #------
 ,{
 "description" : "D2.forward-ddns, duplicate domain",
-"should-fail" : true,
+"logic-error" : "Duplicate domain specified:four.example.com. (<string>:1:184)",
 "data" :
     {
     "forward-ddns" :
@@ -817,7 +819,7 @@
 #----- D2.forward-ddns.dhcp-ddns  tests
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns, unknown parameter",
-"should-fail" : true,
+"syntax-error" : "<string>:1.41-47: got unexpected keyword \"bogus\" in ddns-domains map.",
 "data" :
     {
     "forward-ddns" :
@@ -836,8 +838,8 @@
 
 #----- D2.forward-ddns.dhcp-ddns.name tests
 ,{
-"description" : "D2.forward-ddns.dhcp-ddns, no name",
-"should-fail" : true,
+"description" : "D2.forward-ddns.dhcp-ddns, empty domain",
+"syntax-error" : "<string>:1.42: syntax error, unexpected }, expecting key-name or dns-servers or name or constant string",
 "data" :
     {
     "forward-ddns" :
@@ -856,7 +858,7 @@
 #-----
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns, blank name",
-"should-fail" : true,
+"syntax-error" : "<string>:1.47: Ddns domain name cannot be blank",
 "data" :
     {
     "forward-ddns" :
@@ -876,7 +878,7 @@
 #------ "D2.forward-ddns.dhcp-ddns, key-name tests
 ,{
 "description" : "D2.forward-ddns, no matching key name",
-"should-fail" : true,
+"logic-error" : "DdnsDomain : four.example.com. specifies an undefined key: no.such.key (<string>:1:104)",
 "data" :
     {
     "forward-ddns" :
@@ -910,7 +912,7 @@
 #----- D2.forward-ddns.dhcp-ddns.dns-servers tests
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers, no servers",
-"should-fail" : true,
+"syntax-error" : "<string>:1.59: syntax error, unexpected ], expecting {",
 "data" :
     {
     "forward-ddns" :
@@ -931,7 +933,7 @@
 #----- D2.forward-ddns.dhcp-ddns.dns-servers tests
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers, unknown parameter",
-"should-fail" : true,
+"syntax-error" : "<string>:1.60-66: got unexpected keyword \"bogus\" in dns-servers map.",
 "data" :
     {
     "forward-ddns" :
@@ -957,7 +959,7 @@
 #-----
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers.hostname unsupported",
-"should-fail" : true,
+"syntax-error" : "<string>:1.70: hostname is not yet supported",
 "data" :
     {
     "forward-ddns" :
@@ -1033,7 +1035,7 @@
 #-----
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers.ip-address invalid address ",
-"should-fail" : true,
+"logic-error" : "Dns Server : invalid IP address : bogus (<string>:1:74)",
 "data" :
     {
     "forward-ddns" :
@@ -1086,7 +1088,7 @@
 #-----
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers.port cannot be 0 ",
-"should-fail" : true,
+"syntax-error" : "<string>:1.97: port must be greater than zero but less than 65536",
 "data" :
     {
     "forward-ddns" :
@@ -1127,7 +1129,7 @@
 #------
 ,{
 "description" : "D2.reverse-ddns, unknown parameter",
-"should-fail" : true,
+"syntax-error" : "<string>:1.43-49: got unexpected keyword \"bogus\" in reverse-ddns map.",
 "data" :
     {
     "forward-ddns" : {},
@@ -1176,7 +1178,7 @@
 #------
 ,{
 "description" : "D2.reverse-ddns, duplicate domain",
-"should-fail" : true,
+"logic-error" : "Duplicate domain specified:2.0.192.in-addra.arpa. (<string>:1:211)",
 "data" :
     {
     "forward-ddns" : {},
@@ -1211,7 +1213,7 @@
 #----- D2.reverse-ddns.dhcp-ddns  tests
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns, unknown parameter",
-"should-fail" : true,
+"syntax-error" : "<string>:1.63-69: got unexpected keyword \"bogus\" in ddns-domains map.",
 "data" :
     {
     "forward-ddns" : {},
@@ -1231,7 +1233,7 @@
 #----- D2.reverse-ddns.dhcp-ddns.name tests
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns, no name",
-"should-fail" : true,
+"syntax-error" : "<string>:1.64: syntax error, unexpected }, expecting key-name or dns-servers or name or constant string",
 "data" :
     {
     "forward-ddns" : {},
@@ -1250,7 +1252,7 @@
 #-----
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns, blank name",
-"should-fail" : true,
+"syntax-error" : "<string>:1.69: Ddns domain name cannot be blank",
 "data" :
     {
     "forward-ddns" : {},
@@ -1270,7 +1272,7 @@
 #------ "D2.reverse-ddns.dhcp-ddns, key-name tests
 ,{
 "description" : "D2.reverse-ddns, no matching key name",
-"should-fail" : true,
+"logic-error" : "DdnsDomain : 2.0.192.in-addr.arpa. specifies an undefined key: no.such.key (<string>:1:126)",
 "data" :
     {
     "forward-ddns" : {},
@@ -1304,7 +1306,7 @@
 #----- D2.reverse-ddns.dhcp-ddns.dns-servers tests
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers, no servers",
-"should-fail" : true,
+"syntax-error" : "<string>:1.81: syntax error, unexpected ], expecting {",
 "data" :
     {
     "forward-ddns" : {},
@@ -1325,7 +1327,7 @@
 #----- D2.reverse-ddns.dhcp-ddns.dns-servers tests
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers, unknown parameter",
-"should-fail" : true,
+"syntax-error" : "<string>:1.82-88: got unexpected keyword \"bogus\" in dns-servers map.",
 "data" :
     {
     "forward-ddns" : {},
@@ -1351,7 +1353,7 @@
 #-----
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.hostname unsupported",
-"should-fail" : true,
+"syntax-error" : "<string>:1.92: hostname is not yet supported",
 "data" :
     {
     "forward-ddns" : {},
@@ -1426,7 +1428,7 @@
 #-----
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.ip-address invalid value",
-"should-fail" : true,
+"logic-error" : "Dns Server : invalid IP address : bogus (<string>:1:96)",
 "data" :
     {
     "forward-ddns" : {},
@@ -1479,7 +1481,7 @@
 #-----
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.port cannot be 0 ",
-"should-fail" : true,
+"syntax-error" : "<string>:1.119: port must be greater than zero but less than 65536",
 "data" :
     {
     "forward-ddns" : {},

+ 1 - 1
src/lib/cc/data.h

@@ -186,7 +186,7 @@ public:
         if ((position_.file_ != "") ||          \
             (position_.line_ != 0) ||           \
             (position_.pos_ != 0)) {            \
-            msg_ += " in " + position_.str();   \
+            msg_ += " in (" + position_.str() + ")";   \
         }                                       \
         isc_throw(TypeError, msg_);             \
     }

+ 10 - 2
src/lib/cc/simple_parser.cc

@@ -107,8 +107,16 @@ size_t SimpleParser::setDefaults(isc::data::ElementPtr scope,
             break;
         }
         case Element::integer: {
-            int int_value = boost::lexical_cast<int>(def_value.value_);
-            x.reset(new IntElement(int_value, pos));
+            try {
+                int int_value = boost::lexical_cast<int>(def_value.value_);
+                x.reset(new IntElement(int_value, pos));
+            }
+            catch (const std::exception& ex) {
+                isc_throw(BadValue, "Internal error. Integer value expected for: "
+                                    << def_value.name_ << ", value is: "
+                                    << def_value.value_ );
+            }
+
             break;
         }
         case Element::boolean: {

+ 21 - 25
src/lib/process/d_cfg_mgr.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
@@ -147,6 +147,11 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
     std::string element_id;
 
     try {
+
+        // Make the configuration mutable so we can then insert default values.
+        ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(config_set);
+        setCfgDefaults(mutable_cfg);
+
         // Split the configuration into two maps. The first containing only
         // top-level scalar parameters (i.e. globals), the second containing
         // non-scalar or object elements (maps, lists, etc...).  This allows
@@ -156,7 +161,7 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
         ElementMap objects_map;
 
         isc::dhcp::ConfigPair config_pair;
-        BOOST_FOREACH(config_pair, config_set->mapValue()) {
+        BOOST_FOREACH(config_pair, mutable_cfg->mapValue()) {
             std::string element_id = config_pair.first;
             isc::data::ConstElementPtr element = config_pair.second;
             switch (element->getType()) {
@@ -203,7 +208,7 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
                     isc_throw(DCfgMgrBaseError,
                                "Element required by parsing order is missing: "
                                << element_id << " ("
-                               << config_set->getPosition() << ")");
+                               << mutable_cfg->getPosition() << ")");
                 }
             }
 
@@ -256,36 +261,27 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
 }
 
 void
+DCfgMgrBase::setCfgDefaults(isc::data::ElementPtr) {
+}
+
+void
+DCfgMgrBase::parseElement(const std::string&, isc::data::ConstElementPtr) {
+};
+
+
+void
 DCfgMgrBase::buildParams(isc::data::ConstElementPtr params_config) {
     // Loop through scalars parsing them and committing them to storage.
     BOOST_FOREACH(dhcp::ConfigPair param, params_config->mapValue()) {
-        // Call derivation's method to create the proper parser.
-        dhcp::ParserPtr parser(createConfigParser(param.first,
-                                                  param.second->getPosition()));
-        parser->build(param.second);
-        parser->commit();
+        // Call derivation's element parser to parse the element.
+        parseElement(param.first, param.second);
     }
 }
 
 void DCfgMgrBase::buildAndCommit(std::string& element_id,
                                  isc::data::ConstElementPtr value) {
-    // Call derivation's implementation to create the appropriate parser
-    // based on the element id.
-    ParserPtr parser = createConfigParser(element_id, value->getPosition());
-    if (!parser) {
-        isc_throw(DCfgMgrBaseError, "Could not create parser");
-    }
-
-    // Invoke the parser's build method passing in the value. This will
-    // "convert" the Element form of value into the actual data item(s)
-    // and store them in parser's local storage.
-    parser->build(value);
-
-    // Invoke the parser's commit method. This "writes" the data
-    // item(s) stored locally by the parser into the context.  (Note that
-    // parsers are free to do more than update the context, but that is an
-    // nothing something we are concerned with here.)
-    parser->commit();
+    // Call derivation's element parser to parse the element.
+    parseElement(element_id, value);
 }
 
 }; // end of isc::dhcp namespace

+ 32 - 32
src/lib/process/d_cfg_mgr.h

@@ -244,15 +244,10 @@ typedef std::vector<std::string> ElementIdList;
 /// This allows a derivation to specify the order in which its elements are
 /// parsed if there are dependencies between elements.
 ///
-/// To parse a given element, its id is passed into createConfigParser,
-/// which returns an instance of the appropriate parser.  This method is
-/// abstract so the derivation's implementation determines the type of parser
-/// created. This isolates the knowledge of specific element ids and which
-/// application specific parsers to derivation.
-///
-/// Once the parser has been created, it is used to parse the data value
-/// associated with the element id and update the context with the parsed
-/// results.
+/// To parse a given element, its id along with the element itself, 
+/// is passed into the virtual method, @c parseElement. Derivations are 
+/// expected to converts the element into application specific object(s),
+/// thereby isolating the CPL from application details.
 ///
 /// In the event that an error occurs, parsing is halted and the
 /// configuration context is restored from backup.
@@ -320,13 +315,34 @@ public:
     virtual std::string getConfigSummary(const uint32_t selection) = 0;
 
 protected:
+    /// @brief Adds default values to the given config
+    ///
+    /// Provides derviations a means to add defaults to a configuration
+    /// Element map prior to parsing it.
+    ///
+    /// @param mutable_config - configuration to which defaults should be added
+    virtual void setCfgDefaults(isc::data::ElementPtr mutable_config);
+
+    /// @brief Parses an individual element
+    ///
+    /// Each element to be parsed is passed into this method to be converted
+    /// into the requisite application object(s).
+    ///
+    /// @param element_id name of the element as it is expected in the cfg
+    /// @param element value of the element as ElementPtr
+    ///
+    virtual void parseElement(const std::string& element_id,
+                              isc::data::ConstElementPtr element);
+
     /// @brief Parses a set of scalar configuration elements into global
     /// parameters
     ///
     /// For each scalar element in the set:
-    ///  - create a parser for the element
-    ///  - invoke the parser's build method
-    ///  - invoke the parser's commit method
+    /// - Invoke parseElement
+    /// - If it returns true go to the next element othewise:
+    ///     - create a parser for the element
+    ///     - invoke the parser's build method
+    ///     - invoke the parser's commit method
     ///
     /// This will commit the values to context storage making them accessible
     /// during object parsing.
@@ -334,24 +350,6 @@ protected:
     /// @param params_config set of scalar configuration elements to parse
     virtual void buildParams(isc::data::ConstElementPtr params_config);
 
-    /// @brief  Create a parser instance based on an element id.
-    ///
-    /// Given an element_id returns an instance of the appropriate parser.
-    /// This method is abstract, isolating any direct knowledge of element_ids
-    /// and parsers to within the application-specific derivation.
-    ///
-    /// @param element_id is the string name of the element as it will appear
-    /// in the configuration set.
-    /// @param pos position within the configuration text (or file) of element
-    /// to be parsed.  This is passed for error messaging.
-    ///
-    /// @return returns a ParserPtr to the parser instance.
-    /// @throw throws DCfgMgrBaseError if an error occurs.
-    virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& element_id,
-                       const isc::data::Element::Position& pos
-                       = isc::data::Element::Position()) = 0;
-
     /// @brief Abstract factory which creates a context instance.
     ///
     /// This method is used at the beginning of configuration process to
@@ -377,8 +375,10 @@ private:
 
     /// @brief Parse a configuration element.
     ///
-    /// Given an element_id and data value, instantiate the appropriate
-    /// parser,  parse the data value, and commit the results.
+    /// Given an element_id and data value, invoke parseElement. If
+    /// it returns true the return, otherwise created the appropriate
+    /// parser, parse the data value, and commit the results.
+    ///
     ///
     /// @param element_id is the string name of the element as it will appear
     /// in the configuration set.

+ 15 - 5
src/lib/process/d_controller.cc

@@ -54,6 +54,12 @@ DControllerBase::setController(const DControllerBasePtr& controller) {
     controller_ = controller;
 }
 
+isc::data::ConstElementPtr
+DControllerBase::parseFile(const std::string&) {
+    isc::data::ConstElementPtr elements;
+    return (elements);
+}
+
 void
 DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
 
@@ -168,7 +174,7 @@ DControllerBase::parseArgs(int argc, char* argv[])
             // rather than calling exit() here which disrupts gtest.
             isc_throw(VersionMessage, getVersion(true));
             break;
-            
+
         case 'W':
             // gather Kea config report and throw so main() can catch and
             // return rather than calling exit() here which disrupts gtest.
@@ -257,9 +263,13 @@ DControllerBase::configFromFile() {
                                 "use -c command line option.");
         }
 
-        // Read contents of the file and parse it as JSON
-        isc::data::ConstElementPtr whole_config =
-            isc::data::Element::fromJSONFile(config_file, true);
+        // If parseFile returns an empty pointer, then pass the file onto the
+        // original JSON parser.
+        isc::data::ConstElementPtr whole_config = parseFile(config_file);
+        if (!whole_config) {
+            // Read contents of the file and parse it as JSON
+            whole_config = isc::data::Element::fromJSONFile(config_file, true);
+        }
 
         // Let's configure logging before applying the configuration,
         // so we can log things during configuration process.
@@ -480,7 +490,7 @@ DControllerBase::getVersion(bool extended) {
         tmp << "linked with:" << std::endl;
         tmp << isc::log::Logger::getVersion() << std::endl;
         tmp << isc::cryptolink::CryptoLink::getVersion() << std::endl;
-        tmp << "database:" << std::endl; 
+        tmp << "database:" << std::endl;
 #ifdef HAVE_MYSQL
         tmp << isc::dhcp::MySqlLeaseMgr::getDBVersion() << std::endl;
 #endif

+ 55 - 4
src/lib/process/d_controller.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015,2017 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
@@ -167,7 +167,11 @@ public:
     /// include at least:
     ///
     /// @code
-    ///  { "<module-name>": {<module-config>} }
+    ///  { "<module-name>": {<module-config>}
+    ///
+    ///   # Logging element is optional
+    ///   ,"Logging": {<logger connfig}
+    ///  }
     ///
     ///  where:
     ///     module-name : is a label which uniquely identifies the
@@ -177,8 +181,16 @@ public:
     ///                    the application's configuration values
     /// @endcode
     ///
-    /// The method extracts the set of configuration elements for the
-    /// module-name which matches the controller's app_name_ and passes that
+    /// To translate the JSON content into Elements, @c parseFile() is called
+    /// first.  This virtual method provides derivations a means to parse the
+    /// file content using an alternate parser.  If it returns an empty pointer
+    /// than the JSON parsing providing by Element::fromJSONFile() is called.
+    ///
+    /// Once parsed, the method looks for the Element "Logging" and, if present
+    /// uses it to configure loging.
+    ///
+    /// It then extracts the set of configuration elements for the
+    /// module-name that matches the controller's app_name_ and passes that
     /// set into @c updateConfig().
     ///
     /// The file may contain an arbitrary number of other modules.
@@ -383,6 +395,45 @@ protected:
     /// @throw VersionMessage if the -v or -V arguments is given.
     void parseArgs(int argc, char* argv[]);
 
+
+    ///@brief Parse a given file into Elements
+    ///
+    /// This method provides a means for deriving classes to use alternate
+    /// parsing mechanisms to parse configuration files into the corresponding
+    /// isc::data::Elements. The elements produced must be equivalent to those
+    /// which would be produced by the original JSON parsing.  Implementations
+    /// should throw when encountering errors.
+    ///
+    /// The default implementation returns an empty pointer, signifying to
+    /// callers that they should submit the file to the original parser.
+    ///
+    /// @param file_name pathname of the file to parse
+    ///
+    /// @return pointer to the elements created
+    ///
+    virtual isc::data::ConstElementPtr parseFile(const std::string& file_name);
+
+    ///@brief Parse text into Elements
+    ///
+    /// This method provides a means for deriving classes to use alternate
+    /// parsing mechanisms to parse configuration text into the corresponding
+    /// isc::data::Elements. The elements produced must be equivalent to those
+    /// which would be produced by the original JSON parsing.  Implementations
+    /// should throw when encountering errors.
+    ///
+    /// The default implementation returns an empty pointer, signifying to
+    /// callers that they should submit the text to the original parser.
+    ///
+    /// @param input text to parse
+    ///
+    /// @return pointer to the elements created
+    ///
+    virtual isc::data::ConstElementPtr parseText(const std::string& input) {
+        static_cast<void>(input); // just tu shut up the unused parameter warning
+        isc::data::ConstElementPtr elements;
+        return (elements);
+    }
+
     /// @brief Instantiates the application process and then initializes it.
     /// This is the second step taken during launch, following successful
     /// command line parsing. It is used to invoke the derivation-specific

+ 2 - 21
src/lib/process/tests/d_cfg_mgr_unittests.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
@@ -22,6 +22,7 @@ using namespace std;
 using namespace isc;
 using namespace isc::config;
 using namespace isc::process;
+using namespace isc::data;
 using namespace boost::posix_time;
 
 namespace {
@@ -44,13 +45,6 @@ public:
         return (DCfgContextBasePtr());
     }
 
-    /// @brief Dummy implementation as this method is abstract.
-    virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& /* element_id */,
-                       const isc::data::Element::Position& /* pos */) {
-        return (isc::dhcp::ParserPtr());
-    }
-
     /// @brief Returns summary of configuration in the textual format.
     virtual std::string getConfigSummary(const uint32_t) {
         return ("");
@@ -124,18 +118,6 @@ TEST_F(DStubCfgMgrTest, basicParseTest) {
     answer_ = cfg_mgr_->parseConfig(config_set_);
     EXPECT_TRUE(checkAnswer(0));
 
-    // Verify that an error building the element is caught and returns a
-    // failed parse result.
-    SimFailure::set(SimFailure::ftElementBuild);
-    answer_ = cfg_mgr_->parseConfig(config_set_);
-    EXPECT_TRUE(checkAnswer(1));
-
-    // Verify that an error committing the element is caught and returns a
-    // failed parse result.
-    SimFailure::set(SimFailure::ftElementCommit);
-    answer_ = cfg_mgr_->parseConfig(config_set_);
-    EXPECT_TRUE(checkAnswer(1));
-
     // Verify that an unknown element error is caught and returns a failed
     // parse result.
     SimFailure::set(SimFailure::ftElementUnknown);
@@ -477,5 +459,4 @@ TEST_F(DStubCfgMgrTest, paramPosition) {
     EXPECT_EQ(pos.file_, isc::data::Element::ZERO_POSITION().file_);
 }
 
-
 } // end of anonymous namespace

+ 29 - 0
src/lib/process/tests/d_controller_unittests.cc

@@ -396,6 +396,35 @@ TEST_F(DStubControllerTest, invalidConfigReload) {
     EXPECT_EQ(SIGHUP, signals[0]);
 }
 
+// Tests that the original configuration is retained after a SIGHUP triggered
+// reconfiguration fails due to invalid config content.
+TEST_F(DStubControllerTest, alternateParsing) {
+    controller_->useAlternateParser(true);
+
+    // Setup to raise SIGHUP in 200 ms.
+    TimedSignal sighup(*getIOService(), SIGHUP, 200);
+
+    // Write the config and then run launch() for 500 ms
+    // After startup, which will load the initial configuration this enters
+    // the process's runIO() loop. We will first rewrite the config file.
+    // Next we process the SIGHUP signal which should cause us to reconfigure.
+    time_duration elapsed_time;
+    runWithConfig("{ \"string_test\": \"first value\" }", 500, elapsed_time);
+
+    // Context is still available post launch. Check to see that our
+    // configuration value is still the original value.
+    std::string  actual_value = "";
+    ASSERT_NO_THROW(getContext()->getParam("string_test", actual_value));
+    EXPECT_EQ("alt value", actual_value);
+
+    // Verify that we saw the signal.
+    std::vector<int>& signals = controller_->getProcessedSignals();
+    ASSERT_EQ(1, signals.size());
+    EXPECT_EQ(SIGHUP, signals[0]);
+}
+
+
+
 // Tests that the original configuration is replaced after a SIGHUP triggered
 // reconfiguration succeeds.
 TEST_F(DStubControllerTest, validConfigReload) {

+ 35 - 71
src/lib/process/testutils/d_test_stubs.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,30 +15,6 @@ using namespace boost::asio;
 namespace isc {
 namespace process {
 
-const char* valid_d2_config = "{ "
-                        "\"ip-address\" : \"127.0.0.1\" , "
-                        "\"port\" : 5031, "
-                        "\"tsig-keys\": ["
-                        "{ \"name\": \"d2_key.tmark.org\" , "
-                        "   \"algorithm\": \"HMAC-MD5\" ,"
-                        "   \"secret\": \"LSWXnfkKZjdPJI5QxlpnfQ==\" "
-                        "} ],"
-                        "\"forward-ddns\" : {"
-                        "\"ddns-domains\": [ "
-                        "{ \"name\": \"tmark.org.\" , "
-                        "  \"key-name\": \"d2_key.tmark.org\" , "
-                        "  \"dns-servers\" : [ "
-                        "  { \"ip-address\": \"127.0.0.101\" } "
-                        "] } ] }, "
-                        "\"reverse-ddns\" : {"
-                        "\"ddns-domains\": [ "
-                        "{ \"name\": \" 0.168.192.in.addr.arpa.\" , "
-                        "  \"key-name\": \"d2_key.tmark.org\" , "
-                        "  \"dns-servers\" : [ "
-                        "  { \"ip-address\": \"127.0.0.101\" , "
-                        "    \"port\": 100 } ] } "
-                        "] } }";
-
 // Initialize the static failure flag.
 SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
 
@@ -149,7 +125,7 @@ DStubController::instance() {
 
 DStubController::DStubController()
     : DControllerBase(stub_app_name_, stub_bin_name_),
-      processed_signals_(), record_signal_only_(false) {
+      processed_signals_(), record_signal_only_(false), use_alternate_parser_(false) {
 
     if (getenv("KEA_FROM_BUILD")) {
         setSpecFileName(std::string(getenv("KEA_FROM_BUILD")) +
@@ -218,6 +194,22 @@ DStubController::processSignal(int signum){
     DControllerBase::processSignal(signum);
 }
 
+isc::data::ConstElementPtr
+DStubController::parseFile(const std::string& /*file_name*/) {
+    isc::data::ConstElementPtr elements;
+    if (use_alternate_parser_) {
+        std::ostringstream os;
+
+        os << "{ \"" << getController()->getAppName()
+            << "\": " << std::endl;
+        os <<  "{ \"string_test\": \"alt value\" } ";
+        os << " } " << std::endl;
+        elements = isc::data::Element::fromJSON(os.str());
+    }
+
+    return (elements);
+}
+
 DStubController::~DStubController() {
 }
 
@@ -316,37 +308,6 @@ DControllerTest::InstanceGetter DControllerTest::instanceGetter_ = NULL;
 /// @brief Defines the name of the configuration file to use
 const char* DControllerTest::CFG_TEST_FILE = "d2-test-config.json";
 
-//************************** ObjectParser *************************
-
-ObjectParser::ObjectParser(const std::string& param_name,
-                       ObjectStoragePtr& object_values)
-    : param_name_(param_name), object_values_(object_values) {
-}
-
-ObjectParser::~ObjectParser(){
-}
-
-void
-ObjectParser::build(isc::data::ConstElementPtr new_config) {
-    if (SimFailure::shouldFailOn(SimFailure::ftElementBuild)) {
-        // Simulates an error during element data parsing.
-        isc_throw (DCfgMgrBaseError, "Simulated build exception");
-    }
-
-    value_ = new_config;
-}
-
-void
-ObjectParser::commit() {
-    if (SimFailure::shouldFailOn(SimFailure::ftElementCommit)) {
-        // Simulates an error while committing the parsed element data.
-        throw std::runtime_error("Simulated commit exception");
-    }
-
-    object_values_->setParam(param_name_, value_,
-                             isc::data::Element::Position());
-}
-
 //************************** DStubContext *************************
 
 DStubContext::DStubContext(): object_values_(new ObjectStorage()) {
@@ -389,22 +350,25 @@ DStubCfgMgr::createNewContext() {
     return (DCfgContextBasePtr (new DStubContext()));
 }
 
-isc::dhcp::ParserPtr
-DStubCfgMgr::createConfigParser(const std::string& element_id,
-                                const isc::data::Element::Position& pos) {
-    isc::dhcp::ParserPtr parser;
+void
+DStubCfgMgr::parseElement(const std::string& element_id,
+                          isc::data::ConstElementPtr element) {
     DStubContextPtr context
         = boost::dynamic_pointer_cast<DStubContext>(getContext());
+
     if (element_id == "bool_test") {
-        parser.reset(new isc::dhcp::
-                         BooleanParser(element_id,
-                                       context->getBooleanStorage()));
+        bool value = element->boolValue();
+        context->getBooleanStorage()->setParam(element_id, value,
+                                               element->getPosition()); 
     } else if (element_id == "uint32_test") {
-        parser.reset(new isc::dhcp::Uint32Parser(element_id,
-                                                 context->getUint32Storage()));
+        uint32_t value = element->intValue();
+        context->getUint32Storage()->setParam(element_id, value,
+                                              element->getPosition()); 
+
     } else if (element_id == "string_test") {
-        parser.reset(new isc::dhcp::StringParser(element_id,
-                                                 context->getStringStorage()));
+        std::string value = element->stringValue();
+        context->getStringStorage()->setParam(element_id, value,
+                                              element->getPosition()); 
     } else {
         // Fail only if SimFailure dictates we should.  This makes it easier
         // to test parse ordering, by permitting a wide range of element ids
@@ -412,15 +376,15 @@ DStubCfgMgr::createConfigParser(const std::string& element_id,
         if (SimFailure::shouldFailOn(SimFailure::ftElementUnknown)) {
             isc_throw(DCfgMgrBaseError,
                       "Configuration parameter not supported: " << element_id
-                      << pos);
+                      << element->getPosition());
         }
 
         // Going to assume anything else is an object element.
-        parser.reset(new ObjectParser(element_id, context->getObjectStorage()));
+        context->getObjectStorage()->setParam(element_id, element,
+                                              element->getPosition()); 
     }
 
     parsed_order_.push_back(element_id);
-    return (parser);
 }
 
 }; // namespace isc::process

+ 57 - 67
src/lib/process/testutils/d_test_stubs.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
@@ -30,11 +30,6 @@ using namespace boost::posix_time;
 namespace isc {
 namespace process {
 
-/// @brief Provides a valid DHCP-DDNS configuration for testing basic
-/// parsing fundamentals.
-extern const char* valid_d2_config;
-
-
 /// @brief Class is used to set a globally accessible value that indicates
 /// a specific type of failure to simulate.  Test derivations of base classes
 /// can exercise error handling code paths by testing for specific SimFailure
@@ -235,6 +230,19 @@ public:
        record_signal_only_ = value;
     }
 
+    /// @brief Determines if parseFile() implementation is used
+    ///
+    /// If true, parseFile() will return a Map of elements with fixed content,
+    /// mimicking a controller which is using alternate JSON parsing.
+    /// If false, parseFile() will return an empty pointer mimicking a
+    /// controller which is using original JSON parsing supplied by the
+    /// Element class.
+    ///
+    /// @param value boolean which if true enables record-only behavior
+    void useAlternateParser(bool value) {
+       use_alternate_parser_ = value;
+    }
+
 protected:
     /// @brief Handles additional command line options that are supported
     /// by DStubController.  This implementation supports an option "-x".
@@ -291,6 +299,25 @@ protected:
     /// @param signum OS signal value received
     virtual void processSignal(int signum);
 
+    /// @brief Provides alternate parse file implementation
+    ///
+    /// Overrides the base class implementation to mimick controllers which
+    /// implement alternate file parsing.  If enabled via useAlternateParser()
+    /// the method will return a fixed map of elements reflecting the following
+    /// JSON:
+    ///
+    /// @code
+    ///     { "<name>getController()->getAppName()" :
+    ///          { "string_test": "alt value" };
+    ///     }
+    ///
+    /// @endcode
+    ///
+    ///  where <name> is getController()->getAppName()
+    ///
+    /// otherwise it return an empty pointer.
+    virtual isc::data::ConstElementPtr parseFile(const std::string&);
+
 private:
     /// @brief Constructor is private to protect singleton integrity.
     DStubController();
@@ -301,6 +328,9 @@ private:
     /// @brief Boolean for controlling if signals are merely recorded.
     bool record_signal_only_;
 
+    /// @brief Boolean for controlling if parseFile is "implemented"
+    bool use_alternate_parser_;
+
 public:
     virtual ~DStubController();
 };
@@ -570,51 +600,6 @@ public:
 typedef isc::dhcp::ValueStorage<isc::data::ConstElementPtr> ObjectStorage;
 typedef boost::shared_ptr<ObjectStorage> ObjectStoragePtr;
 
-/// @brief Simple parser derivation for parsing object elements.
-class ObjectParser : public isc::dhcp::DhcpConfigParser {
-public:
-
-    /// @brief Constructor
-    ///
-    /// See @ref DhcpConfigParser class for details.
-    ///
-    /// @param param_name name of the parsed parameter
-    ObjectParser(const std::string& param_name, ObjectStoragePtr& object_values);
-
-    /// @brief Destructor
-    virtual ~ObjectParser();
-
-    /// @brief Builds parameter value.
-    ///
-    /// See @ref DhcpConfigParser class for details.
-    ///
-    /// @param new_config pointer to the new configuration
-    /// @throw throws DCfgMgrBaseError if the SimFailure is set to
-    /// ftElementBuild. This allows for the simulation of an
-    /// exception during the build portion of parsing an element.
-    virtual void build(isc::data::ConstElementPtr new_config);
-
-    /// @brief Commits the parsed value to storage.
-    ///
-    /// See @ref DhcpConfigParser class for details.
-    ///
-    /// @throw throws DCfgMgrBaseError if SimFailure is set to ftElementCommit.
-    /// This allows for the simulation of an exception during the commit
-    /// portion of parsing an element.
-    virtual void commit();
-
-private:
-    /// name of the parsed parameter
-    std::string param_name_;
-
-    /// pointer to the parsed value of the parameter
-    isc::data::ConstElementPtr value_;
-
-    /// Pointer to the storage where committed value is stored.
-    ObjectStoragePtr object_values_;
-};
-
-
 /// @brief Test Derivation of the DCfgContextBase class.
 ///
 /// This class is used to test basic functionality of configuration context.
@@ -690,20 +675,29 @@ public:
     /// @brief Destructor
     virtual ~DStubCfgMgr();
 
-    /// @brief Given an element_id returns an instance of the appropriate
-    /// parser. It supports the element ids as described in the class brief.
+    /// @brief Parses the given element into the appropriate object
     ///
-    /// @param element_id is the string name of the element as it will appear
-    /// in the configuration set.
-    /// @param pos position within the configuration text (or file) of element
-    /// to be parsed.  This is passed for error messaging.
+    /// The method supports three named elements:
     ///
-    /// @return returns a ParserPtr to the parser instance.
-    /// @throw throws DCfgMgrBaseError if SimFailure is ftElementUnknown.
-    virtual isc::dhcp::ParserPtr
-    createConfigParser(const std::string& element_id,
-                       const isc::data::Element::Position& pos
-                       = isc::data::Element::Position());
+    /// -# "bool_test"
+    /// -# "uint32_test"
+    /// -# "string_test"
+    ///
+    /// which are parsed and whose value is then stored in the
+    /// the appropriate context value store.
+    ///
+    /// Any other element_id is treated generically and stored
+    /// in the context's object store, unless the simulated
+    /// error has been set to SimFailure::ftElementUnknown.
+    ///
+    /// @param element_id name of the element to parse
+    /// @param element Element to parse
+    ///
+    /// @throw DCfgMgrBaseError if simulated error is set
+    /// to ftElementUnknown and element_id is not one of
+    /// the named elements.
+    virtual void parseElement(const std::string& element_id,
+                              isc::data::ConstElementPtr element);
 
     /// @brief Returns a summary of the configuration in the textual format.
     ///
@@ -858,10 +852,6 @@ private:
     asiolink::IntervalTimerPtr timer_;
 };
 
-/// @brief Defines a small but valid DHCP-DDNS compliant configuration for
-/// testing configuration parsing fundamentals.
-extern const char* valid_d2_config;
-
 }; // namespace isc::process
 }; // namespace isc