Browse Source

[5110] D2 now uses JSON parsing and SimpleParsers to handle D2 global scalars

new files:
    src/bin/d2/tests/
        d2_simple_parser_unittest.cc
        parser_unittest.cc
        parser_unittest.h

src/bin/d2/d2_cfg_mgr.h
src/bin/d2/d2_cfg_mgr.cc
    D2CfgMgr::parseElement() - overrides base class implementation,
    flag global parameters as parsed without pushing them to D2CfgContext
    value stores

    D2CfgMgr::setCfgDefaults() - overrides base class implementation,
    sets defaults for D2

    D2CfgMgr::buildParams() - revamped to pull parameters from the given
    element map instead of context value stores.  Removed checks that
    are now done by JSON parser.

src/bin/d2/d2_controller.cc
    D2Controller::parseFile() - instead of generic JSON parsing, parse
    file with PARSER_DHCPDDNS context

src/bin/d2/d2_lexer.ll
src/bin/d2/d2_parser.yy
    Changed namespace from dhcp to d2
    Added PARSER_SUB_DHCPDDNS and D2 global parameter parsing
    Added objects for forward-ddns, reverse-ddns, tsig-keys

src/bin/d2/parser_context.cc
src/bin/d2/parser_context.h
    Added PARSER_DHCPDDNS, PARSER_SUB_DHCPDDNS

src/bin/d2/tests/Makefile.am
    Added new test source files

src/bin/d2/tests/d2_cfg_mgr_unittests.cc
src/bin/d2/tests/testdata/d2_cfg_tests.json
    TEST_F(D2CfgMgrTest, configPermutations) - revamped to test JSON parsing
    as well as element parsing

src/bin/d2/tests/parser_unittest.h
src/bin/d2/tests/parser_unittest.cc
    New file that tests basic JSON parsing

src/lib/cc/data.h
    Added parens around TypeError position error text

src/lib/cc/simple_parser.cc
    SimpleParser::setDefaults() - added logic to detect bad lexical casts
    when handling integers
Thomas Markwalder 8 years ago
parent
commit
9493848446

+ 126 - 73
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,148 @@ 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") << ")");
+        isc_throw(D2CfgError, "invalid address (" << str
+                  << ") specified for parameter '" << name
+                  << "' (" << value->getPosition() << ")");
     }
+}
 
-    if ((ip_address.toText() == "0.0.0.0") || (ip_address.toText() == "::")) {
-        isc_throw(D2CfgError, "IP address cannot be \"" << ip_address << "\" ("
-                   << strings->getPosition("ip-address") << ")");
+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 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") << ")");
+dhcp_ddns::NameChangeFormat
+getFormat(const std::string& name, isc::data::ConstElementPtr value) {
+    std::string str = value->stringValue();
+    try {
+        return (dhcp_ddns::stringToNcrFormat(str));
+    } catch (const std::exception& ex) {
+        isc_throw(D2CfgError,
+                  "invalid NameChangeRequest format (" << str
+                  << ") specified for parameter '" << name
+                  << "' (" << value->getPosition() << ")");
     }
+}
 
-    // Fetch and validate dns_server_timeout.
-    uint32_t dns_server_timeout
-        = ints->getOptionalParam("dns-server-timeout",
-                                 D2Params::DFT_DNS_SERVER_TIMEOUT);
+} // anon
 
-    if (dns_server_timeout < 1) {
-        isc_throw(D2CfgError, "DNS server timeout must be larger than 0 ("
-                  << ints->getPosition("dns-server-timeout") << ")");
-    }
+bool
+D2CfgMgr::parseElement(const std::string& element_id,
+                       isc::data::ConstElementPtr element) {
 
-    // Fetch and validate ncr_protocol.
-    dhcp_ddns::NameChangeProtocol ncr_protocol;
+    // Get D2 specific context.
     try {
-        ncr_protocol = dhcp_ddns::
-                       stringToNcrProtocol(strings->
-                                           getOptionalParam("ncr-protocol",
-                                                            D2Params::
-                                                            DFT_NCR_PROTOCOL));
+        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
+        } else {
+            // not something we handle here
+            return (false);
+        }
     } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, "ncr-protocol : "
-                  << ex.what() << " ("
-                  << strings->getPosition("ncr-protocol") << ")");
+        isc_throw(D2CfgError, "element: " << element_id << " : "  << ex.what()
+                              << element->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") << ")");
-    }
+    return (true);
+};
 
-    // Fetch and validate ncr_format.
-    dhcp_ddns::NameChangeFormat ncr_format;
-    try {
-        ncr_format = dhcp_ddns::
-                     stringToNcrFormat(strings->
-                                       getOptionalParam("ncr-format",
-                                                        D2Params::
-                                                        DFT_NCR_FORMAT));
-    } catch (const std::exception& ex) {
-        isc_throw(D2CfgError, "ncr-format : "
-                  << ex.what() << " ("
-                  << strings->getPosition("ncr-format") << ")");
-    }
+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;
+
+    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,7 +347,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;
+    getD2CfgContext()->getD2Params() = params;
 }
 
 isc::dhcp::ParserPtr

+ 25 - 0
src/bin/d2/d2_cfg_mgr.h

@@ -239,6 +239,31 @@ 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
+    ///
+    /// @return true if the element was parsed, false otherwise
+    virtual bool 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

+ 1 - 1
src/bin/d2/d2_controller.cc

