Browse Source

[3316] Implemented a class which encapsulates Vendor Class option.

Marcin Siodelski 11 years ago
parent
commit
ce6bedc169

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

@@ -32,6 +32,7 @@ libb10_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
 libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
 libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
 libb10_dhcp___la_SOURCES += option_vendor.cc option_vendor.h
+libb10_dhcp___la_SOURCES += option_vendor_class.cc option_vendor_class.h
 libb10_dhcp___la_SOURCES += option_int.h
 libb10_dhcp___la_SOURCES += option_int_array.h
 libb10_dhcp___la_SOURCES += option.cc option.h

+ 15 - 2
src/lib/dhcp/opaque_data_tuple.h

@@ -150,11 +150,21 @@ public:
     /// @param other String to compare tuple data against.
     bool equals(const std::string& other) const;
 
+    /// @brief Returns tuple length data field type.
+    LengthFieldType getLengthFieldType() const {
+        return (length_field_type_);
+    }
+
     /// @brief Returns the length of the data in the tuple.
     size_t getLength() const {
         return (data_.size());
     }
 
+    /// @brief Returns a total size of the tuple, including length field.
+    size_t getTotalLength() const {
+        return (getDataFieldSize() + getLength());
+    }
+
     /// @brief Returns a reference to the buffer holding tuple data.
     ///
     /// @warning The returned reference is valid only within the lifetime
@@ -264,19 +274,22 @@ public:
     bool operator!=(const std::string& other);
     //@}
 
-private:
-
     /// @brief Returns the size of the tuple length field.
     ///
     /// The returned value depends on the @c LengthFieldType set for the tuple.
     int getDataFieldSize() const;
 
+private:
+
     /// @brief Buffer which holds the opaque tuple data.
     Buffer data_;
     /// @brief Holds a type of tuple size field (1 byte long or 2 bytes long).
     LengthFieldType length_field_type_;
 };
 
+/// @brief Pointer to the @c OpaqueDataTuple object.
+typedef boost::shared_ptr<OpaqueDataTuple> OpaqueDataTuplePtr;
+
 /// @brief Inserts the @c OpaqueDataTuple as a string into stream.
 ///
 /// This operator gets the tuple data in the textual format and inserts it

+ 153 - 0
src/lib/dhcp/option_vendor_class.cc

