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

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

parent edd9891f
......@@ -32,6 +32,7 @@ libb10_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
libb10_dhcp___la_SOURCES += option_vendor.cc option_vendor.h
libb10_dhcp___la_SOURCES += option_vendor_class.cc option_vendor_class.h
libb10_dhcp___la_SOURCES += option_int.h
libb10_dhcp___la_SOURCES += option_int_array.h
libb10_dhcp___la_SOURCES += option.cc option.h
......
......@@ -150,11 +150,21 @@ public:
/// @param other String to compare tuple data against.
bool equals(const std::string& other) const;
/// @brief Returns tuple length data field type.
LengthFieldType getLengthFieldType() const {
return (length_field_type_);
}
/// @brief Returns the length of the data in the tuple.
size_t getLength() const {
return (data_.size());
}
/// @brief Returns a total size of the tuple, including length field.
size_t getTotalLength() const {
return (getDataFieldSize() + getLength());
}
/// @brief Returns a reference to the buffer holding tuple data.
///
/// @warning The returned reference is valid only within the lifetime
......@@ -264,19 +274,22 @@ public:
bool operator!=(const std::string& other);
//@}
private:
/// @brief Returns the size of the tuple length field.
///
/// The returned value depends on the @c LengthFieldType set for the tuple.
int getDataFieldSize() const;
private:
/// @brief Buffer which holds the opaque tuple data.
Buffer data_;
/// @brief Holds a type of tuple size field (1 byte long or 2 bytes long).
LengthFieldType length_field_type_;
};
/// @brief Pointer to the @c OpaqueDataTuple object.
typedef boost::shared_ptr<OpaqueDataTuple> OpaqueDataTuplePtr;
/// @brief Inserts the @c OpaqueDataTuple as a string into stream.
///
/// This operator gets the tuple data in the textual format and inserts it
......
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <exceptions/exceptions.h>
#include <dhcp/opaque_data_tuple.h>
#include <dhcp/option_vendor_class.h>
namespace isc {
namespace dhcp {
OptionVendorClass::OptionVendorClass(Option::Universe u,
const uint32_t vendor_id)
: Option(u, getOptionCode(u)), vendor_id_(vendor_id) {
if (u == Option::V4) {
addTuple(OpaqueDataTuple(OpaqueDataTuple::LENGTH_1_BYTE));
}
}
OptionVendorClass::OptionVendorClass(Option::Universe u,
OptionBufferConstIter begin,
OptionBufferConstIter end)
: Option(u, getOptionCode(u)) {
unpack(begin, end);
}
void
OptionVendorClass::pack(isc::util::OutputBuffer& buf) {
packHeader(buf);
buf.writeUint32(getVendorId());
for (TuplesCollection::const_iterator it = tuples_.begin();
it != tuples_.end(); ++it) {
// For DHCPv4 V-I Vendor Class option, there is enterprise id before
// every tuple.
if ((getUniverse() == V4) && (it != tuples_.begin())) {
buf.writeUint32(getVendorId());
}
it->pack(buf);
}
}
void
OptionVendorClass::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
if (std::distance(begin, end) < getMinimalLength() - getHeaderLen()) {
isc_throw(OutOfRange, "parsed Vendor Class option data truncated to"
" size " << std::distance(begin, end));
}
// Option must contain at least one enterprise id. It is ok to read 4-byte
// value here because we have checked that the buffer he minimal length.
vendor_id_ = isc::util::readUint32(&(*begin), distance(begin, end));
begin += sizeof(vendor_id_);
// Start reading opaque data.
size_t offset = 0;
while (offset < std::distance(begin, end)) {
// Parse a tuple.
OpaqueDataTuple tuple(getLengthFieldType(), begin + offset, end);
addTuple(tuple);
// The tuple has been parsed correctly which implies that it is safe to
// advance the offset by its total length.
offset += tuple.getTotalLength();
// For DHCPv4 option, there is enterprise id before every opaque data
// tuple. Let's read it, unless we have already reached the end of
// buffer.
if ((getUniverse() == V4) && (begin + offset != end)) {
// Advance the offset by the size of enterprise id.
offset += sizeof(vendor_id_);
// If the offset already ran over the buffer length or there is
// no space left for the empty tuple (thus we add 1), we have
// to signal the option truncation.
if (offset + 1 >= std::distance(begin, end)) {
isc_throw(isc::OutOfRange, "truncated DHCPv4 V-I Vendor Class"
" option - it should contain enterprise id followed"
" by opaque data field tuple");
}
}
}
}
void
OptionVendorClass::addTuple(const OpaqueDataTuple& tuple) {
if (tuple.getLengthFieldType() != getLengthFieldType()) {
isc_throw(isc::BadValue, "attempted to add opaque data tuple having"
" invalid size of the length field "
<< tuple.getDataFieldSize() << " to Vendor Class option");
}
tuples_.push_back(tuple);
}
void
OptionVendorClass::setTuple(const size_t at, const OpaqueDataTuple& tuple) {
if (at >= getTuplesNum()) {
isc_throw(isc::OutOfRange, "attempted to set an opaque data for the"
" vendor option at position " << at << " which is out of"
" range");
} else if (tuple.getLengthFieldType() != getLengthFieldType()) {
isc_throw(isc::BadValue, "attempted to set opaque data tuple having"
" invalid size of the length field "
<< tuple.getDataFieldSize() << " to Vendor Class option");
}
tuples_[at] = tuple;
}
OpaqueDataTuple
OptionVendorClass::getTuple(const size_t at) const {
if (at >= getTuplesNum()) {
isc_throw(isc::OutOfRange, "attempted to get an opaque data for the"
" vendor option at position " << at << " which is out of"
" range");
}
return (tuples_[at]);
}
uint16_t
OptionVendorClass::len() {
// The option starts with the header and enterprise id.
uint16_t length = getHeaderLen() + sizeof(uint32_t);
// Now iterate over existing tuples and add their size.
for (TuplesCollection::const_iterator it = tuples_.begin();
it != tuples_.end(); ++it) {
// For DHCPv4 V-I Vendor Class option, there is enterprise id before
// every tuple.
if ((getUniverse() == V4) && (it != tuples_.begin())) {
length += sizeof(uint32_t);
}
length += it->getTotalLength();
}
return (length);
}
} // namespace isc::dhcp
} // namespace isc
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef OPTION_VENDOR_CLASS_H
#define OPTION_VENDOR_CLASS_H
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/opaque_data_tuple.h>
#include <dhcp/option.h>
#include <util/buffer.h>
#include <boost/shared_ptr.hpp>
#include <stdint.h>
namespace isc {
namespace dhcp {
/// @brief This class encapsulates DHCPv6 Vendor Class and DHCPv4 V-I Vendor
/// Class options.
///
/// The format of DHCPv6 Vendor Class option is described in section 22.16 of
/// RFC3315 and the format of the DHCPv4 V-I Vendor Class option is described
/// in section 3 of RFC3925. Each of these options carries enterprise id
/// followed by the collection of tuples carring opaque data. A single tuple
/// consists of the field holding opaque data length and the actual data.
/// In case of the DHCPv4 V-I Vendor Class each tuple is preceded by the
/// 4-byte long enterprise id. Also, the field which carries the length of
/// the tuple is 1-byte long for DHCPv4 V-I Vendor Class and 2-bytes long
/// for the DHCPv6 Vendor Class option.
///
/// Whether the encapsulated format is DHCPv4 V-I Vendor Class or DHCPv6
/// Vendor Class option is controlled by the @c u (universe) parameter passed
/// to the constructor.
///
/// @todo Currently, the enterprise id field is set to a value of the first
/// enterprise id occurrence in the parsed option. At some point we should
/// be able to differentiate between enterprise ids.
class OptionVendorClass : public Option {
public:
/// @brief Collection of opaque data tuples carried by the option.
typedef std::vector<OpaqueDataTuple> TuplesCollection;
/// @brief Constructor.
///
/// @param u universe (v4 or v6).
/// @param vendor_id vendor enterprise id (unique 32-bit integer).
OptionVendorClass(Option::Universe u, const uint32_t vendor_id);
/// @brief Constructor.
///
/// This constructor creates an instance of the option using a buffer with
/// on-wire data. It may throw an exception if the @c unpack method throws.
///
/// @param u universe (v4 or v6)
/// @param begin Iterator pointing to the beginning of the buffer holding an
/// option.
/// @param end Iterator pointing to the end of the buffer holding an option.
OptionVendorClass(Option::Universe u, OptionBufferConstIter begin,
OptionBufferConstIter end);
/// @brief Renders option into the buffer in the wire format.
///
/// @param [out] buf Buffer to which the option is rendered.
virtual void pack(isc::util::OutputBuffer& buf);
/// @brief Parses buffer holding an option.
///
/// This function parses the buffer holding an option and initializes option
/// properties: enterprise ids and the collection of tuples.
///
/// @param begin Iterator pointing to the beginning of the buffer holding an
/// option.
/// @param end Iterator pointing to the end of the buffer holding an option.
virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
/// @brief Returns enterprise id.
uint32_t getVendorId() const {
return (vendor_id_);
}
/// @brief Adds a new opaque data tuple to the option.
///
/// @param tuple Tuple to be added.
/// @throw isc::BadValue if the type of the tuple doesn't match the
/// universe this option belongs to.
void addTuple(const OpaqueDataTuple& tuple);
/// @brief Replaces tuple at the specified index with a new tuple.
///
/// This function replaces an opaque data tuple at the specified position
/// with the new tuple. If the specified index is out of range an exception
/// is thrown.
///
/// @param at Index at which the tuple should be replaced.
/// @param tuple Tuple to be set.
/// @throw isc::OutOfRange if the tuple position is out of range.
/// @throw isc::BadValue if the type of the tuple doesn't match the
/// universe this option belongs to.
void setTuple(const size_t at, const OpaqueDataTuple& tuple);
/// @brief Returns opaque data tuple at the specified position.
///
/// If the specified position is out of range an exception is thrown.
///
/// @param at Index at which the tuple should be replaced.
/// @throw isc::OutOfRange if the tuple position is out of range.
OpaqueDataTuple getTuple(const size_t at) const;
/// @brief Returns the number of opaque data tuples added to the option.
size_t getTuplesNum() const {
return (tuples_.size());
}
/// @brief Returns collection of opaque data tuples carried in the option.
const TuplesCollection& getTuples() const {
return (tuples_);
}
/// @brief Returns the full length of the option, including option header.
virtual uint16_t len();
private:
/// @brief Returns option code appropriate for the specified universe.
///
/// This function is called by the constructor to map the specified
/// universe to the option code.
///
/// @param u universe (V4 or V6).
/// @return DHCPv4 V-I Vendor Class or DHCPv6 Vendor Class option code.
static uint16_t getOptionCode(Option::Universe u) {
return (u == V4 ? DHO_VIVCO_SUBOPTIONS : D6O_VENDOR_CLASS);
}
/// @brief Returns the tuple length field type for the given universe.
///
/// This function returns the length field type which should be used
/// for the opaque data tuples being added to this option.
///
/// @return Tuple length field type for the universe this option belongs to.
OpaqueDataTuple::LengthFieldType getLengthFieldType() const {
return (getUniverse() == V4 ? OpaqueDataTuple::LENGTH_1_BYTE :
OpaqueDataTuple::LENGTH_2_BYTES);
}
/// @brief Returns minimal length of the option for the given universe.
uint16_t getMinimalLength() const {
return (getUniverse() == Option::V4 ? 7 : 6);
}
/// @brief Enterprise ID.
uint32_t vendor_id_;
/// @brief Collection of opaque data tuples carried by the option.
TuplesCollection tuples_;
};
/// @brief Defines a pointer to the @c OptionVendorClass.
typedef boost::shared_ptr<OptionVendorClass> OptionVendorClassPtr;
}
}
#endif // OPTION_VENDOR_CLASS_H
......@@ -65,6 +65,7 @@ libdhcp___unittests_SOURCES += option_unittest.cc
libdhcp___unittests_SOURCES += option_space_unittest.cc
libdhcp___unittests_SOURCES += option_string_unittest.cc
libdhcp___unittests_SOURCES += option_vendor_unittest.cc
libdhcp___unittests_SOURCES += option_vendor_class_unittest.cc
libdhcp___unittests_SOURCES += pkt4_unittest.cc
libdhcp___unittests_SOURCES += pkt6_unittest.cc
libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
......
// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <exceptions/exceptions.h>
#include <dhcp/option_vendor_class.h>
#include <util/buffer.h>
#include <gtest/gtest.h>
using namespace isc;
using namespace isc::dhcp;
using namespace isc::util;
namespace {
// This test checks that the DHCPv4 option constructor sets the default
// properties to the expected values. This constructor should add an
// empty opaque data tuple (it is essentially the same as adding a 1-byte
// long field which carries a value of 0).
TEST(OptionVendorClass, constructor4) {
OptionVendorClass vendor_class(Option::V4, 1234);
EXPECT_EQ(1234, vendor_class.getVendorId());
// Option length is 1 byte for header + 1 byte for option size +
// 4 bytes of enterprise id + 1 byte for opaque data.
EXPECT_EQ(7, vendor_class.len());
// There should be one empty tuple.
ASSERT_EQ(1, vendor_class.getTuplesNum());
EXPECT_EQ(0, vendor_class.getTuple(0).getLength());
}
// This test checks that the DHCPv6 option constructor sets the default
// properties to the expected values.
TEST(OptionVendorClass, constructor6) {
OptionVendorClass vendor_class(Option::V6, 2345);
EXPECT_EQ(2345, vendor_class.getVendorId());
// Option length is 2 bytes for option code + 2 bytes for option size +
// 4 bytes of enterprise id.
EXPECT_EQ(8, vendor_class.len());
// There should be no tuples.
EXPECT_EQ(0, vendor_class.getTuplesNum());
}
// This test verifies that it is possible to append the opaque data tuple
// to the option and then retrieve it.
TEST(OptionVendorClass, addTuple) {
OptionVendorClass vendor_class(Option::V6, 2345);
// Initially there should be no tuples (for DHCPv6).
ASSERT_EQ(0, vendor_class.getTuplesNum());
// Create a new tuple and add it to the option.
OpaqueDataTuple tuple;
tuple = "xyz";
vendor_class.addTuple(tuple);
// The option should now hold one tuple.
ASSERT_EQ(1, vendor_class.getTuplesNum());
EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
// Add another tuple.
tuple = "abc";
vendor_class.addTuple(tuple);
// The option should now hold exactly two tuples in the order in which
// they were added.
ASSERT_EQ(2, vendor_class.getTuplesNum());
EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
EXPECT_EQ("abc", vendor_class.getTuple(1).getText());
// Attempt to add the tuple with 1 byte long length field should fail
// for DHCPv6 option.
OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_1_BYTE);
EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue);
}
// This test checks that it is possible to replace existing tuple.
TEST(OptionVendorClass, setTuple) {
OptionVendorClass vendor_class(Option::V4, 1234);
// The DHCPv4 option should carry one empty tuple.
ASSERT_EQ(1, vendor_class.getTuplesNum());
ASSERT_TRUE(vendor_class.getTuple(0).getText().empty());
// Replace the empty tuple with non-empty one.
OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
tuple = "xyz";
ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
// There should be one tuple with updated data.
ASSERT_EQ(1, vendor_class.getTuplesNum());
EXPECT_EQ("xyz", vendor_class.getTuple(0).getText());
// Add another one.
tuple = "abc";
vendor_class.addTuple(tuple);
ASSERT_EQ(2, vendor_class.getTuplesNum());
ASSERT_EQ("abc", vendor_class.getTuple(1).getText());
// Try to replace them with new tuples.
tuple = "new_xyz";
ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
ASSERT_EQ(2, vendor_class.getTuplesNum());
EXPECT_EQ("new_xyz", vendor_class.getTuple(0).getText());
tuple = "new_abc";
ASSERT_NO_THROW(vendor_class.setTuple(1, tuple));
ASSERT_EQ(2, vendor_class.getTuplesNum());
EXPECT_EQ("new_abc", vendor_class.getTuple(1).getText());
// For out of range position, exception should be thrown.
tuple = "foo";
EXPECT_THROW(vendor_class.setTuple(2, tuple), isc::OutOfRange);
// Attempt to add the tuple with 2 byte long length field should fail
// for DHCPv4 option.
OpaqueDataTuple tuple2(OpaqueDataTuple::LENGTH_2_BYTES);
EXPECT_THROW(vendor_class.addTuple(tuple2), isc::BadValue);
}
// Check that the returned length of the DHCPv4 option is correct.
TEST(OptionVendorClass, len4) {
OptionVendorClass vendor_class(Option::V4, 1234);
ASSERT_EQ(7, vendor_class.len());
// Replace the default empty tuple.
OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
tuple = "xyz";
ASSERT_NO_THROW(vendor_class.setTuple(0, tuple));
// The total length should get increased by the size of 'xyz'.
EXPECT_EQ(10, vendor_class.len());
// Add another tuple.
tuple = "abc";
vendor_class.addTuple(tuple);
// The total size now grows by the additional enterprise id and the
// 1 byte of the tuple length field and 3 bytes of 'abc'.
EXPECT_EQ(18, vendor_class.len());
}
// Check that the returned length of the DHCPv6 option is correct.
TEST(OptionVendorClass, len6) {
OptionVendorClass vendor_class(Option::V6, 1234);
ASSERT_EQ(8, vendor_class.len());
// Add first tuple.
OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
tuple = "xyz";
ASSERT_NO_THROW(vendor_class.addTuple(tuple));
// The total length grows by 2 bytes of the length field and 3 bytes
// consumed by 'xyz'.
EXPECT_EQ(13, vendor_class.len());
// Add another tuple and check that the total size gets increased.
tuple = "abc";
vendor_class.addTuple(tuple);
EXPECT_EQ(18, vendor_class.len());
}
// Check that the option is rendered to the buffer in wire format.
TEST(OptionVendorClass, pack4) {
OptionVendorClass vendor_class(Option::V4, 1234);
ASSERT_EQ(1, vendor_class.getTuplesNum());
// By default, there is an empty tuple in the option. Let's replace
// it with the tuple with some data.
OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
tuple = "Hello world";
vendor_class.setTuple(0, tuple);
// And add another tuple so as resulting option is a bit more complex.
tuple = "foo";
vendor_class.addTuple(tuple);
// Render the data to the buffer.
OutputBuffer buf(10);
ASSERT_NO_THROW(vendor_class.pack(buf));
ASSERT_EQ(26, buf.getLength());
// Prepare reference data.
const uint8_t ref[] = {
0x7C, 0x18, // option 124, length 24
0, 0, 0x4, 0xD2, // enterprise id 1234
0x0B, // tuple length is 11
0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
0x77, 0x6F, 0x72, 0x6C, 0x64, // world
0, 0, 0x4, 0xD2, // enterprise id 1234
3, // tuple length is 3
0x66, 0x6F, 0x6F // foo
};
// Compare the buffer with reference data.
EXPECT_EQ(0, memcmp(static_cast<const void*>(ref),
static_cast<const void*>(buf.getData()), 26));
}
// Check that the DHCPv6 option is rendered to the buffer in wire format.
TEST(OptionVendorClass, pack6) {
OptionVendorClass vendor_class(Option::V6, 1234);
ASSERT_EQ(0, vendor_class.getTuplesNum());
// Add tuple.
OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
tuple = "Hello world";
vendor_class.addTuple(tuple);
// And add another tuple so as resulting option is a bit more complex.
tuple = "foo";
vendor_class.addTuple(tuple);
// Render the data to the buffer.
OutputBuffer buf(10);
ASSERT_NO_THROW(vendor_class.pack(buf));
ASSERT_EQ(26, buf.getLength());
// Prepare reference data.
const uint8_t ref[] = {
0x00, 0x10, 0x00, 0x16, // option 16, length 22
0x00, 0x00, 0x04, 0xD2, // enterprise id 1234
0x00, 0x0B, // tuple length is 11