Browse Source

[master] Merge branch 'trac2314'

Conflicts:
	src/bin/dhcp6/tests/config_parser_unittest.cc
Marcin Siodelski 12 years ago
parent
commit
27e6119093

+ 104 - 12
src/bin/dhcp4/config_parser.cc

@@ -766,7 +766,10 @@ private:
         }
 
         std::string option_space = getParam<std::string>("space", string_values_);
-        /// @todo Validate option space once #2313 is merged.
+        if (!OptionSpace::validateName(option_space)) {
+            isc_throw(DhcpConfigError, "invalid option space name'"
+                      << option_space << "'");
+        }
 
         OptionDefinitionPtr def;
         if (option_space == "dhcp4" &&
@@ -855,7 +858,7 @@ private:
             // definition of option value makes sense.
             if (def->getName() != option_name) {
                 isc_throw(DhcpConfigError, "specified option name '"
-                          << option_name << " does not match the "
+                          << option_name << "' does not match the "
                           << "option definition: '" << option_space
                           << "." << def->getName() << "'");
             }
@@ -875,6 +878,7 @@ private:
                           << ", code: " << option_code << "): "
                           << ex.what());
             }
+
         }
         // All went good, so we can set the option space name.
         option_space_ = option_space;
@@ -976,7 +980,7 @@ public:
 /// @brief Parser for a single option definition.
 ///
 /// This parser creates an instance of a single option definition.
-class OptionDefParser: DhcpConfigParser {
+class OptionDefParser : public DhcpConfigParser {
 public:
 
     /// @brief Constructor.
@@ -1003,7 +1007,8 @@ public:
             std::string entry(param.first);
             ParserPtr parser;
             if (entry == "name" || entry == "type" ||
-                entry == "record-types" || entry == "space") {
+                entry == "record-types" || entry == "space" ||
+                entry == "encapsulate") {
                 StringParserPtr
                     str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
                 if (str_parser) {
@@ -1053,8 +1058,8 @@ public:
 
     /// @brief Stores the parsed option definition in a storage.
     void commit() {
-        // @todo validate option space name once 2313 is merged.
-        if (storage_ && option_definition_) {
+        if (storage_ && option_definition_ &&
+            OptionSpace::validateName(option_space_name_)) {
             storage_->addItem(option_definition_, option_space_name_);
         }
     }
@@ -1076,11 +1081,10 @@ private:
     void createOptionDef() {
         // Get the option space name and validate it.
         std::string space = getParam<std::string>("space", string_values_);
-        // @todo uncomment the code below when the #2313 is merged.
-        /*        if (!OptionSpace::validateName()) {
+        if (!OptionSpace::validateName(space)) {
             isc_throw(DhcpConfigError, "invalid option space name '"
                       << space << "'");
-                      } */
+        }
 
         // Get other parameters that are needed to create the
         // option definition.
@@ -1088,9 +1092,35 @@ private:
         uint32_t code = getParam<uint32_t>("code", uint32_values_);
         std::string type = getParam<std::string>("type", string_values_);
         bool array_type = getParam<bool>("array", boolean_values_);
+        std::string encapsulates = getParam<std::string>("encapsulate",
+                                                         string_values_);
+
+        // 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));
 
-        OptionDefinitionPtr def(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 = getParam<std::string>("record-types",
@@ -1108,7 +1138,7 @@ private:
                 }
             } catch (const Exception& ex) {
                 isc_throw(DhcpConfigError, "invalid record type values"
-                          << " specified for the option  definition: "
+                          << " specified for the option definition: "
                           << ex.what());
             }
         }
@@ -1330,6 +1360,63 @@ private:
         return (false);
     }
 
+    /// @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) {
+        // 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 (option_space == "dhcp4" &&
+            LibDHCP::isStandardOption(Option::V4, option->getType())) {
+            def = LibDHCP::getOptionDef(Option::V4, 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 =
+                option_def_intermediate.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 =
+                option_defaults.getItems(encapsulated_space);
+            // Append sub-options to the option.
+            BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
+                if (desc.option) {
+                    option->addOption(desc.option);
+                }
+            }
+        }
+    }
+
     /// @brief Create a new subnet using a data from child parsers.
     ///
     /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
@@ -1405,6 +1492,8 @@ private:
                     LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
                         .arg(desc.option->getType()).arg(addr.toText());
                 }
+                // 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);
             }
@@ -1432,6 +1521,9 @@ private:
                 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);
                 }
             }

+ 6 - 0
src/bin/dhcp4/dhcp4.spec

@@ -80,6 +80,12 @@
             "item_type": "string",
             "item_optional": false,
             "item_default": ""
+          },
+
+          { "item_name": "encapsulate",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
           } ]
         }
       },

+ 476 - 14
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -21,6 +21,8 @@
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/config_parser.h>
 #include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <boost/foreach.hpp>
@@ -61,7 +63,7 @@ public:
         EXPECT_EQ(expected_value, it->second);
     }
 
-    // Checks if config_result (result of DHCP server configuration) has
+    // Checks if the result of DHCP server configuration has
     // expected code (0 for success, other for failures).
     // Also stores result in rcode_ and comment_.
     void checkResult(ConstElementPtr status, int expected_code) {
@@ -461,7 +463,8 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
         "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -474,6 +477,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
+    checkResult(status, 0);
 
     // The option definition should now be available in the CfgMgr.
     def = CfgMgr::instance().getOptionDef("isc", 100);
@@ -484,6 +488,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
     EXPECT_EQ(100, def->getCode());
     EXPECT_FALSE(def->getArrayType());
     EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
 }
 
 // The goal of this test is to check whether an option definiiton
@@ -499,7 +504,8 @@ TEST_F(Dhcp4ParserTest, optionDefRecord) {
         "      \"type\": \"record\","
         "      \"array\": False,"
         "      \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -523,6 +529,7 @@ TEST_F(Dhcp4ParserTest, optionDefRecord) {
     EXPECT_EQ(100, def->getCode());
     EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
     EXPECT_FALSE(def->getArrayType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
 
     // The option comprises the record of data fields. Verify that all
     // fields are present and they are of the expected types.
@@ -546,7 +553,8 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
         "      \"type\": \"uint32\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
@@ -554,7 +562,8 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
         "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -578,6 +587,7 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
     EXPECT_EQ(100, def1->getCode());
     EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
     EXPECT_FALSE(def1->getArrayType());
+    EXPECT_TRUE(def1->getEncapsulatedSpace().empty());
 
     // Check the second option definition we have created.
     OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
@@ -588,6 +598,7 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
     EXPECT_EQ(101, def2->getCode());
     EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
     EXPECT_FALSE(def2->getArrayType());
+    EXPECT_TRUE(def2->getEncapsulatedSpace().empty());
 }
 
 // The goal of this test is to verify that the duplicated option
@@ -604,7 +615,8 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
         "      \"type\": \"uint32\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
@@ -612,7 +624,8 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
         "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -640,7 +653,8 @@ TEST_F(Dhcp4ParserTest, optionDefArray) {
         "      \"type\": \"uint32\","
         "      \"array\": True,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -664,6 +678,48 @@ TEST_F(Dhcp4ParserTest, optionDefArray) {
     EXPECT_EQ(100, def->getCode());
     EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
     EXPECT_TRUE(def->getArrayType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+// The purpose of this test to verify that encapsulated option
+// space name may be specified.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulate) {
+
+    // Configuration string. Included the encapsulated
+    // option space name.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"sub-opts-space\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+    EXPECT_EQ("sub-opts-space", def->getEncapsulatedSpace());
 }
 
 /// The purpose of this test is to verify that the option definition
@@ -678,7 +734,8 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -703,7 +760,8 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
         "      \"type\": \"sting\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -728,7 +786,62 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
         "      \"type\": \"record\","
         "      \"array\": False,"
         "      \"record-types\": \"uint32,uint8,sting\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the invalid encapsulated
+/// option space name is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidEncapsulatedSpace) {
+    // Configuration string. The encapsulated option space
+    // name is invalid (% character is not allowed).
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"invalid%space%name\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the encapsulated
+/// option space name can't be specified for the option that
+/// comprises an array of data fields.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulatedSpaceAndArray) {
+    // Configuration string. The encapsulated option space
+    // name is set to non-empty value and the array flag
+    // is set.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": True,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"valid-space-name\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -741,6 +854,31 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
     checkResult(status, 1);
 }
 
