From: Marcin Siodelski Date: Thu, 13 Feb 2014 09:41:26 +0000 (+0100) Subject: [3316] Implemented a class which encapsulates Vendor Class option. X-Git-Tag: bind10-1.2.0beta1-release~35^2~10 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ce6bedc169294934b93edb9d646e7c5e2edf12a2;p=thirdparty%2Fkea.git [3316] Implemented a class which encapsulates Vendor Class option. --- diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index 189b672e90..22904414f6 100644 --- a/src/lib/dhcp/Makefile.am +++ b/src/lib/dhcp/Makefile.am @@ -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 diff --git a/src/lib/dhcp/opaque_data_tuple.h b/src/lib/dhcp/opaque_data_tuple.h index c914b0afcc..ed1b25c659 100644 --- a/src/lib/dhcp/opaque_data_tuple.h +++ b/src/lib/dhcp/opaque_data_tuple.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 OpaqueDataTuplePtr; + /// @brief Inserts the @c OpaqueDataTuple as a string into stream. /// /// This operator gets the tuple data in the textual format and inserts it diff --git a/src/lib/dhcp/option_vendor_class.cc b/src/lib/dhcp/option_vendor_class.cc new file mode 100644 index 0000000000..6f05d52121 --- /dev/null +++ b/src/lib/dhcp/option_vendor_class.cc @@ -0,0 +1,153 @@ +// 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 + +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 diff --git a/src/lib/dhcp/option_vendor_class.h b/src/lib/dhcp/option_vendor_class.h new file mode 100644 index 0000000000..35daff31a2 --- /dev/null +++ b/src/lib/dhcp/option_vendor_class.h @@ -0,0 +1,177 @@ +// 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 +#include +#include +#include +#include +#include +#include + +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 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 OptionVendorClassPtr; + +} +} + +#endif // OPTION_VENDOR_CLASS_H diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am index 46387a7f4b..34bcb6895e 100644 --- a/src/lib/dhcp/tests/Makefile.am +++ b/src/lib/dhcp/tests/Makefile.am @@ -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 diff --git a/src/lib/dhcp/tests/option_vendor_class_unittest.cc b/src/lib/dhcp/tests/option_vendor_class_unittest.cc new file mode 100644 index 0000000000..4028710356 --- /dev/null +++ b/src/lib/dhcp/tests/option_vendor_class_unittest.cc @@ -0,0 +1,393 @@ +// 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 + +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 + 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(ref), + static_cast(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 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + // Compare the buffer with reference data. + EXPECT_EQ(0, memcmp(static_cast(ref), + static_cast(buf.getData()), + buf.getLength())); +} + +// This function checks that the DHCPv4 option with two opaque data tuples +// is parsed correctly. +TEST(OptionVendorClass, unpack4) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 3, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); +} + +// This function checks that the DHCPv4 option with two opaque data tuples +// is parsed correctly. +TEST(OptionVendorClass, unpack6) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0x00, 0x03, // tuple length is 3 + 0x66, 0x6F, 0x6F // foo + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(2, vendor_class->getTuplesNum()); + EXPECT_EQ("Hello world", vendor_class->getTuple(0).getText()); + EXPECT_EQ("foo", vendor_class->getTuple(1).getText()); +} + + +// This test checks that the DHCPv4 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionVendorClass, unpack4EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, // tuple length is 0 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V4, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(DHO_VIVCO_SUBOPTIONS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(1, vendor_class->getTuplesNum()); + EXPECT_TRUE(vendor_class->getTuple(0).getText().empty()); +} + +// This test checks that the DHCPv6 option with opaque data of size 0 +// is correctly parsed. +TEST(OptionVendorClass, unpack6EmptyTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x00 // tuple length is 0 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + ASSERT_EQ(1, vendor_class->getTuplesNum()); + EXPECT_TRUE(vendor_class->getTuple(0).getText().empty()); +} + +// This test checks that exception is thrown when parsing truncated DHCPv4 +// V-I Vendor Class option. +TEST(OptionVendorClass, unpack4Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello + 0x77, 0x6F, 0x72, 0x6C, 0x64, // world + 0, 0, 0x4, 0xD2, // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()), + isc::OutOfRange); +} + +// This test checks that exception is thrown when parsing truncated DHCPv6 +// Vendor Class option. +TEST(OptionVendorClass, unpack6Truncated) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2, // enterprise id 1234 + 0x00, 0x0B, // tuple length is 11 + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello + 0x77, 0x6F, 0x72, 0x6C // worl (truncated d!) + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + EXPECT_THROW(OptionVendorClass (Option::V6, buf.begin(), buf.end()), + isc::dhcp::OpaqueDataTupleError); +} + +// This test checks that exception is thrown when parsing DHCPv4 V-I Vendor +// Class option which doesn't have opaque data length. +TEST(OptionVendorClass, unpack4NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2 // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + ASSERT_THROW(OptionVendorClass (Option::V4, buf.begin(), buf.end()), + isc::OutOfRange); +} + +// This test checks that the DHCPv6 Vendor Class option containing no opaque +// data is parsed correctly. +TEST(OptionVendorClass, unpack6NoTuple) { + // Prepare data to decode. + const uint8_t buf_data[] = { + 0, 0, 0x4, 0xD2 // enterprise id 1234 + }; + OptionBuffer buf(buf_data, buf_data + sizeof(buf_data)); + + OptionVendorClassPtr vendor_class; + ASSERT_NO_THROW( + vendor_class = OptionVendorClassPtr(new OptionVendorClass(Option::V6, + buf.begin(), + buf.end())); + ); + EXPECT_EQ(D6O_VENDOR_CLASS, vendor_class->getType()); + EXPECT_EQ(1234, vendor_class->getVendorId()); + EXPECT_EQ(0, vendor_class->getTuplesNum()); +} + +} // end of anonymous namespace +