Browse Source

[master] Merge branch 'trac2491'

Marcin Siodelski 12 years ago
parent
commit
0a4faa0777

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

@@ -24,6 +24,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
 #include <dhcp/pkt6.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_srv.h>
@@ -348,13 +349,29 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
 }
 
 OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
-
-    // @todo: Implement Option6_StatusCode and rewrite this code here
-    vector<uint8_t> data(text.c_str(), text.c_str() + text.length());
-    data.insert(data.begin(), static_cast<uint8_t>(code % 256));
-    data.insert(data.begin(), static_cast<uint8_t>(code >> 8));
-    OptionPtr status(new Option(Option::V6, D6O_STATUS_CODE, data));
-    return (status);
+    // @todo This function uses OptionCustom class to manage contents
+    // of the data fields. Since this this option is frequently used
+    // it may be good to implement dedicated class to avoid performance
+    // impact.
+
+    // Get the definition of the option holding status code.
+    OptionDefinitionPtr status_code_def =
+        LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
+    // This definition is assumed to be initialized in LibDHCP.
+    assert(status_code_def);
+
+    // As there is no dedicated class to represent Status Code
+    // the OptionCustom class should be returned here.
+    boost::shared_ptr<OptionCustom> option_status =
+        boost::dynamic_pointer_cast<
+            OptionCustom>(status_code_def->optionFactory(Option::V6, D6O_STATUS_CODE));
+    assert(option_status);
+
+    // Set status code to 'code' (0 - means data field #0).
+    option_status->writeInteger(code, 0);
+    // Set a message (1 - means data field #1).
+    option_status->writeString(text, 1);
+    return (option_status);
 }
 
 Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
@@ -430,6 +447,10 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // but different wording below)
     if (!subnet) {
         // Create empty IA_NA option with IAID matching the request.
+        // Note that we don't use OptionDefinition class to create this option.
+        // This is because we prefer using a constructor of Option6IA that
+        // initializes IAID. Otherwise we would have to use setIAID() after
+        // creation of the option which has some performance implications.
         boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
 
         // Insert status code NoAddrsAvail.
@@ -471,6 +492,8 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                                                       hint, fake_allocation);
 
     // Create IA_NA that we will put in the response.
+    // Do not use OptionDefinition to create option's instance so
+    // as we can initialize IAID using a constructor.
     boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
 
     if (lease) {

+ 16 - 5
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -801,12 +801,23 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
     ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
 
     // a dummy content for client-id
-    uint8_t expected[] = {0x0, 0x3, 0x41, 0x42, 0x43, 0x44, 0x45};
-    OptionBuffer exp(expected, expected + sizeof(expected));
-
+    uint8_t expected[] = {
+        0x0, 0xD, // option code = 14
+        0x0, 0x7, // option length = 7
+        0x0, 0x3, // status code = 3
+        0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
+    };
+    // Create the option.
     OptionPtr status = srv->createStatusCode(3, "ABCDE");
-
-    EXPECT_TRUE(status->getData() == exp);
+    // Allocate an output buffer. We will store the option
+    // in wire format here.
+    OutputBuffer buf(sizeof(expected));
+    // Prepare the wire format.
+    ASSERT_NO_THROW(status->pack(buf));
+    // Check that the option buffer has valid length (option header + data).
+    ASSERT_EQ(sizeof(expected), buf.getLength());
+    // Verify the contents of the option.
+    EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected)));
 }
 
 // This test verifies if the selectSubnet() method works as expected.

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

@@ -33,10 +33,12 @@ libb10_dhcp___la_SOURCES += option6_int_array.h
 libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.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
 
 libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcp___la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libb10_dhcp___la_LIBADD  += $(top_builddir)/src/lib/dns/libb10-dns++.la
 libb10_dhcp___la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
 libb10_dhcp___la_LDFLAGS  = -no-undefined -version-info 2:0:0
 

+ 22 - 46
src/lib/dhcp/libdhcp++.cc

@@ -22,6 +22,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int_array.h>
 #include <dhcp/option_definition.h>
+#include <dhcp/std_option_defs.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 
@@ -45,7 +46,7 @@ OptionDefContainer LibDHCP::v4option_defs_;
 OptionDefContainer LibDHCP::v6option_defs_;
 
 const OptionDefContainer&
