Commit edd9891f authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

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

parent f665cef1
......@@ -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
......
// 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);
}
}
}
// 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
......@@ -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
......
// 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; </