+/// The goal of this test is to verify that the option may not
+/// encapsulate option space it belongs to.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulateOwnSpace) {
+    // Configuration string. Option is set to encapsulate
+    // option space it belongs to.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
 
 /// The purpose of this test is to verify that it is not allowed
 /// to override the standard option (that belongs to dhcp4 option
@@ -759,7 +897,8 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"dhcp4\""
+        "      \"space\": \"dhcp4\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -794,7 +933,8 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"dhcp4\""
+        "      \"space\": \"dhcp4\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     json = Element::fromJSON(config);
@@ -907,7 +1047,8 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
         "    \"type\": \"uint32\","
         "    \"array\": False,"
         "    \"record-types\": \"\","
-        "    \"space\": \"isc\""
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
         " } ],"
         "\"subnet4\": [ { "
         "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
@@ -940,6 +1081,166 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
     ASSERT_FALSE(desc3.option);
 }
 
+// The goal of this test is to verify that it is possible to
+// encapsulate option space containing some options with
+// another option. In this test we create base option that
+// encapsulates option space 'isc' that comprises two other
+// options. Also, for all options their definitions are
+// created.
+TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
+
+    // @todo DHCP configurations has many dependencies between
+    // parameters. First of all, configuration for subnet is
+    // inherited from the global values. Thus subnet has to be
+    // configured when all global values have been configured.
+    // Also, an option can encapsulate another option only
+    // if the latter has been configured. For this reason in this
+    // test we created two-stage configuration where first we
+    // created options that belong to encapsulated option space.
+    // In the second stage we add the base option. Also, the Subnet
+    // object is configured in the second stage so it is created
+    // at the very end (when all other parameters are configured).
+
+    // Starting stage 1. Configure sub-options and their definitions.
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 1,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"isc\","
+        "    \"code\": 2,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 1,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 2,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Stage 2. Configure base option and a subnet. Please note that
+    // the configuration from the stage 2 is repeated because BIND
+    // configuration manager sends whole configuration for the lists
+    // where at least one element is being modified or added.
+    config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"base-option\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 222,"
+        "    \"data\": \"11\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 1,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"isc\","
+        "    \"code\": 2,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"base-option\","
+        "    \"code\": 222,"
+        "    \"type\": \"uint8\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"dhcp4\","
+        "    \"encapsulate\": \"isc\""
+        "},"
+        "{"
+        "    \"name\": \"foo\","
+        "    \"code\": 1,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 2,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ]"
+        "}";
+
+
+    json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Get the subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(subnet);
+
+    // We should have one option available.
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(1, options->size());
+
+    // Get the option.
+    Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", 222);
+    EXPECT_TRUE(desc.option);
+    EXPECT_EQ(222, desc.option->getType());
+
+    // This opton should comprise two sub-options.
+    // One of them is 'foo' with code 1.
+    OptionPtr option_foo = desc.option->getOption(1);
+    ASSERT_TRUE(option_foo);
+    EXPECT_EQ(1, option_foo->getType());
+
+    // ...another one 'foo2' with code 2.
+    OptionPtr option_foo2 = desc.option->getOption(2);
+    ASSERT_TRUE(option_foo2);
+    EXPECT_EQ(2, option_foo2->getType());
+}
+
 // Goal of this test is to verify options configuration
 // for a single subnet. In particular this test checks
 // that local options configuration overrides global
@@ -1290,4 +1591,165 @@ TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
     checkResult(status, 1);
 }
 
+// The goal of this test is to verify that the standard option can
+// be configured to encapsulate multiple other options.
+TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
+
+    // The configuration is two stage process in this test.
+    // In the first stahe we create definitions of suboptions
+    // that we will add to the base option.
+    // Let's create some dummy options: foo and foo2.
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"code\": 1,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"code\": 2,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 1,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 2,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"encapsulate\": \"\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Once the definitions have been added we can configure the
+    // standard option #17. This option comprises an enterprise
+    // number and sub options. By convention (introduced in
+    // std_option_defs.h) option named 'vendor-opts'
+    // encapsulates the option space named 'vendor-opts-space'.
+    // We add our dummy options to this option space and thus
+    // they should be included as sub-options in the 'vendor-opts'
+    // option.
+    config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"vendor-encapsulated-options\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 43,"
+        "    \"data\": \"\","
+        "    \"csv-format\": False"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"code\": 1,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"code\": 2,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 1,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 2,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"encapsulate\": \"\""
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ]"
+        "}";
+
+
+    json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Get the subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(subnet);
+
+    // We should have one option available.
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(1, options->size());
+
+    // Get the option.
+    Subnet::OptionDescriptor desc =
+        subnet->getOptionDescriptor("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
+    EXPECT_TRUE(desc.option);
+    EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option->getType());
+
+    // Option with the code 1 should be added as a sub-option.
+    OptionPtr option_foo = desc.option->getOption(1);
+    ASSERT_TRUE(option_foo);
+    EXPECT_EQ(1, option_foo->getType());
+    // This option comprises a single uint32_t value thus it is
+    // represented by OptionInt<uint32_t> class. Let's get the
+    // object of this type.
+    boost::shared_ptr<OptionInt<uint32_t> > option_foo_uint32 =
+        boost::dynamic_pointer_cast<OptionInt<uint32_t> >(option_foo);
+    ASSERT_TRUE(option_foo_uint32);
+    // Validate the value according to the configuration.
+    EXPECT_EQ(1234, option_foo_uint32->getValue());
+
+    // Option with the code 2 should be added as a sub-option.
+    OptionPtr option_foo2 = desc.option->getOption(2);
+    ASSERT_TRUE(option_foo2);
+    EXPECT_EQ(2, option_foo2->getType());
+    // This option comprises the IPV4 address. Such option is
+    // represented by OptionCustom object.
+    OptionCustomPtr option_foo2_v4 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_foo2);
+    ASSERT_TRUE(option_foo2_v4);
+    // Get the IP address carried by this option and validate it.
+    EXPECT_EQ("192.168.2.1", option_foo2_v4->readAddress().toText());
+
+    // Option with the code 3 should not be added.
+    EXPECT_FALSE(desc.option->getOption(3));
+}
+
+
 };

+ 103 - 13
src/bin/dhcp6/config_parser.cc

@@ -102,7 +102,6 @@ OptionStorage option_defaults;
 /// @brief Global storage for option definitions.
 OptionDefStorage option_def_intermediate;
 
-
 /// @brief a dummy configuration parser
 ///
 /// This is a debugging parser. It does not configure anything,
@@ -797,7 +796,10 @@ private:
         }
 
         std::string option_space = getParam<std::string>("space", string_values_);