-LibDHCP::getOptionDefs(Option::Universe u) {
+LibDHCP::getOptionDefs(const Option::Universe u) {
     switch (u) {
     case Option::V4:
         initStdOptionDefs4();
@@ -60,6 +61,17 @@ LibDHCP::getOptionDefs(Option::Universe u) {
     }
 }
 
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
+    const OptionDefContainer& defs = getOptionDefs(u);
+    const OptionDefContainerTypeIndex& idx = defs.get<1>();
+    const OptionDefContainerTypeRange& range = idx.equal_range(code);
+    if (range.first != range.second) {
+        return (*range.first);
+    }
+    return (OptionDefinitionPtr());
+}
+
 OptionPtr
 LibDHCP::optionFactory(Option::Universe u,
                        uint16_t type,
@@ -254,52 +266,16 @@ void
 LibDHCP::initStdOptionDefs6() {
     v6option_defs_.clear();
 
-    struct OptionParams {
-        std::string name;
-        uint16_t code;
-        OptionDataType type;
-        bool array;
-    };
-    OptionParams params[] = {
-        { "CLIENTID", D6O_CLIENTID, OPT_BINARY_TYPE, false },
-        { "SERVERID", D6O_SERVERID, OPT_BINARY_TYPE, false },
-        { "IA_NA", D6O_IA_NA, OPT_RECORD_TYPE, false },
-        { "IAADDR", D6O_IAADDR, OPT_RECORD_TYPE, false },
-        { "ORO", D6O_ORO, OPT_UINT16_TYPE, true },
-        { "ELAPSED_TIME", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false },
-        { "STATUS_CODE", D6O_STATUS_CODE, OPT_RECORD_TYPE, false },
-        { "RAPID_COMMIT", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false },
-        { "DNS_SERVERS", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
-        { "IA_PD", D6O_IA_PD, OPT_RECORD_TYPE, false }
-    };
-    const int params_size = sizeof(params) / sizeof(params[0]);
-
-    for (int i = 0; i < params_size; ++i) {
-        OptionDefinitionPtr definition(new OptionDefinition(params[i].name,
-                                                            params[i].code,
-                                                            params[i].type,
-                                                            params[i].array));
-        switch(params[i].code) {
-        case D6O_IA_NA:
-        case D6O_IA_PD:
-            for (int j = 0; j < 3; ++j) {
-                definition->addRecordField(OPT_UINT32_TYPE);
-            }
-            break;
-        case D6O_IAADDR:
-            definition->addRecordField(OPT_IPV6_ADDRESS_TYPE);
-            definition->addRecordField(OPT_UINT32_TYPE);
-            definition->addRecordField(OPT_UINT32_TYPE);
-            break;
-        case D6O_STATUS_CODE:
-            definition->addRecordField(OPT_UINT16_TYPE);
-            definition->addRecordField(OPT_STRING_TYPE);
-            break;
-        default:
-            // The default case is intentionally left empty
-            // as it does not need any processing.
-            ;
+    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));
+
+        for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
+            definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
         }
+
         try {
             definition->validate();
         } catch (const Exception& ex) {

+ 12 - 1
src/lib/dhcp/libdhcp++.h

@@ -42,7 +42,18 @@ public:
     /// @param u universe of the options (V4 or V6).
     ///
     /// @return collection of option definitions.
-    static const OptionDefContainer& getOptionDefs(Option::Universe u);
+    static const OptionDefContainer& getOptionDefs(const Option::Universe u);
+
+    /// @brief Return the first option definition matching a
+    /// particular option code.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param code option code.
+    ///
+    /// @return reference to an option definition being requested
+    /// or NULL pointer if option definition has not been found.
+    static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+                                            const uint16_t code);
 
     /// @brief Factory function to create instance of option.
     ///

+ 300 - 34
src/lib/dhcp/option_custom.cc

@@ -21,11 +21,28 @@ namespace isc {
 namespace dhcp {
 
 OptionCustom::OptionCustom(const OptionDefinition& def,
+                           Universe u)
+    : Option(u, def.getCode(), OptionBuffer()),
+      definition_(def) {
+    createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
                              Universe u,
                              const OptionBuffer& data)
     : Option(u, def.getCode(), data.begin(), data.end()),
       definition_(def) {
-    createBuffers();
+    // It is possible that no data is provided if an option
+    // is being created on a server side. In such case a bunch
+    // of buffers with default values is first created and then
+    // the values are replaced using writeXXX functions. Thus
+    // we need to detect that no data has been specified and
+    // take a different code path.
+    if (!data_.empty()) {
+        createBuffers(data_);
+    } else {
+        createBuffers();
+    }
 }
 
 OptionCustom::OptionCustom(const OptionDefinition& def,
@@ -34,25 +51,156 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
                              OptionBufferConstIter last)
     : Option(u, def.getCode(), first, last),
       definition_(def) {
-    createBuffers();
+    createBuffers(data_);
+}
+
+void
+OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+    checkArrayType();
+
+    if ((address.getFamily() == AF_INET &&
+         definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
+        (address.getFamily() == AF_INET6 &&
+         definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
+        isc_throw(BadDataTypeCast, "invalid address specified "
+                  << address.toText() << ". Expected a valid IPv"
+                  << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ? "4" : "6")
+                  << " address.");
+    }
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writeAddress(address, buf);
+    buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const bool value) {
+    checkArrayType();
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writeBool(value, buf);
+    buffers_.push_back(buf);
 }
 
 void
 OptionCustom::checkIndex(const uint32_t index) const {
     if (index >= buffers_.size()) {
         isc_throw(isc::OutOfRange, "specified data field index " << index
-                  << " is out of rangex.");
+                  << " is out of range.");
+    }
+}
+
+template<typename T>
+void
+OptionCustom::checkDataType(const uint32_t index) const {
+    // Check that the requested return type is a supported integer.
+    if (!OptionDataTypeTraits<T>::integer_type) {
+        isc_throw(isc::dhcp::InvalidDataType, "specified data type"
+                  " is not a supported integer type.");
+    }
+
+    // Get the option definition type.
+    OptionDataType data_type = definition_.getType();
+    if (data_type == OPT_RECORD_TYPE) {
+        const OptionDefinition::RecordFieldsCollection& record_fields =
+            definition_.getRecordFields();
+        // When we initialized buffers we have already checked that
+        // the number of these buffers is equal to number of option
+        // fields in the record so the condition below should be met.
+        assert(index < record_fields.size());
+        // Get the data type to be returned.
+        data_type = record_fields[index];
+    }
+
+    if (OptionDataTypeTraits<T>::type != data_type) {
+        isc_throw(isc::dhcp::InvalidDataType,
+                  "specified data type " << data_type << " does not"
+                  " match the data type in an option definition for field"
+                  " index " << index);
     }
 }
 
 void
 OptionCustom::createBuffers() {
+    definition_.validate();
+
+    std::vector<OptionBuffer> buffers;
+
+    OptionDataType data_type = definition_.getType();
+    // This function is called when an empty data buffer has been
+    // passed to the constructor. In such cases values for particular
+    // data fields will be set using modifier functions but for now
+    // we need to initialize a set of buffers that are specified
+    // for an option by its definition. Since there is no data yet,
+    // we are going to fill these buffers with default values.
+    if (data_type == OPT_RECORD_TYPE) {
+        // For record types we need to iterate over all data fields
+        // specified in option definition and create corresponding
+        // buffers for each of them.
+        const OptionDefinition::RecordFieldsCollection fields =
+            definition_.getRecordFields();
+
+        for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+             field != fields.end(); ++field) {
+            OptionBuffer buf;
+
+            // For data types that have a fixed size we can use the
+            // utility function to get the buffer's size.
+            size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+            // For variable data sizes the utility function returns zero.
+            // It is ok for string values because the default string
+            // is 'empty'. However for FQDN the empty value is not valid
+            // so we initialize it to '.'.
+            if (data_size == 0 &&
+                *field == OPT_FQDN_TYPE) {
+                OptionDataTypeUtil::writeFqdn(".", buf);
+            } else {
+                // At this point we can resize the buffer. Note that
+                // for string values we are setting the empty buffer
+                // here.
+                buf.resize(data_size);
+            }
+            // We have the buffer with default value prepared so we
+            // add it to the set of buffers.
+            buffers.push_back(buf);
+        }
+    } else if (!definition_.getArrayType() &&
+               data_type != OPT_EMPTY_TYPE) {
+        // For either 'empty' options we don't have to create any buffers
+        // for obvious reason. For arrays we also don't create any buffers
+        // yet because the set of fields that belong to the array is open
+        // ended so we can't allocate required buffers until we know how
+        // many of them are needed.
+        // For non-arrays we have a single value being held by the option
+        // so we have to allocate exactly one buffer.
+        OptionBuffer buf;
+        size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+        if (data_size == 0 &&
+            data_type == OPT_FQDN_TYPE) {
+            OptionDataTypeUtil::writeFqdn(".", buf);
+        } else {
+            // Note that if our option holds a string value then
+            // we are making empty buffer here.
+            buf.resize(data_size);
+        }
+        // Add a buffer that we have created and leave.
+        buffers.push_back(buf);
+    }
+    // The 'swap' is used here because we want to make sure that we
+    // don't touch buffers_ until we successfully allocate all
+    // buffers to be stored there.
+    std::swap(buffers, buffers_);
+}
+
+void
+OptionCustom::createBuffers(const OptionBuffer& data_buf) {
     // Check that the option definition is correct as we are going
     // to use it to split the data_ buffer into set of sub buffers.
     definition_.validate();
 
     std::vector<OptionBuffer> buffers;
-    OptionBuffer::iterator data = data_.begin();
+    OptionBuffer::const_iterator data = data_buf.begin();
 
     OptionDataType data_type = definition_.getType();
     if (data_type == OPT_RECORD_TYPE) {
@@ -68,17 +216,31 @@ OptionCustom::createBuffers() {
             // For fixed-size data type such as boolean, integer, even
             // IP address we can use the utility function to get the required
             // buffer size.
-            int data_size = OptionDataTypeUtil::getDataTypeLen(*field);
-
-            // For variable size types (such as string) the function above
-            // will return 0 so we need to do a runtime check. Since variable
-            // length data fields may be laid only at the end of an option we
-            // consume the rest of this option. Note that validate() function
-            // in OptionDefinition object should have checked whether the
-            // data fields layout is correct (that the variable string fields
-            // are laid at the end).
+            size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+            // For variable size types (e.g. string) the function above will
+            // return 0 so we need to do a runtime check of the length.
             if (data_size == 0) {
-                data_size = std::distance(data, data_.end());
+                // FQDN is a special data type as it stores variable length data
+                // but the data length is encoded in the buffer. The easiest way
+                // to obtain the length of the data is to read the FQDN. The
+                // utility function will return the size of the buffer on success.
+                if (*field == OPT_FQDN_TYPE) {
+                    std::string fqdn =
+                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+                    // The size of the buffer holding an FQDN is always
+                    // 1 byte larger than the size of the string
+                    // representation of this FQDN.
+                    data_size = fqdn.size() + 1;
+                } else {
+                    // In other case we are dealing with string or binary value
+                    // which size can't be determined. Thus we consume the
+                    // remaining part of the buffer for it. Note that variable
+                    // size data can be laid at the end of the option only and
+                    // that the validate() function in OptionDefinition object
+                    // should have checked wheter it is a case for this option.
+                    data_size = std::distance(data, data_buf.end());
+                }
                 if (data_size == 0) {
                     // If we reached the end of buffer we assume that this option is
                     // truncated because there is no remaining data to initialize
@@ -90,7 +252,7 @@ OptionCustom::createBuffers() {
             } else {
                 // Our data field requires that there is a certain chunk of
                 // data left in the buffer. If not, option is truncated.
-                if (std::distance(data, data_.end()) < data_size) {
+                if (std::distance(data, data_buf.end()) < data_size) {
                     isc_throw(OutOfRange, "option buffer truncated");
                 }
             }
@@ -105,39 +267,64 @@ OptionCustom::createBuffers() {
         // data fields of the same type. The type of those fields
         // is held in the data_type variable so let's use it to determine
         // a size of buffers.
-        int data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+        size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
         // The check below will fail if the input buffer is too short
         // for the data size being held by this option.
         // Note that data_size returned by getDataTypeLen may be zero
         // if variable length data is being held by the option but
         // this will not cause this check to throw exception.
-        if (std::distance(data, data_.end()) < data_size) {
+        if (std::distance(data, data_buf.end()) < data_size) {
             isc_throw(OutOfRange, "option buffer truncated");
         }
         // For an array of values we are taking different path because
         // we have to handle multiple buffers.
         if (definition_.getArrayType()) {
-            // We don't perform other checks for data types that can't be
-            // used together with array indicator such as strings, empty field
-            // etc. This is because OptionDefinition::validate function should
-            // have checked this already. Thus data_size must be greater than
-            // zero.
-            assert(data_size > 0);
-            // Get equal chunks of data and store as collection of buffers.
-            // Truncate any remaining part which length is not divisible by
-            // data_size. Note that it is ok to truncate the data if and only
-            // if the data buffer is long enough to keep at least one value.
-            // This has been checked above already.
-            do {
+            while (data != data_buf.end()) {
+                // FQDN is a special case because it is of a variable length.
+                // The actual length for a particular FQDN is encoded within
+                // a buffer so we have to actually read the FQDN from a buffer
+                // to get it.
+                if (data_type == OPT_FQDN_TYPE) {
+                    std::string fqdn =
+                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+                    // The size of the buffer holding an FQDN is always
+                    // 1 byte larger than the size of the string
+                    // representation of this FQDN.
+                    data_size = fqdn.size() + 1;
+                }
+                // We don't perform other checks for data types that can't be
+                // used together with array indicator such as strings, empty field
+                // etc. This is because OptionDefinition::validate function should
+                // have checked this already. Thus data_size must be greater than
+                // zero.
+                assert(data_size > 0);
+                // Get chunks of data and store as a collection of buffers.
+                // Truncate any remaining part which length is not divisible by
+                // data_size. Note that it is ok to truncate the data if and only
+                // if the data buffer is long enough to keep at least one value.
+                // This has been checked above already.
+                if (std::distance(data, data_buf.end()) < data_size) {
+                    break;
+                }
                 buffers.push_back(OptionBuffer(data, data + data_size));
                 data += data_size;
-            } while (std::distance(data, data_.end()) >= data_size);
+            }
         } else {
             // For non-arrays the data_size can be zero because
             // getDataTypeLen returns zero for variable size data types
             // such as strings. Simply take whole buffer.
             if (data_size == 0) {
-                data_size = std::distance(data, data_.end());
+                // For FQDN we get the size by actually reading the FQDN.
+                if (data_type == OPT_FQDN_TYPE) {
+                    std::string fqdn =
+                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+                    // The size of the buffer holding an FQDN is always
+                    // 1 bytes larger than the size of the string
+                    // representation of this FQDN.
+                    data_size = fqdn.size() + 1;
+                } else {
+                    data_size = std::distance(data, data_buf.end());
+                }
             }
             if (data_size > 0) {
                 buffers.push_back(OptionBuffer(data, data + data_size));
@@ -185,6 +372,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
     case OPT_IPV6_ADDRESS_TYPE:
         text << readAddress(index).toText();
         break;
+    case OPT_FQDN_TYPE:
+        text << readFqdn(index);
+        break;
     case OPT_STRING_TYPE:
         text << readString(index);
         break;
@@ -252,8 +442,31 @@ OptionCustom::readAddress(const uint32_t index) const {
         return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
     } else {
         isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
-                  << " IP address. Invalid buffer length " << buffers_[index].size());
+                  << " IP address. Invalid buffer length "
+                  << buffers_[index].size() << ".");
+    }
+}
+
+void
+OptionCustom::writeAddress(const asiolink::IOAddress& address,
+                           const uint32_t index) {
+    using namespace isc::asiolink;
+
+    checkIndex(index);
+
+    if ((address.getFamily() == AF_INET &&
+         buffers_[index].size() != V4ADDRESS_LEN) ||
+        (address.getFamily() == AF_INET6 &&
+         buffers_[index].size() != V6ADDRESS_LEN)) {
+        isc_throw(BadDataTypeCast, "invalid address specified "
+                  << address.toText() << ". Expected a valid IPv"
+                  << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
+                  << " address.");
     }
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writeAddress(address, buf);
+    std::swap(buf, buffers_[index]);
 }
 
 const OptionBuffer&
@@ -262,12 +475,50 @@ OptionCustom::readBinary(const uint32_t index) const {
     return (buffers_[index]);
 }
 
+void
+OptionCustom::writeBinary(const OptionBuffer& buf,
+                          const uint32_t index) {
+    checkIndex(index);
+    buffers_[index] = buf;
+}
+
 bool
 OptionCustom::readBoolean(const uint32_t index) const {
     checkIndex(index);
     return (OptionDataTypeUtil::readBool(buffers_[index]));
 }
 
+void
+OptionCustom::writeBoolean(const bool value, const uint32_t index) {
+    checkIndex(index);
+
+    buffers_[index].clear();
+    OptionDataTypeUtil::writeBool(value, buffers_[index]);
+}
+
+std::string
+OptionCustom::readFqdn(const uint32_t index) const {
+    checkIndex(index);
+    return (OptionDataTypeUtil::readFqdn(buffers_[index]));
+}
+
+void
+OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
+    checkIndex(index);
+
+    // Create a temporay buffer where the FQDN will be written.
+    OptionBuffer buf;
+    // Try to write to the temporary buffer rather than to the
+    // buffers_ member directly guarantees that we don't modify
+    // (clear) buffers_ until we are sure that the provided FQDN
+    // is valid.
+    OptionDataTypeUtil::writeFqdn(fqdn, buf);
+    // If we got to this point it means that the FQDN is valid.
+    // We can move the contents of the teporary buffer to the
+    // target buffer.
+    std::swap(buffers_[index], buf);
+}
+
 std::string
 OptionCustom::readString(const uint32_t index) const {
     checkIndex(index);
@@ -275,11 +526,26 @@ OptionCustom::readString(const uint32_t index) const {
 }
 
 void
+OptionCustom::writeString(const std::string& text, const uint32_t index) {
+    checkIndex(index);
+
+    // Let's clear a buffer as we want to replace the value of the
+    // whole buffer. If we fail to clear the buffer the data will
+    // be appended.
+    buffers_[index].clear();
+    // If the text value is empty we can leave because the buffer
+    // is already empty.
+    if (!text.empty()) {
+        OptionDataTypeUtil::writeString(text, buffers_[index]);
+    }
+}
+
+void
 OptionCustom::unpack(OptionBufferConstIter begin,
                      OptionBufferConstIter end) {
     data_ = OptionBuffer(begin, end);
     // Chop the buffer stored in data_ into set of sub buffers.
-    createBuffers();
+    createBuffers(data_);
 }
 
 uint16_t
@@ -311,7 +577,7 @@ void OptionCustom::setData(const OptionBufferConstIter first,
 
     // Chop the data_ buffer into set of buffers that represent
     // option fields data.
-    createBuffers();
+    createBuffers(data_);
 }
 
 std::string OptionCustom::toText(int indent) {

+ 158 - 34
src/lib/dhcp/option_custom.h

@@ -40,6 +40,17 @@ public:
 
     /// @brief Constructor, used for options to be sent.
     ///
+    /// This constructor creates an instance of an option with default
+    /// data set for all data fields. The option buffers are allocated
+    /// according to data size being stored in particular data fields.
+    /// For variable size data empty buffers are created.
+    ///
+    /// @param def option definition.
+    /// @param u specifies universe (V4 or V6)
+    OptionCustom(const OptionDefinition& def, Universe u);
+
+    /// @brief Constructor, used for options to be sent.
+    ///
     /// This constructor creates an instance of an option from the whole
     /// supplied buffer. This constructor is mainly used to create an
     /// instances of options to be stored in outgoing DHCP packets.
@@ -76,6 +87,37 @@ public:
     OptionCustom(const OptionDefinition& def, Universe u,
                  OptionBufferConstIter first, OptionBufferConstIter last);
 
+    /// @brief Create new buffer and set its value as an IP address.
+    ///
+    /// @param address IPv4 or IPv6 address to be written to
+    /// a buffer being created.
+    void addArrayDataField(const asiolink::IOAddress& address);
+
+    /// @brief Create new buffer and store boolean value in it.
+    ///
+    /// @param value value to be stored in the created buffer.
+    void addArrayDataField(const bool value);
+
+    /// @brief Create new buffer and store integer value in it.
+    ///
+    /// @param value value to be stored in the created buffer.
+    /// @tparam T integer type of the value being stored.
+    template<typename T>
+    void addArrayDataField(const T value) {
+        checkArrayType();
+
+        OptionDataType data_type = definition_.getType();
+        if (OptionDataTypeTraits<T>::type != data_type) {
+            isc_throw(isc::dhcp::InvalidDataType,
+                      "specified data type " << data_type << " does not"
+                      " match the data type in an option definition");
+        }
+
+        OptionBuffer buf;
+        OptionDataTypeUtil::writeInt<T>(value, buf);
+        buffers_.push_back(buf);
+    }
+
     /// @brief Return a number of the data fields.
     ///
     /// @return number of data fields held by the option.
@@ -87,7 +129,17 @@ public:
     ///
     /// @return IP address read from a buffer.
     /// @throw isc::OutOfRange if index is out of range.
-    asiolink::IOAddress readAddress(const uint32_t index) const;
+    asiolink::IOAddress readAddress(const uint32_t index = 0) const;
+
+    /// @brief Write an IP address into a buffer.
+    ///
+    /// @param address IP address being written.
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid.
+    void writeAddress(const asiolink::IOAddress& address,
+                      const uint32_t index = 0);
 
     /// @brief Read a buffer as binary data.
     ///
@@ -95,7 +147,13 @@ public:
     ///
     /// @throw isc::OutOfRange if index is out of range.
     /// @return read buffer holding binary data.
-    const OptionBuffer& readBinary(const uint32_t index) const;
+    const OptionBuffer& readBinary(const uint32_t index = 0) const;
+
+    /// @brief Write binary data into a buffer.
+    ///
+    /// @param buf buffer holding binary data to be written.
+    /// @param index buffer index.
+    void writeBinary(const OptionBuffer& buf, const uint32_t index = 0);
 
     /// @brief Read a buffer as boolean value.
     ///
@@ -103,7 +161,34 @@ public:
     ///
     /// @throw isc::OutOfRange if index is out of range.
     /// @return read boolean value.
-    bool readBoolean(const uint32_t index) const;
+    bool readBoolean(const uint32_t index = 0) const;
+
+    /// @brief Write a boolean value into a buffer.
+    ///
+    /// @param value boolean value to be written.
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    void writeBoolean(const bool value, const uint32_t index = 0);
+
+    /// @brief Read a buffer as FQDN.
+    ///
+    /// @param index buffer index.
+    /// @param [out] len number of bytes read from a buffer.
+    ///
+    /// @throw isc::OutOfRange if buffer index is out of range.
+    /// @throw isc::dhcp::BadDataTypeCast if a buffer being read
+    /// does not hold a valid FQDN.
+    /// @return string representation if FQDN.
+    std::string readFqdn(const uint32_t index = 0) const;
+
+    /// @brief Write an FQDN into a buffer.
+    ///
+    /// @param fqdn text representation of FQDN.
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    void writeFqdn(const std::string& fqdn, const uint32_t index = 0);
 
     /// @brief Read a buffer as integer value.
     ///
@@ -111,38 +196,15 @@ public:
     /// @tparam integer type of a value being returned.
     ///
     /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::InvalidDataType if T is invalid.
     /// @return read integer value.
     template<typename T>
-    T readInteger(const uint32_t index) const {
+    T readInteger(const uint32_t index = 0) const {
+        // Check that the index is not out of range.
         checkIndex(index);
-
-        // Check that the requested return type is a supported integer.
-        if (!OptionDataTypeTraits<T>::integer_type) {
-            isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
-                      " by readInteger is not supported integer type");
-        }
-
-        // Get the option definition type.
-        OptionDataType data_type = definition_.getType();
-        if (data_type == OPT_RECORD_TYPE) {
-            const OptionDefinition::RecordFieldsCollection& record_fields =
-                definition_.getRecordFields();
-            // When we initialized buffers we have already checked that
-            // the number of these buffers is equal to number of option
-            // fields in the record so the condition below should be met.
-            assert(index < record_fields.size());
-            // Get the data type to be returned.
-            data_type = record_fields[index];
-        }
-
-        // Requested data type must match the data type in a record.
-        if (OptionDataTypeTraits<T>::type != data_type) {
-            isc_throw(isc::dhcp::InvalidDataType,
-                      "unable to read option field with index " << index
-                      << " as integer value. The field's data type"
-                      << data_type << " does not match the integer type"
-                      << "returned by the readInteger function.");
-        }
+        // Check that T points to a valid integer type and this type
+        // is consistent with an option definition.
+        checkDataType<T>(index);
         // When we created the buffer we have checked that it has a
         // valid size so this condition here should be always fulfiled.
         assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
@@ -150,13 +212,43 @@ public:
         return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
     }
 
+    /// @brief Write an integer value into a buffer.
+    ///
+    /// @param value integer value to be written.
+    /// @param index buffer index.
+    /// @tparam T integer type of a value being written.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::InvalidDataType if T is invalid.
+    template<typename T>
+    void writeInteger(const T value, const uint32_t index = 0) {
+        // Check that the index is not out of range.
+        checkIndex(index);
+        // Check that T points to a valid integer type and this type
+        // is consistent with an option definition.
+        checkDataType<T>(index);
+        // Get some temporary buffer.
+        OptionBuffer buf;
+        // Try to write to the buffer.
+        OptionDataTypeUtil::writeInt<T>(value, buf);
+        // If successful, replace the old buffer with new one.
+        std::swap(buffers_[index], buf);
+    }
+
     /// @brief Read a buffer as string value.
     ///
     /// @param index buffer index.
     ///
     /// @return string value read from buffer.
     /// @throw isc::OutOfRange if index is out of range.
-    std::string readString(const uint32_t index) const;
+    std::string readString(const uint32_t index = 0) const;
+
+    /// @brief Write a string value into a buffer.
+    ///
+    /// @param text the string value to be written.
+    /// @param buffer index.
+    void writeString(const std::string& text,
+                     const uint32_t index = 0);
 
     /// @brief Parses received buffer.
     ///
@@ -201,6 +293,33 @@ protected:
 
 private:
 
+    /// @brief Verify that the option comprises an array of values.
+    ///
+    /// This helper function is used by createArrayEntry functions
+    /// and throws an exception if the particular option is not
+    /// an array.
+    ///
+    /// @throw isc::InvalidOperation if option is not an array.
+    inline void checkArrayType() const {
+        if (!definition_.getArrayType()) {
+            isc_throw(InvalidOperation, "failed to add new array entry to an"
+                      << " option. The option is not an array.");
+        }
+    }
+
+    /// @brief Verify that the integer type is consistent with option
+    /// field type.
+    ///
+    /// This convenience function checks that the data type specified as T
+    /// is consistent with a type of a data field identified by index.
+    ///
+    /// @param index data field index.
+    /// @tparam data type to be validated.
+    ///
+    /// @throw isc::dhcp::InvalidDataType if the type is invalid.
+    template<typename T>
+    void checkDataType(const uint32_t index) const;
+
     /// @brief Check if data field index is valid.
     ///
     /// @param index Data field index to check.
@@ -208,9 +327,14 @@ private:
     /// @throw isc::OutOfRange if index is out of range.
     void checkIndex(const uint32_t index) const;
 
-    /// @brief Create collection of buffers representing data field values.
+    /// @brief Create a collection of non initialized buffers.
     void createBuffers();
 
+    /// @brief Create collection of buffers representing data field values.
+    ///
+    /// @param data_buf a buffer to be parsed.
+    void createBuffers(const OptionBuffer& data_buf);
+
     /// @brief Return a text representation of a data field.
     ///
     /// @param data_type data type of a field.

+ 40 - 1
src/lib/dhcp/option_data_types.cc

@@ -13,6 +13,8 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dhcp/option_data_types.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
 #include <util/encode/hex.h>
 
 namespace isc {
@@ -207,9 +209,46 @@ OptionDataTypeUtil::writeBool(const bool value,
 }
 
 std::string
+OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
+    // If buffer is empty emit an error.
+    if (buf.empty()) {
+        isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer."
+                  << " The buffer is empty.");
+    }
+    // Set up an InputBuffer so as we can use isc::dns::Name object to get the FQDN.
+    isc::util::InputBuffer in_buf(static_cast<const void*>(&buf[0]), buf.size());
+    try {
+        // Try to create an object from the buffer. If exception is thrown
+        // it means that the buffer doesn't hold a valid domain name (invalid
+        // syntax).
+        isc::dns::Name name(in_buf);
+        return (name.toText());
+    } catch (const isc::Exception& ex) {
+        // Unable to convert the data in the buffer into FQDN.
+        isc_throw(BadDataTypeCast, ex.what());
+    }
+}
+
+void
+OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
+                              std::vector<uint8_t>& buf) {
+    try {
+        isc::dns::Name name(fqdn);
+        isc::dns::LabelSequence labels(name);
+        if (labels.getDataLength() > 0) {
+            size_t read_len = 0;
+            const uint8_t* data = labels.getData(&read_len);
+            buf.insert(buf.end(), data, data + read_len);
+        }
+    } catch (const isc::Exception& ex) {
+        isc_throw(BadDataTypeCast, ex.what());
+    }
+}
+
+std::string
 OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
     std::string value;
-    if (buf.size() > 0) {
+    if (!buf.empty()) {
         value.insert(value.end(), buf.begin(), buf.end());
     }
     return (value);

+ 42 - 2
src/lib/dhcp/option_data_types.h

@@ -225,11 +225,13 @@ public:
     /// @return data type size or zero for variable length types.
     static int getDataTypeLen(const OptionDataType data_type);
 
-    /// @brief Read IPv4 or IPv6 addres from a buffer.
+    /// @brief Read IPv4 or IPv6 address from a buffer.
     ///
     /// @param buf input buffer.
     /// @param family address family: AF_INET or AF_INET6.
     /// 
+    /// @throw isc::dhcp::BadDataTypeCast when the data being read
+    /// is truncated.
     /// @return address being read.
     static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
                                            const short family);
@@ -252,6 +254,9 @@ public:
     /// @brief Read boolean value from a buffer.
     ///
     /// @param buf input buffer.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast when the data being read
+    /// is truncated or the value is invalid (neither 1 nor 0).
     /// @return boolean value read from a buffer.
     static bool readBool(const std::vector<uint8_t>& buf);
 
@@ -268,6 +273,9 @@ public:
     ///
     /// @param buf input buffer.
     /// @tparam integer type of the returned value.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast when the data in the buffer
+    /// is truncated.
     /// @return integer value being read.
     template<typename T>
     static T readInt(const std::vector<uint8_t>& buf) {
@@ -276,7 +284,12 @@ public:
                       " by readInteger is unsupported integer type");
         }
 
-        assert(buf.size() == OptionDataTypeTraits<T>::len);
+        if (buf.size() < OptionDataTypeTraits<T>::len) {
+            isc_throw(isc::dhcp::BadDataTypeCast,
+                      "failed to read an integer value from a buffer"
+                      << " - buffer is truncated.");
+        }
+
         T value;
         switch (OptionDataTypeTraits<T>::len) {
         case 1:
@@ -332,6 +345,33 @@ public:
         }
     }
 
+    /// @brief Read FQDN from a buffer as a string value.
+    ///
+    /// The format of an FQDN within a buffer complies with RFC1035,
+    /// section 3.1.
+    ///
+    /// @param buf input buffer holding a FQDN.
+    ///
+    /// @throw BadDataTypeCast if a FQDN stored within a buffer is
+    /// invalid (e.g. empty, contains invalid characters, truncated).
+    /// @return fully qualified domain name in a text form.
+    static std::string readFqdn(const std::vector<uint8_t>& buf);
+
+    /// @brief Append FQDN into a buffer.
+    ///
+    /// This method appends the Fully Qualified Domain Name (FQDN)
+    /// represented as string value into a buffer. The format of
+    /// the FQDN being stored into a buffer complies with RFC1035,
+    /// section 3.1.
+    ///
+    /// @param fqdn fully qualified domain name to be written.
+    /// @param [out] buf output buffer.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
+    /// is invalid.
+    static void writeFqdn(const std::string& fqdn,
+                          std::vector<uint8_t>& buf);
+
     /// @brief Read string value from a buffer.
     ///
     /// @param buf input buffer.

+ 62 - 33
src/lib/dhcp/option_definition.cc

@@ -19,6 +19,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int.h>
 #include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
 #include <dhcp/option_definition.h>
 #include <util/encode/hex.h>
 
@@ -78,53 +79,81 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                                 OptionBufferConstIter begin,
                                 OptionBufferConstIter end) const {
     validate();
-    
+
     try {
-        if (type_ == OPT_BINARY_TYPE) {
+        switch(type_) {
+        case OPT_EMPTY_TYPE:
+            return (factoryEmpty(u, type));
+
+        case OPT_BINARY_TYPE:
             return (factoryGeneric(u, type, begin, end));
 
-        } else if (type_ == OPT_IPV6_ADDRESS_TYPE && array_type_) {
-            return (factoryAddrList6(type, begin, end));
+        case OPT_UINT8_TYPE:
+            return (array_type_ ? factoryGeneric(u, type, begin, end) :
+                    factoryInteger<uint8_t>(u, type, begin, end));
 
-        } else if (type_ == OPT_IPV4_ADDRESS_TYPE && array_type_) {
-            return (factoryAddrList4(type, begin, end));
+        case OPT_INT8_TYPE:
+            return (array_type_ ? factoryGeneric(u, type, begin, end) :
+                    factoryInteger<int8_t>(u, type, begin, end));
 
-        } else if (type_ == OPT_EMPTY_TYPE) {
-            return (factoryEmpty(u, type));
+        case OPT_UINT16_TYPE:
+            return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+                    factoryInteger<uint16_t>(u, type, begin, end));
 
-        } else if (u == Option::V6 &&
-                   code_ == D6O_IA_NA &&
-                   haveIA6Format()) {
-            return (factoryIA6(type, begin, end));
+        case OPT_INT16_TYPE:
+            return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+                    factoryInteger<int16_t>(u, type, begin, end));
 
-        } else if (u == Option::V6 &&
-                   code_ == D6O_IAADDR &&
-                   haveIAAddr6Format()) {
-            return (factoryIAAddr6(type, begin, end));
+        case OPT_UINT32_TYPE:
+            return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+                    factoryInteger<uint32_t>(u, type, begin, end));
 
-        } else if (type_ == OPT_UINT8_TYPE) {
-            if (array_type_) {
-                return (factoryGeneric(u, type, begin, end));
-            } else {
-                return (factoryInteger<uint8_t>(u, type, begin, end));
-            }
+        case OPT_INT32_TYPE:
+            return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+                    factoryInteger<int32_t>(u, type, begin, end));
 
-        } else if (type_ == OPT_UINT16_TYPE) {
+        case OPT_IPV4_ADDRESS_TYPE:
+            // If definition specifies that an option is an array
+            // of IPv4 addresses we return an instance of specialized
+            // class (OptionAddrLst4). For non-array types there is no
+            // specialized class yet implemented so we drop through
+            // to return an instance of OptionCustom.
             if (array_type_) {
-                return (factoryIntegerArray<uint16_t>(type, begin, end));
-            } else {
-                return (factoryInteger<uint16_t>(u, type, begin, end));
+                return (factoryAddrList4(type, begin, end));
             }
+            break;
 
-        } else if (type_ == OPT_UINT32_TYPE) {
+        case OPT_IPV6_ADDRESS_TYPE:
+            // Handle array type only here (see comments for
+            // OPT_IPV4_ADDRESS_TYPE case).
             if (array_type_) {
-                return (factoryIntegerArray<uint32_t>(type, begin, end));
-            } else {
-                return (factoryInteger<uint32_t>(u, type, begin, end));
+                return (factoryAddrList6(type, begin, end));
+            }
+            break;
+
+        default:
+            if (u == Option::V6) {
+                if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) &&
+                    haveIA6Format()) {
+                    // Return Option6IA instance for IA_PD and IA_NA option
+                    // types only. We don't want to return Option6IA for other
+                    // options that comprise 3 UINT32 data fields because
+                    // Option6IA accessors' and modifiers' names are derived
+                    // from the IA_NA and IA_PD options' field names: IAID,
+                    // T1, T2. Using functions such as getIAID, getT1 etc. for
+                    // options other than IA_NA and IA_PD would be bad practice
+                    // and cause confusion.
+                    return (factoryIA6(type, begin, end));
+
+                } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
+                    // Rerurn Option6IAAddr option instance for the IAADDR
+                    // option only for the same reasons as described in
+                    // for IA_NA and IA_PD above.
+                    return (factoryIAAddr6(type, begin, end));
+                }
             }
-
         }
-        return (factoryGeneric(u, type, begin, end));
+        return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end))));
 
     } catch (const Exception& ex) {
         isc_throw(InvalidOptionValue, ex.what());
@@ -144,7 +173,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
 
     OptionBuffer buf;
     if (!array_type_ && type_ != OPT_RECORD_TYPE) {
-        if (values.size() == 0) {
+        if (values.empty()) {
             isc_throw(InvalidOptionValue, "no option value specified");
         }
         writeToBuffer(values[0], type_, buf);

+ 1 - 1
src/lib/dhcp/option_definition.h

@@ -246,7 +246,7 @@ public:
     /// @throw MalformedOptionDefinition if option definition is invalid.
     /// @throw InvalidOptionValue if data for the option is invalid.
     OptionPtr optionFactory(Option::Universe u, uint16_t type,
-                            const OptionBuffer& buf) const;
+                            const OptionBuffer& buf = OptionBuffer()) const;
 
     /// @brief Option factory.
     ///

+ 155 - 0
src/lib/dhcp/std_option_defs.h

@@ -0,0 +1,155 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef STD_OPTION_DEFS_H
+#define STD_OPTION_DEFS_H
+
+#include <dhcp/option_data_types.h>
+
+namespace {
+
+/// @brief Declare an array holding parameters used to create instance
+/// of a definition for option comprising a record of data fields.
+///
+/// @param name name of the array being declared.
+/// @param types data types of fields that belong to the record.
+#ifndef RECORD_DECL
+#define RECORD_DECL(name, types...) static OptionDataType name[] = { types }
+#endif
+
+/// @brief A pair of values: one pointing to the array holding types of
+/// data fields belonging to the record, and size of this array.
+///
+/// @param name name of the array holding data fields' types.
+#ifndef RECORD_DEF
+#define RECORD_DEF(name) name, sizeof(name) / sizeof(name[0])
+#endif
+
+using namespace isc::dhcp;
+
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+    const char* name;         // option name
+    uint16_t code;            // option code
+    OptionDataType type;      // data type
+    bool array;               // is array
+    OptionDataType* records;  // record fields
+    size_t records_size;      // number of fields in a record
+};
+
+// client-fqdn
+RECORD_DECL(clientFqdnRecords, OPT_UINT8_TYPE, OPT_FQDN_TYPE);
+// geoconf-civic
+RECORD_DECL(geoconfCivicRecords, OPT_UINT8_TYPE, OPT_UINT16_TYPE,
+            OPT_BINARY_TYPE);
+// iaddr
+RECORD_DECL(iaaddrRecords, OPT_IPV6_ADDRESS_TYPE, OPT_UINT32_TYPE,
+            OPT_UINT32_TYPE);
+// ia-na
+RECORD_DECL(ianaRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-pd
+RECORD_DECL(iapdRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-prefix
+RECORD_DECL(iaPrefixRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE,
+            OPT_UINT8_TYPE, OPT_BINARY_TYPE);
+// lq-query
+RECORD_DECL(lqQueryRecords, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
+// lq-relay-data
+RECORD_DECL(lqRelayData, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
+// remote-id
+RECORD_DECL(remoteIdRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// status-code
+RECORD_DECL(statusCodeRecords, OPT_UINT16_TYPE, OPT_STRING_TYPE);
+// vendor-class
+RECORD_DECL(vendorClassRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// vendor-opts
+RECORD_DECL(vendorOptsRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+
+/// Stdandard DHCPv6 option definitions.
+static const OptionDefParams OPTION_DEF_PARAMS6[] = {
+    { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false },
+    { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false },
+    { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(ianaRecords) },
+    { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false },
+    { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(iaaddrRecords) },
+    { "oro", D6O_ORO, OPT_UINT16_TYPE, true },
+    { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false },
+    { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false },
+    { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false },
+    // 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 },
+    { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false },
+    { "status-code", D6O_STATUS_CODE, OPT_RECORD_TYPE, false,
+      RECORD_DEF(statusCodeRecords) },
+    { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false },
+    { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false },
+    { "vendor-class", D6O_VENDOR_CLASS, OPT_RECORD_TYPE, false,
+      RECORD_DEF(vendorClassRecords) },
+    { "vendor-opts", D6O_VENDOR_OPTS, OPT_RECORD_TYPE, false,
+      RECORD_DEF(vendorOptsRecords) },
+    { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false },
+    { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false },
+    { "reconf-accept", D6O_RECONF_ACCEPT, OPT_EMPTY_TYPE, false },
+    { "sip-server-dns", D6O_SIP_SERVERS_DNS, OPT_FQDN_TYPE, true },
+    { "sip-server-addr", D6O_SIP_SERVERS_ADDR, OPT_IPV6_ADDRESS_TYPE, true },
+    { "dns-servers", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+    { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true },
+    { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(iapdRecords) },
+    { "iaprefix", D6O_IAPREFIX, OPT_RECORD_TYPE, false,
+      RECORD_DEF(iaPrefixRecords) },
+    { "nis-servers", D6O_NIS_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+    { "nisp-servers", D6O_NISP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+    { "nis-domain-name", D6O_NIS_DOMAIN_NAME, OPT_FQDN_TYPE, true },
+    { "nisp-domain-name", D6O_NISP_DOMAIN_NAME, OPT_FQDN_TYPE, true },
+    { "sntp-servers", D6O_SNTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+    { "information-refresh-time", D6O_INFORMATION_REFRESH_TIME,
+      OPT_UINT32_TYPE, false },
+    { "bcmcs-server-dns", D6O_BCMCS_SERVER_D, OPT_FQDN_TYPE, true },
+    { "bcmcs-server-addr", D6O_BCMCS_SERVER_A, OPT_IPV6_ADDRESS_TYPE, true },
+    { "geoconf-civic", D6O_GEOCONF_CIVIC, OPT_RECORD_TYPE, false,
+      RECORD_DEF(geoconfCivicRecords) },
+    { "remote-id", D6O_REMOTE_ID, OPT_RECORD_TYPE, false,
+      RECORD_DEF(remoteIdRecords) },
+    { "subscriber-id", D6O_SUBSCRIBER_ID, OPT_BINARY_TYPE, false },
+    { "client-fqdn", D6O_CLIENT_FQDN, OPT_RECORD_TYPE, false,
+      RECORD_DEF(clientFqdnRecords) },
+    { "pana-agent", D6O_PANA_AGENT, OPT_IPV6_ADDRESS_TYPE, true },
+    { "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, OPT_STRING_TYPE, false },
+    { "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, OPT_STRING_TYPE, false },
+    { "ero", D6O_ERO, OPT_UINT16_TYPE, true },
+    { "lq-query", D6O_LQ_QUERY, OPT_RECORD_TYPE, false,
+      RECORD_DEF(lqQueryRecords) },
+    { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false },
+    { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false },
+    { "lq-relay-data", D6O_LQ_RELAY_DATA, OPT_RECORD_TYPE, false,
+      RECORD_DEF(lqRelayData) },
+    { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true }
+
+    // @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
+    // critical right now.
+};
+
+/// Number of option definitions defined.
+const int OPTION_DEF_PARAMS_SIZE6  =
+    sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
+
+}; // anonymous namespace
+
+#endif // STD_OPTION_DEFS_H

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

@@ -35,6 +35,7 @@ libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_int_array_unittest.cc
 libdhcp___unittests_SOURCES += option6_int_unittest.cc
+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

+ 150 - 22
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -22,6 +22,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int.h>
 #include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
 #include <util/buffer.h>
 
 #include <gtest/gtest.h>
@@ -68,8 +69,8 @@ public:
                              const std::type_info& expected_type) {
         // Get all option definitions, we will use them to extract
         // the definition for a particular option code.
-        // We don't have to initialize option deinitions here because they
-        // are initialized in the class'es constructor.
+        // We don't have to initialize option definitions here because they
+        // are initialized in the class's constructor.
         OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
         // Get the container index #1. This one allows for searching
         // option definitions using option code.
@@ -90,13 +91,15 @@ public:
         ASSERT_NO_THROW(def->validate());
         OptionPtr option;
         // Create the option.
-        ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf));
+        ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf))
+            << "Option creation failed to option code " << code;
         // Make sure it is not NULL.
         ASSERT_TRUE(option);
         // And the actual object type is the one that we expect.
         // Note that for many options there are dedicated classes
         // derived from Option class to represent them.
-        EXPECT_TRUE(typeid(*option) == expected_type);
+        EXPECT_TRUE(typeid(*option) == expected_type)
+            << "Invalid class returned for option code " << code;
     }
 };
 
@@ -399,24 +402,149 @@ TEST_F(LibDhcpTest, unpackOptions4) {
 // This test have to be extended once all option definitions are
 // created.
 TEST_F(LibDhcpTest, stdOptionDefs6) {
-    LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
-                                     typeid(Option));
-    LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
-                                     typeid(Option));
-    LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
-                                     typeid(Option6IA));
-    LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
-                                     typeid(Option6IAAddr));
-    LibDhcpTest::testStdOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
-                                     typeid(Option6IntArray<uint16_t>));
-    LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
-                                     typeid(Option6Int<uint16_t>));
-    LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
-                                     typeid(Option));
-    LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
-                                     typeid(Option));
-    LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
-                                     typeid(Option6AddrLst));
+
+    // Create a buffer that holds dummy option data.
+    // It will be used to create most of the options.
+    std::vector<uint8_t> buf(48, 1);
+
+    // Prepare buffer holding an array of FQDNs.
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+    // Initialize a vector with the FQDN data.
+    std::vector<uint8_t> fqdn_buf(data, data + sizeof(data));
+
+    // The CLIENT_FQDN holds a uint8_t value and FQDN. We have
+    // to add the uint8_t value to it and then append the buffer
+    // holding some valid FQDN.
+    std::vector<uint8_t> client_fqdn_buf(1);
+    client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(),
+                           fqdn_buf.end());
+
+    // The actual test starts here for all supported option codes.
+    LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, buf, typeid(Option6IA));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, buf,
+                                    typeid(Option6Int<uint32_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, buf, typeid(Option6IAAddr));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_ORO, buf,
+                                    typeid(Option6IntArray<uint16_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, buf,
+                                    typeid(Option6Int<uint8_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, buf,
+                                    typeid(Option6Int<uint16_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, buf,
+                                    typeid(Option6Int<uint8_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, buf, typeid(Option6IA));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME,
+                                    buf, typeid(Option6Int<uint32_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, buf,typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_ERO, buf,
+                                    typeid(Option6IntArray<uint16_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, buf,
+                                    typeid(Option6Int<uint32_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, buf,
+                                    typeid(Option6AddrLst));
 }
 
 }

+ 531 - 22
src/lib/dhcp/tests/option_custom_unittest.cc

@@ -106,6 +106,17 @@ TEST_F(OptionCustomTest, constructor) {
 
     EXPECT_EQ(Option::V4, option->getUniverse());
     EXPECT_EQ(232, option->getType());
+
+    // Try to create an option using 'empty data' constructor
+    OptionDefinition opt_def3("OPTION_FOO", 1000, "uint32");
+
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def3, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(Option::V6, option->getUniverse());
+    EXPECT_EQ(1000, option->getType());
 }
 
 // The purpose of this test is to verify that 'empty' option definition can
@@ -113,9 +124,10 @@ TEST_F(OptionCustomTest, constructor) {
 TEST_F(OptionCustomTest, emptyData) {
     OptionDefinition opt_def("OPTION_FOO", 232, "empty");
 
+    OptionBuffer buf;
     boost::scoped_ptr<OptionCustom> option;
     ASSERT_NO_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer()));
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
     );
     ASSERT_TRUE(option);
 
@@ -159,8 +171,10 @@ TEST_F(OptionCustomTest, binaryData) {
     EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
 
     // Check that option with "no data" is rejected.
+    buf_in.clear();
     EXPECT_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer())),
+        option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(),
+                                      buf_in.end())),
         isc::OutOfRange
     );
 }
