--- /dev/null
+// 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
--- /dev/null
+// 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