@@ -61,7 +61,7 @@ D2Controller::parseFile(const std::string& file_name) {
 
     // Read contents of the file and parse it as JSON
     D2ParserContext parser;
-    elements = parser.parseFile(file_name, D2ParserContext::PARSER_JSON);
+    elements = parser.parseFile(file_name, D2ParserContext::PARSER_DHCPDDNS);
     if (!elements) {
         isc_throw(isc::BadValue, "no configuration found in file");
     }

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


+ 131 - 55
src/bin/d2/d2_lexer.ll

@@ -25,13 +25,13 @@ namespace {
 
 bool start_token_flag = false;
 
-isc::dhcp::D2ParserContext::ParserType start_token_value;
+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::dhcp::D2ParserContext::fatal(msg)
+#define YY_FATAL_ERROR(msg) isc::d2::D2ParserContext::fatal(msg)
 %}
 
 /* noyywrap disables automatic rewinding for the next file to parse. Since we
@@ -104,9 +104,11 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
         switch (start_token_value) {
         case D2ParserContext::PARSER_JSON:
         default:
-            return isc::dhcp::D2Parser::make_TOPLEVEL_JSON(driver.loc_);
+            return isc::d2::D2Parser::make_TOPLEVEL_JSON(driver.loc_);
         case D2ParserContext::PARSER_DHCPDDNS:
-            return isc::dhcp::D2Parser::make_TOPLEVEL_DHCPDDNS(driver.loc_);
+            return isc::d2::D2Parser::make_TOPLEVEL_DHCPDDNS(driver.loc_);
+        case D2ParserContext::PARSER_SUB_DHCPDDNS:
+            return isc::d2::D2Parser::make_SUB_DHCPDDNS(driver.loc_);
         }
     }
 %}
@@ -154,126 +156,200 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     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::dhcp::D2ParserContext::DHCPDDNS:
-        return isc::dhcp::D2Parser::make_IP_ADDRESS(driver.loc_);
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_IP_ADDRESS(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("ip-address", driver.loc_);
+        return isc::d2::D2Parser::make_STRING("ip-address", driver.loc_);
     }
 }
 
 \"port\" {
     switch(driver.ctx_) {
-    case isc::dhcp::D2ParserContext::DHCPDDNS:
-        return isc::dhcp::D2Parser::make_PORT(driver.loc_);
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_PORT(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("port", driver.loc_);
+        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::dhcp::D2ParserContext::DHCPDDNS:
-        return isc::dhcp::D2Parser::make_NCR_PROTOCOL(driver.loc_);
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_NCR_PROTOCOL(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("ncr-protocol", driver.loc_);
+        return isc::d2::D2Parser::make_STRING("ncr-protocol", driver.loc_);
     }
 }
 
 \"ncr-format\" {
     switch(driver.ctx_) {
-    case isc::dhcp::D2ParserContext::DHCPDDNS:
-        return isc::dhcp::D2Parser::make_NCR_FORMAT(driver.loc_);
+    case isc::d2::D2ParserContext::DHCPDDNS:
+        return isc::d2::D2Parser::make_NCR_FORMAT(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("ncr-format", driver.loc_);
+        return isc::d2::D2Parser::make_STRING("ncr-format", driver.loc_);
     }
 }
 
 (?i:\"UDP\") {
     /* dhcp-ddns value keywords are case insensitive */
-    if (driver.ctx_ == isc::dhcp::D2ParserContext::NCR_PROTOCOL) {
-        return isc::dhcp::D2Parser::make_UDP(driver.loc_);
+    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::dhcp::D2Parser::make_STRING(tmp, driver.loc_);
+    return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
 }
 
 (?i:\"TCP\") {
     /* dhcp-ddns value keywords are case insensitive */
-    if (driver.ctx_ == isc::dhcp::D2ParserContext::NCR_PROTOCOL) {
-        return isc::dhcp::D2Parser::make_TCP(driver.loc_);
+    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::dhcp::D2Parser::make_STRING(tmp, driver.loc_);
+    return isc::d2::D2Parser::make_STRING(tmp, driver.loc_);
 }
 
 (?i:\"JSON\") {
     /* dhcp-ddns value keywords are case insensitive */
-    if (driver.ctx_ == isc::dhcp::D2ParserContext::NCR_FORMAT) {
-        return isc::dhcp::D2Parser::make_JSON(driver.loc_);
+    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::dhcp::D2Parser::make_STRING(tmp, driver.loc_);
+    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_);
+    }
+}
+
+\"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_);
+    }
 }
 
+
 \"Logging\" {
     switch(driver.ctx_) {
-    case isc::dhcp::D2ParserContext::CONFIG:
-        return isc::dhcp::D2Parser::make_LOGGING(driver.loc_);
+    case isc::d2::D2ParserContext::CONFIG:
+        return isc::d2::D2Parser::make_LOGGING(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("Logging", driver.loc_);
+        return isc::d2::D2Parser::make_STRING("Logging", driver.loc_);
     }
 }
 
 \"loggers\" {
     switch(driver.ctx_) {
-    case isc::dhcp::D2ParserContext::LOGGING:
-        return isc::dhcp::D2Parser::make_LOGGERS(driver.loc_);
+    case isc::d2::D2ParserContext::LOGGING:
+        return isc::d2::D2Parser::make_LOGGERS(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("loggers", driver.loc_);
+        return isc::d2::D2Parser::make_STRING("loggers", driver.loc_);
     }
 }
 
 \"output_options\" {
     switch(driver.ctx_) {
-    case isc::dhcp::D2ParserContext::LOGGERS:
-        return isc::dhcp::D2Parser::make_OUTPUT_OPTIONS(driver.loc_);
+    case isc::d2::D2ParserContext::LOGGERS:
+        return isc::d2::D2Parser::make_OUTPUT_OPTIONS(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("output_options", driver.loc_);
+        return isc::d2::D2Parser::make_STRING("output_options", driver.loc_);
     }
 }
 
 \"output\" {
     switch(driver.ctx_) {
-    case isc::dhcp::D2ParserContext::OUTPUT_OPTIONS:
-        return isc::dhcp::D2Parser::make_OUTPUT(driver.loc_);
+    case isc::d2::D2ParserContext::OUTPUT_OPTIONS:
+        return isc::d2::D2Parser::make_OUTPUT(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("output", driver.loc_);
+        return isc::d2::D2Parser::make_STRING("output", driver.loc_);
+    }
+}
+
+\"name\" {
+    switch(driver.ctx_) {
+    case isc::d2::D2ParserContext::LOGGERS:
+        return isc::d2::D2Parser::make_NAME(driver.loc_);
+    default:
+        return isc::d2::D2Parser::make_STRING("name", driver.loc_);
     }
 }
 
 \"debuglevel\" {
     switch(driver.ctx_) {
-    case isc::dhcp::D2ParserContext::LOGGERS:
-        return isc::dhcp::D2Parser::make_DEBUGLEVEL(driver.loc_);
+    case isc::d2::D2ParserContext::LOGGERS:
+        return isc::d2::D2Parser::make_DEBUGLEVEL(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("debuglevel", driver.loc_);
+        return isc::d2::D2Parser::make_STRING("debuglevel", driver.loc_);
     }
 }
 
 \"severity\" {
     switch(driver.ctx_) {
-    case isc::dhcp::D2ParserContext::LOGGERS:
-        return isc::dhcp::D2Parser::make_SEVERITY(driver.loc_);
+    case isc::d2::D2ParserContext::LOGGERS:
+        return isc::d2::D2Parser::make_SEVERITY(driver.loc_);
     default:
-        return isc::dhcp::D2Parser::make_STRING("severity", driver.loc_);
+        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.
@@ -369,7 +445,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
         }
     }
 
