// Copyright (C) 2011-2019 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/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/docsis3_option_defs.h>
#include <dhcp/libdhcp++.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_pdexclude.h>
#include <dhcp/option6_status_code.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_opaque_data_tuples.h>
#include <dhcp/option_space.h>
#include <dhcp/option_string.h>
#include <dhcp/option_vendor.h>
#include <dhcp/option_vendor_class.h>
#include <util/buffer.h>
#include <util/encode/hex.h>

#include <boost/pointer_cast.hpp>

#include <gtest/gtest.h>

#include <iostream>
#include <sstream>
#include <typeinfo>

#include <arpa/inet.h>

using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace isc::util;

namespace {

// DHCPv6 suboptions of Vendor Options Option.
/// @todo move to src/lib/dhcp/docsis3_option_defs.h once #3194 is merged.
const uint16_t OPTION_CMTS_CAPS = 1025;
const uint16_t OPTION_CM_MAC = 1026;

class LibDhcpTest : public ::testing::Test {
public:
    /// @brief Constructor.
    ///
    /// Removes runtime option definitions.
    LibDhcpTest() {
        LibDHCP::clearRuntimeOptionDefs();
    }

    /// @brief Destructor.
    ///
    /// Removes runtime option definitions.
    virtual ~LibDhcpTest() {
        LibDHCP::clearRuntimeOptionDefs();
    }

    /// @brief Generic factory function to create any option.
    ///
    /// Generic factory function to create any option.
    ///
    /// @param u universe (V4 or V6)
    /// @param type option-type
    /// @param buf option-buffer
    static OptionPtr genericOptionFactory(Option::Universe u, uint16_t type,
                                          const OptionBuffer& buf) {
        return (OptionPtr(new Option(u, type, buf)));
    }

    /// @brief Test DHCPv4 option definition.
    ///
    /// This function tests if option definition for standard
    /// option has been initialized correctly.
    ///
    /// @param code option code.
    /// @param begin iterator pointing at beginning of a buffer to
    /// be used to create option instance.
    /// @param end iterator pointing at end of a buffer to be
    /// used to create option instance.
    /// @param expected_type type of the option created by the
    /// factory function returned by the option definition.
    /// @param encapsulates name of the option space being encapsulated
    /// by the option.
    static void testStdOptionDefs4(const uint16_t code,
                                   const OptionBufferConstIter begin,
                                   const OptionBufferConstIter end,
                                   const std::type_info& expected_type,
                                   const std::string& encapsulates = "") {
        // Use V4 universe.
        testStdOptionDefs(Option::V4, DHCP4_OPTION_SPACE, code, begin, end,
                          expected_type, encapsulates);
    }

    /// @brief Test DHCPv6 option definition.
    ///
    /// This function tests if option definition for standard
    /// option has been initialized correctly.
    ///
    /// @param code option code.
    /// @param begin iterator pointing at beginning of a buffer to
    /// be used to create option instance.
    /// @param end iterator pointing at end of a buffer to be
    /// used to create option instance.
    /// @param expected_type type of the option created by the
    /// factory function returned by the option definition.
    /// @param encapsulates name of the option space being encapsulated
    /// by the option.
    static void testStdOptionDefs6(const uint16_t code,
                                   const OptionBufferConstIter begin,
                                   const OptionBufferConstIter end,
                                   const std::type_info& expected_type,
                                   const std::string& encapsulates = "") {
        // Use V6 universe.
        testStdOptionDefs(Option::V6, DHCP6_OPTION_SPACE, code, begin,
                          end, expected_type, encapsulates);
    }

    /// @brief Test DHCPv6 option definition in a given option space.
    ///
    /// This function tests if option definition for an option from a
    /// given option space has been initialized correctly.
    ///
    /// @param option_space option space.
    /// @param code option code.
    /// @param begin iterator pointing at beginning of a buffer to
    /// be used to create option instance.
    /// @param end iterator pointing at end of a buffer to be
    /// used to create option instance.
    /// @param expected_type type of the option created by the
    /// factory function returned by the option definition.
    /// @param encapsulates name of the option space being encapsulated
    /// by the option.
    static void testOptionDefs6(const std::string& option_space,
                                const uint16_t code,
                                const OptionBufferConstIter begin,
                                const OptionBufferConstIter end,
                                const std::type_info& expected_type,
                                const std::string& encapsulates = "") {
        testStdOptionDefs(Option::V6, option_space, code, begin,
                          end, expected_type, encapsulates);
    }

    /// @brief Create a sample DHCPv4 option 82 with suboptions.
    static OptionBuffer createAgentInformationOption() {
        const uint8_t opt_data[] = {
            0x52, 0x0E, // Agent Information Option (length = 14)
            // Suboptions start here...
            0x01, 0x04, // Agent Circuit ID (length = 4)
            0x20, 0x00, 0x00, 0x02, // ID
            0x02, 0x06, // Agent Remote ID
            0x20, 0xE5, 0x2A, 0xB8, 0x15, 0x14 // ID
        };
        return (OptionBuffer(opt_data, opt_data + sizeof(opt_data)));
    }

    /// @brief Create option definitions and store in the container.
    ///
    /// @param spaces_num Number of option spaces to be created.
    /// @param defs_num Number of option definitions to be created for
    /// each option space.
    /// @param [out] defs Container to which option definitions should be
    /// added.
    static void createRuntimeOptionDefs(const uint16_t spaces_num,
                                        const uint16_t defs_num,
                                        OptionDefSpaceContainer& defs) {
        for (uint16_t space = 0; space < spaces_num; ++space) {
            std::ostringstream space_name;
            space_name << "option-space-" << space;
            for (uint16_t code = 0; code < defs_num; ++code) {
                std::ostringstream name;
                name << "name-for-option-" << code;
                OptionDefinitionPtr opt_def(new OptionDefinition(name.str(),
                                                                 code, "string"));
                defs.addItem(opt_def, space_name.str());
            }
        }
    }

    /// @brief Test if runtime option definitions have been added.
    ///
    /// This method uses the same naming conventions for space names and
    /// options names as @c createRuntimeOptionDefs method.
    ///
    /// @param spaces_num Number of option spaces to be tested.
    /// @param defs_num Number of option definitions that should exist
    /// in each option space.
    /// @param should_exist Boolean value which indicates if option
    /// definitions should exist. If this is false, this function will
    /// check that they don't exist.
    static void testRuntimeOptionDefs(const uint16_t spaces_num,
                                      const uint16_t defs_num,
                                      const bool should_exist) {
        for (uint16_t space = 0; space < spaces_num; ++space) {
            std::ostringstream space_name;
            space_name << "option-space-" << space;
            for (uint16_t code = 0; code < defs_num; ++code) {
                std::ostringstream name;
                name << "name-for-option-" << code;
                OptionDefinitionPtr opt_def =
                    LibDHCP::getRuntimeOptionDef(space_name.str(), name.str());
                if (should_exist) {
                    ASSERT_TRUE(opt_def);
                } else {
                    ASSERT_FALSE(opt_def);
                }
            }
        }
    }

private:

    /// @brief Test DHCPv4 or DHCPv6 option definition.
    ///
    /// This function tests if option definition for standard
    /// option has been initialized correctly.
    ///
    /// @param option_space option space.
    /// @param code option code.
    /// @param begin iterator pointing at beginning of a buffer to
    /// be used to create option instance.
    /// @param end iterator pointing at end of a buffer to be
    /// used to create option instance.
    /// @param expected_type type of the option created by the
    /// factory function returned by the option definition.
    /// @param encapsulates name of the option space being encapsulated
    /// by the option.
    static void testStdOptionDefs(const Option::Universe& u,
                                  const std::string& option_space,
                                  const uint16_t code,
                                  const OptionBufferConstIter begin,
                                  const OptionBufferConstIter end,
                                  const std::type_info& expected_type,
                                  const std::string& encapsulates) {
        // Get all option definitions, we will use them to extract
        // the definition for a particular option code.
        // We don't have to initialize option definitions here because they
        // are initialized in the class's constructor.
        OptionDefContainerPtr options = LibDHCP::getOptionDefs(option_space);
        // Get the container index #1. This one allows for searching
        // option definitions using option code.
        const OptionDefContainerTypeIndex& idx = options->get<1>();
        // Get 'all' option definitions for a particular option code.
        // For standard options we expect that the range returned
        // will contain single option as their codes are unique.
        OptionDefContainerTypeRange range = idx.equal_range(code);
        ASSERT_EQ(1, std::distance(range.first, range.second))
            << "Standard option definition for the code " << code
            << " has not been found.";
        // If we have single option definition returned, the
        // first iterator holds it.
        OptionDefinitionPtr def = *(range.first);
        // It should not happen that option definition is NULL but
        // let's make sure (test should take things like that into
        // account).
        ASSERT_TRUE(def) << "Option definition for the code "
                         << code << " is NULL.";
        // Check that option definition is valid.
        ASSERT_NO_THROW(def->validate())
            << "Option definition for the option code " << code
            << " is invalid";
        // Check that the valid encapsulated option space name
        // has been specified.
        EXPECT_EQ(encapsulates, def->getEncapsulatedSpace());
        OptionPtr option;
        // Create the option.
        ASSERT_NO_THROW(option = def->optionFactory(u, code, begin, end))
            << "Option creation failed for 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.
        const Option* optptr = option.get();
        EXPECT_TRUE(typeid(*optptr) == expected_type)
            << "Invalid class returned for option code " << code;
    }
};

// The DHCPv6 options in the wire format, used by multiple tests.
const uint8_t v6packed[] = {
    0, 1, 0, 5, 100, 101, 102, 103, 104, // CLIENT_ID (9 bytes)
    0, 2, 0, 3, 105, 106, 107, // SERVER_ID (7 bytes)
    0, 14, 0, 0, // RAPID_COMMIT (0 bytes)
    0,  6, 0, 4, 108, 109, 110, 111, // ORO (8 bytes)
    0,  8, 0, 2, 112, 113, // ELAPSED_TIME (6 bytes)
    // Vendor Specific Information Option starts here
    0x00, 0x11,  // VSI Option Code
    0x00, 0x16,  // VSI Option Length
    0x00, 0x00, 0x11, 0x8B, // Enterprise ID
    0x04, 0x01,  // CMTS Capabilities Option
    0x00, 0x04,  // Length
    0x01, 0x02,
    0x03, 0x00,  // DOCSIS Version Number
    0x04, 0x02,  // CM MAC Address Suboption
    0x00, 0x06,  // Length
    0x74, 0x56, 0x12, 0x29, 0x97, 0xD0, // Actual MAC Address

};

TEST_F(LibDhcpTest, optionFactory) {
    OptionBuffer buf;
    // Factory functions for specific options must be registered before
    // they can be used to create options instances. Otherwise exception
    // is raised.
    EXPECT_THROW(LibDHCP::optionFactory(Option::V4, DHO_SUBNET_MASK, buf),
                 isc::BadValue);

    // Let's register some factory functions (two v4 and one v6 function).
    // Registration may trigger exception if function for the specified
    // option has been registered already.
    ASSERT_NO_THROW(
        LibDHCP::OptionFactoryRegister(Option::V4, DHO_SUBNET_MASK,
                                       &LibDhcpTest::genericOptionFactory);
    );
    ASSERT_NO_THROW(
        LibDHCP::OptionFactoryRegister(Option::V4, DHO_TIME_OFFSET,
                                       &LibDhcpTest::genericOptionFactory);
    );
    ASSERT_NO_THROW(
        LibDHCP::OptionFactoryRegister(Option::V6, D6O_CLIENTID,
                                       &LibDhcpTest::genericOptionFactory);
    );

    // Invoke factory functions for all options (check if registration
    // was successful).
    OptionPtr opt_subnet_mask;
    opt_subnet_mask = LibDHCP::optionFactory(Option::V4,
                                             DHO_SUBNET_MASK,
                                             buf);
    // Check if non-NULL DHO_SUBNET_MASK option pointer has been returned.
    ASSERT_TRUE(opt_subnet_mask);
    // Validate if type and universe is correct.
    EXPECT_EQ(Option::V4, opt_subnet_mask->getUniverse());
    EXPECT_EQ(DHO_SUBNET_MASK, opt_subnet_mask->getType());
    // Expect that option does not have content..
    EXPECT_EQ(0, opt_subnet_mask->len() - opt_subnet_mask->getHeaderLen());

    // Fill the time offset buffer with 4 bytes of data. Each byte set to 1.
    OptionBuffer time_offset_buf(4, 1);
    OptionPtr opt_time_offset;
    opt_time_offset = LibDHCP::optionFactory(Option::V4,
                                             DHO_TIME_OFFSET,
                                             time_offset_buf);
    // Check if non-NULL DHO_TIME_OFFSET option pointer has been returned.
    ASSERT_TRUE(opt_time_offset);
    // Validate if option length, type and universe is correct.
    EXPECT_EQ(Option::V4, opt_time_offset->getUniverse());
    EXPECT_EQ(DHO_TIME_OFFSET, opt_time_offset->getType());
    EXPECT_EQ(time_offset_buf.size(),
              opt_time_offset->len() - opt_time_offset->getHeaderLen());
    // Validate data in the option.
    EXPECT_TRUE(std::equal(time_offset_buf.begin(), time_offset_buf.end(),
                           opt_time_offset->getData().begin()));

    // Fill the client id buffer with 20 bytes of data. Each byte set to 2.
    OptionBuffer clientid_buf(20, 2);
    OptionPtr opt_clientid;
    opt_clientid = LibDHCP::optionFactory(Option::V6,
                                          D6O_CLIENTID,
                                          clientid_buf);
    // Check if non-NULL D6O_CLIENTID option pointer has been returned.
    ASSERT_TRUE(opt_clientid);
    // Validate if option length, type and universe is correct.
    EXPECT_EQ(Option::V6, opt_clientid->getUniverse());
    EXPECT_EQ(D6O_CLIENTID, opt_clientid->getType());
    EXPECT_EQ(clientid_buf.size(), opt_clientid->len() - opt_clientid->getHeaderLen());
    // Validate data in the option.
    EXPECT_TRUE(std::equal(clientid_buf.begin(), clientid_buf.end(),
                           opt_clientid->getData().begin()));
}

TEST_F(LibDhcpTest, packOptions6) {
    OptionBuffer buf(512);
    isc::dhcp::OptionCollection opts; // list of options

    // generate content for options
    for (unsigned i = 0; i < 64; i++) {
        buf[i]=i+100;
    }

    OptionPtr opt1(new Option(Option::V6, 1, buf.begin() + 0, buf.begin() + 5));
    OptionPtr opt2(new Option(Option::V6, 2, buf.begin() + 5, buf.begin() + 8));
    OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 8));
    OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
    OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));

    OptionPtr cm_mac(new Option(Option::V6, OPTION_CM_MAC,
                                OptionBuffer(v6packed + 54, v6packed  + 60)));

    OptionPtr cmts_caps(new Option(Option::V6, OPTION_CMTS_CAPS,
                                   OptionBuffer(v6packed + 46, v6packed + 50)));

    boost::shared_ptr<OptionInt<uint32_t> >
        vsi(new OptionInt<uint32_t>(Option::V6, D6O_VENDOR_OPTS, 4491));
    vsi->addOption(cm_mac);
    vsi->addOption(cmts_caps);

    opts.insert(make_pair(opt1->getType(), opt1));
    opts.insert(make_pair(opt1->getType(), opt2));
    opts.insert(make_pair(opt1->getType(), opt3));
    opts.insert(make_pair(opt1->getType(), opt4));
    opts.insert(make_pair(opt1->getType(), opt5));
    opts.insert(make_pair(opt1->getType(), vsi));

    OutputBuffer assembled(512);

    EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts));
    EXPECT_EQ(sizeof(v6packed), assembled.getLength());
    EXPECT_EQ(0, memcmp(assembled.getData(), v6packed, sizeof(v6packed)));
}