@@ -0,0 +1,153 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option_vendor_class.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionVendorClass::OptionVendorClass(Option::Universe u,
+                                     const uint32_t vendor_id)
+    : Option(u, getOptionCode(u)), vendor_id_(vendor_id) {
+    if (u == Option::V4) {
+        addTuple(OpaqueDataTuple(OpaqueDataTuple::LENGTH_1_BYTE));
+    }
+}
+
+    OptionVendorClass::OptionVendorClass(Option::Universe u,
+                                         OptionBufferConstIter begin,
+                                         OptionBufferConstIter end)
+    : Option(u, getOptionCode(u)) {
+    unpack(begin, end);
+}
+
+void
+OptionVendorClass::pack(isc::util::OutputBuffer& buf) {
+    packHeader(buf);
+
+    buf.writeUint32(getVendorId());
+
+    for (TuplesCollection::const_iterator it = tuples_.begin();
+         it != tuples_.end(); ++it) {
+        // For DHCPv4 V-I Vendor Class option, there is enterprise id before
+        // every tuple.
+        if ((getUniverse() == V4) && (it != tuples_.begin())) {
+            buf.writeUint32(getVendorId());
+        }
+        it->pack(buf);
+
+    }
+
+}
+
+void
+OptionVendorClass::unpack(OptionBufferConstIter begin,
+                          OptionBufferConstIter end) {
+    if (std::distance(begin, end) < getMinimalLength() - getHeaderLen()) {
+        isc_throw(OutOfRange, "parsed Vendor Class option data truncated to"
+                  " size " << std::distance(begin, end));
+    }
+    // Option must contain at least one enterprise id. It is ok to read 4-byte
+    // value here because we have checked that the buffer he minimal length.
+    vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
+    begin += sizeof(vendor_id_);
+
+    // Start reading opaque data.
+    size_t offset = 0;
+    while (offset < std::distance(begin, end)) {
+        // Parse a tuple.
+        OpaqueDataTuple tuple(getLengthFieldType(), begin + offset, end);
+        addTuple(tuple);
+        // The tuple has been parsed correctly which implies that it is safe to
+        // advance the offset by its total length.
+        offset += tuple.getTotalLength();
+        // For DHCPv4 option, there is enterprise id before every opaque data
+        // tuple. Let's read it, unless we have already reached the end of
+        // buffer.
+        if ((getUniverse() == V4) && (begin + offset != end)) {
+            // Advance the offset by the size of enterprise id.
+            offset += sizeof(vendor_id_);
+            // If the offset already ran over the buffer length or there is
+            // no space left for the empty tuple (thus we add 1), we have
+            // to signal the option truncation.
+            if (offset + 1 >= std::distance(begin, end)) {
+                isc_throw(isc::OutOfRange, "truncated DHCPv4 V-I Vendor Class"
+                          " option - it should contain enterprise id followed"
+                          " by opaque data field tuple");
+            }
+        }
+    }
+}
+
+void
+OptionVendorClass::addTuple(const OpaqueDataTuple& tuple) {
+    if (tuple.getLengthFieldType() != getLengthFieldType()) {
+        isc_throw(isc::BadValue, "attempted to add opaque data tuple having"
+                  " invalid size of the length field "
+                  << tuple.getDataFieldSize() << " to Vendor Class option");
+    }
+
+    tuples_.push_back(tuple);
+}
+
+
+void
+OptionVendorClass::setTuple(const size_t at, const OpaqueDataTuple& tuple) {
+    if (at >= getTuplesNum()) {
+        isc_throw(isc::OutOfRange, "attempted to set an opaque data for the"
+                  " vendor option at position " << at << " which is out of"
+                  " range");
+
+    } else if (tuple.getLengthFieldType() != getLengthFieldType()) {
+        isc_throw(isc::BadValue, "attempted to set opaque data tuple having"
+                  " invalid size of the length field "
+                  << tuple.getDataFieldSize() << " to Vendor Class option");
+    }
+
+    tuples_[at] = tuple;
+}
+
+OpaqueDataTuple
+OptionVendorClass::getTuple(const size_t at) const {
+    if (at >= getTuplesNum()) {
+        isc_throw(isc::OutOfRange, "attempted to get an opaque data for the"
+                  " vendor option at position " << at << " which is out of"
+                  " range");
+    }
+    return (tuples_[at]);
+}
+
+uint16_t
+OptionVendorClass::len() {
+    // The option starts with the header and enterprise id.
+    uint16_t length = getHeaderLen() + sizeof(uint32_t);
+    // Now iterate over existing tuples and add their size.
+    for (TuplesCollection::const_iterator it = tuples_.begin();
+         it != tuples_.end(); ++it) {
+        // For DHCPv4 V-I Vendor Class option, there is enterprise id before
+        // every tuple.
+        if ((getUniverse() == V4) && (it != tuples_.begin())) {
+            length += sizeof(uint32_t);
+        }
+        length += it->getTotalLength();
+
+    }
+
+    return (length);
+}
+
+} // namespace isc::dhcp
+} // namespace isc

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

