From 08d0d10dd79cfa9b4f5b4d42bbfef15846d77d68 Mon Sep 17 00:00:00 2001 From: Marcin Siodelski Date: Mon, 26 Nov 2012 12:09:17 +0100 Subject: [PATCH] [2312] Created OptionCustom class. --- src/lib/dhcp/Makefile.am | 1 + src/lib/dhcp/option_custom.cc | 179 +++++++++++++++++ src/lib/dhcp/option_custom.h | 201 +++++++++++++++++++ src/lib/dhcp/tests/Makefile.am | 1 + src/lib/dhcp/tests/option_custom_unittest.cc | 108 ++++++++++ 5 files changed, 490 insertions(+) create mode 100644 src/lib/dhcp/option_custom.cc create mode 100644 src/lib/dhcp/option_custom.h create mode 100644 src/lib/dhcp/tests/option_custom_unittest.cc diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am index 1c744467f8..732fcc5e1f 100644 --- a/src/lib/dhcp/Makefile.am +++ b/src/lib/dhcp/Makefile.am @@ -23,6 +23,7 @@ libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h libb10_dhcp___la_SOURCES += option.cc option.h libb10_dhcp___la_SOURCES += option_data_types.h libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h +libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc new file mode 100644 index 0000000000..205952211c --- /dev/null +++ b/src/lib/dhcp/option_custom.cc @@ -0,0 +1,179 @@ +// Copyright (C) 2012 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 { + +OptionCustom::OptionCustom(const OptionDefinition& def, + Universe u, + const OptionBuffer& data) + : Option(u, def.getCode(), data.begin(), data.end()), + definition_(def), + init_passed_(true) { + try { + createBuffers(); + } catch (const Exception& ex) { + init_passed_ = false; + } +} + +OptionCustom::OptionCustom(const OptionDefinition& def, + Universe u, + OptionBufferConstIter first, + OptionBufferConstIter last) + : Option(u, def.getCode(), first, last), + definition_(def), + init_passed_(true) { + try { + createBuffers(); + } catch (const Exception& ex) { + init_passed_ = false; + } +} + +void +OptionCustom::checkIndex(const uint32_t index) const { + if (index >= buffers_.size()) { + isc_throw(isc::OutOfRange, "specified data field index " << index + << " is out of rangex."); + } +} + +void +OptionCustom::createBuffers() { + // Check that the option definition is correct as we are going + // to use it to split the data_ buffer into set of sub buffers. + definition_.validate(); + + // @todo create buffers here +} + +void +OptionCustom::pack4(isc::util::OutputBuffer& buf) { + if (len() > 255) { + isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big." + << " At most 255 bytes are supported."); + } + + buf.writeUint8(type_); + buf.writeUint8(len() - getHeaderLen()); + + // @todo write option data here + + LibDHCP::packOptions(buf, options_); + return; +} + +void +OptionCustom::pack6(isc::util::OutputBuffer& buf) { + buf.writeUint16(type_); + buf.writeUint16(len() - getHeaderLen()); + + // @todo write option data here. + + LibDHCP::packOptions(buf, options_); + return; +} + +void +OptionCustom::readAddress(const uint32_t index, asiolink::IOAddress&) const { + checkIndex(index); +} + +bool +OptionCustom::readBoolean(const uint32_t index) const { + checkIndex(index); + return (true); +} + +void +OptionCustom::readString(const uint32_t index, std::string&) const { + checkIndex(index); +} + +void +OptionCustom::unpack(OptionBufferConstIter begin, + OptionBufferConstIter end) { + data_ = OptionBuffer(begin, end); +} + +uint16_t +OptionCustom::len() { + // Returns length of the complete option (data length + DHCPv4/DHCPv6 + // option header) + + // length of the whole option is header and data stored in this option... + int length = getHeaderLen() + data_.size(); + + // ... and sum of lengths of all suboptions + for (OptionCustom::OptionCollection::iterator it = options_.begin(); + it != options_.end(); + ++it) { + length += (*it).second->len(); + } + + // note that this is not equal to lenght field. This value denotes + // number of bytes required to store this option. length option should + // contain (len()-getHeaderLen()) value. + return (length); +} + +bool +OptionCustom::valid() { + if ((universe_ != V4 && universe_ != V6) || + !init_passed_) { + return (false); + } + + return (true); +} + +std::string OptionCustom::toText(int /* =0 */ ) { + std::stringstream tmp; + + /* for (int i = 0; i < indent; i++) + tmp << " "; + + tmp << "type=" << type_ << ", len=" << len()-getHeaderLen() << ": "; + + for (unsigned int i = 0; i < data_.size(); i++) { + if (i) { + tmp << ":"; + } + tmp << setfill('0') << setw(2) << hex + << static_cast(data_[i]); + } + + // print suboptions + for (OptionCollection::const_iterator opt = options_.begin(); + opt != options_.end(); + ++opt) { + tmp << (*opt).second->toText(indent+2); + } */ + return tmp.str(); +} + +void OptionCustom::setData(const OptionBufferConstIter first, + const OptionBufferConstIter last) { + // We will copy entire option buffer, so we have to resize data_. + data_.resize(std::distance(first, last)); + std::copy(first, last, data_.begin()); +} + +} // end of isc::dhcp namespace +} // end of isc namespace diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h new file mode 100644 index 0000000000..4f8e153a13 --- /dev/null +++ b/src/lib/dhcp/option_custom.h @@ -0,0 +1,201 @@ +// Copyright (C) 2012 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_CUSTOM_H +#define OPTION_CUSTOM_H + +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Option with defined data fields represented as buffers that can +/// be accessed using data field index. +/// +/// This class represents an option which has defined structure: data fields +/// of specific types and order. Those fields can be accessed using indexes, +/// where index 0 represents first data field within an option. The last +/// field can be accessed using index equal to 'number of fields' - 1. +/// Internally, the option data is stored as a collection of OptionBuffer +/// objects, each representing data for a particular data field. This data +/// can be converted to the actual data type using methods implemented +/// within this class. This class is used to represent those options that +/// can't be represented by any other specialized class (this excludes the +/// Option class which is generic and can be used to represent any option). +class OptionCustom : public Option { +public: + + class OptionFieldBuffer { + public: + OptionFieldBuffer(OptionDataType type, + const OptionBuffer& buf) + : type_(type), buf_(buf) { + } + + const OptionBuffer& getBuffer() const { + return (buf_); + } + + OptionDataType getType() const { + return (type_); + } + + private: + OptionDataType type_; + OptionBuffer buf_; + }; + + /// @brief Constructor, used for options to be sent. + /// + /// @param u specifies universe (V4 or V6). + /// @param def option definition. + /// @param data content of the option. + OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data); + + /// @brief Constructor, used for received options. + /// + /// @param u specifies universe (V4 or V6). + /// @param def option definition. + /// @param first iterator to the first element that should be copied. + /// @param last iterator to the next element after the last one + /// to be copied. + OptionCustom(const OptionDefinition& def, Universe u, + OptionBufferConstIter first, OptionBufferConstIter last); + + void readAddress(const uint32_t index, asiolink::IOAddress& address) const; + + bool readBoolean(const uint32_t index) const; + + template + T readInteger(const uint32_t index) const { + checkIndex(index); + + if (!OptionDataTypeTraits::integer_type) { + isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned" + " by readInteger is not supported integer type"); + } + + OptionDataType data_type = definition_.getType(); + if (data_type == OPT_RECORD_TYPE) { + const OptionDefinition::RecordFieldsCollection& record_fields = + definition_.getRecordFields(); + assert(index < record_fields.size()); + data_type = record_fields[index]; + } + + if (OptionDataTypeTraits::type != data_type) { + isc_throw(isc::dhcp::InvalidDataType, + "unable to read option field with index " << index + << " as integer value. The field's data type" + << data_type << " does not match the integer type" + << "returned by the readInteger function."); + } + assert(buffers_[index].size() == OptionDataTypeTraits::len); + T value; + switch (OptionDataTypeTraits::len) { + case 1: + value = *(buffers_[index].begin()); + break; + case 2: + value = isc::util::readUint16(&(*buffers_[index].begin())); + break; + case 4: + value = isc::util::readUint32(&(*buffers_[index].begin())); + break; + default: + // This should not happen because we made checks on data types + // but it does not hurt to keep throw statement here. + isc_throw(isc::dhcp::InvalidDataType, + "invalid size of the data type to be read as integer."); + } + return (value); + } + + void readString(const uint32_t index, std::string& value) const; + + /// @brief Parses received buffer. + /// + /// @param begin iterator to first byte of option data + /// @param end iterator to end of option data (first byte after option end) + virtual void unpack(OptionBufferConstIter begin, + OptionBufferConstIter end); + + /// Returns string representation of the option. + /// + /// @param indent number of spaces before printed text. + /// + /// @return string with text representation. + virtual std::string toText(int indent = 0); + + /// Returns length of the complete option (data length + DHCPv4/DHCPv6 + /// option header) + /// + /// @return length of the option + virtual uint16_t len(); + + /// Check if option is valid. + /// + /// @return true, if option is valid. + virtual bool valid(); + + /// Returns pointer to actual data. + /// + /// @return pointer to actual data (or reference to an empty vector + /// if there is no data). + virtual const OptionBuffer& getData() { return (data_); } + + /// @brief Sets content of this option from buffer. + /// + /// Option will be resized to length of buffer. + /// + /// @param first iterator pointing begining of buffer to copy. + /// @param last iterator pointing to end of buffer to copy. + void setData(const OptionBufferConstIter first, + const OptionBufferConstIter last); + +protected: + + /// @brief Writes DHCPv4 option in a wire format to a buffer. + /// + /// @param buf output buffer (option will be stored there). + virtual void pack4(isc::util::OutputBuffer& buf); + + /// @brief Writes DHCPv6 option in a wire format to a buffer. + /// + /// @param buf output buffer (built options will be stored here) + virtual void pack6(isc::util::OutputBuffer& buf); + +private: + + /// @brief Check if data field index is valid. + /// + /// @throw isc::OutOfRange if index is out of range. + void checkIndex(const uint32_t index) const; + + /// @brief Create collection of buffers representing data field values. + void createBuffers(); + + OptionDefinition definition_; + + bool init_passed_; + + std::vector buffers_; +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // OPTION_CUSTOM_H diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am index 945f822428..e66d700903 100644 --- a/src/lib/dhcp/tests/Makefile.am +++ b/src/lib/dhcp/tests/Makefile.am @@ -36,6 +36,7 @@ libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc libdhcp___unittests_SOURCES += option6_int_array_unittest.cc libdhcp___unittests_SOURCES += option6_int_unittest.cc libdhcp___unittests_SOURCES += option_definition_unittest.cc +libdhcp___unittests_SOURCES += option_custom_unittest.cc libdhcp___unittests_SOURCES += option_unittest.cc libdhcp___unittests_SOURCES += pkt4_unittest.cc libdhcp___unittests_SOURCES += pkt6_unittest.cc diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc new file mode 100644 index 0000000000..a9230bd0de --- /dev/null +++ b/src/lib/dhcp/tests/option_custom_unittest.cc @@ -0,0 +1,108 @@ +// Copyright (C) 2012 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 + +using namespace isc; +using namespace isc::asiolink; +using namespace isc::dhcp; + +namespace { + +/// @brief OptionCustomTest test class. +class OptionCustomTest : public ::testing::Test { +public: + /// @brief Constructor. + OptionCustomTest() { + for (int i = 0; i < 255; ++i) { + buf_.push_back(i); + } + } + + void writeAddress(const asiolink::IOAddress& address, + std::vector& buf) { + short family = address.getFamily(); + if (family == AF_INET) { + asio::ip::address_v4::bytes_type buf_addr = + address.getAddress().to_v4().to_bytes(); + buf.insert(buf.end(), buf_addr.begin(), buf_addr.end()); + } else if (family == AF_INET6) { + asio::ip::address_v6::bytes_type buf_addr = + address.getAddress().to_v6().to_bytes(); + buf.insert(buf.end(), buf_addr.begin(), buf_addr.end()); + } + } + + template + void writeInt(T value, std::vector& buf) { + for (int i = 0; i < sizeof(T); ++i) { + buf.push_back(value >> ((3 - i) * 8) & 0xFF); + } + } + + void writeString(const std::string& value, + std::vector& buf) { + buf.resize(buf.size() + value.size()); + std::copy_backward(value.c_str(), value.c_str() + value.size(), + buf.end()); + } + + OptionBuffer buf_; +}; + +TEST_F(OptionCustomTest, constructor) { + /* OptionDefinition opt_def1("OPTION_FOO", 1000, "string", true); + ASSERT_THROW(opt_def1.validate(), isc::Exception); */ +} + +TEST_F(OptionCustomTest, setData) { + OptionDefinition opt_def("OPTION_FOO", 1000, "record"); + ASSERT_NO_THROW(opt_def.addRecordField("uint16")); + ASSERT_NO_THROW(opt_def.addRecordField("boolean")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address")); + ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address")); + ASSERT_NO_THROW(opt_def.addRecordField("string")); + + OptionBuffer buf; + writeInt(8712, buf); + buf.push_back(1); + writeAddress(IOAddress("192.168.0.1"), buf); + writeAddress(IOAddress("2001:db8:1::1"), buf); + writeString("ABCD", buf); + + OptionCustom option(opt_def, Option::V6, buf_.begin(), buf_.begin() + 30); + ASSERT_TRUE(option.valid()); + + uint16_t value0 = 0; + ASSERT_NO_THROW(value0 = option.readInteger(0)); + EXPECT_EQ(0x0001, value0); + bool value1 = false; + ASSERT_NO_THROW(value1 = option.readBoolean(1)); + EXPECT_TRUE(value1); + IOAddress value2("127.0.0.1"); + ASSERT_NO_THROW(option.readAddress(2, value2)); + EXPECT_EQ("192.168.0.1", value2.toText()); + IOAddress value3("::1"); + ASSERT_NO_THROW(option.readAddress(3, value3)); + EXPECT_EQ("2001:db8:1::1", value3.toText()); + std::string value4; + ASSERT_NO_THROW(option.readString(4, value4)); + EXPECT_EQ("ABCD", value4); +} + +} // anonymous namespace