TEST_F(LibDhcpTest, unpackOptions6) {

    // just couple of random options
    // Option is used as a simple option implementation
    // More advanced uses are validated in tests dedicated for
    // specific derived classes.
    isc::dhcp::OptionCollection options; // list of options

    OptionBuffer buf(512);
    memcpy(&buf[0], v6packed, sizeof(v6packed));

    EXPECT_NO_THROW ({
            LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin() + sizeof(v6packed)),
                                    "dhcp6", options);
    });

    EXPECT_EQ(options.size(), 6); // there should be 5 options

    isc::dhcp::OptionCollection::const_iterator x = options.find(1);
    ASSERT_FALSE(x == options.end()); // option 1 should exist
    EXPECT_EQ(1, x->second->getType());  // this should be option 1
    ASSERT_EQ(9, x->second->len()); // it should be of length 9
    ASSERT_EQ(5, x->second->getData().size());
    EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 4, 5)); // data len=5

        x = options.find(2);
    ASSERT_FALSE(x == options.end()); // option 2 should exist
    EXPECT_EQ(2, x->second->getType());  // this should be option 2
    ASSERT_EQ(7, x->second->len()); // it should be of length 7
    ASSERT_EQ(3, x->second->getData().size());
    EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 13, 3)); // data len=3

    x = options.find(14);
    ASSERT_FALSE(x == options.end()); // option 14 should exist
    EXPECT_EQ(14, x->second->getType());  // this should be option 14
    ASSERT_EQ(4, x->second->len()); // it should be of length 4
    EXPECT_EQ(0, x->second->getData().size()); // data len = 0

    x = options.find(6);
    ASSERT_FALSE(x == options.end()); // option 6 should exist
    EXPECT_EQ(6, x->second->getType());  // this should be option 6
    ASSERT_EQ(8, x->second->len()); // it should be of length 8
    // Option with code 6 is the OPTION_ORO. This option is
    // represented by the OptionIntArray<uint16_t> class which
    // comprises the set of uint16_t values. We need to cast the
    // returned pointer to this type to get values stored in it.
    boost::shared_ptr<OptionIntArray<uint16_t> > opt_oro =
        boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(x->second);
    // This value will be NULL if cast was unsuccessful. This is the case
    // when returned option has different type than expected.
    ASSERT_TRUE(opt_oro);
    // Get set of uint16_t values.
    std::vector<uint16_t> opts = opt_oro->getValues();
    // Prepare the reference data.
    std::vector<uint16_t> expected_opts;
    expected_opts.push_back(0x6C6D); // equivalent to: 108, 109
    expected_opts.push_back(0x6E6F); // equivalent to 110, 111
    ASSERT_EQ(expected_opts.size(), opts.size());
    // Validated if option has been unpacked correctly.
    EXPECT_TRUE(std::equal(expected_opts.begin(), expected_opts.end(),
                           opts.begin()));

    x = options.find(8);
    ASSERT_FALSE(x == options.end()); // option 8 should exist
    EXPECT_EQ(8, x->second->getType());  // this should be option 8
    ASSERT_EQ(6, x->second->len()); // it should be of length 9
    // Option with code 8 is OPTION_ELAPSED_TIME. This option is
    // represented by Option6Int<uint16_t> value that holds single
    // uint16_t value.
    boost::shared_ptr<OptionInt<uint16_t> > opt_elapsed_time =
        boost::dynamic_pointer_cast<OptionInt<uint16_t> >(x->second);
    // This value will be NULL if cast was unsuccessful. This is the case
    // when returned option has different type than expected.
    ASSERT_TRUE(opt_elapsed_time);
    // Returned value should be equivalent to two byte values: 112, 113
    EXPECT_EQ(0x7071, opt_elapsed_time->getValue());

    // Check if Vendor Specific Information Option along with suboptions
    // have been parsed correctly.
    x = options.find(D6O_VENDOR_OPTS);
    EXPECT_FALSE(x == options.end());
    EXPECT_EQ(D6O_VENDOR_OPTS, x->second->getType());
    EXPECT_EQ(26, x->second->len());

    // CM MAC Address Option
    OptionPtr cm_mac = x->second->getOption(OPTION_CM_MAC);
    ASSERT_TRUE(cm_mac);
    EXPECT_EQ(OPTION_CM_MAC, cm_mac->getType());
    ASSERT_EQ(10, cm_mac->len());
    EXPECT_EQ(0, memcmp(&cm_mac->getData()[0], v6packed + 54, 6));

    // CMTS Capabilities
    OptionPtr cmts_caps = x->second->getOption(OPTION_CMTS_CAPS);
    ASSERT_TRUE(cmts_caps);
    EXPECT_EQ(OPTION_CMTS_CAPS, cmts_caps->getType());
    ASSERT_EQ(8, cmts_caps->len());
    EXPECT_EQ(0, memcmp(&cmts_caps->getData()[0], v6packed + 46, 4));

    x = options.find(0);
    EXPECT_TRUE(x == options.end()); // option 0 not found

    x = options.find(256); // 256 is htons(1) on little endians. Worth checking
    EXPECT_TRUE(x == options.end()); // option 1 not found

    x = options.find(7);
    EXPECT_TRUE(x == options.end()); // option 2 not found

    x = options.find(32000);
    EXPECT_TRUE(x == options.end()); // option 32000 not found */
}

// Check parsing of an empty DHCPv6 option.
TEST_F(LibDhcpTest, unpackEmptyOption6) {
    // Create option definition for the option code 1024 without fields.
    OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 1024,
                                                     "empty", false));

    // Use it as runtime option definition within standard options space.
    // The tested code should find this option definition within runtime
    // option definitions set when it detects that this definition is
    // not a standard definition.
    OptionDefSpaceContainer defs;
    defs.addItem(opt_def, DHCP6_OPTION_SPACE);
    LibDHCP::setRuntimeOptionDefs(defs);
    LibDHCP::commitRuntimeOptionDefs();

    // Create the buffer holding the structure of the empty option.
    const uint8_t raw_data[] = {
      0x04, 0x00,                // option code = 1024
      0x00, 0x00                 // option length = 0
    };
    size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
    OptionBuffer buf(raw_data, raw_data + raw_data_len);

    // Parse options.
    OptionCollection options;
    ASSERT_NO_THROW(LibDHCP::unpackOptions6(buf, DHCP6_OPTION_SPACE,
                                            options));

    // There should be one option.
    ASSERT_EQ(1, options.size());
    OptionPtr option_empty = options.begin()->second;
    ASSERT_TRUE(option_empty);
    EXPECT_EQ(1024, option_empty->getType());
    EXPECT_EQ(4, option_empty->len());
}

// This test verifies that the following option structure can be parsed:
// - option (option space 'foobar')
//   - sub option (option space 'foo')
//      - sub option (option space 'bar')
TEST_F(LibDhcpTest, unpackSubOptions6) {
    // Create option definition for each level of encapsulation. Each option
    // definition is for the option code 1. Options may have the same
    // option code because they belong to different option spaces.

    // Top level option encapsulates options which belong to 'space-foo'.
    OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, "uint32",
                                                      "space-foo"));\
    // Middle option encapsulates options which belong to 'space-bar'
    OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, "uint16",
                                                      "space-bar"));
    // Low level option doesn't encapsulate any option space.
    OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1,
                                                      "uint8"));

    // Register created option definitions as runtime option definitions.
    OptionDefSpaceContainer defs;
    ASSERT_NO_THROW(defs.addItem(opt_def, "space-foobar"));
    ASSERT_NO_THROW(defs.addItem(opt_def2, "space-foo"));
    ASSERT_NO_THROW(defs.addItem(opt_def3, "space-bar"));
    LibDHCP::setRuntimeOptionDefs(defs);
    LibDHCP::commitRuntimeOptionDefs();

    // Create the buffer holding the structure of options.
    const char raw_data[] = {
        // First option starts here.
        0x00, 0x01,   // option code = 1
        0x00, 0x0F,   // option length = 15
        0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
        // Sub option starts here.
        0x00, 0x01,  // option code = 1
        0x00, 0x07,  // option length = 7
        0x01, 0x02,  // this option carries uint16 value
        // Last option starts here.
        0x00, 0x01,  // option code = 1
        0x00, 0x01,  // option length = 1
        0x00 // This option carries a single uint8 value and has no sub options.
    };
    OptionBuffer buf(raw_data, raw_data + sizeof(raw_data));

    // Parse options.
    OptionCollection options;
    ASSERT_NO_THROW(LibDHCP::unpackOptions6(buf, "space-foobar", options, 0, 0));

    // There should be one top level option.
    ASSERT_EQ(1, options.size());
    boost::shared_ptr<OptionInt<uint32_t> > option_foobar =
        boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()->
                                                          second);
    ASSERT_TRUE(option_foobar);
    EXPECT_EQ(1, option_foobar->getType());
    EXPECT_EQ(0x00010203, option_foobar->getValue());
    // There should be a middle level option held in option_foobar.
    boost::shared_ptr<OptionInt<uint16_t> > option_foo =
        boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar->
                                                          getOption(1));
    ASSERT_TRUE(option_foo);
    EXPECT_EQ(1, option_foo->getType());
    EXPECT_EQ(0x0102, option_foo->getValue());
    // Finally, there should be a low level option under option_foo.
    boost::shared_ptr<OptionInt<uint8_t> > option_bar =
        boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1));
    ASSERT_TRUE(option_bar);
    EXPECT_EQ(1, option_bar->getType());
    EXPECT_EQ(0x0, option_bar->getValue());
}

/// V4 Options being used to test pack/unpack operations.
/// These are variable length options only so as there
/// is no restriction on the data length being carried by them.
/// For simplicity, we assign data of the length 3 for each
/// of them.
static uint8_t v4_opts[] = {
    12,  3, 0,   1,  2,        // Hostname
    60,  3, 10, 11, 12,        // Class Id
    14,  3, 20, 21, 22,        // Merit Dump File
    254, 3, 30, 31, 32,        // Reserved
    128, 3, 40, 41, 42,        // Vendor specific
    125, 11, 0, 0, 0x11, 0x8B, // V-I Vendor-Specific Information (Cable Labs)
    6, 2, 4, 10, 0, 0, 10,     // TFTP servers suboption (2)
    43, 2,                     // Vendor Specific Information
    0xDC, 0,                   // VSI suboption
    0x52, 0x19,                // RAI
    0x01, 0x04, 0x20, 0x00, 0x00, 0x02, // Agent Circuit ID
    0x02, 0x06, 0x20, 0xE5, 0x2A, 0xB8, 0x15, 0x14, // Agent Remote ID
    0x09, 0x09, 0x00, 0x00, 0x11, 0x8B, 0x04, // Vendor Specific Information
    0x01, 0x02, 0x03, 0x00 // Vendor Specific Information continued
};