@@ -0,0 +1,177 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_VENDOR_CLASS_H
+#define OPTION_VENDOR_CLASS_H
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/opaque_data_tuple.h>
+#include <dhcp/option.h>
+#include <util/buffer.h>
+#include <boost/shared_ptr.hpp>
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief This class encapsulates DHCPv6 Vendor Class and DHCPv4 V-I Vendor
+/// Class options.
+///
+/// The format of DHCPv6 Vendor Class option is described in section 22.16 of
+/// RFC3315 and the format of the DHCPv4 V-I Vendor Class option is described
+/// in section 3 of RFC3925. Each of these options carries enterprise id
+/// followed by the collection of tuples carring opaque data. A single tuple
+/// consists of the field holding opaque data length and the actual data.
+/// In case of the DHCPv4 V-I Vendor Class each tuple is preceded by the
+/// 4-byte long enterprise id. Also, the field which carries the length of
+/// the tuple is 1-byte long for DHCPv4 V-I Vendor Class and 2-bytes long
+/// for the DHCPv6 Vendor Class option.
+///
+/// Whether the encapsulated format is DHCPv4 V-I Vendor Class or DHCPv6
+/// Vendor Class option is controlled by the @c u (universe) parameter passed
+/// to the constructor.
+///
+/// @todo Currently, the enterprise id field is set to a value of the first
+/// enterprise id occurrence in the parsed option. At some point we should
+/// be able to differentiate between enterprise ids.
+class OptionVendorClass : public Option {
+public:
+
+    /// @brief Collection of opaque data tuples carried by the option.
+    typedef std::vector<OpaqueDataTuple> TuplesCollection;
+
+    /// @brief Constructor.
+    ///
+    /// @param u universe (v4 or v6).
+    /// @param vendor_id vendor enterprise id (unique 32-bit integer).
+    OptionVendorClass(Option::Universe u, const uint32_t vendor_id);
+
+    /// @brief Constructor.
+    ///
+    /// This constructor creates an instance of the option using a buffer with
+    /// on-wire data. It may throw an exception if the @c unpack method throws.
+    ///
+    /// @param u universe (v4 or v6)
+    /// @param begin Iterator pointing to the beginning of the buffer holding an
+    /// option.
+    /// @param end Iterator pointing to the end of the buffer holding an option.
+    OptionVendorClass(Option::Universe u, OptionBufferConstIter begin,
+                      OptionBufferConstIter end);
+
+    /// @brief Renders option into the buffer in the wire format.
+    ///
+    /// @param [out] buf Buffer to which the option is rendered.
+    virtual void pack(isc::util::OutputBuffer& buf);
+
+    /// @brief Parses buffer holding an option.
+    ///
+    /// This function parses the buffer holding an option and initializes option
+    /// properties: enterprise ids and the collection of tuples.
+    ///
+    /// @param begin Iterator pointing to the beginning of the buffer holding an
+    /// option.
+    /// @param end Iterator pointing to the end of the buffer holding an option.
+    virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+    /// @brief Returns enterprise id.
+    uint32_t getVendorId() const {
+        return (vendor_id_);
+    }
+
+    /// @brief Adds a new opaque data tuple to the option.
+    ///
+    /// @param tuple Tuple to be added.
+    /// @throw isc::BadValue if the type of the tuple doesn't match the
+    /// universe this option belongs to.
+    void addTuple(const OpaqueDataTuple& tuple);
+
+    /// @brief Replaces tuple at the specified index with a new tuple.
+    ///
+    /// This function replaces an opaque data tuple at the specified position
+    /// with the new tuple. If the specified index is out of range an exception
+    /// is thrown.
+    ///
+    /// @param at Index at which the tuple should be replaced.
+    /// @param tuple Tuple to be set.
+    /// @throw isc::OutOfRange if the tuple position is out of range.
+    /// @throw isc::BadValue if the type of the tuple doesn't match the
+    /// universe this option belongs to.
+    void setTuple(const size_t at, const OpaqueDataTuple& tuple);
+
+    /// @brief Returns opaque data tuple at the specified position.
+    ///
+    ///  If the specified position is out of range an exception is thrown.
+    ///
+    /// @param at Index at which the tuple should be replaced.
+    /// @throw isc::OutOfRange if the tuple position is out of range.
+    OpaqueDataTuple getTuple(const size_t at) const;
+
+    /// @brief Returns the number of opaque data tuples added to the option.
+    size_t getTuplesNum() const {
+        return (tuples_.size());
+    }
+
+    /// @brief Returns collection of opaque data tuples carried in the option.
+    const TuplesCollection& getTuples() const {
+        return (tuples_);
+    }
+
+    /// @brief Returns the full length of the option, including option header.
+    virtual uint16_t len();
+
+private:
+
+    /// @brief Returns option code appropriate for the specified universe.
+    ///
+    /// This function is called by the constructor to map the specified
+    /// universe to the option code.
+    ///
+    /// @param u universe (V4 or V6).
+    /// @return DHCPv4 V-I Vendor Class or DHCPv6 Vendor Class option code.
+    static uint16_t getOptionCode(Option::Universe u) {
+        return (u == V4 ? DHO_VIVCO_SUBOPTIONS : D6O_VENDOR_CLASS);
+    }
+
+    /// @brief Returns the tuple length field type for the given universe.
+    ///
+    /// This function returns the length field type which should be used
+    /// for the opaque data tuples being added to this option.
+    ///
+    /// @return Tuple length field type for the universe this option belongs to.
+    OpaqueDataTuple::LengthFieldType getLengthFieldType() const {
+        return (getUniverse() == V4 ? OpaqueDataTuple::LENGTH_1_BYTE :
+                OpaqueDataTuple::LENGTH_2_BYTES);
+    }
+
+    /// @brief Returns minimal length of the option for the given universe.
+    uint16_t getMinimalLength() const {
+        return (getUniverse() == Option::V4 ? 7 : 6);
+    }
+
+    /// @brief Enterprise ID.
+    uint32_t vendor_id_;
+
+    /// @brief Collection of opaque data tuples carried by the option.
+    TuplesCollection tuples_;
+
+};
+
+/// @brief Defines a pointer to the @c OptionVendorClass.
+typedef boost::shared_ptr<OptionVendorClass> OptionVendorClassPtr;
+
+}
+}
+
+#endif // OPTION_VENDOR_CLASS_H

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

