Browse Source

[4497] Implemented deep copy of DHCP options.

Marcin Siodelski 8 years ago
parent
commit
90615322d4

+ 37 - 0
src/lib/dhcp/option.cc

@@ -53,6 +53,31 @@ Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
     check();
 }
 
+Option::Option(const Option& option)
+    : universe_(option.universe_), type_(option.type_),
+      data_(option.data_), options_(),
+      encapsulated_space_(option.encapsulated_space_) {
+    option.getOptionsCopy(options_);
+}
+
+Option&
+Option::operator=(const Option& rhs) {
+    if (&rhs != this) {
+        universe_ = rhs.universe_;
+        type_ = rhs.type_;
+        data_ = rhs.data_;
+        rhs.getOptionsCopy(options_);
+        encapsulated_space_ = rhs.encapsulated_space_;
+    }
+    return (*this);
+}
+
+OptionPtr
+Option::clone() const {
+    OptionPtr option(new Option(*this));
+    return (option);
+}
+
 void
 Option::check() const {
     if ( (universe_ != V4) && (universe_ != V6) ) {
@@ -180,6 +205,18 @@ OptionPtr Option::getOption(uint16_t opt_type) const {
     return OptionPtr(); // NULL
 }
 
+void
+Option::getOptionsCopy(OptionCollection& options_copy) const {
+    OptionCollection local_options;
+    for (OptionCollection::const_iterator it = options_.begin();
+         it != options_.end(); ++it) {
+        OptionPtr copy = it->second->clone();
+        local_options.insert(std::make_pair(it->second->getType(),
+                                            copy));
+    }
+    options_copy.swap(local_options);
+}
+
 bool Option::delOption(uint16_t opt_type) {
     isc::dhcp::OptionCollection::iterator x = options_.find(opt_type);
     if ( x != options_.end() ) {

+ 15 - 0
src/lib/dhcp/option.h

@@ -144,6 +144,12 @@ public:
     Option(Universe u, uint16_t type, OptionBufferConstIter first,
            OptionBufferConstIter last);
 
+    Option(const Option& option);
+
+    Option& operator=(const Option& rhs);
+
+    virtual OptionPtr clone() const;
+
     /// @brief returns option universe (V4 or V6)
     ///
     /// @return universe type
@@ -257,6 +263,8 @@ public:
         return (options_);
     }
 
+    void getOptionsCopy(OptionCollection& options_copy) const;
+
     /// Attempts to delete first suboption of requested type
     ///
     /// @param type Type of option to be deleted.
@@ -364,6 +372,13 @@ public:
 
 protected:
 
+    template<typename OptionType>
+    OptionPtr cloneInternal() const {
+        boost::shared_ptr<OptionType>
+            option(new OptionType(*dynamic_cast<const OptionType*>(this)));
+        return (option);
+    }
+
     /// @brief Store option's header in a buffer.
     ///
     /// This method writes option's header into a buffer in the

+ 5 - 0
src/lib/dhcp/option4_addrlst.cc

@@ -55,6 +55,11 @@ Option4AddrLst::Option4AddrLst(uint8_t type, const IOAddress& addr)
     setAddress(addr);
 }
 
+OptionPtr
+Option4AddrLst::clone() const {
+    return (cloneInternal<Option4AddrLst>());
+}
+
 void
 Option4AddrLst::pack(isc::util::OutputBuffer& buf) const {
 

+ 2 - 0
src/lib/dhcp/option4_addrlst.h

@@ -80,6 +80,8 @@ public:
     Option4AddrLst(uint8_t type, OptionBufferConstIter first,
                    OptionBufferConstIter last);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Writes option in a wire-format to a buffer.
     ///
     /// Method will throw if option storing fails for some reason.

+ 11 - 0
src/lib/dhcp/option4_client_fqdn.cc

@@ -346,11 +346,17 @@ Option4ClientFqdn::Option4ClientFqdn(const Option4ClientFqdn& source)
       impl_(new Option4ClientFqdnImpl(*source.impl_)) {
 }
 
+OptionPtr
+Option4ClientFqdn::clone() const {
+    return (cloneInternal<Option4ClientFqdn>());
+}
+
 Option4ClientFqdn&
 // This assignment operator handles assignment to self, it uses copy
 // constructor of Option4ClientFqdnImpl to copy all required values.
 // cppcheck-suppress operatorEqToSelf
 Option4ClientFqdn::operator=(const Option4ClientFqdn& source) {
+    Option::operator=(source);
     Option4ClientFqdnImpl* old_impl = impl_;
     impl_ = new Option4ClientFqdnImpl(*source.impl_);
     delete(old_impl);
@@ -396,6 +402,11 @@ Option4ClientFqdn::setFlag(const uint8_t flag, const bool set_flag) {
     impl_->flags_ = new_flag;
 }
 
+std::pair<Option4ClientFqdn::Rcode, Option4ClientFqdn::Rcode>
+Option4ClientFqdn::getRcode() const {
+    return (std::make_pair(impl_->rcode1_, impl_->rcode2_));
+}
+
 void
 Option4ClientFqdn::setRcode(const Rcode& rcode) {
     impl_->rcode1_ = rcode;

+ 11 - 2
src/lib/dhcp/option4_client_fqdn.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -11,6 +11,7 @@
 #include <dns/name.h>
 
 #include <string>
+#include <utility>
 
 namespace isc {
 namespace dhcp {
@@ -215,9 +216,11 @@ public:
     explicit Option4ClientFqdn(OptionBufferConstIter first,
                                OptionBufferConstIter last);
 
-   /// @brief Copy constructor
+    /// @brief Copy constructor
     Option4ClientFqdn(const Option4ClientFqdn& source);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Destructor
     virtual ~Option4ClientFqdn();
 
@@ -251,6 +254,12 @@ public:
     /// @brief Sets the flag field value to 0.
     void resetFlags();
 
+    /// @brief Returns @c Rcode objects representing value of RCODE1 and RCODE2.
+    ///
+    /// @return Pair of Rcode objects of which first is the RCODE1 and the
+    /// second is RCODE2.
+    std::pair<Rcode, Rcode> getRcode() const;
+
     /// @brief Set Rcode value.
     ///
     /// @param rcode An @c Rcode object representing value of RCODE1 and RCODE2.

+ 5 - 0
src/lib/dhcp/option6_addrlst.cc

@@ -41,6 +41,11 @@ Option6AddrLst::Option6AddrLst(uint16_t type, OptionBufferConstIter begin,
     unpack(begin, end);
 }
 
+OptionPtr
+Option6AddrLst::clone() const {
+    return (cloneInternal<Option6AddrLst>());
+}
+
 void
 Option6AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
     if (!addr.isV6()) {

+ 2 - 0
src/lib/dhcp/option6_addrlst.h

@@ -45,6 +45,8 @@ public:
     Option6AddrLst(uint16_t type, OptionBufferConstIter begin,
                    OptionBufferConstIter end);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Assembles on-wire form of this option
     ///
     /// @param buf pointer to packet buffer

+ 6 - 0
src/lib/dhcp/option6_client_fqdn.cc

@@ -293,11 +293,17 @@ Option6ClientFqdn::Option6ClientFqdn(const Option6ClientFqdn& source)
       impl_(new Option6ClientFqdnImpl(*source.impl_)) {
 }
 
+OptionPtr
+Option6ClientFqdn::clone() const {
+    return (cloneInternal<Option6ClientFqdn>());
+}
+
 Option6ClientFqdn&
 // This assignment operator handles assignment to self, it uses copy
 // constructor of Option6ClientFqdnImpl to copy all required values.
 // cppcheck-suppress operatorEqToSelf
 Option6ClientFqdn::operator=(const Option6ClientFqdn& source) {
+    Option::operator=(source);
     Option6ClientFqdnImpl* old_impl = impl_;
     impl_ = new Option6ClientFqdnImpl(*source.impl_);
     delete(old_impl);

+ 3 - 1
src/lib/dhcp/option6_client_fqdn.h

@@ -137,9 +137,11 @@ public:
     explicit Option6ClientFqdn(OptionBufferConstIter first,
                                OptionBufferConstIter last);
 
-   /// @brief Copy constructor
+    /// @brief Copy constructor
     Option6ClientFqdn(const Option6ClientFqdn& source);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Destructor
     virtual ~Option6ClientFqdn();
 

+ 5 - 0
src/lib/dhcp/option6_ia.cc

@@ -48,6 +48,11 @@ Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
     unpack(begin, end);
 }
 
+OptionPtr
+Option6IA::clone() const {
+    return (cloneInternal<Option6IA>());
+}
+
 void Option6IA::pack(isc::util::OutputBuffer& buf) const {
     buf.writeUint16(type_);
     buf.writeUint16(len() - OPTION6_HDR_LEN);

+ 2 - 0
src/lib/dhcp/option6_ia.h

@@ -39,6 +39,8 @@ public:
     Option6IA(uint16_t type, OptionBuffer::const_iterator begin,
               OptionBuffer::const_iterator end);
 
+    virtual OptionPtr clone() const;
+
     /// Writes option in wire-format to buf, returns pointer to first unused
     /// byte after stored option.
     ///

+ 5 - 0
src/lib/dhcp/option6_iaaddr.cc

@@ -42,6 +42,11 @@ Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
     unpack(begin, end);
 }
 
+OptionPtr
+Option6IAAddr::clone() const {
+    return (cloneInternal<Option6IAAddr>());
+}
+
 void Option6IAAddr::pack(isc::util::OutputBuffer& buf) const {
 
     buf.writeUint16(type_);

+ 2 - 0
src/lib/dhcp/option6_iaaddr.h

@@ -46,6 +46,8 @@ public:
     Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
                   OptionBuffer::const_iterator end);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Writes option in wire-format.
     ///
     /// Writes option in wire-format to buf, returns pointer to first unused

+ 5 - 0
src/lib/dhcp/option6_iaprefix.cc

@@ -44,6 +44,11 @@ Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator beg
     unpack(begin, end);
 }
 
+OptionPtr
+Option6IAPrefix::clone() const {
+    return (cloneInternal<Option6IAPrefix>());
+}
+
 void Option6IAPrefix::pack(isc::util::OutputBuffer& buf) const {
     if (!addr_.isV6()) {
         isc_throw(isc::BadValue, addr_ << " is not an IPv6 address");

+ 2 - 0
src/lib/dhcp/option6_iaprefix.h

@@ -74,6 +74,8 @@ public:
     Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
                     OptionBuffer::const_iterator end);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Writes option in wire-format.
     ///
     /// Writes option in wire-format to buf, returns pointer to first unused

+ 5 - 0
src/lib/dhcp/option6_status_code.cc

@@ -40,6 +40,11 @@ Option6StatusCode::Option6StatusCode(OptionBufferConstIter begin,
     unpack(begin, end);
 }
 
+OptionPtr
+Option6StatusCode::clone() const {
+    return (cloneInternal<Option6StatusCode>());
+}
+
 void
 Option6StatusCode::pack(isc::util::OutputBuffer& buf) const {
     // Pack option header.

+ 2 - 0
src/lib/dhcp/option6_status_code.h

@@ -37,6 +37,8 @@ public:
     /// @param end Iterator to end of option data (first byte after option end).
     Option6StatusCode(OptionBufferConstIter begin, OptionBufferConstIter end);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Writes option in wire-format.
     ///
     /// Writes option in wire-format to buf, returns pointer to first unused

+ 5 - 0
src/lib/dhcp/option_custom.cc

@@ -40,6 +40,11 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
     createBuffers(getData());
 }
 
+OptionPtr
+OptionCustom::clone() const {
+    return (cloneInternal<OptionCustom>());
+}
+
 void
 OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
     checkArrayType();

+ 2 - 0
src/lib/dhcp/option_custom.h

@@ -79,6 +79,8 @@ public:
     OptionCustom(const OptionDefinition& def, Universe u,
                  OptionBufferConstIter first, OptionBufferConstIter last);
 
+    virtual OptionPtr clone() const;
+
     /// @brief Create new buffer and set its value as an IP address.
     ///
     /// @param address IPv4 or IPv6 address to be written to

+ 7 - 0
src/lib/dhcp/option_int.h

@@ -46,6 +46,9 @@ typedef boost::shared_ptr<OptionUint32> OptionUint32Ptr;
 /// @param T data field type (see above).
 template<typename T>
 class OptionInt: public Option {
+private:
+
+    typedef boost::shared_ptr<OptionInt<T> > OptionIntTypePtr;
 
 public:
     /// @brief Constructor.
@@ -90,6 +93,10 @@ public:
         unpack(begin, end);
     }
 
+    virtual OptionPtr clone() const {
+        return (cloneInternal<OptionInt<T> >());
+    }
+
     /// Writes option in wire-format to buf, returns pointer to first unused
     /// byte after stored option.
     ///

+ 8 - 0
src/lib/dhcp/option_int_array.h

@@ -11,6 +11,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_data_types.h>
 #include <util/io_utilities.h>
+#include <boost/shared_ptr.hpp>
 #include <sstream>
 #include <stdint.h>
 
@@ -53,6 +54,9 @@ typedef boost::shared_ptr<OptionUint32Array> OptionUint32ArrayPtr;
 /// @param T data field type (see above).
 template<typename T>
 class OptionIntArray: public Option {
+private:
+
+    typedef boost::shared_ptr<OptionIntArray<T> > OptionIntArrayTypePtr;
 
 public:
 
@@ -116,6 +120,10 @@ public:
         unpack(begin, end);
     }
 
+    virtual OptionPtr clone() const {
+        return (cloneInternal<OptionIntArray<T> >());
+    }
+
     /// @brief Adds a new value to the array.
     ///
     /// @param value a value being added.

+ 6 - 1
src/lib/dhcp/option_opaque_data_tuples.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -25,6 +25,11 @@ OptionOpaqueDataTuples::OptionOpaqueDataTuples(Option::Universe u,
     unpack(begin, end);
 }
 
+OptionPtr
+OptionOpaqueDataTuples::clone() const {
+    return (cloneInternal<OptionOpaqueDataTuples>());
+}
+
 void
 OptionOpaqueDataTuples::pack(isc::util::OutputBuffer& buf) const {
     packHeader(buf);

+ 5 - 4
src/lib/dhcp/option_opaque_data_tuples.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -62,6 +62,8 @@ public:
                            OptionBufferConstIter begin,
                            OptionBufferConstIter end);
 
+    OptionPtr clone() const;
+
     /// @brief Renders option into the buffer in the wire format.
     ///
     /// @param [out] buf Buffer to which the option is rendered.
@@ -137,12 +139,11 @@ private:
     ///
     /// This function returns the length field type which should be used
     /// for the opaque data tuples being added to this option.
-    /// Currently this class is only used for a DHCPv6 option it may be expanded
-    /// for DHCPv4 in the future.
     ///
     /// @return Tuple length field type for the universe this option belongs to.
     OpaqueDataTuple::LengthFieldType getLengthFieldType() const {
-        return (OpaqueDataTuple::LENGTH_2_BYTES);
+        return (universe_ == Option::V6 ? OpaqueDataTuple::LENGTH_2_BYTES :
+                OpaqueDataTuple::LENGTH_1_BYTE);
     }
 
     /// @brief Returns minimal length of the option for the given universe.

+ 5 - 0
src/lib/dhcp/option_string.cc

@@ -27,6 +27,11 @@ OptionString::OptionString(const Option::Universe u, const uint16_t type,
     unpack(begin, end);
 }
 
+OptionPtr
+OptionString::clone() const {
+    return (cloneInternal<OptionString>());
+}
+
 std::string
 OptionString::getValue() const {
     const OptionBuffer& data = getData();

+ 2 - 0
src/lib/dhcp/option_string.h

@@ -56,6 +56,8 @@ public:
     OptionString(const Option::Universe u, const uint16_t type,
                  OptionBufferConstIter begin, OptionBufferConstIter end);
 
+    OptionPtr clone() const;
+
     /// @brief Returns length of the whole option, including header.
     ///
     /// @return length of the whole option.

+ 4 - 0
src/lib/dhcp/option_vendor.cc

@@ -23,6 +23,10 @@ OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin,
     unpack(begin, end);
 }
 
+OptionPtr
+OptionVendor::clone() const {
+    return (cloneInternal<OptionVendor>());
+}
 
 void OptionVendor::pack(isc::util::OutputBuffer& buf) const {
     packHeader(buf);

+ 2 - 0
src/lib/dhcp/option_vendor.h

@@ -50,6 +50,8 @@ public:
     OptionVendor(Option::Universe u, OptionBufferConstIter begin,
                  OptionBufferConstIter end);
 
+    OptionPtr clone() const;
+
     /// @brief Writes option in wire-format to buf, returns pointer to first
     /// unused byte after stored option.
     ///

+ 8 - 3
src/lib/dhcp/option_vendor_class.cc

@@ -20,13 +20,18 @@ OptionVendorClass::OptionVendorClass(Option::Universe u,
     }
 }
 
-    OptionVendorClass::OptionVendorClass(Option::Universe u,
-                                         OptionBufferConstIter begin,
-                                         OptionBufferConstIter end)
+OptionVendorClass::OptionVendorClass(Option::Universe u,
+                                     OptionBufferConstIter begin,
+                                     OptionBufferConstIter end)
     : Option(u, getOptionCode(u)) {
     unpack(begin, end);
 }
 
+OptionPtr
+OptionVendorClass::clone() const {
+    return (cloneInternal<OptionVendorClass>());
+}
+
 void
 OptionVendorClass::pack(isc::util::OutputBuffer& buf) const {
     packHeader(buf);

+ 2 - 0
src/lib/dhcp/option_vendor_class.h

@@ -69,6 +69,8 @@ public:
     OptionVendorClass(Option::Universe u, OptionBufferConstIter begin,
                       OptionBufferConstIter end);
 
+    OptionPtr clone() const;
+
     /// @brief Renders option into the buffer in the wire format.
     ///
     /// @param [out] buf Buffer to which the option is rendered.

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

@@ -59,6 +59,7 @@ libdhcp___unittests_SOURCES += option_int_unittest.cc
 libdhcp___unittests_SOURCES += option_int_array_unittest.cc
 libdhcp___unittests_SOURCES += option_data_types_unittest.cc
 libdhcp___unittests_SOURCES += option_definition_unittest.cc
+libdhcp___unittests_SOURCES += option_copy_unittest.cc
 libdhcp___unittests_SOURCES += option_custom_unittest.cc
 libdhcp___unittests_SOURCES += option_opaque_data_tuples_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc

+ 1 - 0
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -36,6 +36,7 @@
 
 #include <iostream>
 #include <sstream>
+#include <typeinfo>
 
 #include <arpa/inet.h>
 

+ 585 - 0
src/lib/dhcp/tests/option_copy_unittest.cc

@@ -0,0 +1,585 @@
+// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/option_opaque_data_tuples.h>
+#include <dhcp/option_string.h>
+#include <dhcp/option_vendor.h>
+#include <dhcp/option_vendor_class.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option4_client_fqdn.h>
+#include <dhcp/option6_addrlst.h>
+#include <dhcp/option6_client_fqdn.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_iaprefix.h>
+#include <dhcp/option6_status_code.h>
+#include <util/buffer.h>
+
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+enum OpType {
+    COPY,
+    CLONE,
+    ASSIGN
+};
+
+template<typename OptionType>
+void testCopyAssign(const OpType& op_type,
+                    boost::shared_ptr<OptionType>& option,
+                    boost::shared_ptr<OptionType>& option_copy) {
+    option->setEncapsulatedSpace("foo");
+
+    OptionUint16Ptr sub1 = OptionUint16Ptr(new OptionUint16(Option::V4, 10, 234));
+    Option4AddrLstPtr sub2 =
+        Option4AddrLstPtr(new Option4AddrLst(11, IOAddress("192.0.2.3")));
+    option->addOption(sub1);
+    option->addOption(sub2);
+
+    switch (op_type) {
+    case COPY:
+        option_copy.reset(new OptionType(*option));
+        break;
+    case CLONE:
+        option_copy = boost::dynamic_pointer_cast<OptionType>(option->clone());
+        ASSERT_TRUE(option_copy);
+        break;
+    case ASSIGN:
+        option_copy->setEncapsulatedSpace("bar");
+        *option_copy = *option;
+        break;
+    default:
+        ADD_FAILURE() << "unsupported operation";
+        return;
+    }
+
+    EXPECT_EQ(option->getUniverse(), option_copy->getUniverse());
+    EXPECT_EQ(option->getType(), option_copy->getType());
+    EXPECT_EQ(option->len(), option_copy->len());
+    EXPECT_EQ(option->getEncapsulatedSpace(), option_copy->getEncapsulatedSpace());
+    EXPECT_TRUE(std::equal(option->getData().begin(), option->getData().end(),
+                           option_copy->getData().begin()));
+
+    const OptionCollection& option_subs = option->getOptions();
+    const OptionCollection& option_copy_subs = option_copy->getOptions();
+
+    ASSERT_EQ(option_subs.size(), option_copy_subs.size());
+    OptionCollection::const_iterator it_copy = option_copy_subs.begin();
+    for (OptionCollection::const_iterator it = option_subs.begin();
+         it != option_subs.end(); ++it, ++it_copy) {
+        EXPECT_EQ(it->first, it_copy->first);
+        EXPECT_NE(it->second, it_copy->second);
+        Option* opt_ptr = it->second.get();
+        Option* opt_copy_ptr = it_copy->second.get();
+        EXPECT_TRUE(typeid(*opt_ptr) == typeid(*opt_copy_ptr));
+    }
+
+    std::vector<uint8_t> buf = option->toBinary(true);
+    std::vector<uint8_t> buf_copy = option_copy->toBinary(true);
+
+    ASSERT_EQ(buf.size(), buf_copy.size());
+    EXPECT_TRUE(std::equal(buf_copy.begin(), buf_copy.end(), buf.begin()));
+}
+
+void testOption(const OpType& op_type) {
+    OptionBuffer buf(10, 1);
+    OptionPtr option(new Option(Option::V4, 1, buf));
+    OptionPtr option_copy(new Option(Option::V6, 1000));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    std::vector<uint8_t> binary_copy = option_copy->toBinary(true);
+
+    OptionBuffer buf_modified(10, 2);
+    option->setData(buf_modified.begin(), buf_modified.end());
+
+    std::vector<uint8_t> binary_modified = option->toBinary(true);
+
+    ASSERT_EQ(binary_modified.size(), binary_copy.size());
+    EXPECT_FALSE(std::equal(binary_copy.begin(), binary_copy.end(),
+                            binary_modified.begin()));
+}
+
+TEST(OptionCopyTest, optionConstructor) {
+    testOption(COPY);
+}
+
+TEST(OptionCopyTest, optionClone) {
+    testOption(CLONE);
+}
+
+TEST(OptionCopyTest, optionAssignment) {
+    testOption(ASSIGN);
+}
+
+
+void testOptionInt(const OpType& op_type) {
+    OptionUint16Ptr option(new OptionUint16(Option::V4, 1, 12345));
+    OptionUint16Ptr option_copy(new OptionUint16(Option::V6, 10, 11111));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    ASSERT_EQ(12345, option->getValue());
+    ASSERT_EQ(12345, option_copy->getValue());
+
+    option->setValue(9);
+    ASSERT_EQ(9, option->getValue());
+    EXPECT_EQ(12345, option_copy->getValue());
+}
+
+TEST(OptionCopyTest, optionIntConstructor) {
+    testOptionInt(COPY);
+}
+
+TEST(OptionCopyTest, optionIntClone) {
+    testOptionInt(CLONE);
+}
+
+TEST(OptionCopyTest, optionIntAssignment) {
+    testOptionInt(ASSIGN);
+}
+
+void testOptionIntArray(const OpType& op_type) {
+    OptionUint32ArrayPtr option(new OptionUint32Array(Option::V4, 1));;
+    option->addValue(2345);
+    option->addValue(3456);
+    OptionUint32ArrayPtr option_copy(new OptionUint32Array(Option::V6, 10));
+    option_copy->addValue(5678);
+    option_copy->addValue(6789);
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setValues(std::vector<uint32_t>(2, 7));
+
+    std::vector<uint32_t> values_copy = option_copy->getValues();
+    ASSERT_EQ(2, values_copy.size());
+    EXPECT_EQ(2345, values_copy[0]);
+    EXPECT_EQ(3456, values_copy[1]);
+}
+
+TEST(OptionCopyTest, optionIntArrayConstructor) {
+    testOptionIntArray(COPY);
+}
+
+TEST(OptionCopyTest, optionIntArrayClone) {
+    testOptionIntArray(CLONE);
+}
+
+TEST(OptionCopyTest, optionIntArrayAssignment) {
+    testOptionIntArray(ASSIGN);
+}
+
+template<typename OptionType>
+void testOptionAddrLst(const OpType& op_type,
+                       const IOAddress& option_address,
+                       const IOAddress& option_copy_address,
+                       const IOAddress& option_modified_address) {
+    typedef boost::shared_ptr<OptionType> OptionTypePtr;
+    OptionTypePtr option(new OptionType(1, option_address));
+    OptionTypePtr option_copy(new OptionType(10, option_copy_address));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setAddress(option_modified_address);
+    typename OptionType::AddressContainer addrs_copy = option_copy->getAddresses();
+    ASSERT_EQ(1, addrs_copy.size());
+    EXPECT_EQ(option_address.toText(), addrs_copy[0].toText());
+}
+
+void testOption4AddrLst(const OpType& op_type) {
+    testOptionAddrLst<Option4AddrLst>(op_type,
+                                      IOAddress("127.0.0.1"),
+                                      IOAddress("192.0.2.111"),
+                                      IOAddress("127.0.0.1"));
+}
+
+void testOption6AddrLst(const OpType& op_type) {
+    testOptionAddrLst<Option6AddrLst>(op_type,
+                                      IOAddress("2001:db8:1::2"),
+                                      IOAddress("3001::cafe"),
+                                      IOAddress("3000:1::1"));
+}
+
+TEST(OptionCopyTest, option4AddrLstConstructor) {
+    testOption4AddrLst(COPY);
+}
+
+TEST(OptionCopyTest, option4AddrLstClone) {
+    testOption4AddrLst(CLONE);
+}
+
+TEST(OptionCopyTest, option4AddrLstAssignment) {
+    testOption4AddrLst(ASSIGN);
+}
+
+TEST(OptionCopyTest, option6AddrLstConstructor) {
+    testOption6AddrLst(COPY);
+}
+
+TEST(OptionCopyTest, option6AddrLstClone) {
+    testOption6AddrLst(CLONE);
+}
+
+TEST(OptionCopyTest, option6AddrLstAssignment) {
+    testOption6AddrLst(ASSIGN);
+}
+
+void testOption6IA(const OpType& op_type) {
+    Option6IAPtr option(new Option6IA(D6O_IA_NA, 1234));
+    option->setT1(1000);
+    option->setT2(2000);
+    Option6IAPtr option_copy(new Option6IA(D6O_IA_PD, 5678));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setT1(3000);
+    option->setT2(4000);
+    option->setIAID(5678);
+
+    EXPECT_EQ(1000, option_copy->getT1());
+    EXPECT_EQ(2000, option_copy->getT2());
+    EXPECT_EQ(1234, option_copy->getIAID());
+}
+
+TEST(OptionCopyTest, option6IAConstructor) {
+    testOption6IA(COPY);
+}
+
+TEST(OptionCopyTest, option6IAClone) {
+    testOption6IA(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAAssignment) {
+    testOption6IA(ASSIGN);
+}
+
+void testOption6IAAddr(const OpType& op_type) {
+    Option6IAAddrPtr option(new Option6IAAddr(D6O_IAADDR,
+                                              IOAddress("2001:db8:1::1"),
+                                              60, 90));
+    Option6IAAddrPtr option_copy(new Option6IAAddr(D6O_IAADDR,
+                                                       IOAddress("2001:db8:1::2"),
+                                                       50, 80));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setAddress(IOAddress("2001:db8:1::3"));
+    option->setPreferred(1000);
+    option->setValid(2000);
+
+    EXPECT_EQ("2001:db8:1::1", option_copy->getAddress().toText());
+    EXPECT_EQ(60, option_copy->getPreferred());
+    EXPECT_EQ(90, option_copy->getValid());
+}
+
+TEST(OptionCopyTest, option6IAAddrConstructor) {
+    testOption6IAAddr(COPY);
+}
+
+TEST(OptionCopyTest, option6IAAddrClone) {
+    testOption6IAAddr(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAAddrAssignment) {
+    testOption6IAAddr(ASSIGN);
+}
+
+void testOption6IAPrefix(const OpType& op_type) {
+   Option6IAPrefixPtr option(new Option6IAPrefix(D6O_IAPREFIX,
+                                                  IOAddress("3000::"),
+                                                  64, 60, 90));
+    Option6IAPrefixPtr option_copy(new Option6IAPrefix(D6O_IAPREFIX,
+                                                           IOAddress("3001::"),
+                                                           48, 50, 80));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setPrefix(IOAddress("3002::"), 32);
+    option->setPreferred(1000);
+    option->setValid(2000);
+
+    EXPECT_EQ("3000::", option_copy->getAddress().toText());
+    EXPECT_EQ(64, option_copy->getLength());
+    EXPECT_EQ(60, option_copy->getPreferred());
+    EXPECT_EQ(90, option_copy->getValid());
+}
+
+TEST(OptionCopyTest, option6IAPrefixConstructor) {
+    testOption6IAPrefix(COPY);
+}
+
+TEST(OptionCopyTest, option6IAPrefixClone) {
+    testOption6IAPrefix(CLONE);
+}
+
+TEST(OptionCopyTest, option6IAPrefixAssignment) {
+    testOption6IAPrefix(ASSIGN);
+}
+
+void testOption6StatusCode(const OpType& op_type) {
+    Option6StatusCodePtr option(new Option6StatusCode(STATUS_NoBinding,
+                                                      "no binding"));
+    Option6StatusCodePtr option_copy(new Option6StatusCode(STATUS_Success,
+                                                           "success"));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setStatusCode(STATUS_NoAddrsAvail);
+    option->setStatusMessage("foo");
+
+    EXPECT_EQ(STATUS_NoBinding, option_copy->getStatusCode());
+    EXPECT_EQ("no binding", option_copy->getStatusMessage());
+}
+
+TEST(OptionCopyTest, option6StatusCodeConstructor) {
+    testOption6StatusCode(COPY);
+}
+
+TEST(OptionCopyTest, option6StatusCodeClone) {
+    testOption6StatusCode(CLONE);
+}
+
+TEST(OptionCopyTest, option6StatusCodeAssignment) {
+    testOption6StatusCode(ASSIGN);
+}
+
+void testOptionString(const OpType& op_type) {
+    OptionStringPtr option(new OptionString(Option::V4, 1, "option value"));
+    OptionStringPtr option_copy(new OptionString(Option::V6, 10,
+                                                     "another value"));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setValue("foo");
+    EXPECT_EQ("option value", option_copy->getValue());
+}
+
+TEST(OptionCopyTest, optionStringConstructor) {
+    testOptionString(COPY);
+}
+
+TEST(OptionCopyTest, optionStringClone) {
+    testOptionString(CLONE);
+}
+
+TEST(OptionCopyTest, optionStringAssignment) {
+    testOptionString(ASSIGN);
+}
+
+void testOptionVendor(const OpType& op_type) {
+    OptionVendorPtr option(new OptionVendor(Option::V4, 2986));
+    OptionVendorPtr option_copy(new OptionVendor(Option::V6, 1111));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setVendorId(2222);
+    EXPECT_EQ(2986, option_copy->getVendorId());
+}
+
+TEST(OptionCopyTest, optionVendorConstructor) {
+    testOptionVendor(COPY);
+}
+
+TEST(OptionCopyTest, optionVendorClone) {
+    testOptionVendor(CLONE);
+}
+
+TEST(OptionCopyTest, optionVendorAssignment) {
+    testOptionVendor(ASSIGN);
+}
+
+void testOptionVendorClass(const OpType& op_type) {
+    OptionVendorClassPtr option(new OptionVendorClass(Option::V4, 2986));
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "vendor-class-value";
+    option->setTuple(0, tuple);
+    OptionVendorClassPtr option_copy(new OptionVendorClass(Option::V6,
+                                                               1111));
+    OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "vendor-class-assigned";
+    option_copy->addTuple(tuple_copy);
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    tuple = "modified-vendor-class-value";
+    option->setTuple(0, tuple);
+    tuple = "another-modified-vendor-class-value";
+    option->addTuple(tuple);
+
+    ASSERT_EQ(1, option_copy->getTuplesNum());
+    tuple = option_copy->getTuple(0);
+    EXPECT_TRUE(tuple.equals("vendor-class-value"));
+}
+
+TEST(OptionCopyTest, optionVendorClassConstructor) {
+    testOptionVendorClass(COPY);
+}
+
+TEST(OptionCopyTest, optionVendorClassClone) {
+    testOptionVendorClass(CLONE);
+}
+
+TEST(OptionCopyTest, optionVendorClassAssignment) {
+    testOptionVendorClass(ASSIGN);
+}
+
+template<typename OptionType>
+void testOptionClientFqdn(const OpType& op_type,
+                          boost::shared_ptr<OptionType>& option,
+                          boost::shared_ptr<OptionType>& option_copy) {
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    option->setDomainName("newname", OptionType::PARTIAL);
+    option->setFlag(OptionType::FLAG_S, false);
+    option->setFlag(OptionType::FLAG_N, true);
+
+    Option4ClientFqdnPtr option4 =
+        boost::dynamic_pointer_cast<Option4ClientFqdn>(option);
+    if (option4) {
+        option4->setRcode(64);
+    }
+
+    EXPECT_EQ("myname.example.org.", option_copy->getDomainName());
+    EXPECT_EQ(OptionType::FULL, option_copy->getDomainNameType());
+    EXPECT_TRUE(option_copy->getFlag(OptionType::FLAG_S));
+    EXPECT_FALSE(option_copy->getFlag(OptionType::FLAG_N));
+
+    Option4ClientFqdnPtr option_copy4 =
+        boost::dynamic_pointer_cast<Option4ClientFqdn>(option_copy);
+    if (option_copy4) {
+        EXPECT_EQ(255, option_copy4->getRcode().first.getCode());
+        EXPECT_EQ(255, option_copy4->getRcode().second.getCode());
+    }
+}
+
+void testOption4ClientFqdn(const OpType& op_type) {
+    Option4ClientFqdnPtr
+        option(new Option4ClientFqdn(Option4ClientFqdn::FLAG_S,
+                                     Option4ClientFqdn::Rcode(255),
+                                     "myname.example.org"));
+    Option4ClientFqdnPtr
+        option_copy(new Option4ClientFqdn(Option4ClientFqdn::FLAG_O,
+                                          Option4ClientFqdn::Rcode(0),
+                                          "other.example.org"));
+
+    ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option4ClientFqdn>(op_type, option,
+                                                                    option_copy));
+}
+
+TEST(OptionCopyTest, option4ClientFqdnConstructor) {
+    testOption4ClientFqdn(COPY);
+}
+
+TEST(OptionCopyTest, option4ClientFqdnClone) {
+    testOption4ClientFqdn(CLONE);
+}
+
+TEST(OptionCopyTest, option4ClientFqdnAssignment) {
+    testOption4ClientFqdn(ASSIGN);
+}
+
+void testOption6ClientFqdn(const OpType& op_type) {
+    Option6ClientFqdnPtr
+        option(new Option6ClientFqdn(Option6ClientFqdn::FLAG_S,
+                                     "myname.example.org"));
+    Option6ClientFqdnPtr
+        option_copy(new Option6ClientFqdn(Option6ClientFqdn::FLAG_O,
+                                          "other.example.org"));
+
+    ASSERT_NO_FATAL_FAILURE(testOptionClientFqdn<Option6ClientFqdn>(op_type, option,
+                                                                    option_copy));
+}
+
+TEST(OptionCopyTest, option6ClientFqdnConstructor) {
+    testOption6ClientFqdn(COPY);
+}
+
+TEST(OptionCopyTest, option6ClientFqdnClone) {
+    testOption6ClientFqdn(CLONE);
+}
+
+TEST(OptionCopyTest, option6ClientFqdnAssignment) {
+    testOption6ClientFqdn(ASSIGN);
+}
+
+void testOptionCustom(const OpType& op_type) {
+    OptionDefinition def("foo", 1, "uint16", true);
+    OptionCustomPtr option(new OptionCustom(def, Option::V4));
+    OptionDefinition def_assigned("bar", 10, "record");
+    def_assigned.addRecordField("ipv4-address");
+    def_assigned.addRecordField("uint32");
+    OptionCustomPtr option_copy(new OptionCustom(def_assigned, Option::V6));
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+}
+
+TEST(OptionCopyTest, optionCustomConstructor) {
+    testOptionCustom(COPY);
+}
+
+TEST(OptionCopyTest, optionCustomClone) {
+    testOptionCustom(CLONE);
+}
+
+TEST(OptionCopyTest, optionCustomAssignment) {
+    testOptionCustom(ASSIGN);
+}
+
+void testOptionOpaqueDataTuples(const OpType& op_type) {
+    OptionOpaqueDataTuplesPtr option(new OptionOpaqueDataTuples(Option::V4, 1));
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "a string";
+    option->addTuple(tuple);
+    tuple = "another string";
+    option->addTuple(tuple);
+    OptionOpaqueDataTuplesPtr option_copy(new OptionOpaqueDataTuples(Option::V6, 10));
+    OpaqueDataTuple tuple_copy(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple_copy = "copy string";
+    option_copy->addTuple(tuple_copy);
+
+    ASSERT_NO_FATAL_FAILURE(testCopyAssign(op_type, option, option_copy));
+
+    tuple = "modified-first-tuple";
+    option->setTuple(0, tuple);
+    tuple = "modified-second-tuple";
+    option->addTuple(tuple);
+
+    ASSERT_EQ(2, option_copy->getTuplesNum());
+    EXPECT_TRUE(option_copy->getTuple(0).equals("a string"));
+    EXPECT_TRUE(option_copy->getTuple(1).equals("another string"));
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesConstructor) {
+    testOptionOpaqueDataTuples(COPY);
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesClone) {
+    testOptionOpaqueDataTuples(CLONE);
+}
+
+TEST(OptionCopyTest, optionOpaqueDataTuplesAssign) {
+    testOptionOpaqueDataTuples(ASSIGN);
+}
+
+}

+ 7 - 1
src/lib/dhcp/tests/option_opaque_data_tuples_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -58,6 +58,12 @@ TEST(OptionOpaqueDataTuples, addTuple) {
     // for DHCPv6 option.
     OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE);
     EXPECT_THROW(data_tuple.addTuple(tuple2), isc::BadValue);
+
+    // Similarly, adding a tuple with 2 bytes long length field should
+    // fail for DHCPv4 option.
+    OptionOpaqueDataTuples data_tuple2(Option::V4, 65);
+    OpaqueDataTuple tuple3(OpaqueDataTuple::LENGTH_2_BYTES);
+    EXPECT_THROW(data_tuple2.addTuple(tuple3), isc::BadValue);
 }
 
 // This test checks that it is possible to replace existing tuple.

+ 4 - 0
src/lib/dhcp/tests/option_unittest.cc

@@ -6,9 +6,12 @@
 
 #include <config.h>
 
+#include <asiolink/io_address.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option4_addrlst.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 
@@ -24,6 +27,7 @@
 
 using namespace std;
 using namespace isc;
+using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::util;
 using boost::scoped_ptr;