// This test verifies that pack options for v4 is working correctly.
TEST_F(LibDhcpTest, packOptions4) {

    vector<uint8_t> payload[5];
    for (unsigned i = 0; i < 5; i++) {
        payload[i].resize(3);
        payload[i][0] = i*10;
        payload[i][1] = i*10+1;
        payload[i][2] = i*10+2;
    }

    OptionPtr opt1(new Option(Option::V4, 12, payload[0]));
    OptionPtr opt2(new Option(Option::V4, 60, payload[1]));
    OptionPtr opt3(new Option(Option::V4, 14, payload[2]));
    OptionPtr opt4(new Option(Option::V4,254, payload[3]));
    OptionPtr opt5(new Option(Option::V4,128, payload[4]));

    // Create vendor option instance with DOCSIS3.0 enterprise id.
    OptionVendorPtr vivsi(new OptionVendor(Option::V4, 4491));
    vivsi->addOption(OptionPtr(new Option4AddrLst(DOCSIS3_V4_TFTP_SERVERS,
                                                  IOAddress("10.0.0.10"))));

    OptionPtr vsi(new Option(Option::V4, DHO_VENDOR_ENCAPSULATED_OPTIONS,
                              OptionBuffer()));
    vsi->addOption(OptionPtr(new Option(Option::V4, 0xDC, OptionBuffer())));

    // Add RAI option, which comprises 3 sub-options.

    // Get the option definition for RAI option. This option is represented
    // by OptionCustom which requires a definition to be passed to
    // the constructor.
    OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                        DHO_DHCP_AGENT_OPTIONS);
    ASSERT_TRUE(rai_def);
    // Create RAI option.
    OptionCustomPtr rai(new OptionCustom(*rai_def, Option::V4));

    // The sub-options are created using the bits of v4_opts buffer because
    // we want to use this buffer as a reference to verify that produced
    // option in on-wire format is correct.

    // Create Circuit ID sub-option and add to RAI.
    OptionPtr circuit_id(new Option(Option::V4, RAI_OPTION_AGENT_CIRCUIT_ID,
                                    OptionBuffer(v4_opts + 46,
                                                 v4_opts + 50)));
    rai->addOption(circuit_id);

    // Create Remote ID option and add to RAI.
    OptionPtr remote_id(new Option(Option::V4, RAI_OPTION_REMOTE_ID,
                                   OptionBuffer(v4_opts + 52, v4_opts + 58)));
    rai->addOption(remote_id);

    // Create Vendor Specific Information and add to RAI.
    OptionPtr rai_vsi(new Option(Option::V4, RAI_OPTION_VSI,
                                 OptionBuffer(v4_opts + 60, v4_opts + 69)));
    rai->addOption(rai_vsi);

    isc::dhcp::OptionCollection opts; // list of options
    // Note that we insert each option under the same option code into
    // the map. This guarantees that options are packed in the same order
    // they were added. Otherwise, options would get sorted by code and
    // the resulting buffer wouldn't match with the reference buffer.
    opts.insert(make_pair(opt1->getType(), opt1));
    opts.insert(make_pair(opt1->getType(), opt2));
    opts.insert(make_pair(opt1->getType(), opt3));
    opts.insert(make_pair(opt1->getType(), opt4));
    opts.insert(make_pair(opt1->getType(), opt5));
    opts.insert(make_pair(opt1->getType(), vivsi));
    opts.insert(make_pair(opt1->getType(), vsi));
    opts.insert(make_pair(opt1->getType(), rai));

    OutputBuffer buf(100);
    EXPECT_NO_THROW(LibDHCP::packOptions4(buf, opts));
    ASSERT_EQ(buf.getLength(), sizeof(v4_opts));
    EXPECT_EQ(0, memcmp(v4_opts, buf.getData(), sizeof(v4_opts)));
}

// This test verifies that pack options for v4 is working correctly
// and RAI option is packed last.
TEST_F(LibDhcpTest, packOptions4Order) {

    uint8_t expected[] = {
        12,  3, 0,   1,  2, // Just a random option
        99,  3, 10, 11, 12, // Another random option
        82,  3, 20, 21, 22 // Relay Agent Info option
    };

    vector<uint8_t> payload[3];
    for (unsigned i = 0; i < 3; i++) {
        payload[i].resize(3);
        payload[i][0] = i*10;
        payload[i][1] = i*10+1;
        payload[i][2] = i*10+2;
    }

    OptionPtr opt12(new Option(Option::V4, 12, payload[0]));
    OptionPtr opt99(new Option(Option::V4, 99, payload[1]));
    OptionPtr opt82(new Option(Option::V4, 82, payload[2]));

    // Let's create options. They are added in 82,12,99, but the should be
    // packed in 12,99,82 order (82, which is RAI, should go last)
    isc::dhcp::OptionCollection opts;
    opts.insert(make_pair(opt82->getType(), opt82));
    opts.insert(make_pair(opt12->getType(), opt12));
    opts.insert(make_pair(opt99->getType(), opt99));

    OutputBuffer buf(100);
    EXPECT_NO_THROW(LibDHCP::packOptions4(buf, opts));
    ASSERT_EQ(buf.getLength(), sizeof(expected));
    EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected)));
}

TEST_F(LibDhcpTest, unpackOptions4) {

    vector<uint8_t> v4packed(v4_opts, v4_opts + sizeof(v4_opts));
    isc::dhcp::OptionCollection options; // list of options
    list<uint16_t> deferred;

    ASSERT_NO_THROW(
        LibDHCP::unpackOptions4(v4packed, "dhcp4", options, deferred);
    );

    isc::dhcp::OptionCollection::const_iterator x = options.find(12);
    ASSERT_FALSE(x == options.end()); // option 1 should exist
    // Option 12 holds a string so let's cast it to an appropriate type.
    OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x->second);
    ASSERT_TRUE(option12);
    EXPECT_EQ(12, option12->getType());  // this should be option 12
    ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
    EXPECT_EQ(5, option12->len()); // total option length 5
    EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4_opts + 2, 3)); // data len=3

    x = options.find(60);
    ASSERT_FALSE(x == options.end()); // option 2 should exist
    EXPECT_EQ(60, x->second->getType());  // this should be option 60
    ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
    EXPECT_EQ(5, x->second->len()); // total option length 5
    EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 7, 3)); // data len=3

    x = options.find(14);
    ASSERT_FALSE(x == options.end()); // option 3 should exist
    OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x->second);
    ASSERT_TRUE(option14);
    EXPECT_EQ(14, option14->getType());  // this should be option 14
    ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
    EXPECT_EQ(5, option14->len()); // total option length 5
    EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4_opts + 12, 3)); // data len=3

    x = options.find(254);
    ASSERT_FALSE(x == options.end()); // option 4 should exist
    EXPECT_EQ(254, x->second->getType());  // this should be option 254
    ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
    EXPECT_EQ(5, x->second->len()); // total option length 5
    EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 17, 3)); // data len=3

    x = options.find(128);
    ASSERT_FALSE(x == options.end()); // option 5 should exist
    EXPECT_EQ(128, x->second->getType());  // this should be option 128
    ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
    EXPECT_EQ(5, x->second->len()); // total option length 5
    EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4_opts + 22, 3)); // data len=3

    // Verify that V-I Vendor Specific Information option is parsed correctly.
    x = options.find(125);
    ASSERT_FALSE(x == options.end());
    OptionVendorPtr vivsi = boost::dynamic_pointer_cast<OptionVendor>(x->second);
    ASSERT_TRUE(vivsi);
    EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType());
    EXPECT_EQ(4491, vivsi->getVendorId());
    OptionCollection suboptions = vivsi->getOptions();

    // There should be one suboption of V-I VSI.
    ASSERT_EQ(1, suboptions.size());
    // This vendor option has a standard definition and thus should be
    // converted to appropriate class, i.e. Option4AddrLst. If this cast
    // fails, it means that its definition was not used while it was
    // parsed.
    Option4AddrLstPtr tftp =
        boost::dynamic_pointer_cast<Option4AddrLst>(suboptions.begin()->second);
    ASSERT_TRUE(tftp);
    EXPECT_EQ(DOCSIS3_V4_TFTP_SERVERS, tftp->getType());
    EXPECT_EQ(6, tftp->len());
    Option4AddrLst::AddressContainer addresses = tftp->getAddresses();
    ASSERT_EQ(1, addresses.size());
    EXPECT_EQ("10.0.0.10", addresses[0].toText());

    // Checking DHCP Relay Agent Information Option.
    x = options.find(DHO_DHCP_AGENT_OPTIONS);
    ASSERT_FALSE(x == options.end());
    EXPECT_EQ(DHO_DHCP_AGENT_OPTIONS, x->second->getType());
    // RAI is represented by OptionCustom.
    OptionCustomPtr rai = boost::dynamic_pointer_cast<OptionCustom>(x->second);
    ASSERT_TRUE(rai);
    // RAI should have 3 sub-options: Circuit ID, Agent Remote ID, Vendor
    // Specific Information option. Note that by parsing these suboptions we
    // are checking that unpackOptions4 differentiates between standard option
    // space called "dhcp4" and other option spaces. These sub-options do not
    // belong to standard option space and should be parsed using different
    // option definitions.
    // @todo Currently, definitions for option space "dhcp-agent-options-space"
    // are not defined. Therefore all suboptions will be represented here by
    // the generic Option class.

    // Check that Circuit ID option is among parsed options.
    OptionPtr rai_option = rai->getOption(RAI_OPTION_AGENT_CIRCUIT_ID);
    ASSERT_TRUE(rai_option);
    EXPECT_EQ(RAI_OPTION_AGENT_CIRCUIT_ID, rai_option->getType());
    ASSERT_EQ(6, rai_option->len());
    EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 46, 4));

    // Check that Remote ID option is among parsed options.
    rai_option = rai->getOption(RAI_OPTION_REMOTE_ID);
    ASSERT_TRUE(rai_option);
    EXPECT_EQ(RAI_OPTION_REMOTE_ID, rai_option->getType());
    ASSERT_EQ(8, rai_option->len());
    EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 52, 6));

    // Check that Vendor Specific Information option is among parsed options.
    rai_option = rai->getOption(RAI_OPTION_VSI);
    ASSERT_TRUE(rai_option);
    EXPECT_EQ(RAI_OPTION_VSI, rai_option->getType());
    ASSERT_EQ(11, rai_option->len());
    EXPECT_EQ(0, memcmp(&rai_option->getData()[0], v4_opts + 60, 9));

    // Make sure, that option other than those above is not present.
    EXPECT_FALSE(rai->getOption(10));

    // Check the same for the global option space.
    x = options.find(0);
    EXPECT_TRUE(x == options.end()); // option 0 not found

    x = options.find(1);
    EXPECT_TRUE(x == options.end()); // option 1 not found

    x = options.find(2);
    EXPECT_TRUE(x == options.end()); // option 2 not found

}

