Browse Source

[master] Merge branch 'trac2355'

Thomas Markwalder 12 years ago
parent
commit
066c983e09

File diff suppressed because it is too large
+ 208 - 1541
src/bin/dhcp4/config_parser.cc


+ 11 - 14
src/bin/dhcp4/config_parser.h

@@ -12,9 +12,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <exceptions/exceptions.h>
-#include <dhcpsrv/dhcp_config_parser.h>
 #include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
 #include <stdint.h>
 #include <string>
 
@@ -29,7 +30,8 @@ namespace dhcp {
 
 class Dhcpv4Srv;
 
-/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration 
+/// values.
 ///
 /// This function parses configuration information stored in @c config_set
 /// and configures the @c server by applying the configuration to it.
@@ -42,9 +44,9 @@ class Dhcpv4Srv;
 /// (such as malformed configuration or invalid configuration parameter),
 /// this function returns appropriate error code.
 ///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv4 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
+/// This function is called every time a new configuration is received. The 
+/// extra parameter is a reference to DHCPv4 server component. It is currently
+/// not used and CfgMgr::instance() is accessed instead.
 ///
 /// This method does not throw. It catches all exceptions and returns them as
 /// reconfiguration statuses. It may return the following response codes:
@@ -59,15 +61,10 @@ isc::data::ConstElementPtr
 configureDhcp4Server(Dhcpv4Srv&,
                      isc::data::ConstElementPtr config_set);
 
-
-/// @brief Returns the global uint32_t values storage.
-///
-/// This function must be only used by unit tests that need
-/// to access uint32_t global storage to verify that the
-/// Uint32Parser works as expected.
+/// @brief Returns the global context
 ///
-/// @return a reference to a global uint32 values storage.
-const Uint32Storage& getUint32Defaults();
+/// @return a reference to the global context
+ParserContextPtr& globalContext();
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 3 - 2
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -55,9 +55,10 @@ public:
 
     // Checks if global parameter of name have expected_value
     void checkGlobalUint32(string name, uint32_t expected_value) {
-        const Uint32Storage& uint32_defaults = getUint32Defaults();
+        const Uint32StoragePtr uint32_defaults = 
+                                        globalContext()->uint32_values_;
         try {
-            uint32_t actual_value = uint32_defaults.getParam(name);
+            uint32_t actual_value = uint32_defaults->getParam(name);
             EXPECT_EQ(expected_value, actual_value);
         } catch (DhcpConfigError) {
             ADD_FAILURE() << "Expected uint32 with name " << name

File diff suppressed because it is too large
+ 237 - 1576
src/bin/dhcp6/config_parser.cc


+ 10 - 3
src/bin/dhcp6/config_parser.h

@@ -20,6 +20,8 @@
 
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
+#include <dhcpsrv/dhcp_parsers.h>
+
 #include <string>
 
 namespace isc {
@@ -29,9 +31,9 @@ class Dhcpv6Srv;
 
 /// @brief Configures DHCPv6 server
 ///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv6 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
+/// This function is called every time a new configuration is received. The 
+/// extra parameter is a reference to DHCPv6 server component. It is currently 
+/// not used and CfgMgr::instance() is accessed instead.
 ///
 /// This method does not throw. It catches all exceptions and returns them as
 /// reconfiguration statuses. It may return the following response codes:
@@ -47,6 +49,11 @@ class Dhcpv6Srv;
 isc::data::ConstElementPtr
 configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
 
+/// @brief Returns the global context
+///
+/// @returns a reference to the global context
+ParserContextPtr& globalContext();
+ 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 

+ 2 - 0
src/lib/dhcpsrv/Makefile.am

@@ -39,6 +39,7 @@ libb10_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
 libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
+libb10_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h 
 libb10_dhcpsrv_la_SOURCES += key_from_key.h
 libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
 libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
@@ -59,6 +60,7 @@ libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcpsrv_la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_dhcpsrv_la_LIBADD  += $(top_builddir)/src/lib/cc/libb10-cc.la
 libb10_dhcpsrv_la_LDFLAGS  = -no-undefined -version-info 3:0:0
 if HAVE_MYSQL
 libb10_dhcpsrv_la_LDFLAGS += $(MYSQL_LIBS)

+ 0 - 73
src/lib/dhcpsrv/dhcp_config_parser.h

@@ -130,79 +130,6 @@ public:
     virtual void commit() = 0;
 };
 
-/// @brief A template class that stores named elements of a given data type.
-///
-/// This template class is provides data value storage for configuration parameters
-/// of a given data type.  The values are stored by parameter name and as instances 
-/// of type "ValueType". 
-///
-/// @param ValueType is the data type of the elements to store.
-template<typename ValueType>
-class ValueStorage {
-    public:
-        /// @brief  Stores the the parameter and its value in the store.
-        ///
-        /// If the parameter does not exist in the store, then it will be added,
-        /// otherwise its data value will be updated with the given value. 
-        ///
-        /// @param name is the name of the paramater to store.
-        /// @param value is the data value to store.
-        void setParam(const std::string name, const ValueType& value) {
-            values_[name] = value;
-        }
-
-        /// @brief Returns the data value for the given parameter.
-        ///
-        /// Finds and returns the data value for the given parameter.
-        /// @param name is the name of the parameter for which the data
-        /// value is desired.
-        ///
-        /// @return The paramater's data value of type <ValueType>.
-        /// @throw DhcpConfigError if the parameter is not found.
-        ValueType getParam(const std::string& name) const {
-            typename std::map<std::string, ValueType>::const_iterator param 
-                = values_.find(name);
-
-            if (param == values_.end()) {
-                isc_throw(DhcpConfigError, "Missing parameter '"
-                       << name << "'");
-            }
-
-            return (param->second);
-        }
-
-        /// @brief  Remove the parameter from the store.
-        ///
-        /// Deletes the entry for the given parameter from the store if it 
-        /// exists. 
-        ///
-        /// @param name is the name of the paramater to delete.
-        void delParam(const std::string& name) {
-            values_.erase(name);
-        }
-
-        /// @brief Deletes all of the entries from the store.
-        ///
-        void clear() {
-            values_.clear();
-        }
-
-
-    private:
-        /// @brief An std::map of the data values, keyed by parameter names.
-        std::map<std::string, ValueType> values_;
-};
-
-
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
-typedef ValueStorage<uint32_t> Uint32Storage;
-
-/// @brief a collection of elements that store string values
-typedef ValueStorage<std::string> StringStorage;
-
-/// @brief Storage for parsed boolean values.
-typedef ValueStorage<bool> BooleanStorage;
-
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
 

+ 957 - 0
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -0,0 +1,957 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/iface_mgr.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <map>
+
+using namespace std;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+// *********************** ParserContext  *************************
+
+ParserContext::ParserContext(Option::Universe universe):
+        boolean_values_(new BooleanStorage()),
+        uint32_values_(new Uint32Storage()),
+        string_values_(new StringStorage()),
+        options_(new OptionStorage()),
+        option_defs_(new OptionDefStorage()),
+        universe_(universe) {
+    }
+
+ParserContext::ParserContext(const ParserContext& rhs):
+        boolean_values_(new BooleanStorage(*(rhs.boolean_values_))),
+        uint32_values_(new Uint32Storage(*(rhs.uint32_values_))),
+        string_values_(new StringStorage(*(rhs.string_values_))),
+        options_(new OptionStorage(*(rhs.options_))),
+        option_defs_(new OptionDefStorage(*(rhs.option_defs_))),
+        universe_(rhs.universe_) {
+    }
+
+ParserContext& 
+ParserContext::operator=(const ParserContext& rhs) {
+        if (this != &rhs) {
+            boolean_values_ = 
+                BooleanStoragePtr(new BooleanStorage(*(rhs.boolean_values_)));
+            uint32_values_ = 
+                Uint32StoragePtr(new Uint32Storage(*(rhs.uint32_values_)));
+            string_values_ = 
+                StringStoragePtr(new StringStorage(*(rhs.string_values_)));
+            options_ = OptionStoragePtr(new OptionStorage(*(rhs.options_)));
+            option_defs_ = 
+                OptionDefStoragePtr(new OptionDefStorage(*(rhs.option_defs_)));
+            universe_ = rhs.universe_;
+        }
+        return (*this);
+    }
+
+
+// **************************** DebugParser *************************
+
+DebugParser::DebugParser(const std::string& param_name)
+    :param_name_(param_name) {
+}
+
+void 
+DebugParser::build(ConstElementPtr new_config) {
+    std::cout << "Build for token: [" << param_name_ << "] = ["
+        << value_->str() << "]" << std::endl; 
+    value_ = new_config;
+}
+
+void 
+DebugParser::commit() {
+    // Debug message. The whole DebugParser class is used only for parser
+    // debugging, and is not used in production code. It is very convenient
+    // to keep it around. Please do not turn this cout into logger calls.
+    std::cout << "Commit for token: [" << param_name_ << "] = ["
+                  << value_->str() << "]" << std::endl;
+}
+
+// **************************** BooleanParser  *************************
+
+template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
+    // The Config Manager checks if user specified a
+    // valid value for a boolean parameter: True or False.
+    // We should have a boolean Element, use value directly
+    try {
+        value_ = value->boolValue();
+    } catch (const isc::data::TypeError &) {
+        isc_throw(BadValue, " Wrong value type for " << param_name_ 
+                  << " : build called with a non-boolean element.");
+    }
+}
+
+// **************************** Uin32Parser  *************************
+
+template<> void ValueParser<uint32_t>::build(ConstElementPtr value) {
+    int64_t check;
+    string x = value->str();
+    try {
+        check = boost::lexical_cast<int64_t>(x);
+    } catch (const boost::bad_lexical_cast &) {
+        isc_throw(BadValue, "Failed to parse value " << value->str()
+                  << " as unsigned 32-bit integer.");
+    }
+    if (check > std::numeric_limits<uint32_t>::max()) {
+        isc_throw(BadValue, "Value " << value->str() << "is too large"
+                  << " for unsigned 32-bit integer.");
+    }
+    if (check < 0) {
+        isc_throw(BadValue, "Value " << value->str() << "is negative."
+               << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+    }
+
+    // value is small enough to fit
+    value_ = static_cast<uint32_t>(check);
+}
+
+// **************************** StringParser  *************************
+
+template <> void ValueParser<std::string>::build(ConstElementPtr value) {
+    value_ = value->str();
+    boost::erase_all(value_, "\"");
+}
+
+// ******************** InterfaceListConfigParser *************************
+
+InterfaceListConfigParser::InterfaceListConfigParser(const std::string& 
+                                                     param_name) {
+    if (param_name != "interface") {
+        isc_throw(BadValue, "Internal error. Interface configuration "
+            "parser called for the wrong parameter: " << param_name);
+    }
+}
+
+void 
+InterfaceListConfigParser::build(ConstElementPtr value) {
+    BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+        interfaces_.push_back(iface->str());
+    }
+}
+
+void 
+InterfaceListConfigParser::commit() {
+    /// @todo: Implement per interface listening. Currently always listening
+    /// on all interfaces.
+}
+
+// **************************** OptionDataParser *************************
+OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
+                                  ParserContextPtr global_context)
+    : boolean_values_(new BooleanStorage()), 
+    string_values_(new StringStorage()), uint32_values_(new Uint32Storage()), 
+    options_(options), option_descriptor_(false), 
+    global_context_(global_context) {
+    if (!options_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+             << "options storage may not be NULL");
+    }
+
+    if (!global_context_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+             << "context may may not be NULL");
+    }
+}
+
+void 
+OptionDataParser::build(ConstElementPtr option_data_entries) {
+    BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
+        ParserPtr parser;
+        if (param.first == "name" || param.first == "data" ||
+            param.first == "space") {
+            StringParserPtr name_parser(new StringParser(param.first, 
+                                        string_values_)); 
+            parser = name_parser;
+        } else if (param.first == "code") {
+            Uint32ParserPtr code_parser(new Uint32Parser(param.first, 
+                                       uint32_values_)); 
+            parser = code_parser;
+        } else if (param.first == "csv-format") {
+            BooleanParserPtr value_parser(new BooleanParser(param.first, 
+                                         boolean_values_)); 
+            parser = value_parser;
+        } else {
+            isc_throw(DhcpConfigError,
+                      "Parser error: option-data parameter not supported: "
+                      << param.first);
+        }
+
+        parser->build(param.second);
+        // Before we can create an option we need to get the data from
+        // the child parsers. The only way to do it is to invoke commit
+        // on them so as they store the values in appropriate storages
+        // that this class provided to them. Note that this will not
+        // modify values stored in the global storages so the configuration
+        // will remain consistent even parsing fails somewhere further on.
+        parser->commit();
+    }
+
+    // Try to create the option instance.
+    createOption();
+}
+
+void 
+OptionDataParser::commit() {
+    if (!option_descriptor_.option) {
+        // Before we can commit the new option should be configured. If it is 
+        // not than somebody must have called commit() before build().
+        isc_throw(isc::InvalidOperation, 
+            "parser logic error: no option has been configured and"
+            " thus there is nothing to commit. Has build() been called?");
+    }
+
+    uint16_t opt_type = option_descriptor_.option->getType();
+    Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+    // The getItems() should never return NULL pointer. If there are no
+    // options configured for the particular option space a pointer
+    // to an empty container should be returned.
+    assert(options);
+    Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+    // Try to find options with the particular option code in the main
+    // storage. If found, remove these options because they will be
+    // replaced with new one.
+    Subnet::OptionContainerTypeRange range = idx.equal_range(opt_type);
+    if (std::distance(range.first, range.second) > 0) {
+        idx.erase(range.first, range.second);
+    }
+
+    // Append new option to the main storage.
+    options_->addItem(option_descriptor_, option_space_);
+}
+
+void 
+OptionDataParser::createOption() {
+    // Option code is held in the uint32_t storage but is supposed to
+    // be uint16_t value. We need to check that value in the configuration
+    // does not exceed range of uint8_t and is not zero.
+    uint32_t option_code = uint32_values_->getParam("code");
+    if (option_code == 0) {
+        isc_throw(DhcpConfigError, "option code must not be zero."
+                << " Option code '0' is reserved in DHCPv4.");
+    } else if (option_code > std::numeric_limits<uint8_t>::max()) {
+        isc_throw(DhcpConfigError, "invalid option code '" << option_code
+                << "', it must not exceed '"
+                << std::numeric_limits<uint8_t>::max() << "'");
+    }
+
+    // Check that the option name has been specified, is non-empty and does not
+    // contain spaces
+    std::string option_name = string_values_->getParam("name"); 
+    if (option_name.empty()) {
+        isc_throw(DhcpConfigError, "name of the option with code '"
+                << option_code << "' is empty");
+    } else if (option_name.find(" ") != std::string::npos) {
+        isc_throw(DhcpConfigError, "invalid option name '" << option_name
+                << "', space character is not allowed");
+    }
+
+    std::string option_space = string_values_->getParam("space"); 
+    if (!OptionSpace::validateName(option_space)) {
+        isc_throw(DhcpConfigError, "invalid option space name '"
+                << option_space << "' specified for option '"
+                << option_name << "' (code '" << option_code
+                << "')");
+    }
+
+    // Find the Option Definition for the option by its option code.
+    // findOptionDefinition will throw if not found, no need to test.
+    OptionDefinitionPtr def;
+    if (!(def = findServerSpaceOptionDefinition(option_space, option_code))) {
+        // If we are not dealing with a standard option then we
+        // need to search for its definition among user-configured
+        // options. They are expected to be in the global storage
+        // already.
+        OptionDefContainerPtr defs = 
+            global_context_->option_defs_->getItems(option_space);
+
+        // The getItems() should never return the NULL pointer. If there are
+        // no option definitions for the particular option space a pointer
+        // to an empty container should be returned.
+        assert(defs);
+        const OptionDefContainerTypeIndex& idx = defs->get<1>();
+        OptionDefContainerTypeRange range = idx.equal_range(option_code);
+        if (std::distance(range.first, range.second) > 0) {
+            def = *range.first;
+        }
+        if (!def) {
+            isc_throw(DhcpConfigError, "definition for the option '"
+                      << option_space << "." << option_name
+                      << "' having code '" <<  option_code
+                      << "' does not exist");
+        }
+    }
+
+    // Get option data from the configuration database ('data' field).
+    const std::string option_data = string_values_->getParam("data");
+    const bool csv_format = boolean_values_->getParam("csv-format");
+
+    // Transform string of hexadecimal digits into binary format.
+    std::vector<uint8_t> binary;
+    std::vector<std::string> data_tokens;
+
+    if (csv_format) {
+        // If the option data is specified as a string of comma
+        // separated values then we need to split this string into
+        // individual values - each value will be used to initialize
+        // one data field of an option.
+        data_tokens = isc::util::str::tokens(option_data, ",");
+    } else {
+        // Otherwise, the option data is specified as a string of
+        // hexadecimal digits that we have to turn into binary format.
+        try {
+            util::encode::decodeHex(option_data, binary);
+        } catch (...) {
+            isc_throw(DhcpConfigError, "option data is not a valid"
+                      << " string of hexadecimal digits: " << option_data);
+        }
+    }
+
+    OptionPtr option;
+    if (!def) {
+        if (csv_format) {
+            isc_throw(DhcpConfigError, "the CSV option data format can be"
+                      " used to specify values for an option that has a"
+                      " definition. The option with code " << option_code
+                      << " does not have a definition.");
+        }
+
+        // @todo We have a limited set of option definitions intiialized at 
+        // the moment.  In the future we want to initialize option definitions 
+        // for all options.  Consequently an error will be issued if an option 
+        // definition does not exist for a particular option code. For now it is
+        // ok to create generic option if definition does not exist.
+        OptionPtr option(new Option(global_context_->universe_, 
+                        static_cast<uint16_t>(option_code), binary));
+        // The created option is stored in option_descriptor_ class member 
+        // until the commit stage when it is inserted into the main storage. 
+        // If an option with the same code exists in main storage already the 
+        // old option is replaced.
+        option_descriptor_.option = option;
+        option_descriptor_.persistent = false;
+    } else {
+
+        // Option name should match the definition. The option name
+        // may seem to be redundant but in the future we may want
+        // to reference options and definitions using their names
+        // and/or option codes so keeping the option name in the
+        // definition of option value makes sense.
+        if (def->getName() != option_name) {
+            isc_throw(DhcpConfigError, "specified option name '"
+                      << option_name << "' does not match the "
+                      << "option definition: '" << option_space
+                      << "." << def->getName() << "'");
+        }
+
+        // Option definition has been found so let's use it to create
+        // an instance of our option.
+        try {
+            OptionPtr option = csv_format ?
+                def->optionFactory(global_context_->universe_, 
+                                  option_code, data_tokens) :
+                def->optionFactory(global_context_->universe_, 
+                                  option_code, binary);
+            Subnet::OptionDescriptor desc(option, false);
+            option_descriptor_.option = option;
+            option_descriptor_.persistent = false;
+        } catch (const isc::Exception& ex) {
+            isc_throw(DhcpConfigError, "option data does not match"
+                      << " option definition (space: " << option_space
+                      << ", code: " << option_code << "): "
+                      << ex.what());
+        }
+    }
+
+    // All went good, so we can set the option space name.
+    option_space_ = option_space;
+}
+
+// **************************** OptionDataListParser *************************
+OptionDataListParser::OptionDataListParser(const std::string&, 
+    OptionStoragePtr options, ParserContextPtr global_context,
+    OptionDataParserFactory* optionDataParserFactory)
+    : options_(options), local_options_(new OptionStorage()), 
+    global_context_(global_context),
+    optionDataParserFactory_(optionDataParserFactory) {
+    if (!options_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+             << "options storage may not be NULL");
+    }
+
+    if (!options_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+             << "context may not be NULL");
+    }
+
+    if (!optionDataParserFactory_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+             << "option data parser factory may not be NULL");
+    }
+}
+
+void 
+OptionDataListParser::build(ConstElementPtr option_data_list) {
+    BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
+        boost::shared_ptr<OptionDataParser> 
+            parser((*optionDataParserFactory_)("option-data", 
+                    local_options_, global_context_));
+
+        // options_ member will hold instances of all options thus
+        // each OptionDataParser takes it as a storage.
+        // Build the instance of a single option.
+        parser->build(option_value);
+        // Store a parser as it will be used to commit.
+        parsers_.push_back(parser);
+    }
+}
+
+void 
+OptionDataListParser::commit() {
+    BOOST_FOREACH(ParserPtr parser, parsers_) {
+        parser->commit();
+    }
+
+    // Parsing was successful and we have all configured
+    // options in local storage. We can now replace old values
+    // with new values.
+    std::swap(*local_options_, *options_);
+}
+
+// ******************************** OptionDefParser ****************************
+OptionDefParser::OptionDefParser(const std::string&, 
+                                OptionDefStoragePtr storage)
+    : storage_(storage), boolean_values_(new BooleanStorage()),
+    string_values_(new StringStorage()), uint32_values_(new Uint32Storage()) {
+    if (!storage_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+             << "options storage may not be NULL");
+    }
+}
+
+void 
+OptionDefParser::build(ConstElementPtr option_def) {
+    // Parse the elements that make up the option definition.
+     BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+        std::string entry(param.first);
+        ParserPtr parser;
+        if (entry == "name" || entry == "type" || entry == "record-types" 
+            || entry == "space" || entry == "encapsulate") {
+            StringParserPtr str_parser(new StringParser(entry, 
+                                       string_values_));
+            parser = str_parser;
+        } else if (entry == "code") {
+            Uint32ParserPtr code_parser(new Uint32Parser(entry, 
+                                        uint32_values_));
+            parser = code_parser;
+        } else if (entry == "array") {
+            BooleanParserPtr array_parser(new BooleanParser(entry, 
+                                         boolean_values_));
+            parser = array_parser;
+        } else {
+            isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+        }
+
+        parser->build(param.second);
+        parser->commit();
+    }
+
+    // Create an instance of option definition.
+    createOptionDef();
+
+    // Get all items we collected so far for the particular option space.
+    OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+
+    // Check if there are any items with option code the same as the
+    // one specified for the definition we are now creating.
+    const OptionDefContainerTypeIndex& idx = defs->get<1>();
+    const OptionDefContainerTypeRange& range =
+            idx.equal_range(option_definition_->getCode());
+
+    // If there are any items with this option code already we need
+    // to issue an error because we don't allow duplicates for
+    // option definitions within an option space.
+    if (std::distance(range.first, range.second) > 0) {
+        isc_throw(DhcpConfigError, "duplicated option definition for"
+                << " code '" << option_definition_->getCode() << "'");
+    }
+}
+
+void 
+OptionDefParser::commit() {
+    if (storage_ && option_definition_ &&
+        OptionSpace::validateName(option_space_name_)) {
+            storage_->addItem(option_definition_, option_space_name_);
+    }
+}
+
+void 
+OptionDefParser::createOptionDef() {
+    // Get the option space name and validate it.
+    std::string space = string_values_->getParam("space");
+    if (!OptionSpace::validateName(space)) {
+        isc_throw(DhcpConfigError, "invalid option space name '"
+                  << space << "'");
+    }
+
+    // Get other parameters that are needed to create the
+    // option definition.
+    std::string name = string_values_->getParam("name");
+    uint32_t code = uint32_values_->getParam("code");
+    std::string type = string_values_->getParam("type");
+    bool array_type = boolean_values_->getParam("array");
+    std::string encapsulates = string_values_->getParam("encapsulate");
+
+    // Create option definition.
+    OptionDefinitionPtr def;
+    // We need to check if user has set encapsulated option space
+    // name. If so, different constructor will be used.
+    if (!encapsulates.empty()) {
+        // Arrays can't be used together with sub-options.
+        if (array_type) {
+            isc_throw(DhcpConfigError, "option '" << space << "."
+                      << "name" << "', comprising an array of data"
+                      << " fields may not encapsulate any option space");
+
+        } else if (encapsulates == space) {
+            isc_throw(DhcpConfigError, "option must not encapsulate"
+                      << " an option space it belongs to: '"
+                      << space << "." << name << "' is set to"
+                      << " encapsulate '" << space << "'");
+
+        } else {
+            def.reset(new OptionDefinition(name, code, type,
+                        encapsulates.c_str()));
+        }
+
+    } else {
+        def.reset(new OptionDefinition(name, code, type, array_type));
+
+    }
+
+    // The record-types field may carry a list of comma separated names
+    // of data types that form a record.
+    std::string record_types = string_values_->getParam("record-types");
+
+    // Split the list of record types into tokens.
+    std::vector<std::string> record_tokens =
+    isc::util::str::tokens(record_types, ",");
+    // Iterate over each token and add a record type into
+    // option definition.
+    BOOST_FOREACH(std::string record_type, record_tokens) {
+        try {
+            boost::trim(record_type);
+            if (!record_type.empty()) {
+                    def->addRecordField(record_type);
+            }
+        } catch (const Exception& ex) {
+            isc_throw(DhcpConfigError, "invalid record type values"
+                      << " specified for the option definition: "
+                      << ex.what());
+        }
+    }
+
+    // Check the option definition parameters are valid.
+    try {
+        def->validate();
+    } catch (const isc::Exception& ex) {
+        isc_throw(DhcpConfigError, "invalid option definition"
+                  << " parameters: " << ex.what());
+    }
+
+    // Option definition has been created successfully.
+    option_space_name_ = space;
+    option_definition_ = def;
+}
+
+// ******************************** OptionDefListParser ************************
+OptionDefListParser::OptionDefListParser(const std::string&, 
+    OptionDefStoragePtr storage) :storage_(storage) {
+    if (!storage_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+             << "storage may not be NULL");
+    }
+}
+
+void 
+OptionDefListParser::build(ConstElementPtr option_def_list) {
+    // Clear existing items in the storage.
+    // We are going to replace all of them.
+    storage_->clearItems();
+
+    if (!option_def_list) {
+        isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+                  << " option definitions is NULL");
+    }
+
+    BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+        boost::shared_ptr<OptionDefParser>
+                parser(new OptionDefParser("single-option-def", storage_));
+        parser->build(option_def);
+        parser->commit();
+    }
+}
+
+void 
+OptionDefListParser::commit() {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+    cfg_mgr.deleteOptionDefs();
+
+    // We need to move option definitions from the temporary
+    // storage to the storage.
+    std::list<std::string> space_names =
+    storage_->getOptionSpaceNames();
+
+    BOOST_FOREACH(std::string space_name, space_names) {
+        BOOST_FOREACH(OptionDefinitionPtr def,
+                    *(storage_->getItems(space_name))) {
+            // All option definitions should be initialized to non-NULL
+            // values. The validation is expected to be made by the
+            // OptionDefParser when creating an option definition.
+            assert(def);
+            cfg_mgr.addOptionDef(def, space_name);
+        }
+    }
+}
+
+//****************************** PoolParser ********************************
+PoolParser::PoolParser(const std::string&,  PoolStoragePtr pools)
+        :pools_(pools) {
+
+    if (!pools_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+                  << "storage may not be NULL");
+    }
+}
+
+void 
+PoolParser::build(ConstElementPtr pools_list) {
+    BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
+        // That should be a single pool representation. It should contain
+        // text is form prefix/len or first - last. Note that spaces
+        // are allowed
+        string txt = text_pool->stringValue();
+
+        // first let's remove any whitespaces
+        boost::erase_all(txt, " "); // space
+        boost::erase_all(txt, "\t"); // tabulation
+
+        // Is this prefix/len notation?
+        size_t pos = txt.find("/");
+        if (pos != string::npos) {
+            isc::asiolink::IOAddress addr("::");
+            uint8_t len = 0;
+            try {
+                addr = isc::asiolink::IOAddress(txt.substr(0, pos));
+
+                // start with the first character after /
+                string prefix_len = txt.substr(pos + 1);
+
+                // It is lexical cast to int and then downcast to uint8_t.
+                // Direct cast to uint8_t (which is really an unsigned char)
+                // will result in interpreting the first digit as output
+                // value and throwing exception if length is written on two
+                // digits (because there are extra characters left over).
+    
+                // No checks for values over 128. Range correctness will
+                // be checked in Pool4 constructor.
+                len = boost::lexical_cast<int>(prefix_len);
+            } catch (...)  {
+                isc_throw(DhcpConfigError, "Failed to parse pool "
+                          "definition: " << text_pool->stringValue());
+            }
+
+            PoolPtr pool(poolMaker(addr, len));
+            local_pools_.push_back(pool);
+            continue;
+        }
+
+        // Is this min-max notation?
+        pos = txt.find("-");
+        if (pos != string::npos) {
+            // using min-max notation
+            isc::asiolink::IOAddress min(txt.substr(0,pos));
+            isc::asiolink::IOAddress max(txt.substr(pos + 1));
+
+            PoolPtr pool(poolMaker(min, max));
+            local_pools_.push_back(pool);
+            continue;
+        }
+
+        isc_throw(DhcpConfigError, "Failed to parse pool definition:"
+                  << text_pool->stringValue() <<
+                  ". Does not contain - (for min-max) nor / (prefix/len)");
+        }
+}
+
+void 
+PoolParser::commit() {
+    if (pools_) {
+        // local_pools_ holds the values produced by the build function.
+        // At this point parsing should have completed successfuly so
+        // we can append new data to the supplied storage.
+        pools_->insert(pools_->end(), local_pools_.begin(), local_pools_.end());
+    }
+}
+
+//****************************** SubnetConfigParser *************************
+
+SubnetConfigParser::SubnetConfigParser(const std::string&, 
+                                       ParserContextPtr global_context) 
+    : uint32_values_(new Uint32Storage()), string_values_(new StringStorage()), 
+    pools_(new PoolStorage()), options_(new OptionStorage()),
+    global_context_(global_context) {
+    // The first parameter should always be "subnet", but we don't check
+    // against that here in case some wants to reuse this parser somewhere.
+    if (!global_context_) {
+        isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+                 << "context storage may not be NULL");
+    }
+}
+
+void 
+SubnetConfigParser::build(ConstElementPtr subnet) {
+    BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+        ParserPtr parser(createSubnetConfigParser(param.first));
+        parser->build(param.second);
+        parsers_.push_back(parser);
+    }
+
+    // In order to create new subnet we need to get the data out
+    // of the child parsers first. The only way to do it is to
+    // invoke commit on them because it will make them write
+    // parsed data into storages we have supplied.
+    // Note that triggering commits on child parsers does not
+    // affect global data because we supplied pointers to storages
+    // local to this object. Thus, even if this method fails
+    // later on, the configuration remains consistent.
+    BOOST_FOREACH(ParserPtr parser, parsers_) {
+        parser->commit();
+    }
+
+    // Create a subnet.
+    createSubnet();
+}
+
+void 
+SubnetConfigParser::appendSubOptions(const std::string& option_space, 
+                                     OptionPtr& option) {
+    // Only non-NULL options are stored in option container.
+    // If this option pointer is NULL this is a serious error.
+    assert(option);
+
+    OptionDefinitionPtr def;
+    if (isServerStdOption(option_space, option->getType())) {
+        def = getServerStdOptionDefinition(option->getType());
+        // Definitions for some of the standard options hasn't been
+        // implemented so it is ok to leave here.
+        if (!def) {
+            return;
+        }
+    } else {
+        const OptionDefContainerPtr defs =
+                global_context_->option_defs_->getItems(option_space);
+
+        const OptionDefContainerTypeIndex& idx = defs->get<1>();
+        const OptionDefContainerTypeRange& range =
+        idx.equal_range(option->getType());
+        // There is no definition so we have to leave.
+        if (std::distance(range.first, range.second) == 0) {
+            return;
+        }
+
+        def = *range.first;
+
+        // If the definition exists, it must be non-NULL.
+        // Otherwise it is a programming error.
+        assert(def);
+    }
+
+    // We need to get option definition for the particular option space
+    // and code. This definition holds the information whether our
+    // option encapsulates any option space.
+    // Get the encapsulated option space name.
+    std::string encapsulated_space = def->getEncapsulatedSpace();
+    // If option space name is empty it means that our option does not
+    // encapsulate any option space (does not include sub-options).
+    if (!encapsulated_space.empty()) {
+        // Get the sub-options that belong to the encapsulated
+        // option space.
+        const Subnet::OptionContainerPtr sub_opts =
+                global_context_->options_->getItems(encapsulated_space);
+        // Append sub-options to the option.
+        BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
+            if (desc.option) {
+                option->addOption(desc.option);
+            }
+        }
+    }
+}
+
+void 
+SubnetConfigParser::createSubnet() {
+    std::string subnet_txt;
+    try {
+        subnet_txt = string_values_->getParam("subnet");
+    } catch (const DhcpConfigError &) {
+        // rethrow with precise error
+        isc_throw(DhcpConfigError,
+                 "Mandatory subnet definition in subnet missing");
+    }
+
+    // Remove any spaces or tabs.
+    boost::erase_all(subnet_txt, " ");
+    boost::erase_all(subnet_txt, "\t");
+
+    // The subnet format is prefix/len. We are going to extract
+    // the prefix portion of a subnet string to create IOAddress
+    // object from it. IOAddress will be passed to the Subnet's
+    // constructor later on. In order to extract the prefix we
+    // need to get all characters preceding "/".
+    size_t pos = subnet_txt.find("/");
+    if (pos == string::npos) {
+        isc_throw(DhcpConfigError,
+                  "Invalid subnet syntax (prefix/len expected):" << subnet_txt);
+    }
+
+    // Try to create the address object. It also validates that
+    // the address syntax is ok.
+    isc::asiolink::IOAddress addr(subnet_txt.substr(0, pos));
+    uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+    // Call the subclass's method to instantiate the subnet 
+    initSubnet(addr, len);
+
+    // Add pools to it.
+    for (PoolStorage::iterator it = pools_->begin(); it != pools_->end(); 
+         ++it) {
+        subnet_->addPool(*it);
+    }
+
+    // Configure interface, if defined
+
+    // Get interface name. If it is defined, then the subnet is available
+    // directly over specified network interface.
+    std::string iface;
+    try {
+        iface = string_values_->getParam("interface");
+    } catch (const DhcpConfigError &) {
+        // iface not mandatory so swallow the exception
+    }
+
+    if (!iface.empty()) {
+        if (!IfaceMgr::instance().getIface(iface)) {
+            isc_throw(DhcpConfigError, "Specified interface name " << iface
+                     << " for subnet " << subnet_->toText()
+                     << " is not present" << " in the system.");
+        }
+
+        subnet_->setIface(iface);
+    }
+
+    // We are going to move configured options to the Subnet object.
+    // Configured options reside in the container where options
+    // are grouped by space names. Thus we need to get all space names
+    // and iterate over all options that belong to them.
+    std::list<std::string> space_names = options_->getOptionSpaceNames();
+    BOOST_FOREACH(std::string option_space, space_names) {
+        // Get all options within a particular option space.
+        BOOST_FOREACH(Subnet::OptionDescriptor desc,
+                      *options_->getItems(option_space)) {
+            // The pointer should be non-NULL. The validation is expected
+            // to be performed by the OptionDataParser before adding an
+            // option descriptor to the container.
+            assert(desc.option);
+            // We want to check whether an option with the particular
+            // option code has been already added. If so, we want
+            // to issue a warning.
+            Subnet::OptionDescriptor existing_desc =
+                            subnet_->getOptionDescriptor("option_space",
+                                                 desc.option->getType());
+            if (existing_desc.option) {
+                duplicate_option_warning(desc.option->getType(), addr);
+            }
+            // Add sub-options (if any).
+            appendSubOptions(option_space, desc.option);
+            // In any case, we add the option to the subnet.
+            subnet_->addOption(desc.option, false, option_space);
+        }
+    }
+
+    // Check all global options and add them to the subnet object if
+    // they have been configured in the global scope. If they have been
+    // configured in the subnet scope we don't add global option because
+    // the one configured in the subnet scope always takes precedence.
+    space_names = global_context_->options_->getOptionSpaceNames();
+    BOOST_FOREACH(std::string option_space, space_names) {
+        // Get all global options for the particular option space.
+        BOOST_FOREACH(Subnet::OptionDescriptor desc,
+                *(global_context_->options_->getItems(option_space))) {
+            // The pointer should be non-NULL. The validation is expected
+            // to be performed by the OptionDataParser before adding an
+            // option descriptor to the container.
+            assert(desc.option);
+            // Check if the particular option has been already added.
+            // This would mean that it has been configured in the
+            // subnet scope. Since option values configured in the
+            // subnet scope take precedence over globally configured
+            // values we don't add option from the global storage
+            // if there is one already.
+            Subnet::OptionDescriptor existing_desc =
+                    subnet_->getOptionDescriptor(option_space, 
+                                                desc.option->getType());
+            if (!existing_desc.option) {
+                // Add sub-options (if any).
+                appendSubOptions(option_space, desc.option);
+                subnet_->addOption(desc.option, false, option_space);
+            }
+        }
+    }
+}
+
+isc::dhcp::Triplet<uint32_t> 
+SubnetConfigParser::getParam(const std::string& name) {
+    uint32_t value = 0;
+    try {
+        // look for local value 
+        value = uint32_values_->getParam(name);
+    } catch (const DhcpConfigError &) {
+        try {
+            // no local, use global value 
+            value = global_context_->uint32_values_->getParam(name);
+        } catch (const DhcpConfigError &) {
+            isc_throw(DhcpConfigError, "Mandatory parameter " << name
+                      << " missing (no global default and no subnet-"
+                      << "specific value)");
+        }
+    }
+
+    return (Triplet<uint32_t>(value));
+}
+
+};  // namespace dhcp
+};  // namespace isc