-    return isc::dhcp::D2Parser::make_STRING(decoded, driver.loc_);
+    return isc::d2::D2Parser::make_STRING(decoded, driver.loc_);
 }
 
 \"{JSONStringCharacter}*{ControlCharacter}{ControlCharacterFill}*\" {
@@ -387,12 +463,12 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     driver.error(driver.loc_, "Overflow escape in " + std::string(yytext));
 }
 
-"["    { return isc::dhcp::D2Parser::make_LSQUARE_BRACKET(driver.loc_); }
-"]"    { return isc::dhcp::D2Parser::make_RSQUARE_BRACKET(driver.loc_); }
-"{"    { return isc::dhcp::D2Parser::make_LCURLY_BRACKET(driver.loc_); }
-"}"    { return isc::dhcp::D2Parser::make_RCURLY_BRACKET(driver.loc_); }
-","    { return isc::dhcp::D2Parser::make_COMMA(driver.loc_); }
-":"    { return isc::dhcp::D2Parser::make_COLON(driver.loc_); }
+"["    { 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.
@@ -409,7 +485,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 
     // The parser needs the string form as double conversion is no lossless
-    return isc::dhcp::D2Parser::make_INTEGER(integer, driver.loc_);
+    return isc::d2::D2Parser::make_INTEGER(integer, driver.loc_);
 }
 
 [-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)? {
@@ -422,16 +498,16 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
         driver.error(driver.loc_, "Failed to convert " + tmp + " to a floating point.");
     }
 
-    return isc::dhcp::D2Parser::make_FLOAT(fp, driver.loc_);
+    return isc::d2::D2Parser::make_FLOAT(fp, driver.loc_);
 }
 
 true|false {
     string tmp(yytext);
-    return isc::dhcp::D2Parser::make_BOOLEAN(tmp == "true", driver.loc_);
+    return isc::d2::D2Parser::make_BOOLEAN(tmp == "true", driver.loc_);
 }
 
 null {
-   return isc::dhcp::D2Parser::make_NULL_TYPE(driver.loc_);
+   return isc::d2::D2Parser::make_NULL_TYPE(driver.loc_);
 }
 
 (?i:true) driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