// Check parsing of an empty option.
TEST_F(LibDhcpTest, unpackEmptyOption4) {
    // Create option definition for the option code 254 without fields.
    OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 254,
                                                     "empty", false));

    // Use it as runtime option definition within standard options space.
    // The tested code should find this option definition within runtime
    // option definitions set when it detects that this definition is
    // not a standard definition.
    OptionDefSpaceContainer defs;
    defs.addItem(opt_def, DHCP4_OPTION_SPACE);
    LibDHCP::setRuntimeOptionDefs(defs);
    LibDHCP::commitRuntimeOptionDefs();

    // Create the buffer holding the structure of the empty option.
    const uint8_t raw_data[] = {
      0xFE,                     // option code = 254
      0x00                      // option length = 0
    };
    size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
    OptionBuffer buf(raw_data, raw_data + raw_data_len);

    // Parse options.
    OptionCollection options;
    list<uint16_t> deferred;
    ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, DHCP4_OPTION_SPACE,
                                            options, deferred));

    // There should be one option.
    ASSERT_EQ(1, options.size());
    OptionPtr option_empty = options.begin()->second;
    ASSERT_TRUE(option_empty);
    EXPECT_EQ(254, option_empty->getType());
    EXPECT_EQ(2, option_empty->len());
}

// This test verifies that the following option structure can be parsed:
// - option (option space 'foobar')
//   - sub option (option space 'foo')
//      - sub option (option space 'bar')
// @todo Add more thorough unit tests for unpackOptions.
TEST_F(LibDhcpTest, unpackSubOptions4) {
    // Create option definition for each level of encapsulation. Each option
    // definition is for the option code 1. Options may have the same
    // option code because they belong to different option spaces.

    // Top level option encapsulates options which belong to 'space-foo'.
    OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, "uint32",
                                                      "space-foo"));\
    // Middle option encapsulates options which belong to 'space-bar'
    OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, "uint16",
                                                      "space-bar"));
    // Low level option doesn't encapsulate any option space.
    OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1,
                                                      "uint8"));

    // Register created option definitions as runtime option definitions.
    OptionDefSpaceContainer defs;
    ASSERT_NO_THROW(defs.addItem(opt_def, "space-foobar"));
    ASSERT_NO_THROW(defs.addItem(opt_def2, "space-foo"));
    ASSERT_NO_THROW(defs.addItem(opt_def3, "space-bar"));
    LibDHCP::setRuntimeOptionDefs(defs);
    LibDHCP::commitRuntimeOptionDefs();

    // Create the buffer holding the structure of options.
    const uint8_t raw_data[] = {
        // First option starts here.
        0x01,                   // option code = 1
        0x0B,                   // option length = 11
        0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
        // Sub option starts here.
        0x01,                   // option code = 1
        0x05,                   // option length = 5
        0x01, 0x02,             // this option carries uint16 value
        // Last option starts here.
        0x01,                   // option code = 1
        0x01,                   // option length = 1
        0x00                    // This option carries a single uint8
                                // value and has no sub options.
    };
    size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
    OptionBuffer buf(raw_data, raw_data + raw_data_len);

    // Parse options.
    OptionCollection options;
    list<uint16_t> deferred;
    ASSERT_NO_THROW(LibDHCP::unpackOptions4(buf, "space-foobar",
                                            options, deferred));

    // There should be one top level option.
    ASSERT_EQ(1, options.size());
    boost::shared_ptr<OptionInt<uint32_t> > option_foobar =
        boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()->
                                                          second);
    ASSERT_TRUE(option_foobar);
    EXPECT_EQ(1, option_foobar->getType());
    EXPECT_EQ(0x00010203, option_foobar->getValue());
    // There should be a middle level option held in option_foobar.
    boost::shared_ptr<OptionInt<uint16_t> > option_foo =
        boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar->
                                                          getOption(1));
    ASSERT_TRUE(option_foo);
    EXPECT_EQ(1, option_foo->getType());
    EXPECT_EQ(0x0102, option_foo->getValue());
    // Finally, there should be a low level option under option_foo.
    boost::shared_ptr<OptionInt<uint8_t> > option_bar =
        boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1));
    ASSERT_TRUE(option_bar);
    EXPECT_EQ(1, option_bar->getType());
    EXPECT_EQ(0x0, option_bar->getValue());
}

// Verifies that an Host Name (option 12), will be dropped when empty,
// while subsequent options will still be unpacked.
TEST_F(LibDhcpTest, emptyHostName) {

    uint8_t opts[] = {
        12,  0,             // Empty Hostname
        60,  3, 10, 11, 12  // Class Id
    };

    vector<uint8_t> packed(opts, opts + sizeof(opts));
    isc::dhcp::OptionCollection options; // list of options
    list<uint16_t> deferred;

    ASSERT_NO_THROW(
        LibDHCP::unpackOptions4(packed, "dhcp4", options, deferred);
    );

    // Host Name should not exist, we quietly drop it when empty.
    isc::dhcp::OptionCollection::const_iterator x = options.find(12);
    ASSERT_TRUE(x == options.end());

    // Verify Option 60 exists correctly
    x = options.find(60);
    ASSERT_FALSE(x == options.end());
    EXPECT_EQ(60, x->second->getType());
    ASSERT_EQ(3, x->second->getData().size());
    EXPECT_EQ(5, x->second->len());
    EXPECT_EQ(0, memcmp(&x->second->getData()[0], opts + 4, 3));
};