@@ -197,12 +211,46 @@ TEST_F(OptionCustomTest, booleanData) {
     EXPECT_FALSE(value);
 
     // Check that the option with "no data" is rejected.
+    buf.clear();
     EXPECT_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
         isc::OutOfRange
     );
 }
 
+// The purpose of this test is to verify that the data from a buffer
+// can be read as FQDN.
+TEST_F(OptionCustomTest, fqdnData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+    };
+
+    std::vector<uint8_t> buf(data, data + sizeof(data));
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    std::string domain0 = option->readFqdn(0);
+    EXPECT_EQ("mydomain.example.com.", domain0);
+
+    // Check that the option with truncated data can't be created.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6,
+                                      buf.begin(), buf.begin() + 4)),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
 // The purpose of this test is to verify that the option definition comprising
 // 16-bit signed integer value can be used to create an instance of custom option.
 TEST_F(OptionCustomTest, int16Data) {
@@ -338,6 +386,7 @@ TEST_F(OptionCustomTest, ipv6AddressData) {
     );
 }
 
+
 // The purpose of this test is to verify that the option definition comprising
 // string value can be used to create an instance of custom option.
 TEST_F(OptionCustomTest, stringData) {
@@ -365,8 +414,9 @@ TEST_F(OptionCustomTest, stringData) {
     EXPECT_EQ("hello world!", value);
 
     // Check that option will not be created if empty buffer is provided.
+    buf.clear();
     EXPECT_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
         isc::OutOfRange
     );
 }