+ 765 - 0
src/lib/dhcpsrv/dhcp_parsers.h

@@ -0,0 +1,765 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCP_PARSERS_H
+#define DHCP_PARSERS_H
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/option_space_container.h>
+#include <dhcpsrv/subnet.h>
+#include <exceptions/exceptions.h>
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+                             OptionDefinitionPtr> OptionDefStorage;
+
+/// @brief Shared pointer to option definitions storage.
+typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
+
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+                             Subnet::OptionDescriptor> OptionStorage;
+/// @brief Shared pointer to option storage.
+typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
+
+/// @brief A template class that stores named elements of a given data type.
+///
+/// This template class is provides data value storage for configuration parameters
+/// of a given data type.  The values are stored by parameter name and as instances 
+/// of type "ValueType". 
+///
+/// @param ValueType is the data type of the elements to store.
+template<typename ValueType>
+class ValueStorage {
+    public:
+        /// @brief  Stores the the parameter and its value in the store.
+        ///
+        /// If the parameter does not exist in the store, then it will be added,
+        /// otherwise its data value will be updated with the given value. 
+        ///
+        /// @param name is the name of the paramater to store.
+        /// @param value is the data value to store.
+        void setParam(const std::string& name, const ValueType& value) {
+            values_[name] = value;
+        }
+
+        /// @brief Returns the data value for the given parameter.
+        ///
+        /// Finds and returns the data value for the given parameter.
+        /// @param name is the name of the parameter for which the data
+        /// value is desired.
+        ///
+        /// @return The paramater's data value of type <ValueType>.
+        /// @throw DhcpConfigError if the parameter is not found.
+        ValueType getParam(const std::string& name) const {
+            typename std::map<std::string, ValueType>::const_iterator param 
+                = values_.find(name);
+
+            if (param == values_.end()) {
+                isc_throw(DhcpConfigError, "Missing parameter '"
+                       << name << "'");
+            }
+
+            return (param->second);
+        }
+
+        /// @brief  Remove the parameter from the store.
+        ///
+        /// Deletes the entry for the given parameter from the store if it 
+        /// exists. 
+        ///
+        /// @param name is the name of the paramater to delete.
+        void delParam(const std::string& name) {
+            values_.erase(name);
+        }
+
+        /// @brief Deletes all of the entries from the store.
+        ///
+        void clear() {
+            values_.clear();
+        }
+
+
+    private:
+        /// @brief An std::map of the data values, keyed by parameter names.
+        std::map<std::string, ValueType> values_;
+};
+
+
+/// @brief a collection of elements that store uint32 values 
+typedef ValueStorage<uint32_t> Uint32Storage;
+typedef boost::shared_ptr<Uint32Storage> Uint32StoragePtr;
+
+/// @brief a collection of elements that store string values
+typedef ValueStorage<std::string> StringStorage;
+typedef boost::shared_ptr<StringStorage> StringStoragePtr;
+
+/// @brief Storage for parsed boolean values.
+typedef ValueStorage<bool> BooleanStorage;
+typedef boost::shared_ptr<BooleanStorage> BooleanStoragePtr;
+
+/// @brief Container for the current parsing context. It provides a
+/// single enclosure for the storage of configuration parameters,
+/// options, option definitions, and other context specific information
+/// that needs to be accessible throughout the parsing and parsing
+/// constructs.
+class ParserContext {
+public:
+    /// @brief Constructor
+    /// 
+    /// @param universe is the Option::Universe value of this
+    /// context. 
+    ParserContext(Option::Universe universe);
+
+    /// @brief Copy constructor
+    ParserContext(const ParserContext& rhs);
+
+    /// @brief Storage for boolean parameters.
+    BooleanStoragePtr boolean_values_;
+
+    /// @brief Storage for uint32 parameters.
+    Uint32StoragePtr uint32_values_;
+
+    /// @brief Storage for string parameters.
+    StringStoragePtr string_values_;
+
+    /// @brief Storage for options.
+    OptionStoragePtr options_;
+
+    /// @brief Storage for option definitions.
+    OptionDefStoragePtr option_defs_;
+
+    /// @brief The parsing universe of this context.
+    Option::Universe universe_;
+
+    /// @brief Assignment operator
+    ParserContext& operator=(const ParserContext& rhs);
+};
+
+/// @brief Pointer to various parser context.
+typedef boost::shared_ptr<ParserContext> ParserContextPtr;
+
+/// @brief Simple data-type parser template class 
+///
+/// This is the template class for simple data-type parsers. It supports
+/// parsing a configuration parameter with specific data-type for its 
+/// possible values. It provides a common constructor, commit, and templated 
+/// data storage.  The "build" method implementation must be provided by a 
+/// declaring type.
+/// @param ValueType is the data type of the configuration paramater value
+/// the parser should handle.
+template<typename ValueType>
+class ValueParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param param_name name of the parameter.
+    /// @param storage is a pointer to the storage container where the parsed
+    /// value be stored upon commit.
+    /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+    /// name is empty.
+    /// @throw isc::dhcp::DhcpConfigError if storage is null.
+    ValueParser(const std::string& param_name, 
+        boost::shared_ptr<ValueStorage<ValueType> > storage)
+        : storage_(storage), param_name_(param_name), value_() {
+        // Empty parameter name is invalid.
+        if (param_name_.empty()) {
+            isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+                << "empty parameter name provided");
+        }
+
+        // NUll storage is invalid.
+        if (!storage_) {
+            isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+                << "storage may not be NULL");
+        }
+    }
+
+
+    /// @brief Parse a given element into a value of type <ValueType>
+    ///
+    /// @param value a value to be parsed.
+    ///
+    /// @throw isc::BadValue Typically the implementing type will throw
+    /// a BadValue exception when given an invalid Element to parse. 
+    void build(isc::data::ConstElementPtr value);
+
+    /// @brief Put a parsed value to the storage.
+    void commit() {
+        // If a given parameter already exists in the storage we override
+        // its value. If it doesn't we insert a new element.
+        storage_->setParam(param_name_, value_);
+    }
+    
+private:
+    /// Pointer to the storage where committed value is stored.
+    boost::shared_ptr<ValueStorage<ValueType> > storage_;
+
+    /// Name of the parameter which value is parsed with this parser.
+    std::string param_name_;
+
+    /// Parsed value.
+    ValueType value_;
+};
+
+/// @brief typedefs for simple data type parsers
+typedef ValueParser<bool> BooleanParser;
+typedef ValueParser<uint32_t> Uint32Parser;
+typedef ValueParser<std::string> StringParser;
+
+/// @brief a dummy configuration parser
+///
+/// It is a debugging parser. It does not configure anything,
+/// will accept any configuration and will just print it out
+/// on commit. Useful for debugging existing configurations and
+/// adding new ones.
+class DebugParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// See @ref DhcpConfigParser class for details.
+    ///
+    /// @param param_name name of the parsed parameter
+    DebugParser(const std::string& param_name);
+
+    /// @brief builds parameter value
+    ///
+    /// See @ref DhcpConfigParser class for details.
+    ///
+    /// @param new_config pointer to the new configuration
+    virtual void build(isc::data::ConstElementPtr new_config);
+
+    /// @brief pretends to apply the configuration
+    ///
+    /// This is a method required by base class. It pretends to apply the
+    /// configuration, but in fact it only prints the parameter out.
+    ///
+    /// See @ref DhcpConfigParser class for details.
+    virtual void commit();
+
+private:
+    /// name of the parsed parameter
+    std::string param_name_;
+
+    /// pointer to the actual value of the parameter
+    isc::data::ConstElementPtr value_;
+
+};
+
+/// @brief parser for interface list definition
+///
+/// This parser handles Dhcp4/interface entry.
+/// It contains a list of network interfaces that the server listens on.
+/// In particular, it can contain an entry called "all" or "any" that
+/// designates all interfaces.
+///
+/// It is useful for parsing Dhcp4/interface parameter.
+class InterfaceListConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    ///
+    /// As this is a dedicated parser, it must be used to parse
+    /// "interface" parameter only. All other types will throw exception.
+    ///
+    /// @param param_name name of the configuration parameter being parsed
+    /// @throw BadValue if supplied parameter name is not "interface"
+    InterfaceListConfigParser(const std::string& param_name);
+
+    /// @brief parses parameters value
+    ///
+    /// Parses configuration entry (list of parameters) and adds each element
+    /// to the interfaces list.
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(isc::data::ConstElementPtr value);
+
+    /// @brief commits interfaces list configuration
+    virtual void commit();
+
+private:
+    /// contains list of network interfaces
+    std::vector<std::string> interfaces_;
+};
+
+
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
+class OptionDataParser : public DhcpConfigParser {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param dummy first argument is ignored, all Parser constructors
+    /// accept string as first argument.
+    /// @param options is the option storage in which to store the parsed option
+    /// upon "commit". 
+    /// @param global_context is a pointer to the global context which 
+    /// stores global scope parameters, options, option defintions. 
+    /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
+    OptionDataParser(const std::string&, OptionStoragePtr options, 
+                    ParserContextPtr global_context);
+
+    /// @brief Parses the single option data.
+    ///
+    /// This method parses the data of a single option from the configuration.
+    /// The option data includes option name, option code and data being
+    /// carried by this option. Eventually it creates the instance of the
+    /// option.
+    ///
+    /// @param option_data_entries collection of entries that define value
+    /// for a particular option.
+    /// @throw DhcpConfigError if invalid parameter specified in
+    /// the configuration.
+    /// @throw isc::InvalidOperation if failed to set storage prior to
+    /// calling build.
+    virtual void build(isc::data::ConstElementPtr option_data_entries);
+
+    /// @brief Commits option value.
+    ///
+    /// This function adds a new option to the storage or replaces an existing 
+    /// option with the same code.
+    ///
+    /// @throw isc::InvalidOperation if failed to set pointer to storage or 
+    /// failed
+    /// to call build() prior to commit. If that happens data in the storage
+    /// remain un-modified.
+    virtual void commit();
+
+    /// @brief virtual destructor to ensure orderly destruction of derivations. 
+    virtual ~OptionDataParser(){};
+
+protected:
+    /// @brief Finds an option definition within the server's option space
+    /// 
+    /// Given an option space and an option code, find the correpsonding 
+    /// option defintion within the server's option defintion storage. This
+    /// method is pure virtual requiring derivations to manage which option
+    /// space(s) is valid for search.
+    ///
+    /// @param option_space name of the parameter option space 
+    /// @param option_code numeric value of the parameter to find 
+    /// @return OptionDefintionPtr of the option defintion or an 
+    /// empty OptionDefinitionPtr if not found.
+    /// @throw DhcpConfigError if the option space requested is not valid 
+    /// for this server.
+    virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+            std::string& option_space, uint32_t option_code) = 0;
+
+private:
+
+    /// @brief Create option instance.
+    ///
+    /// Creates an instance of an option and adds it to the provided
+    /// options storage. If the option data parsed by \ref build function
+    /// are invalid or insufficient this function emits an exception.
+    ///
+    /// @warning this function does not check if options_ storage pointer
+    /// is intitialized but this check is not needed here because it is done
+    /// in the \ref build function.
+    ///
+    /// @throw DhcpConfigError if parameters provided in the configuration
+    /// are invalid.
+    void createOption();
+
+    /// Storage for boolean values.
+    BooleanStoragePtr boolean_values_;
+
+    /// Storage for string values (e.g. option name or data).
+    StringStoragePtr string_values_;
+
+    /// Storage for uint32 values (e.g. option code).
+    Uint32StoragePtr uint32_values_;
+
+    /// Pointer to options storage. This storage is provided by
+    /// the calling class and is shared by all OptionDataParser objects.
+    OptionStoragePtr options_;
+
+    /// Option descriptor holds newly configured option.
+    Subnet::OptionDescriptor option_descriptor_;
+
+    /// Option space name where the option belongs to.
+    std::string option_space_;
+
+    /// Parsing context which contains global values, options and option 
+    /// definitions.
+    ParserContextPtr global_context_;
+};
+
+///@brief Function pointer for OptionDataParser factory methods
+typedef OptionDataParser *OptionDataParserFactory(const std::string&, 
+                     OptionStoragePtr options, ParserContextPtr global_context);
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public DhcpConfigParser {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param string& nominally would be param name, this is always ignored.
+    /// @param options parsed option storage for options in this list
+    /// @param global_context is a pointer to the global context which 
+    /// stores global scope parameters, options, option defintions. 
+    /// @param optionDataParserFactory factory method for creating individual 
+    /// option parsers 
+    /// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
+    OptionDataListParser(const std::string&, OptionStoragePtr options, 
+                        ParserContextPtr global_context, 
+                        OptionDataParserFactory *optionDataParserFactory);
+
+    /// @brief Parses entries that define options' data for a subnet.
+    ///
+    /// This method iterates over all entries that define option data
+    /// for options within a single subnet and creates options' instances.
+    ///
+    /// @param option_data_list pointer to a list of options' data sets.
+    /// @throw DhcpConfigError if option parsing failed.
+    void build(isc::data::ConstElementPtr option_data_list);
+
+    /// @brief Commit all option values.
+    ///
+    /// This function invokes commit for all option values.
+    void commit();
+
+private:
+    /// Pointer to options instances storage.
+    OptionStoragePtr options_;
+
+    /// Intermediate option storage. This storage is used by
+    /// lower level parsers to add new options.  Values held
+    /// in this storage are assigned to main storage (options_)
+    /// if overall parsing was successful.
+    OptionStoragePtr local_options_;
+
+    /// Collection of parsers;
+    ParserCollection parsers_;
+
+    /// Parsing context which contains global values, options and option 
+    /// definitions.
+    ParserContextPtr global_context_;
+
+    /// Factory to create server-specific option data parsers
+    OptionDataParserFactory *optionDataParserFactory_;
+};
+
+
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser : public DhcpConfigParser {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param dummy first argument is ignored, all Parser constructors
+    /// accept string as first argument.
+    /// @param storage is the definition storage in which to store the parsed 
+    /// definition upon "commit". 
+    /// @throw isc::dhcp::DhcpConfigError if storage is null.
+    OptionDefParser(const std::string&, OptionDefStoragePtr storage);
+
+    /// @brief Parses an entry that describes single option definition.
+    ///
+    /// @param option_def a configuration entry to be parsed.
+    ///
+    /// @throw DhcpConfigError if parsing was unsuccessful.
+    void build(isc::data::ConstElementPtr option_def);
+
+    /// @brief Stores the parsed option definition in a storage.
+    void commit();
+
+private:
+
+    /// @brief Create option definition from the parsed parameters.
+    void createOptionDef();
+
+    /// Instance of option definition being created by this parser.
+    OptionDefinitionPtr option_definition_;
+    /// Name of the space the option definition belongs to.
+    std::string option_space_name_;
+
+    /// Pointer to a storage where the option definition will be
+    /// added when \ref commit is called.
+    OptionDefStoragePtr storage_;
+
+    /// Storage for boolean values.
+    BooleanStoragePtr boolean_values_;
+
+    /// Storage for string values.
+    StringStoragePtr string_values_;
+
+    /// Storage for uint32 values.
+    Uint32StoragePtr uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : public DhcpConfigParser {
+public:
+    /// @brief Constructor.
+    ///
+    /// @param dummy first argument is ignored, all Parser constructors
+    /// accept string as first argument.
+    /// @param storage is the definition storage in which to store the parsed 
+    /// definitions in this list 
+    /// @throw isc::dhcp::DhcpConfigError if storage is null.
+    OptionDefListParser(const std::string&, OptionDefStoragePtr storage);
+
+    /// @brief Parse configuration entries.
+    ///
+    /// This function parses configuration entries and creates instances
+    /// of option definitions.
+    ///
+    /// @param option_def_list pointer to an element that holds entries
+    /// that define option definitions.
+    /// @throw DhcpConfigError if configuration parsing fails.
+    void build(isc::data::ConstElementPtr option_def_list);
+
+    /// @brief Stores option definitions in the CfgMgr.
+    void commit();
+
+private:
+    /// @brief storage for option definitions.
+    OptionDefStoragePtr storage_; 
+};
+
+/// @brief a collection of pools
+///
+/// That type is used as intermediate storage, when pools are parsed, but there is
+/// no subnet object created yet to store them.
+typedef std::vector<PoolPtr> PoolStorage;
+typedef boost::shared_ptr<PoolStorage> PoolStoragePtr;
+
+/// @brief parser for pool definition
+///
+/// This abstract parser handles pool definitions, i.e. a list of entries of one
+/// of two syntaxes: min-max and prefix/len. Pool objects are created
+/// and stored in chosen PoolStorage container.
+///
+/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[X]/pool parameters.
+class PoolParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor.
+   
+
+    /// @param dummy first argument is ignored, all Parser constructors
+    /// accept string as first argument.
+    /// @param pools is the storage in which to store the parsed pool 
+    /// upon "commit". 
+    /// @throw isc::dhcp::DhcpConfigError if storage is null.
+    PoolParser(const std::string&,  PoolStoragePtr pools);
+
+    /// @brief parses the actual list
+    ///
+    /// This method parses the actual list of interfaces.
+    /// No validation is done at this stage, everything is interpreted as
+    /// interface name.
+    /// @param pools_list list of pools defined for a subnet
+    /// @throw isc::dhcp::DhcpConfigError when pool parsing fails
+    virtual void build(isc::data::ConstElementPtr pools_list);
+
+    /// @brief Stores the parsed values in a storage provided
+    ///        by an upper level parser.
+    virtual void commit();
+
+protected:
+    /// @brief Creates a Pool object given a IPv4 prefix and the prefix length.
+    ///
+    /// @param addr is the IP  prefix of the pool.
+    /// @param len is the prefix length.
+    /// @param ignored dummy parameter to provide symmetry between 
+    /// @return returns a PoolPtr to the new Pool object. 
+    virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len, 
+                           int32_t ptype=0) = 0;
+
+    /// @brief Creates a Pool object given starting and ending IP addresses.
+    ///
+    /// @param min is the first IP address in the pool.
+    /// @param max is the last IP address in the pool.
+    /// @param ptype is the type of pool to create (not used by all derivations)
+    /// @return returns a PoolPtr to the new Pool object.
+    virtual PoolPtr poolMaker(isc::asiolink::IOAddress &min, 
+                           isc::asiolink::IOAddress &max, int32_t ptype=0) = 0;
+
+    /// @brief pointer to the actual Pools storage
+    ///
+    /// That is typically a storage somewhere in Subnet parser
+    /// (an upper level parser).
+    PoolStoragePtr pools_;
+
+    /// A temporary storage for pools configuration. It is a
+    /// storage where pools are stored by build function.
+    PoolStorage local_pools_;
+};
+
+/// @brief this class parses a single subnet
+///
+/// This class parses the whole subnet definition. It creates parsers
+/// for received configuration parameters as needed.
+class SubnetConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    SubnetConfigParser(const std::string&, ParserContextPtr global_context);
+
+    /// @brief parses parameter value
+    ///
+    /// @param subnet pointer to the content of subnet definition
+    ///
+    /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
+    virtual void build(isc::data::ConstElementPtr subnet); 
+
+    /// @brief Adds the created subnet to a server's configuration.
+    virtual void commit() = 0;
+
+protected:
+    /// @brief creates parsers for entries in subnet definition
+    ///
+    /// @param config_id name od the entry
+    ///
+    /// @return parser object for specified entry name
+    /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+    ///        for unknown config element
+    virtual DhcpConfigParser* createSubnetConfigParser(
+                                            const std::string& config_id) = 0;
+
+    /// @brief Determines if the given option space name and code describe
+    /// a standard option for the  server. 
+    ///
+    /// @param option_space is the name of the option space to consider
+    /// @param code is the numeric option code to consider
+    /// @return returns true if the space and code are part of the server's
+    /// standard options.
+    virtual bool isServerStdOption(std::string option_space, uint32_t code) = 0;
+
+    /// @brief Returns the option definition for a given option code from
+    /// the server's standard set of options.
+    /// @param code is the numeric option code of the desired option definition.
+    /// @return returns a pointer the option definition
+    virtual OptionDefinitionPtr getServerStdOptionDefinition (
+                                                             uint32_t code) = 0;
+
+    /// @brief Issues a server specific warning regarding duplicate subnet
+    /// options. 
+    /// 
+    /// @param code is the numeric option code of the duplicate option
+    /// @param addr is the subnet address 
+    /// @todo a means to know the correct logger and perhaps a common
+    /// message would allow this method to be emitted by the base class.
+    virtual void duplicate_option_warning(uint32_t code, 
+        isc::asiolink::IOAddress& addr) = 0;
+
+    /// @brief Instantiates the subnet based on a given IP prefix and prefix 
+    /// length.  
+    /// 
+    /// @param addr is the IP prefix of the subnet.
+    /// @param len is the prefix length 
+    virtual void initSubnet(isc::asiolink::IOAddress addr, uint8_t len) = 0;
+
+    /// @brief Returns value for a given parameter (after using inheritance)
+    ///
+    /// This method implements inheritance. For a given parameter name, it first
+    /// checks if there is a global value for it and overwrites it with specific
+    /// value if such value was defined in subnet.
+    ///
+    /// @param name name of the parameter
+    /// @return triplet with the parameter name
+    /// @throw DhcpConfigError when requested parameter is not present
+    isc::dhcp::Triplet<uint32_t> getParam(const std::string& name);
+
+private:
+
+    /// @brief Append sub-options to an option.
+    ///
+    /// @param option_space a name of the encapsulated option space.
+    /// @param option option instance to append sub-options to.
+    void appendSubOptions(const std::string& option_space, OptionPtr& option);
+
+    /// @brief Create a new subnet using a data from child parsers.
+    ///
+    /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing 
+    /// failed.
+    void createSubnet();
+
+protected:
+
+    /// Storage for subnet-specific integer values.
+    Uint32StoragePtr uint32_values_;
+
+    /// Storage for subnet-specific string values.
+    StringStoragePtr string_values_;
+
+    /// Storage for pools belonging to this subnet.
+    PoolStoragePtr pools_;
+
+    /// Storage for options belonging to this subnet.
+    OptionStoragePtr options_;
+
+    /// Parsers are stored here.
+    ParserCollection parsers_;
+
+    /// Pointer to the created subnet object.
+    isc::dhcp::SubnetPtr subnet_;
+
+    /// Parsing context which contains global values, options and option 
+    /// definitions.
+    ParserContextPtr global_context_;
+};
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP_PARSERS_H
+