TEST_F(LibDhcpTest, stdOptionDefs4) {

    // 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);
    OptionBufferConstIter begin = buf.begin();
    OptionBufferConstIter end = buf.end();

    LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_MASK, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_TIME_OFFSET, begin, begin + 4,
                                    typeid(OptionInt<int32_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_ROUTERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_TIME_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_LOG_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_COOKIE_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_LPR_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_IMPRESS_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_RESOURCE_LOCATION_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_HOST_NAME, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_BOOT_SIZE, begin, begin + 2,
                                    typeid(OptionInt<uint16_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_MERIT_DUMP, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_SWAP_SERVER, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_ROOT_PATH, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_EXTENSIONS_PATH, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_IP_FORWARDING, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_NON_LOCAL_SOURCE_ROUTING, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_POLICY_FILTER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_MAX_DGRAM_REASSEMBLY, begin, begin + 2,
                                    typeid(OptionInt<uint16_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_IP_TTL, begin, begin + 1,
                                    typeid(OptionInt<uint8_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_AGING_TIMEOUT, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_PLATEAU_TABLE, begin, begin + 10,
                                    typeid(OptionIntArray<uint16_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_INTERFACE_MTU, begin, begin + 2,
                                    typeid(OptionInt<uint16_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_ALL_SUBNETS_LOCAL, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_BROADCAST_ADDRESS, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_PERFORM_MASK_DISCOVERY, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_MASK_SUPPLIER, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_DISCOVERY, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_SOLICITATION_ADDRESS, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_STATIC_ROUTES, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_TRAILER_ENCAPSULATION, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_ARP_CACHE_TIMEOUT, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_IEEE802_3_ENCAPSULATION, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_TCP_TTL, begin, begin + 1,
                                    typeid(OptionInt<uint8_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_INTERVAL, begin,
                                    begin + 4, typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_GARBAGE, begin, begin + 1,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_NIS_DOMAIN, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_NIS_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_NTP_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NAME_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_DD_SERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NODE_TYPE, begin, begin + 1,
                                    typeid(OptionInt<uint8_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_SCOPE, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_FONT_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_X_DISPLAY_MANAGER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REQUESTED_ADDRESS, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_LEASE_TIME, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_OPTION_OVERLOAD, begin, begin + 1,
                                    typeid(OptionInt<uint8_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE_TYPE, begin, begin + 1,
                                    typeid(OptionInt<uint8_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_SERVER_IDENTIFIER, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_PARAMETER_REQUEST_LIST, begin, end,
                                    typeid(OptionUint8Array));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MAX_MESSAGE_SIZE, begin, begin + 2,
                                    typeid(OptionInt<uint16_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_RENEWAL_TIME, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REBINDING_TIME, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs4(DHO_NWIP_DOMAIN_NAME, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_NWIP_SUBOPTIONS, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs4(DHO_NISP_DOMAIN_NAME, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_NISP_SERVER_ADDR, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_TFTP_SERVER_NAME, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_BOOT_FILE_NAME, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_HOME_AGENT_ADDRS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_SMTP_SERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_POP3_SERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_NNTP_SERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_WWW_SERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_FINGER_SERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_IRC_SERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_STREETTALK_SERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_STDASERVER, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_USER_CLASS, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 5,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 9,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_DIRECTORY_AGENT, begin, begin + 45,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, end,
                                    typeid(Option4SlpServiceScope));

    // Check also with empty scope list
    LibDhcpTest::testStdOptionDefs4(DHO_SERVICE_SCOPE, begin, begin + 1,
                                    typeid(Option4SlpServiceScope));

    LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, begin + 3,
                                    typeid(Option4ClientFqdn));

    // The following option requires well formed buffer to be created from.
    // Not just a dummy one. This buffer includes some suboptions.
    OptionBuffer agent_info_buf = createAgentInformationOption();
    LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS,
                                    agent_info_buf.begin(),
                                    agent_info_buf.end(),
                                    typeid(OptionCustom),
                                    "dhcp-agent-options-space");

    LibDhcpTest::testStdOptionDefs4(DHO_NDS_SERVERS, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_NDS_TREE_NAME, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_NDS_CONTEXT, begin, end,
                                    typeid(OptionString));

    // Prepare buffer holding an array of FQDNs.
    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,
        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(fqdn_data, fqdn_data + sizeof(fqdn_data));

    LibDhcpTest::testStdOptionDefs4(DHO_BCMCS_DOMAIN_NAME_LIST,
                                    fqdn_buf.begin(),
                                    fqdn_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_BCMCS_IPV4_ADDR, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_AUTHENTICATE, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs4(DHO_CLIENT_LAST_TRANSACTION_TIME,
                                    begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_ASSOCIATED_IP, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_AUTO_CONFIG, begin, begin + 1,
                                    typeid(OptionInt<uint8_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVICE_SEARCH, begin, begin + 4,
                                    typeid(OptionIntArray<uint16_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_SELECTION, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_SYSTEM, begin, end,
                                    typeid(OptionIntArray<uint16_t>));

    LibDhcpTest::testStdOptionDefs4(DHO_NDI, begin, begin + 3,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_UUID_GUID, begin, begin + 17,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_USER_AUTH, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_GEOCONF_CIVIC, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs4(DHO_PCODE, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_TCODE, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_NETINFO_ADDR, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_NETINFO_TAG, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_URL, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(),
                                    fqdn_buf.end(), typeid(OptionCustom));

    // V-I Vendor option requires specially crafted data.
    const char vivco_data[] = {
        1, 2, 3, 4, // enterprise id
        3, 1, 2, 3  // first byte is opaque data length, the rest is opaque data
    };
    std::vector<uint8_t> vivco_buf(vivco_data, vivco_data + sizeof(vivco_data));
    const char vivsio_data[] = {
        1, 2, 3, 4, // enterprise id
        4,          // first byte is vendor block length
        1, 2, 3, 4  // option type=1 length=2
    };
    std::vector<uint8_t> vivsio_buf(vivsio_data, vivsio_data + sizeof(vivsio_data));

    LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, vivco_buf.begin(),
                                    vivco_buf.end(), typeid(OptionVendorClass));


    LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, vivsio_buf.begin(),
                                    vivsio_buf.end(), typeid(OptionVendor));

    LibDhcpTest::testStdOptionDefs4(DHO_PANA_AGENT, begin, end,
                                    typeid(Option4AddrLst));

    // Prepare buffer holding one FQDN.
    const char fqdn1_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
    };
    // Initialize a vector with the FQDN data.
    std::vector<uint8_t> fqdn1_buf(fqdn1_data,
                                   fqdn1_data + sizeof(fqdn1_data));

    LibDhcpTest::testStdOptionDefs4(DHO_V4_LOST, fqdn1_buf.begin(),
                                    fqdn1_buf.end(), typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_CAPWAP_AC_V4, begin, end,
                                    typeid(Option4AddrLst));

    LibDhcpTest::testStdOptionDefs4(DHO_SIP_UA_CONF_SERVICE_DOMAINS,
                                    fqdn_buf.begin(),
                                    fqdn_buf.end(),
                                    typeid(OptionCustom));

    std::vector<uint8_t> rdnss1_buf(begin, begin + 9);
    rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end());

    LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss1_buf.begin(),
                                    rdnss1_buf.end(),
                                    typeid(OptionCustom));

    std::vector<uint8_t> rdnss_buf(begin, begin + 9);
    rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end());

    LibDhcpTest::testStdOptionDefs4(DHO_RDNSS_SELECT, rdnss_buf.begin(),
                                    rdnss_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_V4_PORTPARAMS, begin, begin + 4,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_V4_CAPTIVE_PORTAL, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 22,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_6RD, begin, begin + 46,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs4(DHO_V4_ACCESS_DOMAIN, fqdn1_buf.begin(),
                                    fqdn1_buf.end(), typeid(OptionCustom));
}

// Test that definitions of standard options have been initialized
// correctly.
// @todo Only limited number of option definitions are now created
// This test have to be extended once all option definitions are
// created.
TEST_F(LibDhcpTest, stdOptionDefs6) {

    // 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);
    OptionBufferConstIter begin = buf.begin();
    OptionBufferConstIter end = buf.end();

    // Prepare buffer holding one FQDN.
    const char data1[] = {
        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
    };
    // Initialize a vector with the FQDN data1.
    std::vector<uint8_t> fqdn1_buf(data1, data1 + sizeof(data1));

    // 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));

    // Prepare buffer holding a vendor option
    const char vopt_data[] = {
        1, 2, 3, 4,                               // enterprise=0x1020304
        0, 100,                                   // type=100
        0, 6,                                     // length=6
        102, 111, 111, 98, 97, 114                // data="foobar"
    };
    // Initialize a vector with the suboption data.
    std::vector<uint8_t> vopt_buf(vopt_data, vopt_data + sizeof(vopt_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());

    // Initialize test buffer for Vendor Class option.
    const char vclass_data[] = {
        0x00, 0x01, 0x02, 0x03,
        0x00, 0x01, 0x02
    };
    std::vector<uint8_t> vclass_buf(vclass_data,
                                    vclass_data + sizeof(vclass_data));;

    // Initialize test buffer for Bootfile Param option.
    const char bparam_data[] = {
        0x00, 0x01, 0x02
    };
    std::vector<uint8_t> bparam_buf(bparam_data,
                                    bparam_data + sizeof(bparam_data));;

    // The actual test starts here for all supported option codes.
    LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, begin, end,
                                    typeid(Option6IA));

    LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, begin, end,
                                    typeid(Option6IAAddr));

    LibDhcpTest::testStdOptionDefs6(D6O_ORO, begin, end,
                                    typeid(OptionIntArray<uint16_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, begin, begin + 1,
                                    typeid(OptionInt<uint8_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, begin, begin + 2,
                                    typeid(OptionInt<uint16_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_AUTH, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_UNICAST, begin, begin + 16,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, begin, end,
                                    typeid(Option6StatusCode));

    LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, vclass_buf.begin(),
                                    vclass_buf.end(),
                                    typeid(OptionVendorClass));

    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, vopt_buf.begin(),
                                    vopt_buf.end(),
                                    typeid(OptionVendor),
                                    "vendor-opts-space");

    LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, begin, begin + 1,
                                    typeid(OptionInt<uint8_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf.begin(),
                                    fqdn_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf.begin(),
                                    fqdn_buf.end(), typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, begin, end,
                                    typeid(Option6IA));

    LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, begin, begin + 25,
                                    typeid(Option6IAPrefix));

    LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf.begin(),
                                    fqdn_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf.begin(),
                                    fqdn_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME,
                                    begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf.begin(),
                                    fqdn_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf.begin(),
                                    client_fqdn_buf.end(),
                                    typeid(Option6ClientFqdn));

    LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs6(D6O_ERO, begin, end,
                                    typeid(OptionIntArray<uint16_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, begin, end,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_V6_LOST,
                                    fqdn1_buf.begin(), fqdn1_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_CAPWAP_AC_V6, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_RELAY_ID, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_V6_ACCESS_DOMAIN,
                                    fqdn1_buf.begin(), fqdn1_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_SIP_UA_CS_LIST,
                                    fqdn_buf.begin(), fqdn_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_URL, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs6(D6O_BOOTFILE_PARAM, bparam_buf.begin(),
                                    bparam_buf.end(),
                                    typeid(OptionOpaqueDataTuples));

    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_ARCH_TYPE, begin, end,
                                    typeid(OptionIntArray<uint16_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_NII, begin, begin + 3,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_AFTR_NAME, fqdn1_buf.begin(),
                                    fqdn1_buf.end(), typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_ERP_LOCAL_DOMAIN_NAME,
                                    fqdn_buf.begin(), fqdn_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_RSOO, begin, end,
                                    typeid(OptionCustom),
                                    "rsoo-opts");

    LibDhcpTest::testStdOptionDefs6(D6O_PD_EXCLUDE, begin, end,
                                    typeid(Option6PDExclude));

    std::vector<uint8_t> rdnss1_buf(begin, begin + 17);
    rdnss1_buf.insert(rdnss1_buf.end(), fqdn1_buf.begin(), fqdn1_buf.end());

    LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss1_buf.begin(),
                                    rdnss1_buf.end(),
                                    typeid(OptionCustom));

    std::vector<uint8_t> rdnss_buf(begin, begin + 17);
    rdnss_buf.insert(rdnss_buf.end(), fqdn_buf.begin(), fqdn_buf.end());

    LibDhcpTest::testStdOptionDefs6(D6O_RDNSS_SELECTION, rdnss_buf.begin(),
                                    rdnss_buf.end(),
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_LINKLAYER_ADDR, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_LINK_ADDRESS, begin, begin + 16,
                                    typeid(OptionCustom));

    LibDhcpTest::testStdOptionDefs6(D6O_SOL_MAX_RT, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_INF_MAX_RT, begin, begin + 4,
                                    typeid(OptionInt<uint32_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_MSG, begin, end,
                                    typeid(Option));

    LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_O_DHCPV6_SERVER, begin, end,
                                    typeid(Option6AddrLst));

    LibDhcpTest::testStdOptionDefs6(D6O_V6_CAPTIVE_PORTAL, begin, end,
                                    typeid(OptionString));

    LibDhcpTest::testStdOptionDefs6(D6O_RELAY_SOURCE_PORT, begin, begin + 2,
                                    typeid(OptionInt<uint16_t>));

    LibDhcpTest::testStdOptionDefs6(D6O_IPV6_ADDRESS_ANDSF, begin, end,
                                    typeid(Option6AddrLst));

    // RFC7598 options
    LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
                                 typeid(OptionCustom), "s46-rule-options");
    LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_RULE, begin, end,
                                 typeid(OptionCustom), "s46-rule-options");
    LibDhcpTest::testOptionDefs6(MAPE_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
                                 typeid(OptionCustom));
    LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_BR, begin, end,
                                 typeid(OptionCustom));
    LibDhcpTest::testOptionDefs6(MAPT_V6_OPTION_SPACE, D6O_S46_DMR, begin, end,
                                 typeid(OptionCustom));
    LibDhcpTest::testOptionDefs6(LW_V6_OPTION_SPACE, D6O_S46_V4V6BIND, begin,
                                 end, typeid(OptionCustom),
                                 "s46-v4v6bind-options");
    LibDhcpTest::testOptionDefs6(V4V6_RULE_OPTION_SPACE, D6O_S46_PORTPARAMS,
                                 begin, end, typeid(OptionCustom), "");
    LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPE, begin, end,
                                    typeid(OptionCustom),
                                    "s46-cont-mape-options");
    LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_MAPT, begin, end,
                                    typeid(OptionCustom),
                                    "s46-cont-mapt-options");
    LibDhcpTest::testStdOptionDefs6(D6O_S46_CONT_LW, begin, end,
                                    typeid(OptionCustom),
                                    "s46-cont-lw-options");

}

// This test checks if the DHCPv6 option definition can be searched by
// an option name.
TEST_F(LibDhcpTest, getOptionDefByName6) {
    // Get all definitions.
    const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP6_OPTION_SPACE);
    // For each definition try to find it using option name.
    for (OptionDefContainer::const_iterator def = defs->begin();
         def != defs->end(); ++def) {
        OptionDefinitionPtr def_by_name =
            LibDHCP::getOptionDef(DHCP6_OPTION_SPACE, (*def)->getName());
        ASSERT_TRUE(def_by_name);
        ASSERT_TRUE(**def == *def_by_name);
    }
}


// This test checks if the DHCPv4 option definition can be searched by
// an option name.
TEST_F(LibDhcpTest, getOptionDefByName4) {
    // Get all definitions.
    const OptionDefContainerPtr defs = LibDHCP::getOptionDefs(DHCP4_OPTION_SPACE);
    // For each definition try to find it using option name.
    for (OptionDefContainer::const_iterator def = defs->begin();
         def != defs->end(); ++def) {
        OptionDefinitionPtr def_by_name =
            LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, (*def)->getName());
        ASSERT_TRUE(def_by_name);
        ASSERT_TRUE(**def == *def_by_name);
    }
}