-        /// @todo Validate option space once #2313 is merged.
+        if (!OptionSpace::validateName(option_space)) {
+            isc_throw(DhcpConfigError, "invalid option space name'"
+                      << option_space << "'");
+        }
 
         OptionDefinitionPtr def;
         if (option_space == "dhcp6" &&
@@ -886,7 +888,7 @@ private:
             // definition of option value makes sense.
             if (def->getName() != option_name) {
                 isc_throw(DhcpConfigError, "specified option name '"
-                          << option_name << " does not match the "
+                          << option_name << "' does not match the "
                           << "option definition: '" << option_space
                           << "." << def->getName() << "'");
             }
@@ -1033,8 +1035,8 @@ public:
         BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
             std::string entry(param.first);
             ParserPtr parser;
-            if (entry == "name" || entry == "type" ||
-                entry == "record-types" || entry == "space") {
+            if (entry == "name" || entry == "type" || entry == "record-types" ||
+                entry == "space" || entry == "encapsulate") {
                 StringParserPtr
                     str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
                 if (str_parser) {
@@ -1084,8 +1086,8 @@ public:
 
     /// @brief Stores the parsed option definition in the data store.
     void commit() {
-        // @todo validate option space name once 2313 is merged.
-        if (storage_ && option_definition_) {
+        if (storage_ && option_definition_ &&
+            OptionSpace::validateName(option_space_name_)) {
             storage_->addItem(option_definition_, option_space_name_);
         }
     }
@@ -1107,11 +1109,10 @@ private:
     void createOptionDef() {
         // Get the option space name and validate it.
         std::string space = getParam<std::string>("space", string_values_);
-        // @todo uncomment the code below when the #2313 is merged.
-        /*        if (!OptionSpace::validateName()) {
+        if (!OptionSpace::validateName(space)) {
             isc_throw(DhcpConfigError, "invalid option space name '"
                       << space << "'");
-                      } */
+        }
 
         // Get other parameters that are needed to create the
         // option definition.
@@ -1119,9 +1120,36 @@ private:
         uint32_t code = getParam<uint32_t>("code", uint32_values_);
         std::string type = getParam<std::string>("type", string_values_);
         bool array_type = getParam<bool>("array", boolean_values_);
+        std::string encapsulates = getParam<std::string>("encapsulate",
+                                                         string_values_);
+
+        // 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));
+
+        }
 
-        OptionDefinitionPtr def(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 = getParam<std::string>("record-types",
@@ -1139,7 +1167,7 @@ private:
                 }
             } catch (const Exception& ex) {
                 isc_throw(DhcpConfigError, "invalid record type values"
-                          << " specified for the option  definition: "
+                          << " specified for the option definition: "
                           << ex.what());
             }
         }
@@ -1358,6 +1386,63 @@ private:
         return (false);
     }
 
+    /// @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) {
+        // 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 (option_space == "dhcp6" &&
+            LibDHCP::isStandardOption(Option::V6, option->getType())) {
+            def = LibDHCP::getOptionDef(Option::V6, 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 =
+                option_def_intermediate.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 =
+                option_defaults.getItems(encapsulated_space);
+            // Append sub-options to the option.
+            BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
+                if (desc.option) {
+                    option->addOption(desc.option);
+                }
+            }
+        }
+    }
+
     /// @brief Create a new subnet using a data from child parsers.
     ///
     /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
@@ -1458,6 +1543,8 @@ private:
                     LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
                         .arg(desc.option->getType()).arg(addr.toText());
                 }
+                // 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);
             }
@@ -1485,6 +1572,9 @@ private:
                 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);
                 }
             }

+ 6 - 0
src/bin/dhcp6/dhcp6.spec

@@ -86,6 +86,12 @@
             "item_type": "string",
             "item_optional": false,
             "item_default": ""
+          },
+
+          { "item_name": "encapsulate",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
           } ]
         }
       },

+ 2 - 7
src/bin/dhcp6/dhcp6_srv.cc

@@ -411,14 +411,9 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
     }
     // Get the list of options that client requested.
     const std::vector<uint16_t>& requested_opts = option_oro->getValues();
-    // Get the list of options configured for a subnet.
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-    // Try to match requested options with those configured for a subnet.
-    // If match is found, append configured option to the answer message.
     BOOST_FOREACH(uint16_t opt, requested_opts) {
-        const Subnet::OptionContainerTypeRange& range = idx.equal_range(opt);
-        BOOST_FOREACH(Subnet::OptionDescriptor desc, range) {
+        Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", opt);
+        if (desc.option) {
             answer->addOption(desc.option);
         }
     }

+ 454 - 76
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -18,6 +18,8 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
 #include <dhcp6/config_parser.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -573,7 +575,8 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
         "      \"type\": \"ipv6-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -611,7 +614,8 @@ TEST_F(Dhcp6ParserTest, optionDefRecord) {
         "      \"type\": \"record\","
         "      \"array\": False,"
         "      \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -658,7 +662,8 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) {
         "      \"type\": \"uint32\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
@@ -666,7 +671,8 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) {
         "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -716,7 +722,8 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
         "      \"type\": \"uint32\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
@@ -724,7 +731,8 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
         "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -752,7 +760,8 @@ TEST_F(Dhcp6ParserTest, optionDefArray) {
         "      \"type\": \"uint32\","
         "      \"array\": True,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -778,6 +787,47 @@ TEST_F(Dhcp6ParserTest, optionDefArray) {
     EXPECT_TRUE(def->getArrayType());
 }
 
+// The purpose of this test to verify that encapsulated option
+// space name may be specified.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulate) {
+
+    // Configuration string. Included the encapsulated
+    // option space name.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"sub-opts-space\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+    EXPECT_EQ("sub-opts-space", def->getEncapsulatedSpace());
+}
+
 /// The purpose of this test is to verify that the option definition
 /// with invalid name is not accepted.
 TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
@@ -790,7 +840,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -815,7 +866,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
         "      \"type\": \"sting\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -840,7 +892,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
         "      \"type\": \"record\","
         "      \"array\": False,"
         "      \"record-types\": \"uint32,uint8,sting\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -853,6 +906,85 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
     checkResult(status, 1);
 }
 
+/// The goal of this test is to verify that the invalid encapsulated
+/// option space name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidEncapsulatedSpace) {
+    // Configuration string. The encapsulated option space
+    // name is invalid (% character is not allowed).
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"invalid%space%name\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the encapsulated
+/// option space name can't be specified for the option that
+/// comprises an array of data fields.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulatedSpaceAndArray) {
+    // Configuration string. The encapsulated option space
+    // name is set to non-empty value and the array flag
+    // is set.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": True,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"valid-space-name\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the option may not
+/// encapsulate option space it belongs to.
+TEST_F(Dhcp6ParserTest, optionDefEncapsulateOwnSpace) {
+    // Configuration string. Option is set to encapsulate
+    // option space it belongs to.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
 
 /// The purpose of this test is to verify that it is not allowed
 /// to override the standard option (that belongs to dhcp6 option
@@ -871,7 +1003,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"dhcp6\""
+        "      \"space\": \"dhcp6\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -906,7 +1039,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"dhcp6\""
+        "      \"space\": \"dhcp6\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     json = Element::fromJSON(config);
@@ -1028,7 +1162,8 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
         "    \"type\": \"uint32\","
         "    \"array\": False,"
         "    \"record-types\": \"\","
-        "    \"space\": \"isc\""
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
         " } ],"
         "\"subnet6\": [ { "
         "    \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -1061,81 +1196,164 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
     ASSERT_FALSE(desc3.option);
 }
 
-// The goal of this test is to verify options configuration
-// for a single subnet. In particular this test checks
-// that local options configuration overrides global
-// option setting.
-TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
-    ConstElementPtr x;
+// The goal of this test is to verify that it is possible to
+// encapsulate option space containing some options with
+// another option. In this test we create base option that
+// encapsulates option space 'isc' that comprises two other
+// options. Also, for all options their definitions are
+// created.
+TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
+
+    // @todo DHCP configurations has many dependencies between
+    // parameters. First of all, configuration for subnet is
+    // inherited from the global values. Thus subnet has to be
+    // configured when all global values have been configured.
+    // Also, an option can encapsulate another option only
+    // if the latter has been configured. For this reason in this
+    // test we created two-stage configuration where first we
+    // created options that belong to encapsulated option space.
+    // In the second stage we add the base option. Also, the Subnet
+    // object is configured in the second stage so it is created
+    // at the very end (when all other parameters are configured).
+
+    // Starting stage 1. Configure sub-options and their definitions.
     string config = "{ \"interface\": [ \"all\" ],"
-        "\"preferred-lifetime\": 3000,"
-        "\"rebind-timer\": 2000, "
-        "\"renew-timer\": 1000, "
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
-        "      \"name\": \"subscriber-id\","
-        "      \"space\": \"dhcp6\","
-        "      \"code\": 38,"
-        "      \"data\": \"AB\","
-        "      \"csv-format\": False"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 110,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"isc\","
+        "    \"code\": 111,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 110,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 111,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Stage 2. Configure base option and a subnet. Please note that
+    // the configuration from the stage 2 is repeated because BIND
+    // configuration manager sends whole configuration for the lists
+    // where at least one element is being modified or added.
+    config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"base-option\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 100,"
+        "    \"data\": \"11\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 110,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"isc\","
+        "    \"code\": 111,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"base-option\","
+        "    \"code\": 100,"
+        "    \"type\": \"uint8\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"dhcp6\","
+        "    \"encapsulate\": \"isc\""
+        "},"
+        "{"
+        "    \"name\": \"foo\","
+        "    \"code\": 110,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 111,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
         " } ],"
         "\"subnet6\": [ { "
         "    \"pool\": [ \"2001:db8:1::/80\" ],"