+ 39 - 26
src/lib/dhcpsrv/subnet.cc

@@ -33,11 +33,13 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
      last_allocated_(lastAddrInPrefix(prefix, len)) {
     if ((prefix.isV6() && len > 128) ||
         (prefix.isV4() && len > 32)) {
-        isc_throw(BadValue, "Invalid prefix length specified for subnet: " << len);
+        isc_throw(BadValue, 
+                  "Invalid prefix length specified for subnet: " << len);
     }
 }
 
-bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
+bool 
+Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
     IOAddress first = firstAddrInPrefix(prefix_, prefix_len_);
     IOAddress last = lastAddrInPrefix(prefix_, prefix_len_);
 
@@ -84,7 +86,8 @@ Subnet::getOptionDescriptor(const std::string& option_space,
     return (*range.first);
 }
 
-std::string Subnet::toText() const {
+std::string 
+Subnet::toText() const {
     std::stringstream tmp;
     tmp << prefix_.toText() << "/" << static_cast<unsigned int>(prefix_len_);
     return (tmp.str());
@@ -101,12 +104,14 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
     }
 }
 
-void Subnet::addPool(const PoolPtr& pool) {
+void 
+Subnet::addPool(const PoolPtr& pool) {
     IOAddress first_addr = pool->getFirstAddress();
     IOAddress last_addr = pool->getLastAddress();
 
     if (!inRange(first_addr) || !inRange(last_addr)) {
-        isc_throw(BadValue, "Pool (" << first_addr.toText() << "-" << last_addr.toText()
+        isc_throw(BadValue, "Pool (" << first_addr.toText() << "-" 
+                  << last_addr.toText()
                   << " does not belong in this (" << prefix_.toText() << "/"
                   << static_cast<int>(prefix_len_) << ") subnet4");
     }
@@ -119,15 +124,16 @@ void Subnet::addPool(const PoolPtr& pool) {
 PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
 
     PoolPtr candidate;
-    for (PoolCollection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+    for (PoolCollection::iterator pool = pools_.begin(); 
+         pool != pools_.end(); ++pool) {
 
-        // if we won't find anything better, then let's just use the first pool
+        // If we won't find anything better, then let's just use the first pool
         if (!candidate) {
             candidate = *pool;
         }
 
-        // if the client provided a pool and there's a pool that hint is valid in,
-        // then let's use that pool
+        // If the client provided a pool and there's a pool that hint is valid 
+        // in, then let's use that pool
         if ((*pool)->inRange(hint)) {
             return (*pool);
         }
@@ -135,29 +141,44 @@ PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
     return (candidate);
 }
 
+void 
+Subnet::setIface(const std::string& iface_name) {
+    iface_ = iface_name;
+}
+
+std::string 
+Subnet::getIface() const {
+    return (iface_);
+}
+
+
 
 void
 Subnet4::validateOption(const OptionPtr& option) const {
     if (!option) {
-        isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
+        isc_throw(isc::BadValue, 
+                  "option configured for subnet must not be NULL");
     } else if (option->getUniverse() != Option::V4) {
-        isc_throw(isc::BadValue, "expected V4 option to be added to the subnet");
+        isc_throw(isc::BadValue, 
+                  "expected V4 option to be added to the subnet");
     }
 }
 
-bool Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
+bool 
+Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
 
     // Let's start with checking if it even belongs to that subnet.
     if (!inRange(addr)) {
         return (false);
     }
 
-    for (PoolCollection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+    for (PoolCollection::const_iterator pool = pools_.begin(); 
+         pool != pools_.end(); ++pool) {
         if ((*pool)->inRange(addr)) {
             return (true);
         }
     }
-    // there's no pool that address belongs to
+    // There's no pool that address belongs to
     return (false);
 }
 
@@ -177,21 +198,13 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
 void
 Subnet6::validateOption(const OptionPtr& option) const {
     if (!option) {
-        isc_throw(isc::BadValue, "option configured for subnet must not be NULL");
+        isc_throw(isc::BadValue, 
+                  "option configured for subnet must not be NULL");
     } else if (option->getUniverse() != Option::V6) {
-        isc_throw(isc::BadValue, "expected V6 option to be added to the subnet");
+        isc_throw(isc::BadValue, 
+                  "expected V6 option to be added to the subnet");
     }
 }
 
-
-void Subnet6::setIface(const std::string& iface_name) {
-    iface_ = iface_name;
-}
-
-std::string Subnet6::getIface() const {
-    return (iface_);
-}
-
-
 } // end of isc::dhcp namespace
 } // end of isc namespace

+ 12 - 13
src/lib/dhcpsrv/subnet.h

@@ -299,7 +299,18 @@ public:
         return pools_;
     }
 
-    /// @brief returns textual representation of the subnet (e.g. "2001:db8::/64")
+    /// @brief sets name of the network interface for directly attached networks
+    ///
+    /// @param iface_name name of the interface
+    void setIface(const std::string& iface_name);
+
+    /// @brief network interface name used to reach subnet (or "" for remote 
+    /// subnets)
+    /// @return network interface name for directly attached subnets or ""
+    std::string getIface() const;
+
+    /// @brief returns textual representation of the subnet (e.g. 
+    /// "2001:db8::/64")
     ///
     /// @return textual representation
     virtual std::string toText() const;
@@ -451,18 +462,6 @@ public:
         return (preferred_);
     }
 
-    /// @brief sets name of the network interface for directly attached networks
-    ///
-    /// A subnet may be reachable directly (not via relays). In DHCPv6 it is not
-    /// possible to decide that based on addresses assigned to network interfaces,
-    /// as DHCPv6 operates on link-local (and site local) addresses.
-    /// @param iface_name name of the interface
-    void setIface(const std::string& iface_name);
-
-    /// @brief network interface name used to reach subnet (or "" for remote subnets)
-    /// @return network interface name for directly attached subnets or ""
-    std::string getIface() const;
-
     /// @brief sets interface-id option (if defined)
     ///
     /// @param ifaceid pointer to interface-id option

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

@@ -34,6 +34,7 @@ libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
+libdhcpsrv_unittests_SOURCES += dhcp_parsers_unittest.cc
 if HAVE_MYSQL
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
 endif

+ 2 - 1
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -15,7 +15,7 @@
 #include <config.h>
 
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
 #include <dhcp/dhcp6.h>
 
@@ -164,6 +164,7 @@ public:
         // make sure we start with a clean configuration
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().deleteSubnets6();
+        CfgMgr::instance().deleteOptionDefs();
     }
 
     /// @brief generates interface-id option based on provided text