@@ -419,8 +469,9 @@ TEST_F(OptionCustomTest, booleanDataArray) {
 
     // Check that empty buffer can't be used to create option holding
     // array of boolean values.
+    buf.clear();
     EXPECT_THROW(
-         option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+         option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
          isc::OutOfRange
     );
 }
@@ -472,7 +523,6 @@ TEST_F(OptionCustomTest, uint32DataArray) {
                                       buf.begin() + 3)),
         isc::OutOfRange
     );
-
 }
 
 // The purpose of this test is to verify that the option definition comprising
@@ -575,6 +625,45 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) {
     );
 }
 
+// The purpose of this test is to verify that the option comprising
+// an array of FQDN values can be created from a buffer which holds
+// multiple FQDN values encoded as described in the RFC1035, section
+// 3.1
+TEST_F(OptionCustomTest, fqdnDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn", true);
+
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+
+    // Create a buffer that holds two FQDNs.
+    std::vector<uint8_t> buf(data, data + sizeof(data));
+
+    // Create an option from using a buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We expect that two FQDN values have been extracted
+    // from a buffer.
+    ASSERT_EQ(2, option->getDataFieldsNum());
+
+    // Validate both values.
+    std::string domain0 = option->readFqdn(0);
+    EXPECT_EQ("mydomain.example.com.", domain0);
+
+    std::string domain1 = option->readFqdn(1);
+    EXPECT_EQ("example.com.", domain1);
+}
+
 // The purpose of this test is to verify that the option definition comprising
 // a record of various data fields can be used to create an instance of
 // custom option.