-        "    \"subnet\": \"2001:db8:1::/64\", "
-        "    \"option-data\": [ {"
-        "          \"name\": \"subscriber-id\","
-        "          \"space\": \"dhcp6\","
-        "          \"code\": 38,"
-        "          \"data\": \"AB CDEF0105\","
-        "          \"csv-format\": False"
-        "        },"
-        "        {"
-        "          \"name\": \"preference\","
-        "          \"space\": \"dhcp6\","
-        "          \"code\": 7,"
-        "          \"data\": \"01\","
-        "          \"csv-format\": False"
-        "        } ]"
-        " } ],"
-        "\"valid-lifetime\": 4000 }";
+        "    \"subnet\": \"2001:db8:1::/64\""
+        " } ]"
+        "}";
 
-    ElementPtr json = Element::fromJSON(config);
 
-    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    json = Element::fromJSON(config);
 
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Get the subnet.
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
     ASSERT_TRUE(subnet);
-    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
-    ASSERT_EQ(2, options->size());
 
-    // Get the search index. Index #1 is to search using option code.
-    const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
-
-    // Get the options for specified index. Expecting one option to be
-    // returned but in theory we may have multiple options with the same
-    // code so we get the range.
-    std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
-              Subnet::OptionContainerTypeIndex::const_iterator> range =
-        idx.equal_range(D6O_SUBSCRIBER_ID);
-    // Expect single option with the code equal to 38.
-    ASSERT_EQ(1, std::distance(range.first, range.second));
-    const uint8_t subid_expected[] = {
-        0xAB, 0xCD, 0xEF, 0x01, 0x05
-    };
-    // Check if option is valid in terms of code and carried data.
-    testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
-               sizeof(subid_expected));
+    // We should have one option available.
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(1, options->size());
 
-    range = idx.equal_range(D6O_PREFERENCE);
-    ASSERT_EQ(1, std::distance(range.first, range.second));
-    // Do another round of testing with second option.
-    const uint8_t pref_expected[] = {
-        0x01
-    };
-    testOption(*range.first, D6O_PREFERENCE, pref_expected,
-               sizeof(pref_expected));
+    // Get the option.
+    Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp6", 100);
+    EXPECT_TRUE(desc.option);
+    EXPECT_EQ(100, desc.option->getType());
+
+    // This opton should comprise two sub-options.
+    // Onf of them is 'foo' with code 110.
+    OptionPtr option_foo = desc.option->getOption(110);
+    ASSERT_TRUE(option_foo);
+    EXPECT_EQ(110, option_foo->getType());
+
+    // ...another one 'foo2' with code 111.
+    OptionPtr option_foo2 = desc.option->getOption(111);
+    ASSERT_TRUE(option_foo2);
+    EXPECT_EQ(111, option_foo2->getType());
 }
 
 // Goal of this test is to verify options configuration
@@ -1377,4 +1595,164 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     EXPECT_EQ(1516, optionIA->getT2());
 }
 
+// The goal of this test is to verify that the standard option can
+// be configured to encapsulate multiple other options.
+TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
+
+    // The configuration is two stage process in this test.
+    // In the first stahe we create definitions of suboptions
+    // that we will add to the base option.
+    // Let's create some dummy options: foo and foo2.
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"code\": 110,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"code\": 111,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 110,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 111,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"encapsulate\": \"\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Once the definitions have been added we can configure the
+    // standard option #17. This option comprises an enterprise
+    // number and sub options. By convention (introduced in
+    // std_option_defs.h) option named 'vendor-opts'
+    // encapsulates the option space named 'vendor-opts-space'.
+    // We add our dummy options to this option space and thus
+    // they should be included as sub-options in the 'vendor-opts'
+    // option.
+    config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"vendor-opts\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 17,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"code\": 110,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"code\": 111,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 110,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 111,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-opts-space\","
+        "    \"encapsulate\": \"\""
+        " } ],"
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\""
+        " } ]"
+        "}";
+
+
+    json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Get the subnet.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+
+    // We should have one option available.
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(1, options->size());
+
+    // Get the option.
+    Subnet::OptionDescriptor desc =
+        subnet->getOptionDescriptor("dhcp6", D6O_VENDOR_OPTS);
+    EXPECT_TRUE(desc.option);
+    EXPECT_EQ(D6O_VENDOR_OPTS, desc.option->getType());
+
+    // Option with the code 110 should be added as a sub-option.
+    OptionPtr option_foo = desc.option->getOption(110);
+    ASSERT_TRUE(option_foo);
+    EXPECT_EQ(110, option_foo->getType());
+    // This option comprises a single uint32_t value thus it is
+    // represented by OptionInt<uint32_t> class. Let's get the
+    // object of this type.
+    boost::shared_ptr<OptionInt<uint32_t> > option_foo_uint32 =
+        boost::dynamic_pointer_cast<OptionInt<uint32_t> >(option_foo);
+    ASSERT_TRUE(option_foo_uint32);
+    // Validate the value according to the configuration.
+    EXPECT_EQ(1234, option_foo_uint32->getValue());
+
+    // Option with the code 111 should be added as a sub-option.
+    OptionPtr option_foo2 = desc.option->getOption(111);
+    ASSERT_TRUE(option_foo2);
+    EXPECT_EQ(111, option_foo2->getType());
+    // This option comprises the IPV4 address. Such option is
+    // represented by OptionCustom object.
+    OptionCustomPtr option_foo2_v4 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_foo2);
+    ASSERT_TRUE(option_foo2_v4);
+    // Get the IP address carried by this option and validate it.
+    EXPECT_EQ("192.168.2.1", option_foo2_v4->readAddress().toText());
+
+    // Option with the code 112 should not be added.
+    EXPECT_FALSE(desc.option->getOption(112));
+}
+
 };

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

@@ -32,6 +32,7 @@ libb10_dhcp___la_SOURCES += option.cc option.h
 libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
 libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
 libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
+libb10_dhcp___la_SOURCES += option_space.cc option_space.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
 libb10_dhcp___la_SOURCES += std_option_defs.h

+ 57 - 8
src/lib/dhcp/libdhcp++.cc

@@ -329,10 +329,35 @@ LibDHCP::initStdOptionDefs4() {
 
     // Now let's add all option definitions.
     for (int i = 0; i < OPTION_DEF_PARAMS_SIZE4; ++i) {
-        OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
-                                                            OPTION_DEF_PARAMS4[i].code,
-                                                            OPTION_DEF_PARAMS4[i].type,
-                                                            OPTION_DEF_PARAMS4[i].array));
+        std::string encapsulates(OPTION_DEF_PARAMS4[i].encapsulates);
+        if (!encapsulates.empty() && OPTION_DEF_PARAMS4[i].array) {
+            isc_throw(isc::BadValue, "invalid standard option definition: "
+                      << "option with code '" << OPTION_DEF_PARAMS4[i].code
+                      << "' may not encapsulate option space '"
+                      << encapsulates << "' because the definition"
+                      << " indicates that this option comprises an array"
+                      << " of values");
+        }
+
+        // Depending whether the option encapsulates an option space or not
+        // we pick different constructor to create an instance of the option
+        // definition.
+        OptionDefinitionPtr definition;
+        if (encapsulates.empty()) {
+            // Option does not encapsulate any option space.
+            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
+                                                  OPTION_DEF_PARAMS4[i].code,
+                                                  OPTION_DEF_PARAMS4[i].type,
+                                                  OPTION_DEF_PARAMS4[i].array));
+
+        } else {
+            // Option does encapsulate an option space.
+            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
+                                                  OPTION_DEF_PARAMS4[i].code,
+                                                  OPTION_DEF_PARAMS4[i].type,
+                                                  OPTION_DEF_PARAMS4[i].encapsulates));
+
+        }
 
         for (int rec = 0; rec < OPTION_DEF_PARAMS4[i].records_size; ++rec) {
             definition->addRecordField(OPTION_DEF_PARAMS4[i].records[rec]);
@@ -358,10 +383,34 @@ LibDHCP::initStdOptionDefs6() {
     v6option_defs_.clear();
 
     for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
-        OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
-                                                            OPTION_DEF_PARAMS6[i].code,
-                                                            OPTION_DEF_PARAMS6[i].type,
-                                                            OPTION_DEF_PARAMS6[i].array));
+        std::string encapsulates(OPTION_DEF_PARAMS6[i].encapsulates);
+        if (!encapsulates.empty() && OPTION_DEF_PARAMS6[i].array) {
+            isc_throw(isc::BadValue, "invalid standard option definition: "
+                      << "option with code '" << OPTION_DEF_PARAMS6[i].code
+                      << "' may not encapsulate option space '"
+                      << encapsulates << "' because the definition"
+                      << " indicates that this option comprises an array"
+                      << " of values");
+        }
+
+        // Depending whether an option encapsulates an option space or not
+        // we pick different constructor to create an instance of the option
+        // definition.
+        OptionDefinitionPtr definition;
+        if (encapsulates.empty()) {
+            // Option does not encapsulate any option space.
+            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
+                                                  OPTION_DEF_PARAMS6[i].code,
+                                                  OPTION_DEF_PARAMS6[i].type,
+                                                  OPTION_DEF_PARAMS6[i].array));
+        } else {
+            // Option does encapsulate an option space.
+            definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
+                                                  OPTION_DEF_PARAMS6[i].code,
+                                                  OPTION_DEF_PARAMS6[i].type,
+                                                  OPTION_DEF_PARAMS6[i].encapsulates));
+
+        }
 
         for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
             definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);