+ 519 - 0
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -0,0 +1,519 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <config/ccsession.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/dhcp_parsers.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+#include <boost/foreach.hpp>
+
+#include <map>
+#include <string>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+/// @brief DHCP Parser test fixture class
+class DhcpParserTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    ///
+    DhcpParserTest() {
+    }
+};
+
+
+/// @brief Check BooleanParser basic functionality.
+/// 
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Rejects a non-boolean element.
+/// 3. Builds with a valid true value.
+/// 4. Bbuils with a valid false value.
+/// 5. Updates storage upon commit.
+TEST_F(DhcpParserTest, booleanParserTest) {
+
+    const std::string name = "boolParm";
+
+    // Verify that parser does not allow empty for storage.
+    BooleanStoragePtr bs;
+    EXPECT_THROW(BooleanParser(name, bs), isc::dhcp::DhcpConfigError);
+
+    // Construct parser for testing.
+    BooleanStoragePtr storage(new BooleanStorage());
+    BooleanParser parser(name, storage);
+
+    // Verify that parser with rejects a non-boolean element.
+    ElementPtr wrong_element = Element::create("I am a string");
+    EXPECT_THROW(parser.build(wrong_element), isc::BadValue);
+
+    // Verify that parser will build with a valid true value.
+    bool test_value = true;
+    ElementPtr element = Element::create(test_value);
+    ASSERT_NO_THROW(parser.build(element));
+
+    // Verify that commit updates storage.
+    bool actual_value = !test_value;
+    parser.commit();
+    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+    EXPECT_EQ(test_value, actual_value);
+
+    // Verify that parser will build with a valid false value.
+    test_value = false;
+    element->setValue(test_value);
+    EXPECT_NO_THROW(parser.build(element));
+
+    // Verify that commit updates storage.
+    actual_value = ~test_value;
+    parser.commit();
+    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+    EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check StringParser basic functionality
+/// 
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Builds with a nont string value.
+/// 3. Builds with a string value.
+/// 4. Updates storage upon commit.
+TEST_F(DhcpParserTest, stringParserTest) {
+
+    const std::string name = "strParm";
+
+    // Verify that parser does not allow empty for storage.
+    StringStoragePtr bs;
+    EXPECT_THROW(StringParser(name, bs), isc::dhcp::DhcpConfigError);
+
+    // Construct parser for testing.
+    StringStoragePtr storage(new StringStorage());
+    StringParser parser(name, storage);
+
+    // Verify that parser with accepts a non-string element.
+    ElementPtr element = Element::create(9999);
+    EXPECT_NO_THROW(parser.build(element));
+
+    // Verify that commit updates storage.
+    parser.commit();
+    std::string actual_value;
+    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+    EXPECT_EQ("9999", actual_value);
+
+    // Verify that parser will build with a string value.
+    const std::string test_value = "test value";
+    element = Element::create(test_value);
+    ASSERT_NO_THROW(parser.build(element));
+
+    // Verify that commit updates storage.
+    parser.commit();
+    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+    EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check Uint32Parser basic functionality
+/// 
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Rejects a non-integer element.
+/// 3. Rejects a negative value.
+/// 4. Rejects too large a value.
+/// 5. Builds with value of zero.
+/// 6. Builds with a value greater than zero.
+/// 7. Updates storage upon commit.
+TEST_F(DhcpParserTest, uint32ParserTest) {
+
+    const std::string name = "intParm";
+
+    // Verify that parser does not allow empty for storage.
+    Uint32StoragePtr bs;
+    EXPECT_THROW(Uint32Parser(name, bs), isc::dhcp::DhcpConfigError);
+
+    // Construct parser for testing.
+    Uint32StoragePtr storage(new Uint32Storage());
+    Uint32Parser parser(name, storage);
+
+    // Verify that parser with rejects a non-interger element.
+    ElementPtr wrong_element = Element::create("I am a string");
+    EXPECT_THROW(parser.build(wrong_element), isc::BadValue);
+
+    // Verify that parser with rejects a negative value.
+    ElementPtr int_element = Element::create(-1);
+    EXPECT_THROW(parser.build(int_element), isc::BadValue);
+
+    // Verify that parser with rejects too large a value provided we are on 
+    // 64-bit platform. 
+    if (sizeof(long) > sizeof(uint32_t)) {
+        long max = (long)(std::numeric_limits<uint32_t>::max()) + 1;
+        int_element->setValue(max);
+        EXPECT_THROW(parser.build(int_element), isc::BadValue);
+    }
+
+    // Verify that parser will build with value of zero.
+    int test_value = 0;
+    int_element->setValue((long)test_value);
+    ASSERT_NO_THROW(parser.build(int_element));
+
+    // Verify that commit updates storage.
+    parser.commit();
+    uint32_t actual_value = 0;
+    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+    EXPECT_EQ(test_value, actual_value);
+
+    // Verify that parser will build with a valid positive value.
+    test_value = 77;
+    int_element->setValue((long)test_value);
+    ASSERT_NO_THROW(parser.build(int_element));
+
+    // Verify that commit updates storage.
+    parser.commit();
+    EXPECT_NO_THROW((actual_value = storage->getParam(name)));
+    EXPECT_EQ(test_value, actual_value);
+}
+
+/// @brief Check InterfaceListParser  basic functionality
+///
+/// Verifies that the parser:
+/// 1. Does not allow empty for storage.
+/// 2. Does not allow name other than "interface"
+///
+/// InterfaceListParser doesn't do very much, this test will need to 
+/// expand once it does.
+TEST_F(DhcpParserTest, interfaceListParserTest) {
+
+    const std::string name = "interface";
+
+    // Verify that parser constructor fails if parameter name isn't "interface"
+    EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);
+
+    InterfaceListConfigParser parser(name);
+    ElementPtr list_element = Element::createList();
+    list_element->add(Element::create("eth0"));
+    list_element->add(Element::create("eth1"));
+}
+
+/// @brief Test Implementation of abstract OptionDataParser class. Allows 
+/// testing basic option parsing.   
+class UtestOptionDataParser : public OptionDataParser {
+public:
+
+    UtestOptionDataParser(const std::string&, 
+        OptionStoragePtr options, ParserContextPtr global_context) 
+        :OptionDataParser("", options, global_context) {
+    }
+
+    static OptionDataParser* factory(const std::string& param_name,
+        OptionStoragePtr options, ParserContextPtr global_context) {
+        return (new UtestOptionDataParser(param_name, options, global_context));
+    }
+
+protected:
+    // Dummy out last two params since test derivation doesn't use them.
+    virtual OptionDefinitionPtr findServerSpaceOptionDefinition (
+                std::string&, uint32_t) {
+        OptionDefinitionPtr def;
+        // always return empty 
+        return (def);
+    }
+};
+
+/// @brief Test Fixture class which provides basic structure for testing 
+/// configuration parsing.  This is essentially the same structure provided
+/// by dhcp servers.
+class ParseConfigTest : public ::testing::Test {
+public:
+    /// @brief Constructor
+    ParseConfigTest() {
+        reset_context();
+    }
+
+    ~ParseConfigTest() {
+        reset_context();
+    }
+
+    /// @brief Parses a configuration.   
+    ///
+    /// Parse the given configuration, populating the context storage with
+    /// the parsed elements.  
+    /// 
+    /// @param config_set is the set of elements to parse.
+    /// @return returns an ConstElementPtr containing the numeric result
+    /// code and outcome comment.
+    isc::data::ConstElementPtr parseElementSet(isc::data::ConstElementPtr 
+                                           config_set) {
+        // Answer will hold the result.
+        ConstElementPtr answer;
+        if (!config_set) {
+            answer = isc::config::createAnswer(1,
+                                 string("Can't parse NULL config"));
+            return (answer);
+        }
+
+        // option parsing must be done last, so save it if we hit if first
+        ParserPtr option_parser;
+
+        ConfigPair config_pair;
+        try {
+            // Iteraate over the config elements.
+            const std::map<std::string, ConstElementPtr>& values_map =
+                                                      config_set->mapValue();
+            BOOST_FOREACH(config_pair, values_map) {
+                // Create the parser based on element name.
+                ParserPtr parser(createConfigParser(config_pair.first));
+                // Options must be parsed last
+                if (config_pair.first == "option-data") {
+                    option_parser = parser;
+                } else {
+                    // Anything else  we can call build straight away.
+                    parser->build(config_pair.second);
+                    parser->commit();
+                }
+            }
+
+            // The option values parser is the next one to be run.
+            std::map<std::string, ConstElementPtr>::const_iterator 
+                                option_config = values_map.find("option-data");
+            if (option_config != values_map.end()) {
+                option_parser->build(option_config->second);
+                option_parser->commit();
+            }
+
+            // Everything was fine. Configuration is successful.
+            answer = isc::config::createAnswer(0, "Configuration committed.");
+        } catch (const isc::Exception& ex) {
+            answer = isc::config::createAnswer(1,
+                        string("Configuration parsing failed: ") + ex.what());
+
+        } catch (...) {
+            answer = isc::config::createAnswer(1,
+                                        string("Configuration parsing failed"));
+        }
+
+        return (answer);
+    }
+
+    /// @brief Create an element parser based on the element name.
+    ///
+    /// Note that currently it only supports option-defs and option-data, 
+    /// 
+    /// @param config_id is the name of the configuration element. 
+    /// @return returns a raw pointer to DhcpConfigParser. Note caller is
+    /// responsible for deleting it once no longer needed.
+    /// @throw throws NotImplemented if element name isn't supported.
+    DhcpConfigParser* createConfigParser(const std::string& config_id) {
+        DhcpConfigParser* parser = NULL;
+        if (config_id.compare("option-data") == 0) {
+            parser = new OptionDataListParser(config_id, 
+                                          parser_context_->options_, 
+                                          parser_context_,
+                                          UtestOptionDataParser::factory);
+        } else if (config_id.compare("option-def") == 0) {
+            parser  = new OptionDefListParser(config_id, 
+                                          parser_context_->option_defs_);
+        } else {
+            isc_throw(NotImplemented,
+                "Parser error: configuration parameter not supported: "
+                << config_id);
+        }
+
+        return (parser);
+    }
+
+    /// @brief Convenicee method for parsing a configuration 
+    /// 
+    /// Given a configuration string, convert it into Elements
+    /// and parse them. 
+    /// @param config is the configuration string to parse
+    ///
+    /// @return retuns 0 if the configuration parsed successfully, 
+    /// non-zero otherwise failure.
+    int parseConfiguration (std::string &config) {    
+        int rcode_ = 1;
+        // Turn config into elements.
+        // Test json just to make sure its valid.
+        ElementPtr json = Element::fromJSON(config);
+        EXPECT_TRUE(json);
+        if (json) {
+            ConstElementPtr status = parseElementSet(json);
+            ConstElementPtr comment_ = parseAnswer(rcode_, status);
+        }
+
+        return (rcode_);
+    }
+
+    /// @brief Find an option definition for a given space and code within 
+    /// the parser context.
+    /// @param space is the space name of the desired option.
+    /// @param code is the numeric "type" of the desired option.
+    /// @return returns an OptionDefinitionPtr which points to the found
+    /// definition or is empty.
+    /// ASSERT_ tests don't work inside functions that return values 
+    OptionDefinitionPtr getOptionDef(std::string space, uint32_t code)
+    {
+        OptionDefinitionPtr def;
+        OptionDefContainerPtr defs = 
+                            parser_context_->option_defs_->getItems(space);
+        // Should always be able to get definitions list even if it is empty.
+        EXPECT_TRUE(defs);
+        if (defs) {
+            // Attempt to find desired definiton.
+            const OptionDefContainerTypeIndex& idx = defs->get<1>();
+            const OptionDefContainerTypeRange& range = idx.equal_range(code);
+            int cnt = std::distance(range.first, range.second);
+            EXPECT_EQ(1, cnt);
+            if (cnt == 1) {
+                def = *(idx.begin());
+            }
+        }
+        return (def); 
+    }
+
+    /// @brief Find an option for a given space and code within the parser 
+    /// context.
+    /// @param space is the space name of the desired option.
+    /// @param code is the numeric "type" of the desired option.
+    /// @return returns an OptionPtr which points to the found
+    /// option or is empty.
+    /// ASSERT_ tests don't work inside functions that return values 
+    OptionPtr getOptionPtr(std::string space, uint32_t code)
+    {
+        OptionPtr option_ptr;
+        Subnet::OptionContainerPtr options = 
+                            parser_context_->options_->getItems(space);
+        // Should always be able to get options list even if it is empty.
+        EXPECT_TRUE(options);
+        if (options) {
+            // Attempt to find desired option.
+            const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+            const Subnet::OptionContainerTypeRange& range = 
+                                                        idx.equal_range(code);
+            int cnt = std::distance(range.first, range.second);
+            EXPECT_EQ(1, cnt);
+            if (cnt == 1) {
+                Subnet::OptionDescriptor desc = *(idx.begin()); 
+                option_ptr = desc.option; 
+                EXPECT_TRUE(option_ptr);
+            }
+        }
+
+        return (option_ptr); 
+    }
+
+    /// @brief Wipes the contents of the context to allowing another parsing 
+    /// during a given test if needed.
+    void reset_context(){
+        // Note set context universe to V6 as it has to be something.
+        CfgMgr::instance().deleteSubnets4();
+        CfgMgr::instance().deleteSubnets6();
+        CfgMgr::instance().deleteOptionDefs();
+        parser_context_.reset(new ParserContext(Option::V6));
+    }
+
+    /// @brief Parser context - provides storage for options and definitions
+    ParserContextPtr parser_context_;
+};
+
+/// @brief Check Basic parsing of option definitions.
+/// 
+/// Note that this tests basic operation of the OptionDefinitionListParser and
+/// OptionDefinitionParser.  It uses a simple configuration consisting of one
+/// one definition and verifies that it is parsed and committed to storage
+/// correctly.
+TEST_F(ParseConfigTest, basicOptionDefTest) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
+        "  } ]"
+        "}";
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config);
+    ASSERT_TRUE(rcode == 0);
+
+    // Verify that the option definition can be retrieved.
+    OptionDefinitionPtr def = getOptionDef("isc", 100); 
+    ASSERT_TRUE(def);
+
+    // Verify that the option definition is correct.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_FALSE(def->getArrayType());
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+/// @brief Check Basic parsing of options.
+/// 
+/// Note that this tests basic operation of the OptionDataListParser and
+/// OptionDataParser.  It uses a simple configuration consisting of one
+/// one definition and matching option data.  It verifies that the option
+/// is parsed and committed to storage correctly.
+TEST_F(ParseConfigTest, basicOptionDataTest) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
+        " } ], "
+        " \"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 100,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ]"
+        "}";
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config);
+    ASSERT_TRUE(rcode == 0);
+
+    // Verify that the option can be retrieved.
+    OptionPtr opt_ptr = getOptionPtr("isc", 100);
+    ASSERT_TRUE(opt_ptr);
+
+    // Verify that the option definition is correct.
+    std::string val = "type=100, len=4, data fields:\n "
+                      " #0 192.168.2.1 ( ipv4-address ) \n";
+
+    EXPECT_EQ(val, opt_ptr->toText());
+}
+
+};  // Anonymous namespace
+