@@ -584,20 +673,30 @@ TEST_F(OptionCustomTest, recordData) {
     OptionDefinition opt_def("OPTION_FOO", 1000, "record");
     ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
     ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+    ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
     ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
     ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
     ASSERT_NO_THROW(opt_def.addRecordField("string"));
 
+    const char fqdn_data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+    };
+
     OptionBuffer buf;
-    // Initialize field 0.
+    // Initialize field 0 to 8712.
     writeInt<uint16_t>(8712, buf);
     // Initialize field 1 to 'true'
     buf.push_back(static_cast<unsigned short>(1));
-    // Initialize field 2 to IPv4 address.
+    // Initialize field 2 to 'mydomain.example.com'.
+    buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
+    // Initialize field 3 to IPv4 address.
     writeAddress(IOAddress("192.168.0.1"), buf);
-    // Initialize field 3 to IPv6 address.
+    // Initialize field 4 to IPv6 address.
     writeAddress(IOAddress("2001:db8:1::1"), buf);
-    // Initialize field 4 to string value.
+    // Initialize field 5 to string value.
     writeString("ABCD", buf);
 
     boost::scoped_ptr<OptionCustom> option;
@@ -606,8 +705,8 @@ TEST_F(OptionCustomTest, recordData) {
     );
     ASSERT_TRUE(option);
 