// This test checks if the definition of the DHCPv6 vendor option can
// be searched by option name.
TEST_F(LibDhcpTest, getVendorOptionDefByName6) {
    const OptionDefContainerPtr& defs =
        LibDHCP::getVendorOption6Defs(VENDOR_ID_CABLE_LABS);
    ASSERT_TRUE(defs);
    for (OptionDefContainer::const_iterator def = defs->begin();
         def != defs->end(); ++def) {
        OptionDefinitionPtr def_by_name =
            LibDHCP::getVendorOptionDef(Option::V6, VENDOR_ID_CABLE_LABS,
                                        (*def)->getName());
        ASSERT_TRUE(def_by_name);
        ASSERT_TRUE(**def == *def_by_name);
    }
}

// This test checks if the definition of the DHCPv4 vendor option can
// be searched by option name.
TEST_F(LibDhcpTest, getVendorOptionDefByName4) {
    const OptionDefContainerPtr& defs =
        LibDHCP::getVendorOption4Defs(VENDOR_ID_CABLE_LABS);
    ASSERT_TRUE(defs);
    for (OptionDefContainer::const_iterator def = defs->begin();
         def != defs->end(); ++def) {
        OptionDefinitionPtr def_by_name =
            LibDHCP::getVendorOptionDef(Option::V4, VENDOR_ID_CABLE_LABS,
                                        (*def)->getName());
        ASSERT_TRUE(def_by_name);
        ASSERT_TRUE(**def == *def_by_name);
    }
}

// This test checks handling of uncompressed FQDN list.
TEST_F(LibDhcpTest, fqdnList) {
    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                    DHO_DOMAIN_SEARCH);
    ASSERT_TRUE(def);

    // Prepare buffer holding an array of FQDNs.
    const uint8_t fqdn[] = {
        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,
        3, 99, 111, 109,                          // "com"
        0
    };
    /* This size is used later so protect ourselves against changes */
    static_assert(sizeof(fqdn) == 40,
                  "incorrect uncompressed domain list size");
    // Initialize a vector with the FQDN data.
    std::vector<uint8_t> fqdn_buf(fqdn, fqdn + sizeof(fqdn));

    OptionPtr option;
    ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
                                                DHO_DOMAIN_SEARCH,
                                                fqdn_buf.begin(),
                                                fqdn_buf.end()));
    ASSERT_TRUE(option);
    OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option);
    ASSERT_TRUE(names);
    EXPECT_EQ(sizeof(fqdn), names->len() - names->getHeaderLen());
    ASSERT_EQ(3, names->getDataFieldsNum());
    EXPECT_EQ("mydomain.example.com.", names->readFqdn(0));
    EXPECT_EQ("example.com.", names->readFqdn(1));
    EXPECT_EQ("com.", names->readFqdn(2));

    LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, fqdn_buf.begin(),
                                    fqdn_buf.end(), typeid(OptionCustom));
}

// This test checks handling of compressed FQDN list.
// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual
// compression algorithm).
TEST_F(LibDhcpTest, fqdnListCompressed) {
    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                    DHO_DOMAIN_SEARCH);
    ASSERT_TRUE(def);

    const uint8_t compressed[] = {
        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,
        192, 9,                                   // pointer to example.com
        192, 17                                   // pointer to com
    };
    std::vector<uint8_t> compressed_buf(compressed,
                                        compressed + sizeof(compressed));
    OptionPtr option;
    ASSERT_NO_THROW(option = def->optionFactory(Option::V4,
                                                DHO_DOMAIN_SEARCH,
                                                compressed_buf.begin(),
                                                compressed_buf.end()));
    ASSERT_TRUE(option);
    OptionCustomPtr names = boost::dynamic_pointer_cast<OptionCustom>(option);
    ASSERT_TRUE(names);
    /* Use the uncompress length here (cf fqdnList) */
    EXPECT_EQ(40, names->len() - names->getHeaderLen());
    ASSERT_EQ(3, names->getDataFieldsNum());
    EXPECT_EQ("mydomain.example.com.", names->readFqdn(0));
    EXPECT_EQ("example.com.", names->readFqdn(1));
    EXPECT_EQ("com.", names->readFqdn(2));
}

// Check that incorrect FQDN list compression is rejected.
// See RFC3397, section 2 (and 4.1.4 of RFC1035 for the actual
// compression algorithm).
TEST_F(LibDhcpTest, fqdnListBad) {
    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                    DHO_DOMAIN_SEARCH);
    ASSERT_TRUE(def);

    const uint8_t bad[] = {
        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,
        192, 80,                                  // too big/forward pointer
        192, 11                                   // pointer to com
    };
    std::vector<uint8_t> bad_buf(bad, bad + sizeof(bad));

    OptionPtr option;
    EXPECT_THROW(option = def->optionFactory(Option::V4,
                                             DHO_DOMAIN_SEARCH,
                                             bad_buf.begin(),
                                             bad_buf.end()),
                 InvalidOptionValue);
}