@@ -65,6 +65,7 @@ libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += option_space_unittest.cc
 libdhcp___unittests_SOURCES += option_string_unittest.cc
 libdhcp___unittests_SOURCES += option_vendor_unittest.cc
+libdhcp___unittests_SOURCES += option_vendor_class_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_unittest.cc

+ 393 - 0
src/lib/dhcp/tests/option_vendor_class_unittest.cc

@@ -0,0 +1,393 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <exceptions/exceptions.h>
+#include <dhcp/option_vendor_class.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+// This test checks that the DHCPv4 option constructor sets the default
+// properties to the expected values. This constructor should add an
+// empty opaque data tuple (it is essentially the same as adding a 1-byte
+// long field which carries a value of 0).
+TEST(OptionVendorClass, constructor4) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    EXPECT_EQ(1234, vendor_class.getVendorId());
+    // Option length is 1 byte for header + 1 byte for option size +
+    // 4 bytes of enterprise id + 1 byte for opaque data.
+    EXPECT_EQ(7, vendor_class.len());
+    // There should be one empty tuple.
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    EXPECT_EQ(0, vendor_class.getTuple(0).getLength());
+}
+
+// This test checks that the DHCPv6 option constructor sets the default
+// properties to the expected values.
+TEST(OptionVendorClass, constructor6) {
+    OptionVendorClass vendor_class(Option::V6, 2345);
+    EXPECT_EQ(2345, vendor_class.getVendorId());
+    // Option length is 2 bytes for option code + 2 bytes for option size +
+    // 4 bytes of enterprise id.
+    EXPECT_EQ(8, vendor_class.len());
+    // There should be no tuples.
+    EXPECT_EQ(0, vendor_class.getTuplesNum());
+}
+
+// This test verifies that it is possible to append the opaque data tuple
+// to the option and then retrieve it.
+TEST(OptionVendorClass, addTuple) {
+    OptionVendorClass vendor_class(Option::V6, 2345);
+    // Initially there should be no tuples (for DHCPv6).
+    ASSERT_EQ(0, vendor_class.getTuplesNum());
+    // Create a new tuple and add it to the option.
+    OpaqueDataTuple tuple;
+    tuple = "xyz";
+    vendor_class.addTuple(tuple);
+    // The option should now hold one tuple.
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+    // Add another tuple.
+    tuple = "abc";
+    vendor_class.addTuple(tuple);
+    // The option should now hold exactly two tuples in the order in which
+    // they were added.
+    ASSERT_EQ(2, vendor_class.getTuplesNum());
+    EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+    EXPECT_EQ("abc", vendor_class.getTuple(1).getText());
+
+    // Attempt to add the tuple with 1 byte long length field should fail
+    // for DHCPv6 option.
+    OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE);
+    EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue);
+}
+
+// This test checks that it is possible to replace existing tuple.
+TEST(OptionVendorClass, setTuple) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    // The DHCPv4 option should carry one empty tuple.
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    ASSERT_TRUE(vendor_class.getTuple(0).getText().empty());
+    // Replace the empty tuple with non-empty one.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "xyz";
+    ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+    // There should be one tuple with updated data.
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
+
+    // Add another one.
+    tuple = "abc";
+    vendor_class.addTuple(tuple);
+    ASSERT_EQ(2, vendor_class.getTuplesNum());
+    ASSERT_EQ("abc", vendor_class.getTuple(1).getText());
+
+    // Try to replace them with new tuples.
+    tuple = "new_xyz";
+    ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+    ASSERT_EQ(2, vendor_class.getTuplesNum());
+    EXPECT_EQ("new_xyz", vendor_class.getTuple(0).getText());
+
+    tuple = "new_abc";
+    ASSERT_NO_THROW(vendor_class.setTuple(1, tuple));
+    ASSERT_EQ(2, vendor_class.getTuplesNum());
+    EXPECT_EQ("new_abc", vendor_class.getTuple(1).getText());
+
+    // For out of range position, exception should be thrown.
+    tuple = "foo";
+    EXPECT_THROW(vendor_class.setTuple(2, tuple), isc::OutOfRange);
+
+    // Attempt to add the tuple with 2 byte long length field should fail
+    // for DHCPv4 option.
+    OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_2_BYTES);
+    EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue);
+}
+
+// Check that the returned length of the DHCPv4 option is correct.
+TEST(OptionVendorClass, len4) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    ASSERT_EQ(7, vendor_class.len());
+    // Replace the default empty tuple.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "xyz";
+    ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
+    // The total length should get increased by the size of 'xyz'.
+    EXPECT_EQ(10, vendor_class.len());
+    // Add another tuple.
+    tuple = "abc";
+    vendor_class.addTuple(tuple);
+    // The total size now grows by the additional enterprise id and the
+    // 1 byte of the tuple length field and 3 bytes of 'abc'.
+    EXPECT_EQ(18, vendor_class.len());
+}
+
+// Check that the returned length of the DHCPv6 option is correct.
+TEST(OptionVendorClass, len6) {
+    OptionVendorClass vendor_class(Option::V6, 1234);
+    ASSERT_EQ(8, vendor_class.len());
+    // Add first tuple.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "xyz";
+    ASSERT_NO_THROW(vendor_class.addTuple(tuple));
+    // The total length grows by 2 bytes of the length field and 3 bytes
+    // consumed by 'xyz'.
+    EXPECT_EQ(13, vendor_class.len());
+    // Add another tuple and check that the total size gets increased.
+    tuple = "abc";
+    vendor_class.addTuple(tuple);
+    EXPECT_EQ(18, vendor_class.len());
+}
+
+// Check that the option is rendered to the buffer in wire format.
+TEST(OptionVendorClass, pack4) {
+    OptionVendorClass vendor_class(Option::V4, 1234);
+    ASSERT_EQ(1, vendor_class.getTuplesNum());
+    // By default, there is an empty tuple in the option. Let's replace
+    // it with the tuple with some data.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "Hello world";
+    vendor_class.setTuple(0, tuple);
+    // And add another tuple so as resulting option is a bit more complex.
+    tuple = "foo";
+    vendor_class.addTuple(tuple);
+
+    // Render the data to the buffer.
+    OutputBuffer buf(10);
+    ASSERT_NO_THROW(vendor_class.pack(buf));
+    ASSERT_EQ(26, buf.getLength());
+
+    // Prepare reference data.
+    const uint8_t ref[] = {
+        0x7C, 0x18,                         // option 124, length 24
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x0B,                               // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        3,                                  // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    // Compare the buffer with reference data.
+    EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+                        static_cast<const void*>(buf.getData()), 26));
+}
+
+// Check that the DHCPv6 option is rendered to the buffer in wire format.
+TEST(OptionVendorClass, pack6) {
+    OptionVendorClass vendor_class(Option::V6, 1234);
+    ASSERT_EQ(0, vendor_class.getTuplesNum());
+    // Add tuple.
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    tuple = "Hello world";
+    vendor_class.addTuple(tuple);
+    // And add another tuple so as resulting option is a bit more complex.
+    tuple = "foo";
+    vendor_class.addTuple(tuple);
+
+    // Render the data to the buffer.
+    OutputBuffer buf(10);
+    ASSERT_NO_THROW(vendor_class.pack(buf));
+    ASSERT_EQ(26, buf.getLength());
+
+    // Prepare reference data.
+    const uint8_t ref[] = {
+        0x00, 0x10, 0x00, 0x16,             // option 16, length 22
+        0x00, 0x00, 0x04, 0xD2,             // enterprise id 1234
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0x00, 0x03,                         // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    // Compare the buffer with reference data.
+    EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
+                        static_cast<const void*>(buf.getData()),
+                        buf.getLength()));
+}
+
+// This function checks that the DHCPv4 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionVendorClass, unpack4) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x0B,                               // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        3,                                  // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    ASSERT_EQ(2, vendor_class->getTuplesNum());
+    EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText());
+    EXPECT_EQ("foo", vendor_class->getTuple(1).getText());
+}
+
+// This function checks that the DHCPv4 option with two opaque data tuples
+// is parsed correctly.
+TEST(OptionVendorClass, unpack6) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0x00, 0x03,                         // tuple length is 3
+        0x66, 0x6F, 0x6F                    // foo
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    ASSERT_EQ(2, vendor_class->getTuplesNum());
+    EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText());
+    EXPECT_EQ("foo", vendor_class->getTuple(1).getText());
+}
+
+
+// This test checks that the DHCPv4 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionVendorClass, unpack4EmptyTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x00,                               // tuple length is 0
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    ASSERT_EQ(1, vendor_class->getTuplesNum());
+    EXPECT_TRUE(vendor_class->getTuple(0).getText().empty());
+}
+
+// This test checks that the DHCPv6 option with opaque data of size 0
+// is correctly parsed.
+TEST(OptionVendorClass, unpack6EmptyTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,  // enterprise id 1234
+        0x00, 0x00        // tuple length is 0
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    ASSERT_EQ(1, vendor_class->getTuplesNum());
+    EXPECT_TRUE(vendor_class->getTuple(0).getText().empty());
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv4
+// V-I Vendor Class option.
+TEST(OptionVendorClass, unpack4Truncated) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x0B,                               // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64,       // world
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    EXPECT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()),
+                 isc::OutOfRange);
+}
+
+// This test checks that exception is thrown when parsing truncated DHCPv6
+// Vendor Class option.
+TEST(OptionVendorClass, unpack6Truncated) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2,                    // enterprise id 1234
+        0x00, 0x0B,                         // tuple length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C              // worl (truncated d!)
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    EXPECT_THROW(OptionVendorClass (Option::V6, buf.begin(), buf.end()),
+                 isc::dhcp::OpaqueDataTupleError);
+}
+
+// This test checks that exception is thrown when parsing DHCPv4 V-I Vendor
+// Class option which doesn't have opaque data length.
+TEST(OptionVendorClass, unpack4NoTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2  // enterprise id 1234
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    ASSERT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()),
+                 isc::OutOfRange);
+}
+
+// This test checks that the DHCPv6 Vendor Class option containing no opaque
+// data is parsed correctly.
+TEST(OptionVendorClass, unpack6NoTuple) {
+    // Prepare data to decode.
+    const uint8_t buf_data[] = {
+        0, 0, 0x4, 0xD2  // enterprise id 1234
+    };
+    OptionBuffer buf(buf_data, buf_data + sizeof(buf_data));
+
+    OptionVendorClassPtr vendor_class;
+    ASSERT_NO_THROW(
+        vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6,
+                                                                  buf.begin(),
+                                                                  buf.end()));
+    );
+    EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType());
+    EXPECT_EQ(1234, vendor_class->getVendorId());
+    EXPECT_EQ(0, vendor_class->getTuplesNum());
+}
+
+} // end of anonymous namespace
+