-    // We should have 5 data fields.
-    ASSERT_EQ(5, option->getDataFieldsNum());
+    // We should have 6 data fields.
+    ASSERT_EQ(6, option->getDataFieldsNum());
 
     // Verify value in the field 0.
     uint16_t value0 = 0;
@@ -620,19 +719,24 @@ TEST_F(OptionCustomTest, recordData) {
     EXPECT_TRUE(value1);
 
     // Verify value in the field 2.
-    IOAddress value2("127.0.0.1");
-    ASSERT_NO_THROW(value2 = option->readAddress(2));
-    EXPECT_EQ("192.168.0.1", value2.toText());
+    std::string value2 = "";
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ("mydomain.example.com.", value2);
 
     // Verify value in the field 3.
-    IOAddress value3("::1");
+    IOAddress value3("127.0.0.1");
     ASSERT_NO_THROW(value3 = option->readAddress(3));
-    EXPECT_EQ("2001:db8:1::1", value3.toText());
+    EXPECT_EQ("192.168.0.1", value3.toText());
 
     // Verify value in the field 4.
-    std::string value4;
-    ASSERT_NO_THROW(value4 = option->readString(4));
-    EXPECT_EQ("ABCD", value4);
+    IOAddress value4("::1");
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+    // Verify value in the field 5.
+    std::string value5;
+    ASSERT_NO_THROW(value5 = option->readString(5));
+    EXPECT_EQ("ABCD", value5);
 }
 
 // The purpose of this test is to verify that truncated buffer
@@ -683,6 +787,413 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
     );
 }
 