+ 4 - 1
src/lib/dhcp/option_custom.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -352,6 +352,9 @@ private:
     std::vector<OptionBuffer> buffers_;
 };
 
+/// A pointer to the OptionCustom object.
+typedef boost::shared_ptr<OptionCustom> OptionCustomPtr;
+
 } // namespace isc::dhcp
 } // namespace isc
 

+ 36 - 3
src/lib/dhcp/option_definition.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -21,6 +21,7 @@
 #include <dhcp/option_definition.h>
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/option_space.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 #include <boost/algorithm/string/classification.hpp>
@@ -40,7 +41,8 @@ OptionDefinition::OptionDefinition(const std::string& name,
     : name_(name),
       code_(code),
       type_(OPT_UNKNOWN_TYPE),
-      array_type_(array_type) {
+      array_type_(array_type),
+      encapsulated_space_("") {
     // Data type is held as enum value by this class.
     // Use the provided option type string to get the
     // corresponding enum value.
@@ -54,7 +56,33 @@ OptionDefinition::OptionDefinition(const std::string& name,
     : name_(name),
       code_(code),
       type_(type),
-      array_type_(array_type) {
+      array_type_(array_type),
+      encapsulated_space_("") {
+}
+
+OptionDefinition::OptionDefinition(const std::string& name,
+                                   const uint16_t code,
+                                   const std::string& type,
+                                   const char* encapsulated_space)
+    : name_(name),
+      code_(code),
+      // Data type is held as enum value by this class.
+      // Use the provided option type string to get the
+      // corresponding enum value.
+      type_(OptionDataTypeUtil::getDataType(type)),
+      array_type_(false),
+      encapsulated_space_(encapsulated_space) {
+}
+
+OptionDefinition::OptionDefinition(const std::string& name,
+                                   const uint16_t code,
+                                   const OptionDataType type,
+                                   const char* encapsulated_space)
+    : name_(name),
+      code_(code),
+      type_(type),
+      array_type_(false),
+      encapsulated_space_(encapsulated_space) {
 }
 
 void
@@ -228,6 +256,11 @@ OptionDefinition::validate() const {
         all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) {
         err_str << "invalid option name '" << name_ << "'";
 
+    } else if (!encapsulated_space_.empty() &&
+               !OptionSpace::validateName(encapsulated_space_)) {
+        err_str << "invalid encapsulated option space name: '"
+                << encapsulated_space_ << "'";
+
     } else if (type_ >= OPT_UNKNOWN_TYPE) {
         // Option definition must be of a known type.
         err_str << "option type value " << type_ << " is out of range.";

+ 57 - 9
src/lib/dhcp/option_definition.h

@@ -146,10 +146,10 @@ public:
     /// @param type option data type as string.
     /// @param array_type array indicator, if true it indicates that the
     /// option fields are the array.
-    OptionDefinition(const std::string& name,
-                     const uint16_t code,
-                     const std::string& type,
-                     const bool array_type = false);
+    explicit OptionDefinition(const std::string& name,
+                              const uint16_t code,
+                              const std::string& type,
+                              const bool array_type = false);
 
     /// @brief Constructor.
     ///
@@ -158,10 +158,49 @@ public:
     /// @param type option data type.
     /// @param array_type array indicator, if true it indicates that the
     /// option fields are the array.
-    OptionDefinition(const std::string& name,
-                     const uint16_t code,
-                     const OptionDataType type,
-                     const bool array_type = false);
+    explicit OptionDefinition(const std::string& name,
+                              const uint16_t code,
+                              const OptionDataType type,
+                              const bool array_type = false);
+
+    /// @brief Constructor.
+    ///
+    /// This constructor sets the name of the option space that is
+    /// encapsulated by this option. The encapsulated option space
+    /// indentifies sub-options that are carried within this option.
+    /// This constructor does not allow to set array indicator
+    /// because options comprising an array of data fields must
+    /// not be used with sub-options.
+    ///
+    /// @param name option name.
+    /// @param code option code.
+    /// @param type option data type given as string.
+    /// @param encapsulated_space name of the option space being
+    /// encapsulated by this option.
+    explicit OptionDefinition(const std::string& name,
+                              const uint16_t code,
+                              const std::string& type,
+                              const char* encapsulated_space);
+
+    /// @brief Constructor.
+    ///
+    /// This constructor sets the name of the option space that is
+    /// encapsulated by this option. The encapsulated option space
+    /// indentifies sub-options that are carried within this option.
+    /// This constructor does not allow to set array indicator
+    /// because options comprising an array of data fields must
+    /// not be used with sub-options.
+    ///
+    /// @param name option name.
+    /// @param code option code.
+    /// @param type option data type.
+    /// @param encapsulated_space name of the option space being
+    /// encapsulated by this option.
+    explicit OptionDefinition(const std::string& name,
+                              const uint16_t code,
+                              const OptionDataType type,
+                              const char* encapsulated_space);
+
 
     /// @brief Adds data field to the record.
     ///
@@ -192,10 +231,17 @@ public:
     /// @return option code.
     uint16_t getCode() const { return (code_); }
 
+    /// @brief Return name of the encapsulated option space.
+    ///
+    /// @return name of the encapsulated option space.
+    std::string getEncapsulatedSpace() const {
+        return (encapsulated_space_);
+    }
+
     /// @brief Return option name.
     ///
     /// @return option name.
-    const std::string& getName() const { return (name_); }
+    std::string getName() const { return (name_); }
 
     /// @brief Return list of record fields.
     ///
@@ -456,6 +502,8 @@ private:
     OptionDataType type_;
     /// Indicates wheter option is a single value or array.
     bool array_type_;
+    /// Name of the space being encapsulated by this option.
+    std::string encapsulated_space_;
     /// Collection of data fields within the record.
     RecordFieldsCollection record_fields_;
 };

+ 1 - 1
src/lib/dhcpsrv/option_space.cc

@@ -12,7 +12,7 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <dhcpsrv/option_space.h>
+#include <dhcp/option_space.h>
 #include <boost/algorithm/string/classification.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 

src/lib/dhcpsrv/option_space.h → src/lib/dhcp/option_space.h


+ 122 - 122
src/lib/dhcp/std_option_defs.h

@@ -50,6 +50,8 @@ struct OptionDefParams {
     bool array;                    // is array
     const OptionDataType* records; // record fields
     size_t records_size;           // number of fields in a record
+    const char* encapsulates;      // option space encapsulated by
+                                   // the particular option.
 };
 
 // fqdn option record fields.
@@ -64,128 +66,128 @@ RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_STRING_TYPE);
 
 /// @brief Definitions of standard DHCPv4 options.
 const OptionDefParams OPTION_DEF_PARAMS4[] = {
-    { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
-    { "time-offset", DHO_TIME_OFFSET, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
-    { "routers", DHO_ROUTERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
-    { "time-servers", DHO_TIME_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+    { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+    { "time-offset", DHO_TIME_OFFSET, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+    { "routers", DHO_ROUTERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "time-servers", DHO_TIME_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "name-servers", DHO_NAME_SERVERS, OPT_IPV4_ADDRESS_TYPE,
-      false, NO_RECORD_DEF },
+      false, NO_RECORD_DEF, "" },
     { "domain-name-servers", DHO_DOMAIN_NAME_SERVERS,
-      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
-    { "log-servers", DHO_LOG_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "log-servers", DHO_LOG_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "cookie-servers", DHO_COOKIE_SERVERS, OPT_IPV4_ADDRESS_TYPE,
-      true, NO_RECORD_DEF },
-    { "lpr-servers", DHO_LPR_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
-    { "impress-servers", DHO_IMPRESS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      true, NO_RECORD_DEF, "" },
+    { "lpr-servers", DHO_LPR_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "impress-servers", DHO_IMPRESS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "resource-location-servers", DHO_RESOURCE_LOCATION_SERVERS,
-      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
-    { "host-name", DHO_HOST_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF },
-    { "boot-size", DHO_BOOT_SIZE, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
-    { "merit-dump", DHO_MERIT_DUMP, OPT_STRING_TYPE, false, NO_RECORD_DEF },
-    { "domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF },
-    { "swap-server", DHO_SWAP_SERVER, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
-    { "root-path", DHO_ROOT_PATH, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "host-name", DHO_HOST_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "boot-size", DHO_BOOT_SIZE, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+    { "merit-dump", DHO_MERIT_DUMP, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF, "" },
+    { "swap-server", DHO_SWAP_SERVER, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+    { "root-path", DHO_ROOT_PATH, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
     { "extensions-path", DHO_EXTENSIONS_PATH, OPT_STRING_TYPE,
-      false, NO_RECORD_DEF },
-    { "ip-forwarding", DHO_IP_FORWARDING, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+      false, NO_RECORD_DEF, "" },
+    { "ip-forwarding", DHO_IP_FORWARDING, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
     { "non-local-source-routing", DHO_NON_LOCAL_SOURCE_ROUTING,
-      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
-    { "policy-filter", DHO_POLICY_FILTER, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+    { "policy-filter", DHO_POLICY_FILTER, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "max-dgram-reassembly", DHO_MAX_DGRAM_REASSEMBLY,
-      OPT_UINT16_TYPE, false, NO_RECORD_DEF },
-    { "default-ip-ttl", DHO_DEFAULT_IP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+    { "default-ip-ttl", DHO_DEFAULT_IP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
     { "path-mtu-aging-timeout", DHO_PATH_MTU_AGING_TIMEOUT,
-      OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "path-mtu-plateau-table", DHO_PATH_MTU_PLATEAU_TABLE,
-      OPT_UINT16_TYPE, true, NO_RECORD_DEF },
-    { "interface-mtu", DHO_INTERFACE_MTU, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+    { "interface-mtu", DHO_INTERFACE_MTU, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
     { "all-subnets-local", DHO_ALL_SUBNETS_LOCAL,
-      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
     { "broadcast-address", DHO_BROADCAST_ADDRESS,
-      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
     { "perform-mask-discovery", DHO_PERFORM_MASK_DISCOVERY,
-      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
-    { "mask-supplier", DHO_MASK_SUPPLIER, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+    { "mask-supplier", DHO_MASK_SUPPLIER, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
     { "router-discovery", DHO_ROUTER_DISCOVERY,
-      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
     { "router-solicitation-address", DHO_ROUTER_SOLICITATION_ADDRESS,
-      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
     { "static-routes", DHO_STATIC_ROUTES,
-      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "trailer-encapsulation", DHO_TRAILER_ENCAPSULATION,
-      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
     { "arp-cache-timeout", DHO_ARP_CACHE_TIMEOUT,
-      OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "ieee802-3-encapsulation", DHO_IEEE802_3_ENCAPSULATION,
-      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
-    { "default-tcp-ttl", DHO_DEFAULT_TCP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+    { "default-tcp-ttl", DHO_DEFAULT_TCP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
     { "tcp-keepalive-internal", DHO_TCP_KEEPALIVE_INTERVAL,
-      OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "tcp-keepalive-garbage", DHO_TCP_KEEPALIVE_GARBAGE,
-      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
-    { "nis-domain", DHO_NIS_DOMAIN, OPT_STRING_TYPE, false, NO_RECORD_DEF },
-    { "nis-servers", DHO_NIS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
-    { "ntp-servers", DHO_NTP_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF, "" },
+    { "nis-domain", DHO_NIS_DOMAIN, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "nis-servers", DHO_NIS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+    { "ntp-servers", DHO_NTP_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "vendor-encapsulated-options", DHO_VENDOR_ENCAPSULATED_OPTIONS,
-      OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+      OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "vendor-encapsulated-options-space" },
     { "netbios-name-servers", DHO_NETBIOS_NAME_SERVERS,
-      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "netbios-dd-server", DHO_NETBIOS_DD_SERVER,
-      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "netbios-node-type", DHO_NETBIOS_NODE_TYPE,
-      OPT_UINT8_TYPE, false, NO_RECORD_DEF },
-    { "netbios-scope", DHO_NETBIOS_SCOPE, OPT_STRING_TYPE, false, NO_RECORD_DEF },
-    { "font-servers", DHO_FONT_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+    { "netbios-scope", DHO_NETBIOS_SCOPE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "font-servers", DHO_FONT_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "x-display-manager", DHO_X_DISPLAY_MANAGER,
-      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "dhcp-requested-address", DHO_DHCP_REQUESTED_ADDRESS,
-      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
-    { "dhcp-lease-time", DHO_DHCP_LEASE_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
+    { "dhcp-lease-time", DHO_DHCP_LEASE_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "dhcp-option-overload", DHO_DHCP_OPTION_OVERLOAD,
-      OPT_UINT8_TYPE, false, NO_RECORD_DEF },
-    { "dhcp-message-type", DHO_DHCP_MESSAGE_TYPE, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+    { "dhcp-message-type", DHO_DHCP_MESSAGE_TYPE, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
     { "dhcp-server-identifier", DHO_DHCP_SERVER_IDENTIFIER,
-      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
     { "dhcp-parameter-request-list", DHO_DHCP_PARAMETER_REQUEST_LIST,
-      OPT_UINT8_TYPE, true, NO_RECORD_DEF },
-    { "dhcp-message", DHO_DHCP_MESSAGE, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT8_TYPE, true, NO_RECORD_DEF, "" },
+    { "dhcp-message", DHO_DHCP_MESSAGE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
     { "dhcp-max-message-size", DHO_DHCP_MAX_MESSAGE_SIZE,
-      OPT_UINT16_TYPE, false, NO_RECORD_DEF },
-    { "dhcp-renewal-time", DHO_DHCP_RENEWAL_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+    { "dhcp-renewal-time", DHO_DHCP_RENEWAL_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME,
-      OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER,
-      OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+      OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER,
-      OPT_BINARY_TYPE, false, NO_RECORD_DEF },
-    { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF },
-    { "nwip-suboptions", DHO_NWIP_SUBOPTIONS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
-    { "user-class", DHO_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
-    { "fqdn", DHO_FQDN, OPT_RECORD_TYPE, false, RECORD_DEF(FQDN_RECORDS) },
+      OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "nwip-suboptions", DHO_NWIP_SUBOPTIONS, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "user-class", DHO_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "fqdn", DHO_FQDN, OPT_RECORD_TYPE, false, RECORD_DEF(FQDN_RECORDS), "" },
     { "dhcp-agent-options", DHO_DHCP_AGENT_OPTIONS,
-      OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+      OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "dhcp-agent-options-space" },
     // Unfortunatelly the AUTHENTICATE option contains a 64-bit
     // data field called 'replay-detection' that can't be added
     // as a record field to a custom option. Also, there is no
     // dedicated option class to handle it so we simply return
     // binary option type for now.
     // @todo implement a class to handle AUTH option.
-    { "authenticate", DHO_AUTHENTICATE, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    { "authenticate", DHO_AUTHENTICATE, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "client-last-transaction-time", DHO_CLIENT_LAST_TRANSACTION_TIME,
-      OPT_UINT32_TYPE, false, NO_RECORD_DEF },
-    { "associated-ip", DHO_ASSOCIATED_IP, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+      OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+    { "associated-ip", DHO_ASSOCIATED_IP, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
     { "subnet-selection", DHO_SUBNET_SELECTION,
-      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+      OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
     // The following options need a special encoding of data
     // being carried by them. Therefore, there is no way they can
     // be handled by OptionCustom. We may need to implement
     // dedicated classes to handle them. Until that happens
     // let's treat them as 'binary' options.
-    { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "vivco-suboptions", DHO_VIVCO_SUBOPTIONS,
-      OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+      OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, OPT_BINARY_TYPE,
-      false, NO_RECORD_DEF }
+      false, NO_RECORD_DEF, "" }
 
         // @todo add definitions for all remaning options.
 };
@@ -222,8 +224,6 @@ RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
 // vendor-class
 RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
-// vendor-opts
-RECORD_DECL(VENDOR_OPTS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 
 /// Standard DHCPv6 option definitions.
 ///
@@ -236,84 +236,84 @@ RECORD_DECL(VENDOR_OPTS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
 /// warning about lack of initializers for some struct members
 /// causing build to fail.
 const OptionDefParams OPTION_DEF_PARAMS6[] = {
-    { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
-    { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
-    { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(IA_NA_RECORDS) },
-    { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
-    { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(IAADDR_RECORDS) },
-    { "oro", D6O_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
-    { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
-    { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
-    { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(IA_NA_RECORDS), "" },
+    { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
+    { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(IAADDR_RECORDS), "" },
+    { "oro", D6O_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+    { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
+    { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false, NO_RECORD_DEF, "" },
+    { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     // Unfortunatelly the AUTH option contains a 64-bit data field
     // called 'replay-detection' that can't be added as a record
     // field to a custom option. Also, there is no dedicated
     // option class to handle it so we simply return binary
     // option type for now.
     // @todo implement a class to handle AUTH option.
-    { "auth", D6O_AUTH, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
-    { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF },
+    { "auth", D6O_AUTH, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" },
     { "status-code", D6O_STATUS_CODE, OPT_RECORD_TYPE, false,
-      RECORD_DEF(STATUS_CODE_RECORDS) },
-    { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
-    { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+      RECORD_DEF(STATUS_CODE_RECORDS), "" },
+    { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "" },
+    { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
     { "vendor-class", D6O_VENDOR_CLASS, OPT_RECORD_TYPE, false,
-      RECORD_DEF(VENDOR_CLASS_RECORDS) },
-    { "vendor-opts", D6O_VENDOR_OPTS, OPT_RECORD_TYPE, false,
-      RECORD_DEF(VENDOR_OPTS_RECORDS) },
-    { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
-    { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+      RECORD_DEF(VENDOR_CLASS_RECORDS), "" },
+    { "vendor-opts", D6O_VENDOR_OPTS, OPT_UINT32_TYPE, false,
+      NO_RECORD_DEF, "vendor-opts-space" },
+    { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false, NO_RECORD_DEF, "" },
     { "reconf-accept", D6O_RECONF_ACCEPT, OPT_EMPTY_TYPE, false,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "sip-server-dns", D6O_SIP_SERVERS_DNS, OPT_FQDN_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "sip-server-addr", D6O_SIP_SERVERS_ADDR, OPT_IPV6_ADDRESS_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "dns-servers", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
-      NO_RECORD_DEF },
-    { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF },
-    { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(IA_PD_RECORDS) },
+      NO_RECORD_DEF, "" },
+    { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF, "" },
+    { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(IA_PD_RECORDS), "" },
     { "iaprefix", D6O_IAPREFIX, OPT_RECORD_TYPE, false,
-      RECORD_DEF(IA_PREFIX_RECORDS) },
+      RECORD_DEF(IA_PREFIX_RECORDS), "" },
     { "nis-servers", D6O_NIS_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "nisp-servers", D6O_NISP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "nis-domain-name", D6O_NIS_DOMAIN_NAME, OPT_FQDN_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "nisp-domain-name", D6O_NISP_DOMAIN_NAME, OPT_FQDN_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "sntp-servers", D6O_SNTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "information-refresh-time", D6O_INFORMATION_REFRESH_TIME,
-      OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+      OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "bcmcs-server-dns", D6O_BCMCS_SERVER_D, OPT_FQDN_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "bcmcs-server-addr", D6O_BCMCS_SERVER_A, OPT_IPV6_ADDRESS_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "geoconf-civic", D6O_GEOCONF_CIVIC, OPT_RECORD_TYPE, false,
-      RECORD_DEF(GEOCONF_CIVIC_RECORDS) },
+      RECORD_DEF(GEOCONF_CIVIC_RECORDS), "" },
     { "remote-id", D6O_REMOTE_ID, OPT_RECORD_TYPE, false,
-      RECORD_DEF(REMOTE_ID_RECORDS) },
+      RECORD_DEF(REMOTE_ID_RECORDS), "" },
     { "subscriber-id", D6O_SUBSCRIBER_ID, OPT_BINARY_TYPE, false,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "client-fqdn", D6O_CLIENT_FQDN, OPT_RECORD_TYPE, false,
-      RECORD_DEF(CLIENT_FQDN_RECORDS) },
+      RECORD_DEF(CLIENT_FQDN_RECORDS), "" },
     { "pana-agent", D6O_PANA_AGENT, OPT_IPV6_ADDRESS_TYPE, true,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, OPT_STRING_TYPE, false,
-      NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
     { "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, OPT_STRING_TYPE, false,
-      NO_RECORD_DEF },
-    { "ero", D6O_ERO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+      NO_RECORD_DEF, "" },
+    { "ero", D6O_ERO, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
     { "lq-query", D6O_LQ_QUERY, OPT_RECORD_TYPE, false,
-      RECORD_DEF(LQ_QUERY_RECORDS) },
-    { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
-    { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+      RECORD_DEF(LQ_QUERY_RECORDS), "" },
+    { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "" },
+    { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF, "" },
     { "lq-relay-data", D6O_LQ_RELAY_DATA, OPT_RECORD_TYPE, false,
-      RECORD_DEF(LQ_RELAY_DATA_RECORDS) },
+      RECORD_DEF(LQ_RELAY_DATA_RECORDS), "" },
     { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
-      NO_RECORD_DEF }
+      NO_RECORD_DEF, "" }
 
     // @todo There is still a bunch of options for which we have to provide
     // definitions but we don't do it because they are not really

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

@@ -40,6 +40,7 @@ libdhcp___unittests_SOURCES += option_data_types_unittest.cc
 libdhcp___unittests_SOURCES += option_definition_unittest.cc
 libdhcp___unittests_SOURCES += option_custom_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc
+libdhcp___unittests_SOURCES += option_space_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += duid_unittest.cc

+ 25 - 9
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-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
@@ -68,12 +68,16 @@ public:
     /// used to create option instance.
     /// @param expected_type type of the option created by the
     /// factory function returned by the option definition.
+    /// @param encapsulates name of the option space being encapsulated
+    /// by the option.
     static void testStdOptionDefs4(const uint16_t code,
                                    const OptionBufferConstIter begin,
                                    const OptionBufferConstIter end,
-                                   const std::type_info& expected_type) {
+                                   const std::type_info& expected_type,
+                                   const std::string& encapsulates = "") {
         // Use V4 universe.
-        testStdOptionDefs(Option::V4, code, begin, end, expected_type);
+        testStdOptionDefs(Option::V4, code, begin, end, expected_type,
+                          encapsulates);
     }
 
     /// @brief Test DHCPv6 option definition.
@@ -88,12 +92,16 @@ public:
     /// used to create option instance.
     /// @param expected_type type of the option created by the
     /// factory function returned by the option definition.
+    /// @param encapsulates name of the option space being encapsulated
+    /// by the option.
     static void testStdOptionDefs6(const uint16_t code,
                                    const OptionBufferConstIter begin,
                                    const OptionBufferConstIter end,
-                                   const std::type_info& expected_type) {
+                                   const std::type_info& expected_type,
+                                   const std::string& encapsulates = "") {
         // Use V6 universe.
-        testStdOptionDefs(Option::V6, code, begin, end, expected_type);
+        testStdOptionDefs(Option::V6, code, begin, end, expected_type,
+                          encapsulates);
     }
 private:
 
@@ -109,11 +117,14 @@ private:
     /// used to create option instance.
     /// @param expected_type type of the option created by the
     /// factory function returned by the option definition.
+    /// @param encapsulates name of the option space being encapsulated
+    /// by the option.
     static void testStdOptionDefs(const Option::Universe u,
                                   const uint16_t code,
                                   const OptionBufferConstIter begin,
                                   const OptionBufferConstIter end,
-                                  const std::type_info& expected_type) {
+                                  const std::type_info& expected_type,
+                                  const std::string& encapsulates) {
         // Get all option definitions, we will use them to extract
         // the definition for a particular option code.
         // We don't have to initialize option definitions here because they
@@ -141,6 +152,9 @@ private:
         ASSERT_NO_THROW(def->validate())
             << "Option definition for the option code " << code
             << " is invalid";
+        // Check that the valid encapsulated option space name
+        // has been specified.
+        EXPECT_EQ(encapsulates, def->getEncapsulatedSpace());
         OptionPtr option;
         // Create the option.
         ASSERT_NO_THROW(option = def->optionFactory(u, code, begin, end))
@@ -648,7 +662,8 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
                                     typeid(Option4AddrLst));
 
     LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_ENCAPSULATED_OPTIONS, begin, end,
-                                    typeid(Option));
+                                    typeid(Option),
+                                    "vendor-encapsulated-options-space");
 
     LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NAME_SERVERS, begin, end,
                                     typeid(Option4AddrLst));
@@ -717,7 +732,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
                                     typeid(OptionCustom));
 
     LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS, begin, end,
-                                    typeid(Option));
+                                    typeid(Option), "dhcp-agent-options-space");
 
     LibDhcpTest::testStdOptionDefs4(DHO_AUTHENTICATE, begin, end,
                                     typeid(Option));
@@ -816,7 +831,8 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
                                     typeid(OptionCustom));
 
     LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, begin, end,
-                                    typeid(OptionCustom));
+                                    typeid(OptionInt<uint32_t>),
+                                    "vendor-opts-space");
 
     LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, begin, end,
                                     typeid(Option));

+ 40 - 11
src/lib/dhcp/tests/option_definition_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -53,38 +53,62 @@ public:
 TEST_F(OptionDefinitionTest, constructor) {
     // Specify the option data type as string. This should get converted
     // to enum value returned by getType().
-    OptionDefinition opt_def1("OPTION_CLIENTID", 1, "string");
+    OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, "string");
     EXPECT_EQ("OPTION_CLIENTID", opt_def1.getName());
 
     EXPECT_EQ(1, opt_def1.getCode());
     EXPECT_EQ(OPT_STRING_TYPE,  opt_def1.getType());
     EXPECT_FALSE(opt_def1.getArrayType());
+    EXPECT_TRUE(opt_def1.getEncapsulatedSpace().empty());
     EXPECT_NO_THROW(opt_def1.validate());
 
     // Specify the option data type as an enum value.
-    OptionDefinition opt_def2("OPTION_RAPID_COMMIT", 14,
+    OptionDefinition opt_def2("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT,
                               OPT_EMPTY_TYPE);
     EXPECT_EQ("OPTION_RAPID_COMMIT", opt_def2.getName());
     EXPECT_EQ(14, opt_def2.getCode());
     EXPECT_EQ(OPT_EMPTY_TYPE, opt_def2.getType());
     EXPECT_FALSE(opt_def2.getArrayType());
-    EXPECT_NO_THROW(opt_def1.validate());
+    EXPECT_TRUE(opt_def2.getEncapsulatedSpace().empty());
+    EXPECT_NO_THROW(opt_def2.validate());
+
+    // Specify encapsulated option space name and option data type
+    // as enum value.
+    OptionDefinition opt_def3("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+                              OPT_UINT32_TYPE, "isc");
+    EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def3.getName());
+    EXPECT_EQ(D6O_VENDOR_OPTS, opt_def3.getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, opt_def3.getType());
+    EXPECT_FALSE(opt_def3.getArrayType());
+    EXPECT_EQ("isc", opt_def3.getEncapsulatedSpace());
+    EXPECT_NO_THROW(opt_def3.validate());
+
+    // Specify encapsulated option space name and option data type
+    // as string value.
+    OptionDefinition opt_def4("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+                              "uint32", "isc");
+    EXPECT_EQ("OPTION_VENDOR_OPTS", opt_def4.getName());
+    EXPECT_EQ(D6O_VENDOR_OPTS, opt_def4.getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, opt_def4.getType());
+    EXPECT_FALSE(opt_def4.getArrayType());
+    EXPECT_EQ("isc", opt_def4.getEncapsulatedSpace());
+    EXPECT_NO_THROW(opt_def4.validate());
 
     // Check if it is possible to set that option is an array.
-    OptionDefinition opt_def3("OPTION_NIS_SERVERS", 27,
+    OptionDefinition opt_def5("OPTION_NIS_SERVERS", 27,
                               OPT_IPV6_ADDRESS_TYPE,
                               true);
-    EXPECT_EQ("OPTION_NIS_SERVERS", opt_def3.getName());
-    EXPECT_EQ(27, opt_def3.getCode());
-    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def3.getType());
-    EXPECT_TRUE(opt_def3.getArrayType());
-    EXPECT_NO_THROW(opt_def3.validate());
+    EXPECT_EQ("OPTION_NIS_SERVERS", opt_def5.getName());
+    EXPECT_EQ(27, opt_def5.getCode());
+    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def5.getType());
+    EXPECT_TRUE(opt_def5.getArrayType());
+    EXPECT_NO_THROW(opt_def5.validate());
 
     // The created object is invalid if invalid data type is specified but
     // constructor shouldn't throw exception. The object is validated after
     // it has been created.
     EXPECT_NO_THROW(
-        OptionDefinition opt_def4("OPTION_SERVERID",
+        OptionDefinition opt_def6("OPTION_SERVERID",
                                   OPT_UNKNOWN_TYPE + 10,
                                   OPT_STRING_TYPE);
     );
@@ -213,6 +237,11 @@ TEST_F(OptionDefinitionTest, validate) {
                                "record");
     opt_def16.addRecordField("uint8");
     opt_def16.addRecordField("string");
+
+    // Check invalid encapsulated option space name.
+    OptionDefinition opt_def17("OPTION_VENDOR_OPTS", D6O_VENDOR_OPTS,
+                               "uint32", "invalid%space%name");
+    EXPECT_THROW(opt_def17.validate(), MalformedOptionDefinition);
 }
 
 

+ 1 - 1
src/lib/dhcpsrv/tests/option_space_unittest.cc

@@ -14,7 +14,7 @@
 
 #include <config.h>
 
-#include <dhcpsrv/option_space.h>
+#include <dhcp/option_space.h>
 
 #include <gtest/gtest.h>
 

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

@@ -43,7 +43,6 @@ libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
 if HAVE_MYSQL
 libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
 endif
-libb10_dhcpsrv_la_SOURCES += option_space.cc option_space.h
 libb10_dhcpsrv_la_SOURCES += option_space_container.h
 libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
 libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h

+ 1 - 1
src/lib/dhcpsrv/cfgmgr.h

@@ -18,7 +18,7 @@
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
 #include <dhcp/option_definition.h>
-#include <dhcpsrv/option_space.h>
+#include <dhcp/option_space.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>

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

@@ -37,7 +37,6 @@ libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
 if HAVE_MYSQL
 libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
 endif
-libdhcpsrv_unittests_SOURCES += option_space_unittest.cc
 libdhcpsrv_unittests_SOURCES += pool_unittest.cc
 libdhcpsrv_unittests_SOURCES += schema_copy.h
 libdhcpsrv_unittests_SOURCES += subnet_unittest.cc