Browse Source

[3316] Added an object encapsulating opaque-data carried in Vendor Class.

Marcin Siodelski 11 years ago
parent
commit
edd9891f58

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

@@ -23,6 +23,7 @@ libb10_dhcp___la_SOURCES += iface_mgr_error_handler.h
 libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
+libb10_dhcp___la_SOURCES += opaque_data_tuple.cc opaque_data_tuple.h
 libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
 libb10_dhcp___la_SOURCES += option4_client_fqdn.cc option4_client_fqdn.h
 libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h

+ 123 - 0
src/lib/dhcp/opaque_data_tuple.cc

@@ -0,0 +1,123 @@
+// 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 <dhcp/opaque_data_tuple.h>
+
+namespace isc {
+namespace dhcp {
+
+OpaqueDataTuple::OpaqueDataTuple(LengthFieldType length_field_type)
+    : length_field_type_(length_field_type) {
+}
+
+void
+OpaqueDataTuple::append(const std::string& text) {
+    // Don't do anything if text is empty.
+    if (!text.empty()) {
+        append(&text[0], text.size());
+    }
+}
+
+void
+OpaqueDataTuple::assign(const std::string& text) {
+    // If empty string is being assigned, reset the buffer.
+    if (text.empty()) {
+        clear();
+    } else {
+        assign(&text[0], text.size());
+    }
+}
+
+void
+OpaqueDataTuple::clear() {
+    data_.clear();
+}
+
+bool
+OpaqueDataTuple::equals(const std::string& other) const {
+    return (getText() == other);
+}
+
+std::string
+OpaqueDataTuple::getText() const {
+    // Convert the data carried in the buffer to a string.
+    return (std::string(data_.begin(), data_.end()));
+}
+
+void
+OpaqueDataTuple::pack(isc::util::OutputBuffer& buf) const {
+    if (getLength() == 0) {
+        isc_throw(OpaqueDataTupleError, "failed to create on-wire format of the"
+                  " opaque data field, because the field appears to be empty");
+    } else if ((1 << getDataFieldSize() * 8) <= getLength()) {
+        isc_throw(OpaqueDataTupleError, "failed to create on-wire format of the"
+                  " opaque data field, because current data length "
+                  << getLength() << " exceeds the maximum size for the length"
+                  << " field size " << getDataFieldSize());
+    }
+
+    if (getDataFieldSize() == 1) {
+        buf.writeUint8(static_cast<uint8_t>(getLength()));
+    } else {
+        buf.writeUint16(getLength());
+    }
+
+    buf.writeData(&getData()[0], getLength());
+}
+
+int
+OpaqueDataTuple::getDataFieldSize() const {
+    return (length_field_type_ == LENGTH_1_BYTE ? 1 : 2);
+}
+
+OpaqueDataTuple&
+OpaqueDataTuple::operator=(const std::string& other) {
+    // Replace existing data with the new data converted from a string.
+    assign(&other[0], other.length());
+    return (*this);
+}
+
+bool
+OpaqueDataTuple::operator==(const std::string& other) {
+    return (equals(other));
+}
+
+bool
+OpaqueDataTuple::operator!=(const std::string& other) {
+    return (!equals(other));
+}
+
+std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple) {
+    os << tuple.getText();
+    return (os);
+}
+
+std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple) {
+    // We will replace the current data with new data.
+    tuple.clear();
+    char buf[256];
+    // Read chunks of data as long as we haven't reached the end of the stream.
+    while (!is.eof()) {
+        is.read(buf, sizeof(buf));
+        // Append the chunk of data we have just read. It is fine if the
+        // gcount is 0, because append() function will check that.
+        tuple.append(buf, is.gcount());
+    }
+    // Clear eof flag, so as the stream can be read again.
+    is.clear();
+    return (is);
+}
+
+}
+}

+ 304 - 0
src/lib/dhcp/opaque_data_tuple.h