@@ -444,7 +520,7 @@ null {
 
 <<EOF>> {
     if (driver.states_.empty()) {
-        return isc::dhcp::D2Parser::make_END(driver.loc_);
+        return isc::d2::D2Parser::make_END(driver.loc_);
     }
     driver.loc_ = driver.locs_.back();
     driver.locs_.pop_back();

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


+ 85 - 51
src/bin/d2/d2_parser.h

@@ -32,7 +32,7 @@
 
 /**
  ** \file d2_parser.h
- ** Define the isc::dhcp::parser class.
+ ** Define the isc::d2::parser class.
  */
 
 // C++ LALR(1) parser skeleton written by Akim Demaille.
@@ -48,7 +48,7 @@
 #include <boost/lexical_cast.hpp>
 #include <d2/parser_context_decl.h>
 
-using namespace isc::dhcp;
+using namespace isc::d2;
 using namespace isc::data;
 using namespace std;
 
@@ -136,7 +136,7 @@ using namespace std;
 #endif  /* ! defined D2_PARSER_DEBUG */
 
 #line 14 "d2_parser.yy" // lalr1.cc:392
-namespace isc { namespace dhcp {
+namespace isc { namespace d2 {
 #line 141 "d2_parser.h" // lalr1.cc:392
 
 
@@ -360,20 +360,23 @@ namespace isc { namespace dhcp {
         TOKEN_TCP = 273,
         TOKEN_NCR_FORMAT = 274,
         TOKEN_JSON = 275,
-        TOKEN_LOGGING = 276,
-        TOKEN_LOGGERS = 277,
-        TOKEN_NAME = 278,
-        TOKEN_OUTPUT_OPTIONS = 279,
-        TOKEN_OUTPUT = 280,
-        TOKEN_DEBUGLEVEL = 281,
-        TOKEN_SEVERITY = 282,
-        TOKEN_TOPLEVEL_JSON = 283,
-        TOKEN_TOPLEVEL_DHCPDDNS = 284,
-        TOKEN_SUB_DHCPDDNS = 285,
-        TOKEN_STRING = 286,
-        TOKEN_INTEGER = 287,
-        TOKEN_FLOAT = 288,
-        TOKEN_BOOLEAN = 289
+        TOKEN_FORWARD_DDNS = 276,
+        TOKEN_REVERSE_DDNS = 277,
+        TOKEN_TSIG_KEYS = 278,
+        TOKEN_LOGGING = 279,
+        TOKEN_LOGGERS = 280,
+        TOKEN_NAME = 281,
+        TOKEN_OUTPUT_OPTIONS = 282,
+        TOKEN_OUTPUT = 283,
+        TOKEN_DEBUGLEVEL = 284,
+        TOKEN_SEVERITY = 285,
+        TOKEN_TOPLEVEL_JSON = 286,
+        TOKEN_TOPLEVEL_DHCPDDNS = 287,
+        TOKEN_SUB_DHCPDDNS = 288,
+        TOKEN_STRING = 289,
+        TOKEN_INTEGER = 290,
+        TOKEN_FLOAT = 291,
+        TOKEN_BOOLEAN = 292
       };
     };
 
@@ -566,6 +569,18 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_FORWARD_DDNS (const location_type& l);
+
+    static inline
+    symbol_type
+    make_REVERSE_DDNS (const location_type& l);
+
+    static inline
+    symbol_type
+    make_TSIG_KEYS (const location_type& l);
+
+    static inline
+    symbol_type
     make_LOGGING (const location_type& l);
 
     static inline
@@ -622,7 +637,7 @@ namespace isc { namespace dhcp {
 
 
     /// Build a parser object.
-    D2Parser (isc::dhcp::D2ParserContext& ctx_yyarg);
+    D2Parser (isc::d2::D2ParserContext& ctx_yyarg);
     virtual ~D2Parser ();
 
     /// Parse.
@@ -825,17 +840,17 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 111,     ///< Last index in yytable_.
-      yynnts_ = 63,  ///< Number of nonterminal symbols.
+      yylast_ = 126,     ///< Last index in yytable_.
+      yynnts_ = 69,  ///< Number of nonterminal symbols.
       yyfinal_ = 8, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 35  ///< Number of tokens.
+      yyntokens_ = 38  ///< Number of tokens.
     };
 
 
     // User arguments.
-    isc::dhcp::D2ParserContext& ctx;
+    isc::d2::D2ParserContext& ctx;
   };
 
   // Symbol number corresponding to token number t.
@@ -875,9 +890,10 @@ namespace isc { namespace dhcp {
        2,     2,     2,     2,     2,     2,     1,     2,     3,     4,
        5,     6,     7,     8,     9,    10,    11,    12,    13,    14,
       15,    16,    17,    18,    19,    20,    21,    22,    23,    24,
-      25,    26,    27,    28,    29,    30,    31,    32,    33,    34
+      25,    26,    27,    28,    29,    30,    31,    32,    33,    34,
+      35,    36,    37
     };
-    const unsigned int user_token_number_max_ = 289;
+    const unsigned int user_token_number_max_ = 292;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -910,24 +926,24 @@ namespace isc { namespace dhcp {
   {
       switch (other.type_get ())
     {
-      case 40: // value
-      case 67: // ncr_protocol_value
+      case 43: // value
+      case 70: // ncr_protocol_value
         value.copy< ElementPtr > (other.value);
         break;
 
-      case 34: // "boolean"
+      case 37: // "boolean"
         value.copy< bool > (other.value);
         break;
 
-      case 33: // "floating point"
+      case 36: // "floating point"
         value.copy< double > (other.value);
         break;
 
-      case 32: // "integer"
+      case 35: // "integer"
         value.copy< int64_t > (other.value);
         break;
 
-      case 31: // "constant string"
+      case 34: // "constant string"
         value.copy< std::string > (other.value);
         break;
 
@@ -948,24 +964,24 @@ namespace isc { namespace dhcp {
     (void) v;
       switch (this->type_get ())
     {
-      case 40: // value
-      case 67: // ncr_protocol_value
+      case 43: // value
+      case 70: // ncr_protocol_value
         value.copy< ElementPtr > (v);
         break;
 
-      case 34: // "boolean"
+      case 37: // "boolean"
         value.copy< bool > (v);
         break;
 
-      case 33: // "floating point"
+      case 36: // "floating point"
         value.copy< double > (v);
         break;
 
-      case 32: // "integer"
+      case 35: // "integer"
         value.copy< int64_t > (v);
         break;
 
-      case 31: // "constant string"
+      case 34: // "constant string"
         value.copy< std::string > (v);
         break;
 
@@ -1045,24 +1061,24 @@ namespace isc { namespace dhcp {
     // Type destructor.
     switch (yytype)
     {
-      case 40: // value
-      case 67: // ncr_protocol_value
+      case 43: // value
+      case 70: // ncr_protocol_value
         value.template destroy< ElementPtr > ();
         break;
 
-      case 34: // "boolean"
+      case 37: // "boolean"
         value.template destroy< bool > ();
         break;
 
-      case 33: // "floating point"
+      case 36: // "floating point"
         value.template destroy< double > ();
         break;
 
-      case 32: // "integer"
+      case 35: // "integer"
         value.template destroy< int64_t > ();
         break;
 
-      case 31: // "constant string"
+      case 34: // "constant string"
         value.template destroy< std::string > ();
         break;
 
@@ -1089,24 +1105,24 @@ namespace isc { namespace dhcp {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 40: // value
-      case 67: // ncr_protocol_value
+      case 43: // value
+      case 70: // ncr_protocol_value
         value.move< ElementPtr > (s.value);
         break;
 
-      case 34: // "boolean"
+      case 37: // "boolean"
         value.move< bool > (s.value);
         break;
 
-      case 33: // "floating point"
+      case 36: // "floating point"
         value.move< double > (s.value);
         break;
 
-      case 32: // "integer"
+      case 35: // "integer"
         value.move< int64_t > (s.value);
         break;
 
-      case 31: // "constant string"
+      case 34: // "constant string"
         value.move< std::string > (s.value);
         break;
 
@@ -1168,7 +1184,7 @@ namespace isc { namespace dhcp {
        0,   256,   257,   258,   259,   260,   261,   262,   263,   264,
      265,   266,   267,   268,   269,   270,   271,   272,   273,   274,
      275,   276,   277,   278,   279,   280,   281,   282,   283,   284,
-     285,   286,   287,   288,   289
+     285,   286,   287,   288,   289,   290,   291,   292
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -1288,6 +1304,24 @@ namespace isc { namespace dhcp {
   }
 
   D2Parser::symbol_type
+  D2Parser::make_FORWARD_DDNS (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_FORWARD_DDNS, l);
+  }
+
+  D2Parser::symbol_type
+  D2Parser::make_REVERSE_DDNS (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_REVERSE_DDNS, l);
+  }
+
+  D2Parser::symbol_type
+  D2Parser::make_TSIG_KEYS (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_TSIG_KEYS, l);
+  }
+
+  D2Parser::symbol_type
   D2Parser::make_LOGGING (const location_type& l)
   {
     return symbol_type (token::TOKEN_LOGGING, l);
@@ -1373,8 +1407,8 @@ namespace isc { namespace dhcp {
 
 
 #line 14 "d2_parser.yy" // lalr1.cc:392
-} } // isc::dhcp
-#line 1378 "d2_parser.h" // lalr1.cc:392
+} } // isc::d2
+#line 1412 "d2_parser.h" // lalr1.cc:392
 
 
 

+ 41 - 7
src/bin/d2/d2_parser.yy

@@ -11,7 +11,7 @@
 %define api.prefix {d2_parser_}
 %define api.token.constructor
 %define api.value.type variant
-%define api.namespace {isc::dhcp}
+%define api.namespace {isc::d2}
 %define parse.assert
 %code requires
 {
@@ -21,12 +21,12 @@
 #include <boost/lexical_cast.hpp>
 #include <d2/parser_context_decl.h>
 
-using namespace isc::dhcp;
+using namespace isc::d2;
 using namespace isc::data;
 using namespace std;
 }
 // The parsing context.
-%param { isc::dhcp::D2ParserContext& ctx }
+%param { isc::d2::D2ParserContext& ctx }
 %locations
 %define parse.trace
 %define parse.error verbose
@@ -38,7 +38,7 @@ using namespace std;
 
 %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 dhcp6_lexer.ll.
+// Actual regexps for tokens are defined in d2_lexer.ll.
 %token
   END  0  "end of file"
   COMMA ","
@@ -61,6 +61,9 @@ using namespace std;
   TCP "TCP"
   NCR_FORMAT "ncr-format"
   JSON "JSON"
+  FORWARD_DDNS "forward-ddns"
+  REVERSE_DDNS "reverse-ddns"
+  TSIG_KEYS "tsig-keys"
 
   LOGGING "Logging"
   LOGGERS "loggers"
@@ -242,6 +245,9 @@ dhcpddns_param: ip_address
                | dns_server_timeout
                | ncr_protocol
                | ncr_format
+               | forward_ddns
+               | reverse_ddns
+               | tsig_keys
                | unknown_map_entry
                ;
 
@@ -254,13 +260,20 @@ ip_address: IP_ADDRESS {
 };
 
 port: PORT COLON INTEGER {
+    if ($3 <= 0) {
+        error(@3, "port must be greater than zero");
+    } 
     ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
     ctx.stack_.back()->set("port", i);
 };
 
 dns_server_timeout: DNS_SERVER_TIMEOUT COLON INTEGER {
-    ElementPtr i(new IntElement($3, ctx.loc2pos(@3)));
-    ctx.stack_.back()->set("dns-server-timeout", i);
+    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 {
@@ -283,6 +296,27 @@ ncr_format: NCR_FORMAT {
     ctx.leave();
 };
 
+forward_ddns : FORWARD_DDNS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("forward-ddns", $4);
+    ctx.leave();
+};
+
+reverse_ddns : REVERSE_DDNS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("reverse-ddns", $4);
+    ctx.leave();
+};
+
+tsig_keys : TSIG_KEYS {
+    ctx.enter(ctx.NO_KEYWORD);
+} COLON value {
+    ctx.stack_.back()->set("tsig-keys", $4);
+    ctx.leave();
+};
+
 dhcp6_json_object: DHCP6 {
     ctx.enter(ctx.NO_KEYWORD);
 } COLON value {
@@ -417,7 +451,7 @@ output_param: OUTPUT {
 %%
 
 void
-isc::dhcp::D2Parser::error(const location_type& loc,
+isc::d2::D2Parser::error(const location_type& loc,
                               const std::string& what)
 {
     ctx.error(loc, what);

+ 3 - 3
src/bin/d2/d2_simple_parser.cc

@@ -11,7 +11,7 @@
 using namespace isc::data;
 
 namespace isc {
-namespace dhcp {
+namespace d2 {
 /// @brief This sets of arrays define the default values and
 ///        values inherited (derived) between various scopes.
 ///
@@ -36,8 +36,8 @@ const SimpleDefaults D2SimpleParser::D2_GLOBAL_DEFAULTS = {
     { "ip-address",         Element::string, "127.0.0.1" },
     { "port",               Element::integer, "53001" },
     { "dns-server-timeout", Element::integer, "100" },
-    { "ncr-protocol",       Element::integer, "UDP" },
-    { "ncr-format",         Element::integer, "JSON" }
+    { "ncr-protocol",       Element::string, "UDP" },
+    { "ncr-format",         Element::string, "JSON" }
 };
 
 /// @}

+ 1 - 1
src/bin/d2/d2_simple_parser.h

@@ -10,7 +10,7 @@
 #include <cc/simple_parser.h>
 
 namespace isc {
-namespace dhcp {
+namespace d2 {
 
 /// @brief SimpleParser specialized for D2
 ///

+ 3 - 3
src/bin/d2/location.hh

@@ -32,7 +32,7 @@
 
 /**
  ** \file location.hh
- ** Define the isc::dhcp::location class.
+ ** Define the isc::d2::location class.
  */
 
 #ifndef YY_D2_PARSER_LOCATION_HH_INCLUDED
@@ -41,7 +41,7 @@
 # include "position.hh"
 
 #line 14 "d2_parser.yy" // location.cc:337
-namespace isc { namespace dhcp {
+namespace isc { namespace d2 {
 #line 46 "location.hh" // location.cc:337
   /// Abstract a location.
   class location
@@ -187,6 +187,6 @@ namespace isc { namespace dhcp {
   }
 
 #line 14 "d2_parser.yy" // location.cc:337
-} } // isc::dhcp
+} } // isc::d2
 #line 192 "location.hh" // location.cc:337
 #endif // !YY_D2_PARSER_LOCATION_HH_INCLUDED

+ 4 - 4
src/bin/d2/parser_context.cc

@@ -13,7 +13,7 @@
 #include <limits>
 
 namespace isc {
-namespace dhcp {
+namespace d2 {
 
 D2ParserContext::D2ParserContext()
   : ctx_(NO_KEYWORD), trace_scanning_(false), trace_parsing_(false)
@@ -43,7 +43,7 @@ D2ParserContext::parseFile(const std::string& filename, ParserType parser_type)
 
 isc::data::ElementPtr
 D2ParserContext::parseCommon() {
-    isc::dhcp::D2Parser parser(*this);
+    isc::d2::D2Parser parser(*this);
     // Uncomment this to get detailed parser logs.
     // trace_parsing_ = true;
     parser.set_debug_level(trace_parsing_);
@@ -68,7 +68,7 @@ D2ParserContext::parseCommon() {
 
 
 void
-D2ParserContext::error(const isc::dhcp::location& loc, const std::string& what)
+D2ParserContext::error(const isc::d2::location& loc, const std::string& what)
 {
     isc_throw(D2ParseError, loc << ": " << what);
 }
@@ -86,7 +86,7 @@ D2ParserContext::fatal (const std::string& what)
 }
 
 isc::data::Element::Position
-D2ParserContext::loc2pos(isc::dhcp::location& loc)
+D2ParserContext::loc2pos(isc::d2::location& loc)
 {
     const std::string& file = *loc.begin.filename;
     const uint32_t line = loc.begin.line;

+ 8 - 8
src/bin/d2/parser_context.h

@@ -14,13 +14,13 @@
 #include <exceptions/exceptions.h>
 
 // Tell Flex the lexer's prototype ...
-#define YY_DECL isc::dhcp::D2Parser::symbol_type d2_parser_lex (D2ParserContext& driver)
+#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 dhcp {
+namespace d2 {
 
 /// @brief Evaluation error exception raised when trying to parse.
 ///
@@ -49,10 +49,10 @@ public:
         PARSER_JSON,
 
         ///< Used while parsing top level (contains DhcpDdns, Logging, others)
-        PARSER_CONFIG,
+        PARSER_DHCPDDNS,
 
         ///< Used while parsing content of DhcpDdns.
-        PARSER_DHCPDDNS
+        PARSER_SUB_DHCPDDNS
     } ParserType;
 
     /// @brief Default constructor.
@@ -116,7 +116,7 @@ public:
     /// @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::dhcp::location& loc, const std::string& what);
+    void error(const isc::d2::location& loc, const std::string& what);
 
     /// @brief Error handler
     ///
@@ -143,7 +143,7 @@ public:
     ///
     /// @param loc location in bison format
     /// @return Position in format accepted by Element
-    isc::data::Element::Position loc2pos(isc::dhcp::location& loc);
+    isc::data::Element::Position loc2pos(isc::d2::location& loc);
 
     /// @brief Defines syntactic contexts for lexical tie-ins
     typedef enum {
@@ -183,10 +183,10 @@ public:
     ///
     /// The lexer will keep updating it. This variable will be useful
     /// for logging errors.
-    isc::dhcp::location loc_;
+    isc::d2::location loc_;
 
     /// @brief Location stack
-    std::vector<isc::dhcp::location> locs_;
+    std::vector<isc::d2::location> locs_;
 
     /// @brief Lexer state stack
     std::vector<struct yy_buffer_state*> states_;

+ 1 - 1
src/bin/d2/parser_context_decl.h

@@ -10,7 +10,7 @@
 /// @file d2/parser_context_decl.h Forward declaration of the ParserContext class
 
 namespace isc {
-namespace dhcp {
+namespace d2 {
 
 class D2ParserContext;
 

+ 3 - 3
src/bin/d2/position.hh

@@ -32,7 +32,7 @@
 
 /**
  ** \file position.hh
- ** Define the isc::dhcp::position class.
+ ** Define the isc::d2::position class.
  */
 
 #ifndef YY_D2_PARSER_POSITION_HH_INCLUDED
@@ -51,7 +51,7 @@
 # endif
 
 #line 14 "d2_parser.yy" // location.cc:337
-namespace isc { namespace dhcp {
+namespace isc { namespace d2 {
 #line 56 "position.hh" // location.cc:337
   /// Abstract a position.
   class position
@@ -175,6 +175,6 @@ namespace isc { namespace dhcp {
   }
 
 #line 14 "d2_parser.yy" // location.cc:337
-} } // isc::dhcp
+} } // isc::d2
 #line 180 "position.hh" // location.cc:337
 #endif // !YY_D2_PARSER_POSITION_HH_INCLUDED

+ 3 - 3
src/bin/d2/stack.hh

@@ -32,7 +32,7 @@
 
 /**
  ** \file stack.hh
- ** Define the isc::dhcp::stack class.
+ ** Define the isc::d2::stack class.
  */
 
 #ifndef YY_D2_PARSER_STACK_HH_INCLUDED
@@ -41,7 +41,7 @@
 # include <vector>
 
 #line 14 "d2_parser.yy" // stack.hh:151
-namespace isc { namespace dhcp {
+namespace isc { namespace d2 {
 #line 46 "stack.hh" // stack.hh:151
   template <class T, class S = std::vector<T> >
   class stack
@@ -151,7 +151,7 @@ namespace isc { namespace dhcp {
   };
 
 #line 14 "d2_parser.yy" // stack.hh:151
-} } // isc::dhcp
+} } // isc::d2
 #line 156 "stack.hh" // stack.hh:151
 
 #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

+ 72 - 20
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,7 @@
 #include <config/module_spec.h>
 #include <d2/d2_config.h>
 #include <d2/d2_cfg_mgr.h>
+#include <d2/parser_context.h>
 #include <dhcpsrv/testutils/config_result_check.h>
 #include <process/testutils/d_test_stubs.h>
 #include <test_data_files_config.h>
@@ -172,7 +173,8 @@ public:
             return;
         }
 
-        FAIL() << "Parsing failed as expected but lacks position : " << *comment;
+        FAIL() << "Parsing failed as expected but lacks position : " << *comment
+            << ", filename:" << file_name;
     }
 
     /// @brief Pointer the D2Params most recently parsed.
@@ -433,7 +435,7 @@ TEST_F(D2CfgMgrTest, defaultValues) {
     EXPECT_EQ(D2Params::DFT_DNS_SERVER_TIMEOUT,
               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\" , "
@@ -449,7 +451,7 @@ TEST_F(D2CfgMgrTest, defaultValues) {
     EXPECT_EQ(dhcp_ddns::stringToNcrProtocol(D2Params::DFT_NCR_PROTOCOL),
               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\" , "
@@ -1641,6 +1643,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 +1664,15 @@ TEST_F(D2CfgMgrTest, matchReverse) {
 ///
 /// #    Each test has:
 /// #      1. description - optional text description
-/// #      2. should-fail - bool indicator if parsing is expected to file
+/// #      2. syntax-error - text of syntax error D2Parser is expected to emit
+/// #         (defaults to blank = no error expected)
+/// #      3. logic-error - indicator if post-parsing element logic should fail
 /// #         (defaults to false)
-/// #       3. data - configuration text to parse
+/// #      4. data - configuration text to parse
 /// #
 ///      "description" : "<text describing test>",
-///      "should_fail" : <true|false> ,
+///      "syntax-error" : ""|"<exact text from parser including position>" ,
+///      "logic_error" : <true|false> ,
 ///      "data" :
 ///          {
 /// #        configuration elements here
@@ -1695,14 +1706,18 @@ 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
-    //  3. data - configuration text to parse
+    //  2. syntax-error - error text JSON parser should emit including position,
+    //     defaults to blank (i.e. no error)
+    //  3. logic-error - bool indicator if post-JSON element processing should
+    //     fail
+    //     (defaults to false)
+    //  4. 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.
+    // expected outcome as given by logic-error.
     isc::data::ConstElementPtr test;
     ASSERT_TRUE(tests->get("test-list"));
     BOOST_FOREACH(test, tests->get("test-list")->listValue()) {
@@ -1714,12 +1729,20 @@ TEST_F(D2CfgMgrTest, configPermutations) {
             elem->getValue(description);
         }
 
-        // Grab the outcome flag, should-fail, defaults to false if it's
+        // Grab the outcome flag, logic-error, defaults to false if it's
         // not specified.
-        bool should_fail = false;
-        elem = test->get("should-fail");
+        bool logic_error = false;
+        elem = test->get("logic-error");
         if (elem)  {
-            elem->getValue(should_fail);
+            elem->getValue(logic_error);
+        }
+
+        // If the JSON parsing is expected to detect an error, the test
+        // should have specified the error message text.
+        std::string expected_syntax_error = "";
+        elem = test->get("syntax-error");
+        if (elem) {
+            elem->getValue(expected_syntax_error);
         }
 
         // Grab the test's configuration data.
@@ -1727,12 +1750,41 @@ 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.
+        // Submit the configuration text to the D2Parser.  This is somewhat
+        // artifical as normally the JSON parsing is invoked by @c
+        // DController::configFromFile().  Doing it here, explicitly, allows us
+        // test both syntax and logic errors without the weight of a full
+        // server.  Currently the assumption is that the test data is the
+        // content permitted within a DhcpDdns element only.
+        try {
+            // First we turn the test data back into JSON text
+            D2ParserContext context;
+            stringstream os;
+            data->toJSON(os);
+            // Parse the test data
+            data = context.parseString(os.str(),
+                                       D2ParserContext::PARSER_SUB_DHCPDDNS);
+        } catch (const std::exception& ex) {
+            if (expected_syntax_error.empty()) {
+                // We have an unexpected syntax error.
+                ADD_FAILURE() << description << ", "
+                              << "Unexpected syntax error:" << ex.what();
+            } else {
+                // We expected and got a syntax error, we need to make sure
+                // we got the right one.
+                EXPECT_EQ(expected_syntax_error, ex.what())
+                            << description << ", syntax error mismatch";
+
+                // Expected or not, skip logic testing on syntax errors.
+                continue;
+           }
+        }
+
+        // Attempt to Element parse the configuration. We verify that we get the
+        // expected outcome, and if it was supposed to fail that the explanation
+        // contains position information.
         checkAnswerWithError(cfg_mgr_->parseConfig(data),
-                             (should_fail ? SHOULD_FAIL : SHOULD_PASS),
-                             test_file);
+                             (logic_error ? SHOULD_FAIL : SHOULD_PASS));
     }
 }
 

+ 109 - 0
src/bin/d2/tests/d2_simple_parser_unittest.cc

@@ -0,0 +1,109 @@
+// 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 <config.h>
+#include <gtest/gtest.h>
+#include <d2/d2_simple_parser.h>
+#include <d2/tests/parser_unittest.h>
+#include <cc/data.h>
+
+#include <boost/lexical_cast.hpp>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Text fixture class for D2SimpleParser
+class D2SimpleParserTest : public ::testing::Test {
+public:
+    /// @brief Checks if specified element matches the given integer default
+    ///
+    /// @param element defaulted element to check
+    /// @param deflt SimpleDefault which supplied the default valaue
+    void checkIntegerValue(const ConstElementPtr& element, const SimpleDefault& deflt)  {
+        ASSERT_TRUE(element);
+
+        // Verify it is an integer.
+        ASSERT_EQ(Element::integer, element->getType());
+
+        // Turn default value string into an int.
+        int64_t default_value = 0;
+        ASSERT_NO_THROW(default_value = boost::lexical_cast<int64_t>(deflt.value_));
+
+        // Verify it has the expected value.
+        EXPECT_EQ(default_value, element->intValue());
+    }
+
+    /// @brief Checks if specified element matches the given boolean default
+    ///
+    /// @param element defaulted element to check
+    /// @param deflt SimpleDefault which supplied the default valaue
+    void checkBooleanValue(const ConstElementPtr& element, const SimpleDefault& deflt)  {
+        ASSERT_TRUE(element);
+
+        // Verify it is a bool.
+        ASSERT_EQ(Element::boolean, element->getType());
+
+        // Turn default value string into a bool.
+        bool default_value = false;
+        ASSERT_NO_THROW(boost::lexical_cast<bool>(deflt.value_));
+
+        // Verify it has the expected value.
+        EXPECT_EQ(default_value, element->boolValue());
+    }
+
+    /// @brief Checks if specified element matches the given string default
+    ///
+    /// @param element defaulted element to check
+    /// @param deflt SimpleDefault which supplied the default valaue
+    void checkStringValue(const ConstElementPtr& element, const SimpleDefault& deflt)  {
+        ASSERT_TRUE(element);
+
+        // Verify it's a string
+        ASSERT_EQ(Element::string, element->getType());
+
+        // Verify it has the expected value
+        EXPECT_EQ(deflt.value_, element->stringValue());
+    }
+};
+
+// This test checks if global defaults are properly set for D2.
+TEST_F(D2SimpleParserTest, globalD2Defaults) {
+
+    ElementPtr empty = isc::d2::test::parseJSON("{ }");
+    size_t num = 0;
+
+    EXPECT_NO_THROW(num = D2SimpleParser::setAllDefaults(empty));
+
+    // We expect 5 parameters to be inserted.
+    EXPECT_EQ(num, 5);
+
+    // Let's go over all parameters we have defaults for.
+    BOOST_FOREACH(SimpleDefault deflt, D2SimpleParser::D2_GLOBAL_DEFAULTS) {
+        ConstElementPtr x;
+        ASSERT_NO_THROW(x = empty->get(deflt.name_));
+        
+        EXPECT_TRUE(x); 
+        if (x) {
+            if (deflt.type_ == Element::integer) {
+                checkIntegerValue(x, deflt);
+            } else if (deflt.type_ == Element::boolean) {
+                checkBooleanValue(x, deflt);
+            } else if (deflt.type_ == Element::string) {
+                checkStringValue(x, deflt);
+            } else {
+                // add them if we need to. Like what do you if it's a map?
+                ADD_FAILURE() << "default type not supported:" << deflt.name_
+                            << " ,type: " << deflt.type_;
+            } 
+        }
+    }
+}
+
+};
+

+ 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\", \"betelguese\" ], "
+                    "\"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());
+}
+
+};
+};
+};

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

@@ -0,0 +1,37 @@
+// 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>
+#include <testutils/io_utils.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

+ 60 - 57
src/bin/d2/tests/testdata/d2_cfg_tests.json

@@ -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,7 @@
 # as well as validating this as the smallest config which makes writing
 # permutations easier.
 "description" : "D2 smallest, valid config",
-"should-fail" : false,
+"logic-error" : false,
 "data" :
     {
     "forward-ddns" : {},
@@ -36,7 +39,7 @@
 #-----
 ,{
 "description" : "D2 missing forward-ddns map",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "reverse-ddns" : {},
@@ -47,7 +50,7 @@
 #-----
 ,{
 "description" : "D2 missing reverse-ddns map",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -59,7 +62,7 @@
 #-----
 ,{
 "description" : "D2 missing tsig-keys list",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -70,7 +73,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 +86,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 +99,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 +138,7 @@
 #-----
 ,{
 "description" : "D2Params.ip-address invalid value",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "ip-address" : "bogus",
@@ -161,7 +164,7 @@
 #-----
 ,{
 "description" : "D2Params.port can't be 0",
-"should-fail" : true,
+"syntax-error" : "<string>:1.33: port must be greater than zero",
 "data" :
     {
     "port" : 0,
@@ -174,7 +177,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 +202,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 +215,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 +241,7 @@
 #-----
 ,{
 "description" : "D2Params.ncr-protocol, unsupported TCP",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "ncr-protocol" : "TCP",
@@ -252,7 +255,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 +282,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 +316,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, missing key name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -331,7 +334,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, blank key name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -350,7 +353,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, duplicate key name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -418,7 +421,7 @@
 #----- D2.tsig-keys, algorithm tests
 ,{
 "description" : "D2.tsig-keys, missing algorithm",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -436,7 +439,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, blank algorithm",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -455,7 +458,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, invalid algorithm",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -523,7 +526,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, invalid digest-bits",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -543,7 +546,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-MD5",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -563,7 +566,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA1",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -583,7 +586,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA224",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -603,7 +606,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA256",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -623,7 +626,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA384",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -643,7 +646,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, too small truncated HMAC-SHA512",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -663,7 +666,7 @@
 #----- D2.tsig-keys, secret tests
 ,{
 "description" : "D2.tsig-keys, missing secret",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -681,7 +684,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, blank secret",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -700,7 +703,7 @@
 #-----
 ,{
 "description" : "D2.tsig-keys, invalid secret",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -733,7 +736,7 @@
 #------
 ,{
 "description" : "D2.forward-ddns, unknown parameter",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -782,7 +785,7 @@
 #------
 ,{
 "description" : "D2.forward-ddns, duplicate domain",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -817,7 +820,7 @@
 #----- D2.forward-ddns.dhcp-ddns  tests
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns, unknown parameter",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -837,7 +840,7 @@
 #----- D2.forward-ddns.dhcp-ddns.name tests
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns, no name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -856,7 +859,7 @@
 #-----
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns, blank name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -876,7 +879,7 @@
 #------ "D2.forward-ddns.dhcp-ddns, key-name tests
 ,{
 "description" : "D2.forward-ddns, no matching key name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -910,7 +913,7 @@
 #----- D2.forward-ddns.dhcp-ddns.dns-servers tests
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers, no servers",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -931,7 +934,7 @@
 #----- D2.forward-ddns.dhcp-ddns.dns-servers tests
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers, unknown parameter",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -957,7 +960,7 @@
 #-----
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers.hostname unsupported",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -1033,7 +1036,7 @@
 #-----
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers.ip-address invalid address ",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -1086,7 +1089,7 @@
 #-----
 ,{
 "description" : "D2.forward-ddns.dhcp-ddns.dns-servers.port cannot be 0 ",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" :
@@ -1127,7 +1130,7 @@
 #------
 ,{
 "description" : "D2.reverse-ddns, unknown parameter",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1176,7 +1179,7 @@
 #------
 ,{
 "description" : "D2.reverse-ddns, duplicate domain",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1211,7 +1214,7 @@
 #----- D2.reverse-ddns.dhcp-ddns  tests
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns, unknown parameter",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1231,7 +1234,7 @@
 #----- D2.reverse-ddns.dhcp-ddns.name tests
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns, no name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1250,7 +1253,7 @@
 #-----
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns, blank name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1270,7 +1273,7 @@
 #------ "D2.reverse-ddns.dhcp-ddns, key-name tests
 ,{
 "description" : "D2.reverse-ddns, no matching key name",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1304,7 +1307,7 @@
 #----- D2.reverse-ddns.dhcp-ddns.dns-servers tests
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers, no servers",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1325,7 +1328,7 @@
 #----- D2.reverse-ddns.dhcp-ddns.dns-servers tests
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers, unknown parameter",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1351,7 +1354,7 @@
 #-----
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.hostname unsupported",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1426,7 +1429,7 @@
 #-----
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.ip-address invalid value",
-"should-fail" : true,
+"logic-error" : true,
 "data" :
     {
     "forward-ddns" : {},
@@ -1479,7 +1482,7 @@
 #-----
 ,{
 "description" : "D2.reverse-ddns.dhcp-ddns.dns-servers.port cannot be 0 ",
-"should-fail" : true,
+"logic-error" : true,
 "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_);             \
     }

+ 11 - 3
src/lib/cc/simple_parser.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -101,8 +101,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: {