+// The purpose of this test is to verify that an option comprising
+// single data field with binary data can be used and that this
+// binary data is properly initialized to a default value. This
+// test also checks that it is possible to override this default
+// value.
+TEST_F(OptionCustomTest, setBinaryData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "binary");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Get the default binary value.
+    OptionBuffer buf;
+    ASSERT_NO_THROW(option->readBinary());
+    // The buffer is by default empty.
+    EXPECT_TRUE(buf.empty());
+    // Prepare input buffer with some dummy data.
+    OptionBuffer buf_in(10);
+    for (int i = 0; i < buf_in.size(); ++i) {
+        buf_in[i] = i;
+    }
+    // Try to override the default binary buffer.
+    ASSERT_NO_THROW(option->writeBinary(buf_in));
+    // And check that it has been actually overriden.
+    ASSERT_NO_THROW(buf = option->readBinary());
+    ASSERT_EQ(buf_in.size(), buf.size());
+    EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that an option comprising
+// single boolean data field can be created and that its default
+// value can be overriden by a new value.
+TEST_F(OptionCustomTest, setBooleanData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+    // Check that the default boolean value is false.
+    bool value = false;
+    ASSERT_NO_THROW(value = option->readBoolean());
+    EXPECT_FALSE(value);
+    // Check that we can override the default value.
+    ASSERT_NO_THROW(option->writeBoolean(true));
+    // Finally, check that it has been actually overriden.
+    ASSERT_NO_THROW(value = option->readBoolean());
+    EXPECT_TRUE(value);
+}
+
+/// The purpose of this test is to verify that the data field value
+/// can be overriden by a new value.
+TEST_F(OptionCustomTest, setUint32Data) {
+    // Create a definition of an option that holds single
+    // uint32 value.
+    OptionDefinition opt_def("OPTION_FOO", 1000, "uint32");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // The default value for integer data fields is 0.
+    uint32_t value = 0;
+    ASSERT_NO_THROW(option->readInteger<uint32_t>());
+    EXPECT_EQ(0, value);
+
+    // Try to set the data field value to something different
+    // than 0.
+    ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234));
+
+    // Verify that it has been set.
+    ASSERT_NO_THROW(value = option->readInteger<uint32_t>());
+    EXPECT_EQ(1234, value);
+}
+
+// The purpose of this test is to verify that an option comprising
+// single IPv4 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv4AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4));
+    );
+    ASSERT_TRUE(option);
+
+    asiolink::IOAddress address("127.0.0.1");
+    ASSERT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("0.0.0.0", address.toText());
+
+    EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1")));
+
+    EXPECT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("192.168.0.1", address.toText());
+}
+
+// The purpose of this test is to verify that an opton comprising
+// single IPv6 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv6AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    asiolink::IOAddress address("::1");
+    ASSERT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("::", address.toText());
+
+    EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1")));
+
+    EXPECT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("2001:db8:1::1", address.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// single string value can be created and that this value
+// is initialized to the default value. Also, this test checks that
+// this value can be overwritten by a new value.
+TEST_F(OptionCustomTest, setStringData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Get the default value of the option.
+    std::string value;
+    ASSERT_NO_THROW(value = option->readString());
+    // By default the string data field is empty.
+    EXPECT_TRUE(value.empty());
+    // Write some text to this field.
+    ASSERT_NO_THROW(option->writeString("hello world"));
+    // Check that it has been actually written.
+    EXPECT_NO_THROW(value = option->readString());
+    EXPECT_EQ("hello world", value);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// a default FQDN value can be created and that this value can be
+/// overriden after the option has been created.
+TEST_F(OptionCustomTest, setFqdnData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+    // Read a default FQDN value from the option.
+    std::string fqdn;
+    ASSERT_NO_THROW(fqdn = option->readFqdn());
+    EXPECT_EQ(".", fqdn);
+    // Try override the default FQDN value.
+    ASSERT_NO_THROW(option->writeFqdn("example.com"));
+    // Check that the value has been actually overriden.
+    ASSERT_NO_THROW(fqdn = option->readFqdn());
+    EXPECT_EQ("example.com.", fqdn);
+}
+
+// The purpose of this test is to verify that an option carrying
+// an array of boolean values can be created with no values
+// initially and that values can be later added to it.
+TEST_F(OptionCustomTest, setBooleanDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", true);
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Initially, the array should contain no values.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add some boolean values to it.
+    ASSERT_NO_THROW(option->addArrayDataField(true));
+    ASSERT_NO_THROW(option->addArrayDataField(false));
+    ASSERT_NO_THROW(option->addArrayDataField(true));
+
+    // Verify that the new data fields can be added.
+    bool value0 = false;
+    ASSERT_NO_THROW(value0 = option->readBoolean(0));
+    EXPECT_TRUE(value0);
+    bool value1 = true;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_FALSE(value1);
+    bool value2 = false;
+    ASSERT_NO_THROW(value2 = option->readBoolean(2));
+    EXPECT_TRUE(value2);
+}
+
+// The purpose of this test is to verify that am option carying
+// an array of 16-bit signed integer values can be created with
+// no values initially and that the values can be later added to it.
+TEST_F(OptionCustomTest, setUint16DataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "uint16", true);
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Initially, the array should contain no values.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add 3 new data fields holding integer values.
+    ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(67));
+    ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(876));
+    ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(32222));
+
+    // We should now have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Check that the values have been correctly set.
+    uint16_t value0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(67, value0);
+    uint16_t value1;
+    ASSERT_NO_THROW(value1 = option->readInteger<uint16_t>(1));
+    EXPECT_EQ(876, value1);
+    uint16_t value2;
+    ASSERT_NO_THROW(value2 = option->readInteger<uint16_t>(2));
+    EXPECT_EQ(32222, value2);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv4 address can be created with no addresses and that
+/// multiple IPv4 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv4AddressDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address", true);
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4));
+    );
+    ASSERT_TRUE(option);
+
+    // Expect that the array does not contain any data fields yet.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add 3 IPv4 addresses.
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.1")));
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.2")));
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.3")));
+
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Check that all IP addresses have been set correctly.
+    IOAddress address0("127.0.0.1");
+    ASSERT_NO_THROW(address0 = option->readAddress(0));
+    EXPECT_EQ("192.168.0.1", address0.toText());
+    IOAddress address1("127.0.0.1");
+    ASSERT_NO_THROW(address1 = option->readAddress(1));
+    EXPECT_EQ("192.168.0.2", address1.toText());
+    IOAddress address2("127.0.0.1");
+    ASSERT_NO_THROW(address2 = option->readAddress(2));
+    EXPECT_EQ("192.168.0.3", address2.toText());
+
+    // Add invalid address (IPv6 instead of IPv4).
+    EXPECT_THROW(
+        option->addArrayDataField(IOAddress("2001:db8:1::1")),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv6 address can be created with no addresses and that
+/// multiple IPv6 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv6AddressDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Initially, the array does not contain any data fields.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add 3 new IPv6 addresses into the array.
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::1")));
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::2")));
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::3")));
+
+    // We should have now 3 addresses added.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Check that they have correct values set.
+    IOAddress address0("::1");
+    ASSERT_NO_THROW(address0 = option->readAddress(0));
+    EXPECT_EQ("2001:db8:1::1", address0.toText());
+    IOAddress address1("::1");
+    ASSERT_NO_THROW(address1 = option->readAddress(1));
+    EXPECT_EQ("2001:db8:1::2", address1.toText());
+    IOAddress address2("::1");
+    ASSERT_NO_THROW(address2 = option->readAddress(2));
+    EXPECT_EQ("2001:db8:1::3", address2.toText());
+
+    // Add invalid address (IPv4 instead of IPv6).
+    EXPECT_THROW(
+        option->addArrayDataField(IOAddress("192.168.0.1")),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+TEST_F(OptionCustomTest, setRecordData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+    ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // The number of elements should be equal to number of elements
+    // in the record.
+    ASSERT_EQ(6, option->getDataFieldsNum());
+
+    // Check that the default values have been correctly set.
+    uint16_t value0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(0, value0);
+    bool value1 = true;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_FALSE(value1);
+    std::string value2;
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ(".", value2);
+    IOAddress value3("127.0.0.1");
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("0.0.0.0", value3.toText());
+    IOAddress value4("2001:db8:1::1");
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("::", value4.toText());
+    std::string value5 = "xyz";
+    ASSERT_NO_THROW(value5 = option->readString(5));
+    EXPECT_TRUE(value5.empty());
+
+    // Override each value with a new value.
+    ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+    ASSERT_NO_THROW(option->writeBoolean(true, 1));
+    ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+    ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+    ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+    ASSERT_NO_THROW(option->writeString("hello world", 5));
+
+    // Check that the new values have been correctly set.
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(1234, value0);
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_TRUE(value1);
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ("example.com.", value2);
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("192.168.0.1", value3.toText());
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("2001:db8:1::100", value4.toText());
+    ASSERT_NO_THROW(value5 = option->readString(5));
+    EXPECT_EQ(value5, "hello world");
+}
+
 // The purpose of this test is to verify that pack function for
 // DHCPv4 custom option works correctly.
 TEST_F(OptionCustomTest, pack4) {
@@ -901,6 +1412,4 @@ TEST_F(OptionCustomTest, invalidIndex) {
     EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
 }
 
-
-
 } // anonymous namespace

+ 491 - 0
src/lib/dhcp/tests/option_data_types_unittest.cc

@@ -0,0 +1,491 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option_data_types.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test class for option data type utilities.
+class OptionDataTypesTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    OptionDataTypesTest() { }
+
+    /// @brief Write IP address into a buffer.
+    ///
+    /// @param address address to be written.
+    /// @param [out] buf output buffer.
+    void writeAddress(const asiolink::IOAddress& address,
+                      std::vector<uint8_t>& buf) {
+        short family = address.getFamily();
+        if (family == AF_INET) {
+            asio::ip::address_v4::bytes_type buf_addr =
+                address.getAddress().to_v4().to_bytes();
+            buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+        } else if (family == AF_INET6) {
+            asio::ip::address_v6::bytes_type buf_addr =
+                address.getAddress().to_v6().to_bytes();
+            buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+        }
+    }
+
+    /// @brief Write integer (signed or unsigned) into a buffer.
+    ///
+    /// @param value integer value.
+    /// @param [out] buf output buffer.
+    /// @tparam integer type.
+    template<typename T>
+    void writeInt(T value, std::vector<uint8_t>& buf) {
+        for (int i = 0; i < sizeof(T); ++i) {
+            buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+        }
+    }
+
+    /// @brief Write a string into a buffer.
+    ///
+    /// @param value string to be written into a buffer.
+    /// @param buf output buffer.
+    void writeString(const std::string& value,
+                     std::vector<uint8_t>& buf) {
+        buf.resize(buf.size() + value.size());
+        std::copy_backward(value.c_str(), value.c_str() + value.size(),
+                           buf.end());
+    }
+};
+
+// The goal of this test is to verify that an IPv4 address being
+// stored in a buffer (wire format) can be read into IOAddress
+// object.
+TEST_F(OptionDataTypesTest, readAddress) {
+    // Create some IPv4 address.
+    asiolink::IOAddress address("192.168.0.1");
+    // And store it in a buffer in a wire format.
+    std::vector<uint8_t> buf;
+    writeAddress(address, buf);
+
+    // Now, try to read the IP address with a utility function
+    // being under test.
+    asiolink::IOAddress address_out("127.0.0.1");
+    EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET));
+
+    // Check that the read address matches address that
+    // we used as input.
+    EXPECT_EQ(address.toText(), address_out.toText());
+
+    // Check that an attempt to read the buffer as IPv6 address
+    // causes an error as the IPv6 address needs at least 16 bytes
+    // long buffer.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readAddress(buf, AF_INET6),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    buf.clear();
+
+    // Do another test like this for IPv6 address.
+    address = asiolink::IOAddress("2001:db8:1:0::1");
+    writeAddress(address, buf);
+    EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6));
+    EXPECT_EQ(address.toText(), address_out.toText());
+
+    // Truncate the buffer and expect an error to be reported when
+    // trying to read it.
+    buf.resize(buf.size() - 1);
+    EXPECT_THROW(
+        OptionDataTypeUtil::readAddress(buf, AF_INET6),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The goal of this test is to verify that an IPv6 address
+// is properly converted to wire format and stored in a
+// buffer.
+TEST_F(OptionDataTypesTest, writeAddress) {
+    // Encode an IPv6 address 2001:db8:1::1 in wire format.
+    // This will be used as reference data to validate if
+    // an IPv6 address is stored in a buffer properly.
+    const uint8_t data[] = {
+        0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0,
+        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1
+    };
+    std::vector<uint8_t> buf_in(data, data + sizeof(data));
+
+    // Create IPv6 address object.
+    asiolink::IOAddress address("2001:db8:1::1");
+    // Define the output buffer to write IP address to.
+    std::vector<uint8_t> buf_out;
+    // Write the address to the buffer.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+    // Make sure that input and output buffers have the same size
+    // so we can compare them.
+    ASSERT_EQ(buf_in.size(), buf_out.size());
+    // And finally compare them.
+    EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+    buf_out.clear();
+
+    // Do similar test for IPv4 address.
+    address = asiolink::IOAddress("192.168.0.1");
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+    ASSERT_EQ(4, buf_out.size());
+    // Verify that the IP address has been written correctly.
+    EXPECT_EQ(192, buf_out[0]);
+    EXPECT_EQ(168, buf_out[1]);
+    EXPECT_EQ(0, buf_out[2]);
+    EXPECT_EQ(1, buf_out[3]);
+}
+
+// The purpose of this test is to verify that binary data represented
+// as a string of hexadecimal digits can be written to a buffer.
+TEST_F(OptionDataTypesTest, writeBinary) {
+    // Prepare the reference data.
+    const char data[] = {
+        0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
+        0x6, 0x7, 0x8, 0x9, 0xA, 0xB
+    };
+    std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+    // Create empty vector where binary data will be written to.
+    std::vector<uint8_t> buf;
+    ASSERT_NO_THROW(
+        OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf)
+    );
+    // Verify that the buffer contains valid data.
+    ASSERT_EQ(buf_ref.size(), buf.size());
+    EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that the boolean value stored
+// in a buffer is correctly read from this buffer.
+TEST_F(OptionDataTypesTest, readBool) {
+    // Create an input buffer.
+    std::vector<uint8_t> buf;
+    // 'true' value is encoded as 1 ('false' is encoded as 0)
+    buf.push_back(1);
+
+    // Read the value from the buffer.
+    bool value = false;
+    ASSERT_NO_THROW(
+        value = OptionDataTypeUtil::readBool(buf);
+    );
+    // Verify the value.
+    EXPECT_TRUE(value);
+    // Check if 'false' is read correctly either.
+    buf[0] = 0;
+    ASSERT_NO_THROW(
+        value = OptionDataTypeUtil::readBool(buf);
+    );
+    EXPECT_FALSE(value);
+
+    // Check that invalid value causes exception.
+    buf[0] = 5;
+    ASSERT_THROW(
+        OptionDataTypeUtil::readBool(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The purpose of this test is to verify that boolean values
+// are correctly encoded in a buffer as '1' for 'true' and
+// '0' for 'false' values.
+TEST_F(OptionDataTypesTest, writeBool) {
+    // Create a buffer we will write to.
+    std::vector<uint8_t> buf;
+    // Write the 'true' value to the buffer.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf));
+    // We should now have 'true' value stored in a buffer.
+    ASSERT_EQ(1, buf.size());
+    EXPECT_EQ(buf[0], 1);
+    // Let's append another value to make sure that it is not always
+    // 'true' value being written.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf));
+    ASSERT_EQ(2, buf.size());
+    // Check that the first value has not changed.
+    EXPECT_EQ(buf[0], 1);
+    // Check the the second value is correct.
+    EXPECT_EQ(buf[1], 0);
+}
+
+// The purpose of this test is to verify that the integer values
+// of different types are correctly read from a buffer.
+TEST_F(OptionDataTypesTest, readInt) {
+    std::vector<uint8_t> buf;
+
+    // Write an 8-bit unsigned integer value to the buffer.
+    writeInt<uint8_t>(129, buf);
+    uint8_t valueUint8 = 0;
+    // Read the value and check that it is valid.
+    ASSERT_NO_THROW(
+        valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf);
+    );
+    EXPECT_EQ(129, valueUint8);
+
+    // Try to read 16-bit value from a buffer holding 8-bit value.
+    // This should result in an exception.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readInt<uint16_t>(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Clear the buffer for the next check we are going to do.
+    buf.clear();
+
+    // Test uint16_t value.
+    writeInt<uint16_t>(1234, buf);
+    uint16_t valueUint16 = 0;
+    ASSERT_NO_THROW(
+        valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf);
+    );
+    EXPECT_EQ(1234, valueUint16);
+
+    // Try to read 32-bit value from a buffer holding 16-bit value.
+    // This should result in an exception.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readInt<uint32_t>(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    buf.clear();
+
+    // Test uint32_t value.
+    writeInt<uint32_t>(56789, buf);
+    uint32_t valueUint32 = 0;
+    ASSERT_NO_THROW(
+        valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf);
+    );
+    EXPECT_EQ(56789, valueUint32);
+    buf.clear();
+
+    // Test int8_t value.
+    writeInt<int8_t>(-65, buf);
+    int8_t valueInt8 = 0;
+    ASSERT_NO_THROW(
+        valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf);
+    );
+    EXPECT_EQ(-65, valueInt8);
+    buf.clear();
+
+    // Try to read 16-bit value from a buffer holding 8-bit value.
+    // This should result in an exception.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readInt<int16_t>(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Test int16_t value.
+    writeInt<int16_t>(2345, buf);
+    int32_t valueInt16 = 0;
+    ASSERT_NO_THROW(
+        valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf);
+    );
+    EXPECT_EQ(2345, valueInt16);
+    buf.clear();
+
+    // Try to read 32-bit value from a buffer holding 16-bit value.
+    // This should result in an exception.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readInt<int32_t>(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Test int32_t value.
+    writeInt<int32_t>(-16543, buf);
+    int32_t valueInt32 = 0;
+    ASSERT_NO_THROW(
+        valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf);
+    );
+    EXPECT_EQ(-16543, valueInt32);
+
+    buf.clear();
+}
+
+// The purpose of this test is to verify that integer values of different
+// types are correctly written to a buffer.
+TEST_F(OptionDataTypesTest, writeInt) {
+    // Prepare the reference buffer.
+    const uint8_t data[] = {
+        0x7F, // 127
+        0x03, 0xFF, // 1023
+        0x00, 0x00, 0x10, 0x00, // 4096
+        0xFF, 0xFF, 0xFC, 0x00, // -1024
+        0x02, 0x00, // 512
+        0x81 // -127
+    };
+    std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+
+    // Fill in the buffer with data. Each write operation appends an
+    // integer value. Eventually the buffer holds all values and should
+    // match with the reference buffer.
+    std::vector<uint8_t> buf;
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf));
+
+    // Make sure that the buffer has the same size as the reference
+    // buffer.
+    ASSERT_EQ(buf_ref.size(), buf.size());
+    // Compare buffers.
+    EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that FQDN is read from
+// a buffer and returned as a text. The representation of the FQDN
+// in the buffer complies with RFC1035, section 3.1.
+// This test also checks that if invalid (truncated) FQDN is stored
+// in a buffer the appropriate exception is returned when trying to
+// read it as a string.
+TEST_F(OptionDataTypesTest, readFqdn) {
+    // The binary representation of the "mydomain.example.com".
+    // Values: 8, 7, 3 and 0 specify the lengths of subsequent
+    // labels within the FQDN.
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+
+    // Make a vector out of the data.
+    std::vector<uint8_t> buf(data, data + sizeof(data));
+
+    // Read the buffer as FQDN and verify its correctness.
+    std::string fqdn;
+    EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf));
+    EXPECT_EQ("mydomain.example.com.", fqdn);
+
+    // By resizing the buffer we simulate truncation. The first
+    // length field (8) indicate that the first label's size is
+    // 8 but the actual buffer size is 5. Expect that conversion
+    // fails.
+    buf.resize(5);
+    EXPECT_THROW(
+        OptionDataTypeUtil::readFqdn(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Another special case: provide an empty buffer.
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::readFqdn(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The purpose of this test is to verify that FQDN's syntax is validated
+// and that FQDN is correctly written to a buffer in a format described
+// in RFC1035 section 3.1.
+TEST_F(OptionDataTypesTest, writeFqdn) {
+    // Create empty buffer. The FQDN will be written to it.
+    OptionBuffer buf;
+    // Write a domain name into the buffer in the format described
+    // in RFC1035 section 3.1. This function should not throw
+    // exception because domain name is well formed.
+    EXPECT_NO_THROW(
+        OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf)
+    );
+    // The length of the data is 22 (8 bytes for "mydomain" label,
+    // 7 bytes for "example" label, 3 bytes for "com" label and
+    // finally 4 bytes positions between labels where length
+    // information is stored.
+    ASSERT_EQ(22, buf.size());
+
+    // Verify that length fields between labels hold valid values.
+    EXPECT_EQ(8, buf[0]);  // length of "mydomain"
+    EXPECT_EQ(7, buf[9]);  // length of "example"
+    EXPECT_EQ(3, buf[17]); // length of "com"
+    EXPECT_EQ(0, buf[21]); // zero byte at the end.
+
+    // Verify that labels are valid.
+    std::string label0(buf.begin() + 1, buf.begin() + 9);
+    EXPECT_EQ("mydomain", label0);
+
+    std::string label1(buf.begin() + 10, buf.begin() + 17);
+    EXPECT_EQ("example", label1);
+
+    std::string label2(buf.begin() + 18, buf.begin() + 21);
+    EXPECT_EQ("com", label2);
+
+    // The tested function is supposed to append data to a buffer
+    // so let's check that it is a case by appending another domain.
+    OptionDataTypeUtil::writeFqdn("hello.net", buf);
+
+    // The buffer length should be now longer.
+    ASSERT_EQ(33, buf.size());
+
+    // Check the length fields for new labels being appended.
+    EXPECT_EQ(5, buf[22]);
+    EXPECT_EQ(3, buf[28]);
+
+    // And check that labels are ok.
+    std::string label3(buf.begin() + 23, buf.begin() + 28);
+    EXPECT_EQ("hello", label3);
+
+    std::string label4(buf.begin() + 29, buf.begin() + 32);
+    EXPECT_EQ("net", label4);
+
+    // Check that invalid (empty) FQDN is rejected and expected
+    // exception type is thrown.
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::writeFqdn("", buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Check another invalid domain name (with repeated dot).
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::writeFqdn("example..com", buf),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The purpose of this test is to verify that the string
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readString) {
+    // Prepare a buffer with some string in it.
+    std::vector<uint8_t> buf;
+    writeString("hello world", buf);
+
+    // Read the string from the buffer.
+    std::string value;
+    ASSERT_NO_THROW(
+        value = OptionDataTypeUtil::readString(buf);
+    );
+    // Check that it is valid.
+    EXPECT_EQ("hello world", value);
+}
+
+// The purpose of this test is to verify that a string can be
+// stored in a buffer correctly.
+TEST_F(OptionDataTypesTest, writeString) {
+    // Prepare a buffer with a reference data.
+    std::vector<uint8_t> buf_ref;
+    writeString("hello world!", buf_ref);
+    // Create empty buffer we will write to.
+    std::vector<uint8_t> buf;
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf));
+    // Compare two buffers.
+    ASSERT_EQ(buf_ref.size(), buf.size());
+    EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+} // anonymous namespace

+ 2 - 1
src/lib/dhcp/tests/option_definition_unittest.cc

@@ -23,6 +23,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int.h>
 #include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
 #include <dhcp/option_definition.h>
 #include <exceptions/exceptions.h>
 
@@ -883,7 +884,7 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
         option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
     );
     ASSERT_TRUE(option_v6);
-    ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
+    ASSERT_TRUE(typeid(*option_v6) == typeid(OptionCustom));
     std::vector<uint8_t> data = option_v6->getData();
     std::vector<uint8_t> ref_data(values[0].c_str(), values[0].c_str()
                                   + values[0].length());