@@ -0,0 +1,304 @@
+// 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 OPAQUE_DATA_TUPLE_H
+#define OPAQUE_DATA_TUPLE_H
+
+#include <util/buffer.h>
+#include <util/io_utilities.h>
+#include <iostream>
+#include <iterator>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when there operation on @c OpaqueDataTuple
+/// object results in an error.
+class OpaqueDataTupleError : public Exception {
+public:
+    OpaqueDataTupleError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Represents a single instance of the opaque data preceded by length.
+///
+/// Some of the DHCP options, such as Vendor Class option (16) in DHCPv6 or
+/// V-I Vendor Class option (124) in DHCPv4 may carry multiple pairs of
+/// opaque-data preceded by its length. Such pairs are called tuples. This class
+/// represents a single instance of the tuple in the DHCPv4 or DHCPv6 option.
+///
+/// Although, the primary purpose of this class is to represent data tuples in
+/// Vendor Class options, there may be other options defined in the future that
+/// may have similar structure and this class can be used to represent the data
+/// tuples in these new options too.
+///
+/// This class exposes a set of convenience methods to assign and retrieve the
+/// opaque data from the tuple. It also implements a method to render the tuple
+/// data into a wire format, as well as a method to create an instance of the
+/// tuple from the wire format.
+class OpaqueDataTuple {
+public:
+
+    /// @brief Size of the length field in the tuple.
+    ///
+    /// In the wire format, the tuple consists of the two fields: one holding
+    /// a length of the opaque data size, second holding opaque data. The first
+    /// field's size may be equal to 1 or 2 bytes. Usually, the tuples carried
+    /// in the DHCPv6 options have 2 byte long length fields, the tuples carried
+    /// in DHCPv4 options have 1 byte long length fields.
+    enum LengthFieldType {
+        LENGTH_1_BYTE,
+        LENGTH_2_BYTES
+    };
+
+    /// @brief Defines a type of the data buffer used to hold the opaque data.
+    typedef std::vector<uint8_t> Buffer;
+
+    /// @brief Default constructor.
+    ///
+    /// @param length_field_size Length of the field which holds the size of
+    /// the tuple.
+    OpaqueDataTuple(LengthFieldType length_field_type = LENGTH_2_BYTES);
+
+    /// @brief Constructor
+    ///
+    /// Creates a tuple from on-wire data. It calls @c OpaqueDataTuple::unpack
+    /// internally.
+    ///
+    /// @param begin Iterator pointing to the begining of the buffer holding
+    /// wire data.
+    /// @param end Iterator pointing to the end of the buffer holding wire data.
+    /// @tparam InputIterator Type of the iterators passed to this function.
+    /// @throw It may throw an exception if the @unpack throws.
+    template<typename InputIterator>
+    OpaqueDataTuple(LengthFieldType length_field_type, InputIterator begin,
+                    InputIterator end)
+        : length_field_type_(length_field_type) {
+        unpack(begin, end);
+    }
+
+    /// @brief Appends data to the tuple.
+    ///
+    /// This function appends the data of the specified length to the tuple.
+    /// If the speficified buffer length is greater than the size of the buffer,
+    /// the behavior of this function is undefined.
+    ///
+    /// @param data Iterator pointing to the beginning of the buffer being
+    /// appended. The source buffer may be an STL object or an array of
+    /// characters. In the latter case, the pointer to the beginning of this
+    /// array should be passed.
+    /// @param len Length of the source buffer.
+    /// @tparam InputIterator Type of the iterator pointing to the beginning of
+    /// the source buffer.
+    template<typename InputIterator>
+    void append(InputIterator data, const size_t len) {
+        data_.insert(data_.end(), data, data + len);
+    }
+
+    /// @brief Appends string to the tuple.
+    ///
+    /// In most cases, the tuple will carry a string. This function appends the
+    /// string to the tuple.
+    ///
+    /// @param text String to be appended in the tuple.
+    void append(const std::string& text);
+
+    /// @brief Assigns data to the tuple.
+    ///
+    /// This function replaces existing data in the tuple with the new data.
+    /// If the speficified buffer length is greater than the size of the buffer,
+    /// the behavior of this function is undefined.
+    /// @param data Iterator pointing to the beginning of the buffer being
+    /// assigned. The source buffer may be an STL object or an array of
+    /// characters. In the latter case, the pointer to the beginning of this
+    /// array should be passed.
+    /// @param len Length of the source buffer.
+    /// @tparam InputIterator Type of the iterator pointing to the beginning of
+    /// the source buffer.
+    template<typename InputIterator>
+    void assign(InputIterator data, const size_t len) {
+        data_.assign(data, data + len);
+    }
+
+    /// @brief Assigns string data to the tuple.
+    ///
+    /// In most cases, the tuple will carry a string. This function sets the
+    /// string to the tuple.
+    ///
+    /// @param text String to be assigned to the tuple.
+    void assign(const std::string& text);
+
+    /// @brief Removes the contents of the tuple.
+    void clear();
+
+    /// @brief Checks if the data carried in the tuple match the string.
+    ///
+    /// @param other String to compare tuple data against.
+    bool equals(const std::string& other) const;
+
+    /// @brief Returns the length of the data in the tuple.
+    size_t getLength() const {
+        return (data_.size());
+    }
+
+    /// @brief Returns a reference to the buffer holding tuple data.
+    ///
+    /// @warning The returned reference is valid only within the lifetime
+    /// of the object which returned it. The use of the returned reference
+    /// after the object has been destroyed yelds undefined behavior.
+    const Buffer& getData() const {
+        return (data_);
+    }
+
+    /// @brief Return the tuple data in the textual format.
+    std::string getText() const;
+
+    /// @brief Renders the tuple to a buffer in the wire format.
+    ///
+    /// This function creates the following wire representation of the tuple:
+    /// - 1 or 2 bytes holding a length of the data.
+    /// - variable number of bytes holding data.
+    /// and writes it to the specified buffer. The new are appended to the
+    /// buffer, so as data existing in the buffer is preserved.
+    ///
+    /// The tuple is considered malformed if one of the follwing occurs:
+    /// - the size of the data is 0 (tuple is empty),
+    /// - the size of the data is greater than 255 and the size of the length
+    /// field is 1 byte (see @c LengthFieldType).
+    /// - the size of the data is greater than 65535 and the size of the length
+    /// field is 2 bytes (see @c LengthFieldType).
+    ///
+    /// Function will throw an exception if trying to render malformed tuple.
+    ///
+    /// @param [out] buf Buffer to which the data is rendered.
+    ///
+    /// @throw OpaqueDataTupleError if failed to render the data to the
+    /// buffer because the tuple is malformed.
+    void pack(isc::util::OutputBuffer& buf) const;
+
+    /// @brief Parses wire data and creates a tuple from it.
+    ///
+    /// This function parses on-wire data stored in the provided buffer and
+    /// stores it in the tuple object. The wire data must include at least the
+    /// data field of the length matching the specified @c LengthFieldType.
+    /// The remaining buffer length (excluding the length field) must be equal
+    /// or greater than the length carried in the length field. If any of these
+    /// two conditions is not met, an exception is thrown.
+    ///
+    /// This function allows opaque data with the length of 0.
+    ///
+    /// @param begin Iterator pointing to the begining of the buffer holding
+    /// wire data.
+    /// @param end Iterator pointing to the end of the buffer holding wire data.
+    /// @tparam InputIterator Type of the iterators passed to this function.
+    template<typename InputIterator>
+    void unpack(InputIterator begin, InputIterator end) {
+        Buffer buf(begin, end);
+        // The buffer must at least hold the size of the data.
+        if (std::distance(begin, end) < getDataFieldSize()) {
+            isc_throw(OpaqueDataTupleError,
+                      "unable to parse the opaque data tuple, the buffer"
+                      " length is " << std::distance(begin, end)
+                      << ", expected at least " << getDataFieldSize());
+        }
+        // Read the data length from the length field, depending on the
+        // size of the data field (1 or 2 bytes).
+        size_t len = getDataFieldSize() == 1 ? *begin :
+            isc::util::readUint16(&(*begin), std::distance(begin, end));
+        // Now that we have the expected data size, let's check that the
+        // reminder of the buffer is long enough.
+        begin += getDataFieldSize();
+        if (std::distance(begin, end) < len) {
+            isc_throw(OpaqueDataTupleError,
+                      "unable to parse the opaque data tuple, the buffer"
+                      " length is " << std::distance(begin, end)
+                      << ", but the length of the tuple in the length field"
+                      " is " << len);
+        }
+        // The buffer length is long enough to read the desired amount of data.
+        assign(begin, len);
+    }
+
+    /// @name Assignment and comparison operators.
+    //{@
+
+    /// @brief Assignment operator.
+    ///
+    /// This operator assigns the string data to the tuple.
+    ///
+    /// @param other string to be assigned to the tuple.
+    /// @return Tuple object after assignment.
+    OpaqueDataTuple& operator=(const std::string& other);
+
+    /// @brief Equality operator.
+    ///
+    /// This operator compares the string given as an argument to the data
+    /// carried in the tuple in the textual format.
+    ///
+    /// @param other String to compare the tuple against.
+    /// @return true if data carried in the tuple is equal to the string.
+    bool operator==(const std::string& other);
+
+    /// @brief Inequality operator.
+    ///
+    /// This operator compares the string given as an argument to the data
+    /// carried in the tuple for inequality.
+    ///
+    /// @param other String to compare the tuple against.
+    /// @return true if the data carried in the tuple is unequal the given
+    /// string.
+    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;
+
+    /// @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 Inserts the @c OpaqueDataTuple as a string into stream.
+///
+/// This operator gets the tuple data in the textual format and inserts it
+/// into the output stream.
+///
+/// @param os Stream object on which insertion is performed.
+/// @param tuple Object encapsulating a tuple which data in the textual format
+/// is inserted into the stream.
+/// @return Reference to the same stream but after insertion operation.
+std::ostream& operator<<(std::ostream& os, const OpaqueDataTuple& tuple);
+
+/// @brief Inserts data carried in the stream into the tuple.
+///
+/// this operator inserts data carried in the input stream and inserts it to
+/// the @c OpaqueDataTuple object. The existing data is replaced with new data.
+///
+/// @param is Input stream from which the data will be inserted.
+/// @param tuple @c OpaqueDataTuple object to which the data will be inserted.
+/// @return Input stream after insertion to the tuple is performed.
+std::istream& operator>>(std::istream& is, OpaqueDataTuple& tuple);
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif

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

@@ -48,6 +48,7 @@ libdhcp___unittests_SOURCES += hwaddr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_test_config.cc iface_mgr_test_config.h
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
+libdhcp___unittests_SOURCES += opaque_data_tuple_unittest.cc
 libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option4_client_fqdn_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc

+ 468 - 0
src/lib/dhcp/tests/opaque_data_tuple_unittest.cc

@@ -0,0 +1,468 @@
+// 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 <dhcp/opaque_data_tuple.h>
+#include <util/buffer.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+// This test checks that when the default constructor is called, the data buffer
+// is empty.
+TEST(OpaqueDataTuple, constructor) {
+    OpaqueDataTuple tuple;
+    // There should be no data in the tuple.
+    EXPECT_EQ(0, tuple.getLength());
+    EXPECT_TRUE(tuple.getData().empty());
+    EXPECT_TRUE(tuple.getText().empty());
+}
+
+// Test that the constructor which takes the buffer as argument parses the
+// wire data.
+TEST(OpaqueDataTuple, constructorParse1Byte) {
+    const char wire_data[] = {
+        0x0B,                               // Length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
+    };
+
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE, wire_data,
+                          wire_data + sizeof(wire_data));
+
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_EQ("Hello world", tuple.getText());
+
+}
+
+// Test that the constructor which takes the buffer as argument parses the
+// wire data.
+TEST(OpaqueDataTuple, constructorParse2Bytes) {
+    const char wire_data[] = {
+        0x00, 0x0B,                         // Length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
+    };
+
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES, wire_data,
+                          wire_data + sizeof(wire_data));
+
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_EQ("Hello world", tuple.getText());
+
+}
+
+
+// This test checks that it is possible to set the tuple data using raw buffer.
+TEST(OpaqueDataTuple, assignData) {
+    OpaqueDataTuple tuple;
+    // Initially the tuple buffer should be empty.
+    OpaqueDataTuple::Buffer buf = tuple.getData();
+    ASSERT_TRUE(buf.empty());
+    // Prepare some input data and assign to the tuple.
+    const uint8_t data1[] = {
+        0xCA, 0xFE, 0xBE, 0xEF
+    };
+    tuple.assign(data1, sizeof(data1));
+    // Tuple should now hold the data we assigned.
+    ASSERT_EQ(sizeof(data1), tuple.getLength());
+    buf = tuple.getData();
+    EXPECT_TRUE(std::equal(buf.begin(), buf.end(), data1));
+
+    // Prepare the other set of data and assign to the tuple.
+    const uint8_t data2[] = {
+        1, 2, 3, 4, 5, 6
+    };
+    tuple.assign(data2, sizeof(data2));
+    // The new data should have replaced the old data.
+    ASSERT_EQ(sizeof(data2), tuple.getLength());
+    buf = tuple.getData();
+    EXPECT_TRUE(std::equal(buf.begin(), buf.end(), data2));
+}
+
+// This test checks thet it is possible to append the data to the tuple using
+// raw buffer.
+TEST(OpaqueDataTuple, appendData) {
+    OpaqueDataTuple tuple;
+    // Initially the tuple buffer should be empty.
+    OpaqueDataTuple::Buffer buf = tuple.getData();
+    ASSERT_TRUE(buf.empty());
+    // Prepare some input data and append to the empty tuple.
+    const uint8_t data1[] = {
+        0xCA, 0xFE, 0xBE, 0xEF
+    };
+    tuple.append(data1, sizeof(data1));
+    // The tuple should now hold only the data we appended.
+    ASSERT_EQ(sizeof(data1), tuple.getLength());
+    buf = tuple.getData();
+    EXPECT_TRUE(std::equal(buf.begin(), buf.end(), data1));
+    // Prepare the new set of data and append.
+    const uint8_t data2[] = {
+        1, 2, 3, 4, 5, 6
+    };
+    tuple.append(data2, sizeof(data2));
+    // We expect that the tuple now has both sets of data we appended. In order
+    // to verify that, we have to concatenate the input data1 and data2.
+    std::vector<uint8_t> data12(data1, data1 + sizeof(data1));
+    data12.insert(data12.end(), data2, data2 + sizeof(data2));
+    // The size of the tuple should be a sum of data1 and data2 lengths.
+    ASSERT_EQ(sizeof(data1) + sizeof(data2), tuple.getLength());
+    buf = tuple.getData();
+    EXPECT_TRUE(std::equal(buf.begin(), buf.end(), data12.begin()));
+}
+
+// This test checks that it is possible to assign the string to the tuple.
+TEST(OpaqueDataTuple, assignString) {
+    OpaqueDataTuple tuple;
+    // Initially, the tuple should be empty.
+    ASSERT_EQ(0, tuple.getLength());
+    // Assign some string data.
+    tuple.assign("Some string");
+    // Verify that the data has been assigned.
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_EQ("Some string", tuple.getText());
+    // Assign some other string.
+    tuple.assign("Different string");
+    // The new string should have replaced the old string.
+    EXPECT_EQ(16, tuple.getLength());
+    EXPECT_EQ("Different string", tuple.getText());
+}
+
+// This test checks that it is possible to append the string to the tuple.
+TEST(OpaqueDataTuple, appendString) {
+    OpaqueDataTuple tuple;
+    // Initially the tuple should be empty.
+    ASSERT_EQ(0, tuple.getLength());
+    // Append the string to it.
+    tuple.append("First part");
+    ASSERT_EQ(10, tuple.getLength());
+    ASSERT_EQ("First part", tuple.getText());
+    // Now append the other string.
+    tuple.append(" and second part");
+    EXPECT_EQ(26, tuple.getLength());
+    // The resulting data in the tuple should be a concatenation of both
+    // strings.
+    EXPECT_EQ("First part and second part", tuple.getText());
+}
+
+// This test checks that equals function correctly checks that the tuple
+// holds a given string but it doesn't hold the other.
+TEST(OpaqueDataTuple, equals) {
+    OpaqueDataTuple tuple;
+    // Tuple is supposed to be empty so it is not equal xyz.
+    EXPECT_FALSE(tuple.equals("xyz"));
+    // Assign xyz.
+    tuple = "xyz";
+    // The tuple should be equal xyz, but not abc.
+    EXPECT_FALSE(tuple.equals("abc"));
+    EXPECT_TRUE(tuple.equals("xyz"));
+    // Assign abc to the tuple.
+    tuple = "abc";
+    // It should be now opposite.
+    EXPECT_TRUE(tuple.equals("abc"));
+    EXPECT_FALSE(tuple.equals("xyz"));
+}
+
+// This test checks that the conversion of the data in the tuple to the string
+// is performed correctly.
+TEST(OpaqueDataTuple, getText) {
+    OpaqueDataTuple tuple;
+    // Initially the tuple should be empty.
+    ASSERT_TRUE(tuple.getText().empty());
+    // ASCII representation of 'Hello world'.
+    const char as_ascii[] = {
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
+    };
+    // Assign it to the tuple.
+    tuple.assign(as_ascii, sizeof(as_ascii));
+    // Conversion to string should give as the original text.
+    EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// This test verifies the behavior of (in)equality and assignment operators.
+TEST(OpaqueDataTuple, operators) {
+    OpaqueDataTuple tuple;
+    // Tuple should be empty initially.
+    ASSERT_EQ(0, tuple.getLength());
+    // Check assignment.
+    tuple = "Hello World";
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_TRUE(tuple == "Hello World");
+    EXPECT_TRUE(tuple != "Something else");
+    // Assign something else to make sure it affects the tuple.
+    tuple = "Something else";
+    EXPECT_EQ(14, tuple.getLength());
+    EXPECT_TRUE(tuple == "Something else");
+    EXPECT_TRUE(tuple != "Hello World");
+}
+
+// This test verifies that the tuple is inserted in the textual format to the
+// output stream.
+TEST(OpaqueDataTuple, operatorOutputStream) {
+    OpaqueDataTuple tuple;
+    // The tuple should be empty initially.
+    ASSERT_EQ(0, tuple.getLength());
+    // The tuple is empty, so assigning its content to the output stream should
+    // be no-op and result in the same text in the stream.
+    std::ostringstream s;
+    s << "Some text";
+    s << tuple;
+    EXPECT_EQ("Some text", s.str());
+    // Now, let's assign some text to the tuple and call operator again.
+    // The new text should be added to the stream.
+    tuple = " and some other text";
+    s << tuple;
+    EXPECT_EQ(s.str(), "Some text and some other text");
+
+}
+
+// This test verifies that the value of the tuple can be initialized from the
+// input stream.
+TEST(OpaqueDataTuple, operatorInputStream) {
+    OpaqueDataTuple tuple;
+    // The tuple should be empty initially.
+    ASSERT_EQ(0, tuple.getLength());
+    // The input stream has some text. This text should be appended to the
+    // tuple.
+    std::istringstream s;
+    s.str("Some text");
+    s >> tuple;
+    EXPECT_EQ("Some text", tuple.getText());
+    // Now, let's assign some other text to the stream. This new text should be
+    // assigned to the tuple.
+    s.str("And some other");
+    s >> tuple;
+    EXPECT_EQ("And some other", tuple.getText());
+}
+
+// This test checks that the tuple is correctly encoded in the wire format when
+// the size of the length field is 1 byte.
+TEST(OpaqueDataTuple, pack1Byte) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    // Initially, the tuple should be empty.
+    ASSERT_EQ(0, tuple.getLength());
+    // The empty data doesn't make much sense, so the pack() should not
+    // allow it.
+    OutputBuffer out_buf(10);
+    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+    // Set the data for tuple.
+    std::vector<uint8_t> data;
+    for (int i = 0; i < 100; ++i) {
+        data.push_back(i);
+    }
+    tuple.assign(data.begin(), data.size());
+    // The pack should now succeed.
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+    // The rendered buffer should be 101 bytes long - 1 byte for length,
+    // 100 bytes for the actual data.
+    ASSERT_EQ(101, out_buf.getLength());
+    // Get the rendered data into the vector for convenience.
+    std::vector<uint8_t>
+        render_data(static_cast<const uint8_t*>(out_buf.getData()),
+                    static_cast<const uint8_t*>(out_buf.getData()) + 101);
+    // The first byte is a length byte. It should hold the length of 100.
+    EXPECT_EQ(100, render_data[0]);
+    // Verify that the rendered data is correct.
+    EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
+                           data.begin()));
+    // Reset the output buffer for another test.
+    out_buf.clear();
+    // Fill in the tuple buffer so as it reaches maximum allowed length. The
+    // maximum length is 255 when the size of the length field is one byte.
+    for (int i = 100; i < 255; ++i) {
+        data.push_back(i);
+    }
+    ASSERT_EQ(255, data.size());
+    tuple.assign(data.begin(), data.size());
+    // The pack() should be successful again.
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+    // The rendered buffer should be 256 bytes long. The first byte holds the
+    // opaque data length, the remaining bytes hold the actual data.
+    ASSERT_EQ(256, out_buf.getLength());
+    // Check that the data is correct.
+    render_data.assign(static_cast<const uint8_t*>(out_buf.getData()),
+                       static_cast<const uint8_t*>(out_buf.getData()) + 256);
+    EXPECT_EQ(255, render_data[0]);
+    EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
+                           data.begin()));
+    // Clear output buffer for another test.
+    out_buf.clear();
+    // Add one more value to the tuple. Now, the resulting buffer should exceed
+    // the maximum length. An attempt to pack() should fail.
+    data.push_back(255);
+    tuple.assign(data.begin(), data.size());
+    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+}
+
+// This test checks that the tuple is correctly encoded in the wire format when
+// the size of the length field is 2 bytes.
+TEST(OpaqueDataTuple, pack2Bytes) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Initially, the tuple should be empty.
+    ASSERT_EQ(0, tuple.getLength());
+    // The empty data doesn't make much sense, so the pack() should not
+    // allow it.
+    OutputBuffer out_buf(10);
+    EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+    // Set the data for tuple.
+    std::vector<uint8_t> data;
+    for (int i = 0; i < 512; ++i) {
+        data.push_back(i);
+    }
+    tuple.assign(data.begin(), data.size());
+    // The pack should now succeed.
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+    // The rendered buffer should be 514 bytes long - 2 bytes for length,
+    // 512 bytes for the actual data.
+    ASSERT_EQ(514, out_buf.getLength());
+    // Get the rendered data into the vector for convenience.
+    std::vector<uint8_t>
+        render_data(static_cast<const uint8_t*>(out_buf.getData()),
+                    static_cast<const uint8_t*>(out_buf.getData()) + 514);
+    // The first two bytes hold the length of 512.
+    uint16_t len = (render_data[0] << 8) + render_data[1];
+    EXPECT_EQ(512, len);
+    // Verify that the rendered data is correct.
+    EXPECT_TRUE(std::equal(render_data.begin() + 2, render_data.end(),
+                           data.begin()));
+
+    // Without clearing the output buffer, try to do it again. The pack should
+    // append the data to the current buffer.
+    ASSERT_NO_THROW(tuple.pack(out_buf));
+    EXPECT_EQ(1028, out_buf.getLength());
+}
+
+// This test verifies that the tuple is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack1Byte) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    const char wire_data[] = {
+        0x0B,                               // Length is 11
+        0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+        0x77, 0x6F, 0x72, 0x6C, 0x64        // world
+    };
+
+    ASSERT_NO_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)));
+    EXPECT_EQ(11, tuple.getLength());
+    EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// This test verifies that the tuple having a length of 0, is decoded from
+// the wire format.
+TEST(OpaqueDataTuple, unpack1ByteZeroLength) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    tuple = "Hello world";
+    ASSERT_NE(tuple.getLength(), 0);
+
+    const char wire_data[] = {
+        0
+    };
+    ASSERT_NO_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)));
+
+    EXPECT_EQ(0, tuple.getLength());
+}
+
+// This test verfifies that exception is thrown if the empty buffer is being
+// parsed.
+TEST(OpaqueDataTuple, unpack1ByteEmptyBuffer) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    const char wire_data[] = {
+        1, 2, 3
+    };
+    EXPECT_THROW(tuple.unpack(wire_data, wire_data), OpaqueDataTupleError);
+}
+
+// This test verifies that exception if thrown when parsing truncated buffer.
+TEST(OpaqueDataTuple, unpack1ByteTruncatedBuffer) {
+   OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+    const char wire_data[] = {
+        10, 2, 3
+    };
+    EXPECT_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)),
+                 OpaqueDataTupleError);
+}
+
+// This test verifies that the tuple is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack2Byte) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    std::vector<uint8_t> wire_data;
+    // Set tuple length to 400 (0x190).
+    wire_data.push_back(1);
+    wire_data.push_back(0x90);
+    // Fill in the buffer with some data.
+    for (int i = 0; i < 400; ++i) {
+        wire_data.push_back(i);
+    }
+    // The unpack shoud succeed.
+    ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+    // The decoded length should be 400.
+    ASSERT_EQ(400, tuple.getLength());
+    // And the data should match.
+    EXPECT_TRUE(std::equal(wire_data.begin() + 2, wire_data.end(),
+                           tuple.getData().begin()));
+}
+
+// This test verifies that the tuple having a length of 0, is decoded from
+// the wire format.
+TEST(OpaqueDataTuple, unpack2ByteZeroLength) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    // Set some data for the tuple.
+    tuple = "Hello world";
+    ASSERT_NE(tuple.getLength(), 0);
+    // The buffer holds just a length field with the value of 0.
+    const char wire_data[] = {
+        0, 0
+    };
+    // The empty tuple should be successfully decoded.
+    ASSERT_NO_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)));
+    // The data should be replaced with an empty buffer.
+    EXPECT_EQ(0, tuple.getLength());
+}
+
+// This test verifies that exception is thrown if the empty buffer is being
+// parsed.
+TEST(OpaqueDataTuple, unpack2ByteEmptyBuffer) {
+    OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+    //  Initialize the input buffer with some data, just to avoid initializing
+    // empty array.
+    const char wire_data[] = {
+        1, 2, 3
+    };
+    // Pass empty buffer (first iterator equal to second iterator).
+    // This should not be accepted.
+    EXPECT_THROW(tuple.unpack(wire_data, wire_data), OpaqueDataTupleError);
+}
+
+// This test verifies that exception if thrown when parsing truncated buffer.
+TEST(OpaqueDataTuple, unpack2ByteTruncatedBuffer) {
+   OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+   // Specify the data with the length of 10, but limit the buffer size to
+   // 2 bytes.
+   const char wire_data[] = {
+       0, 10, 2, 3
+   };
+   // This should fail because the buffer is truncated.
+   EXPECT_THROW(tuple.unpack(wire_data, wire_data + sizeof(wire_data)),
+                OpaqueDataTupleError);
+}
+
+
+} // anonymous namespace