From: Marcin Siodelski Date: Tue, 11 Feb 2014 18:36:42 +0000 (+0100) Subject: [3316] Added an object encapsulating opaque-data carried in Vendor Class. X-Git-Tag: bind10-1.2.0beta1-release~35^2~11 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=edd9891f58fe8f665a30f50a3bc284a9338eb024;p=thirdparty%2Fkea.git [3316] Added an object encapsulating opaque-data carried in Vendor Class. --- diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index 640bac1b88..189b672e90 100644 --- a/src/lib/dhcp/Makefile.am +++ b/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 diff --git a/src/lib/dhcp/opaque_data_tuple.cc b/src/lib/dhcp/opaque_data_tuple.cc new file mode 100644 index 0000000000..42c04eb44a --- /dev/null +++ b/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 + +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(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); +} + +} +} diff --git a/src/lib/dhcp/opaque_data_tuple.h b/src/lib/dhcp/opaque_data_tuple.h new file mode 100644 index 0000000000..c914b0afcc --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +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 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 + 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 + 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 + 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 + 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 diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am index d41246a51f..46387a7f4b 100644 --- a/src/lib/dhcp/tests/Makefile.am +++ b/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 diff --git a/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc new file mode 100644 index 0000000000..8ed915ac61 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +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 + 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 + 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 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 + 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 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 + render_data(static_cast(out_buf.getData()), + static_cast(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(out_buf.getData()), + static_cast(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 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 + render_data(static_cast(out_buf.getData()), + static_cast(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 + 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 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