// Check that empty (truncated) option is rejected.
TEST_F(LibDhcpTest, fqdnListTrunc) {
    OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
                                                    DHO_DOMAIN_SEARCH);
    ASSERT_TRUE(def);

    std::vector<uint8_t> empty;

    OptionPtr option;
    EXPECT_THROW(option = def->optionFactory(Option::V4,
                                             DHO_DOMAIN_SEARCH,
                                             empty.begin(),
                                             empty.end()),
                 InvalidOptionValue);
}

// tests whether v6 vendor-class option can be parsed properly.
TEST_F(LibDhcpTest, vendorClass6) {

    isc::dhcp::OptionCollection options; // Will store parsed option here

    // Exported from wireshark: vendor-class option with enterprise-id = 4491
    // and a single data entry containing "eRouter1.0"
    string vendor_class_hex = "001000100000118b000a65526f75746572312e30";
    OptionBuffer bin;

    // Decode the hex string and store it in bin (which happens
    // to be OptionBuffer format)
    isc::util::encode::decodeHex(vendor_class_hex, bin);

    ASSERT_NO_THROW ({
            LibDHCP::unpackOptions6(bin, "dhcp6", options);
        });

    EXPECT_EQ(options.size(), 1); // There should be 1 option.

    // Option vendor-class should be there
    ASSERT_FALSE(options.find(D6O_VENDOR_CLASS) == options.end());

    // It should be of type OptionVendorClass
    boost::shared_ptr<OptionVendorClass> vclass =
        boost::dynamic_pointer_cast<OptionVendorClass>(options.begin()->second);
    ASSERT_TRUE(vclass);

    // Let's investigate if the option content is correct

    // 3 fields expected: vendor-id, data-len and data
    EXPECT_EQ(4491, vclass->getVendorId());
    EXPECT_EQ(20, vclass->len());
    ASSERT_EQ(1, vclass->getTuplesNum());
    EXPECT_EQ("eRouter1.0", vclass->getTuple(0).getText());
}

// This test verifies that it is possible to add runtime option definitions,
// retrieve them and remove them.
TEST_F(LibDhcpTest, setRuntimeOptionDefs) {
    // Create option definitions in 5 namespaces.
    OptionDefSpaceContainer defs;
    createRuntimeOptionDefs(5, 100, defs);

    // Apply option definitions.
    ASSERT_NO_THROW(LibDHCP::setRuntimeOptionDefs(defs));

    // Retrieve all inserted option definitions.
    testRuntimeOptionDefs(5, 100, true);

    // Attempting to retrieve non existing definitions.
    EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-non-existent", 1));
    EXPECT_FALSE(LibDHCP::getRuntimeOptionDef("option-space-0", 145));

    // Remove all runtime option definitions.
    ASSERT_NO_THROW(LibDHCP::clearRuntimeOptionDefs());

    // All option definitions should be gone now.
    testRuntimeOptionDefs(5, 100, false);
}

// This test verifies the processing of option 43
TEST_F(LibDhcpTest, option43) {
    // Check shouldDeferOptionUnpack()
    EXPECT_TRUE(LibDHCP::shouldDeferOptionUnpack(DHCP4_OPTION_SPACE, 43));
    EXPECT_FALSE(LibDHCP::shouldDeferOptionUnpack(DHCP4_OPTION_SPACE, 44));
    EXPECT_FALSE(LibDHCP::shouldDeferOptionUnpack(DHCP6_OPTION_SPACE, 43));

    // Check last resort
    OptionDefinitionPtr def;
    def = LibDHCP::getLastResortOptionDef(DHCP6_OPTION_SPACE, 43);
    EXPECT_FALSE(def);
    def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 44);
    EXPECT_FALSE(def);
    def = LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE, 43);
    ASSERT_TRUE(def);
    EXPECT_FALSE(def->getArrayType());
    EXPECT_EQ(43, def->getCode());
    EXPECT_EQ("vendor-encapsulated-options-space", def->getEncapsulatedSpace());
    EXPECT_EQ("vendor-encapsulated-options", def->getName());
    EXPECT_EQ(0, def->getRecordFields().size());
    EXPECT_EQ(OptionDataType::OPT_EMPTY_TYPE, def->getType());

    OptionDefinitionPtr def_by_name =
        LibDHCP::getLastResortOptionDef(DHCP4_OPTION_SPACE,
                                        "vendor-encapsulated-options");
    EXPECT_TRUE(def_by_name);
    EXPECT_EQ(def, def_by_name);
}

// RFC7598 defines several options for configuration of lw4over6 devices.
// These options are have complex structure, so dedicated tests are needed
// to test them reliably.
TEST_F(LibDhcpTest, sw46options) {


    // This constant defines the following structure:
    // MAP-E container
    //  - BR address option
    //  - S46 rule option
    //    - portparameters
    //  - S46 rule option
    std::vector<uint8_t> mape_bin = {
        0, 94, 0, 64,  // MAP-E container with 3 suboptions

        0, 90, 0, 16,                    // BR address
        0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, // 2001:db8::abcd
        0, 0, 0, 0, 0, 0, 0xab, 0xcd,

        0, 89, 0, 16+8, // S46 rule
        128, // flags = 128 (F flag set)
        4, // ea-len
        30, // prefix4-len
        192, 0, 2, 192, // ipv4-prefix = 192.168.0.192
        64, // prefix6-len = /64
        0x20, 0x01, 0xd, 0xb8, 0, 1, 2, 3, // ipv6-prefix = 2001:db8::1:203::/64

        0, 93, 0, 4, // S46_PORTPARAMS option
        8, 6, 0xfc, 0x00, // offset is 8, psid-len 6, psid is fc00

        0, 89, 0, 12, // S46 rule
        0, // flags = 0 (F flag clear)
        6, // ea-len
        32, // prefix4-len
        192, 0, 2, 1, // ipv4-prefix = 192.168.0.1
        32, // prefix6-len = /32
        0x20, 0x01, 0xd, 0xb8 // ipv6-prefix = 2001:db8::/32
    };

    // List of parsed options will be stored here.
    isc::dhcp::OptionCollection options;

    OptionBuffer buf(mape_bin);

    size_t parsed;

    EXPECT_NO_THROW (parsed = LibDHCP::unpackOptions6(buf, "dhcp6", options));
    EXPECT_EQ(mape_bin.size(), parsed);

    // We expect to have exactly one option (with tons of suboptions, but we'll
    // get to that in a minute)
    EXPECT_EQ(1, options.size());
    auto opt = options.find(D6O_S46_CONT_MAPE);
    ASSERT_FALSE(opt == options.end());

    // Ok, let's iterate over the options. Start with the top one.
    using boost::shared_ptr;
    shared_ptr<OptionCustom> mape = dynamic_pointer_cast<OptionCustom>(opt->second);
    ASSERT_TRUE(mape);
    EXPECT_EQ(D6O_S46_CONT_MAPE, mape->getType());
    EXPECT_EQ(68, mape->len());
    EXPECT_EQ(64, mape->getData().size());

    // Let's check if there's a border router option.
    ASSERT_TRUE(mape->getOption(D6O_S46_BR));

    // Make sure the option is of proper type, not just plain Option
    shared_ptr<OptionCustom> br =
        dynamic_pointer_cast<OptionCustom>(mape->getOption(D6O_S46_BR));
    ASSERT_TRUE(br);
    EXPECT_EQ("type=00090, len=00016: 2001:db8::abcd (ipv6-address)", br->toText());

    // Now let's check the suboptions. There should be 3 (BR, 2x rule)
    const OptionCollection& subopts = mape->getOptions();
    ASSERT_EQ(3, subopts.size());
    EXPECT_EQ(1, subopts.count(D6O_S46_BR));
    EXPECT_EQ(2, subopts.count(D6O_S46_RULE));

    // Let's check the rules. There should be two of them.
    auto range = subopts.equal_range(D6O_S46_RULE);
    ASSERT_EQ(2, std::distance(range.first, range.second));
    OptionPtr opt1 = range.first->second;
    OptionPtr opt2 = (++range.first)->second;
    shared_ptr<OptionCustom> rule1 = dynamic_pointer_cast<OptionCustom>(opt1);
    shared_ptr<OptionCustom> rule2 = dynamic_pointer_cast<OptionCustom>(opt2);
    ASSERT_TRUE(rule1);
    ASSERT_TRUE(rule2);

    EXPECT_EQ("type=00089, len=00024: 128 (uint8) 4 (uint8) 30 (uint8) "
              "192.0.2.192 (ipv4-address)  (ipv6-prefix),\noptions:\n"
              "  type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", rule1->toText());

    EXPECT_EQ("type=00089, len=00012: 0 (uint8) 6 (uint8) 32 (uint8) "
              "192.0.2.1 (ipv4-address)  (ipv6-prefix)", rule2->toText());

    // Finally, check that the subsuboption in the first rule is ok
    OptionPtr subsubopt = opt1->getOption(D6O_S46_PORTPARAMS);
    shared_ptr<OptionCustom> portparam = dynamic_pointer_cast<OptionCustom>(subsubopt);
    ASSERT_TRUE(portparam);

    EXPECT_EQ("type=00093, len=00004: 8 (uint8) len=6,psid=63 (psid)", portparam->toText());
}

} // end of anonymous space
