Browse Source

[2312] Created OptionCustom class.

Marcin Siodelski 12 years ago
parent
commit
08d0d10dd7

+ 1 - 0
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

+ 179 - 0
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 <dhcp/libdhcp++.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_custom.h>
+
+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<unsigned short>(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

+ 201 - 0
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 <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <util/io_utilities.h>
+
+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<typename T>
+    T readInteger(const uint32_t index) const {
+        checkIndex(index);
+
+        if (!OptionDataTypeTraits<T>::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<T>::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<T>::len);
+        T value;
+        switch (OptionDataTypeTraits<T>::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<OptionBuffer> buffers_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_CUSTOM_H

+ 1 - 0
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

+ 108 - 0
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 <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/option_custom.h>
+#include <gtest/gtest.h>
+
+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<uint8_t>& 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<typename T>
+    void writeInt(T value, std::vector<uint8_t>& 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<uint8_t>& 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<uint16_t>(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<uint16_t>(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