Browse Source

[1955] Added basic unit tests and DHCP v4 packet handler.

Marcin Siodelski 13 years ago
parent
commit
5c430ad5ed

+ 2 - 0
tests/tools/perfdhcp/Makefile.am

@@ -28,6 +28,8 @@ perfdhcp_SOURCES  = perfdhcp.cc
 perfdhcp_SOURCES += localized_option.h
 perfdhcp_SOURCES += command_options.cc command_options.h
 perfdhcp_SOURCES += perf_pkt6.cc perf_pkt6.h
+perfdhcp_SOURCES += perf_pkt4.cc perf_pkt4.h
+perfdhcp_SOURCES += pkt_transform.cc pkt_transform.h
 
 perfdhcp_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 perfdhcp_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la

+ 61 - 0
tests/tools/perfdhcp/perf_pkt4.cc

@@ -0,0 +1,61 @@
+// Copyright (C) 2011  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 <iostream>
+#include <exceptions/exceptions.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/dhcp6.h>
+
+#include "perf_pkt4.h"
+#include "pkt_transform.h"
+
+using namespace std;
+using namespace isc;
+using namespace dhcp;
+
+namespace isc {
+namespace perfdhcp {
+
+    PerfPkt4::PerfPkt4(const uint8_t* buf, uint32_t len, size_t transid_offset, uint32_t transid) :
+    Pkt4(buf, len),
+    transid_offset_(transid_offset) {
+    transid_ = transid;
+}
+
+PerfPkt4::PerfPkt4(const uint8_t* buf, uint32_t len, size_t transid_offset) :
+    Pkt4(buf, len),
+    transid_offset_(transid_offset) {
+}
+
+bool
+PerfPkt4::rawPack() {
+    return (PktTransform::pack(dhcp::Option::V4, 
+                               data_, 
+                               options_, 
+                               transid_offset_,
+                               transid_,
+                               bufferOut_));
+}
+
+bool
+PerfPkt4::rawUnpack() {
+    return (PktTransform::unpack(dhcp::Option::V4, 
+                                 data_, 
+                                 options_, 
+                                 transid_offset_,
+                                 transid_));
+}
+
+} // namespace perfdhcp
+} // namespace isc

+ 149 - 0
tests/tools/perfdhcp/perf_pkt4.h

@@ -0,0 +1,149 @@
+// Copyright (C) 2011  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 __PERF_PKT4_H
+#define __PERF_PKT4_H
+
+#include <time.h>
+#include <boost/shared_ptr.hpp>
+#include <dhcp/pkt4.h>
+
+#include "localized_option.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief PerfPkt4 (DHCPv4 packet)
+///
+/// This class extends functionality of \ref isc::dhcp::Pkt4 by
+/// adding ability to specify options offset in DHCP message
+/// and override options' contents with new option.
+/// This approach is useful when we create paket object from
+/// raw template buffer from file and we want to use it as
+/// a base to create test packets to be sent to DHCP server.
+///
+/// Some of the contents of such a template packets always
+/// have to be replaced e.g. transaction id, IA_NA. Other
+/// contents (options) may be changed e.g. elapsed time,
+/// server id.
+///
+/// In order to create packet from raw template buffer
+/// we have to pass this buffer along with transaction id
+/// offset. Class will read transaction id from the buffer.
+/// Next, in order to replace contents of selected options
+/// in a template packet, we need to add these selected options
+/// to packet object using addOption() method. Please note
+/// that options must be of the
+/// \ref isc::perfdhcp::LocalizedOption type.
+///
+/// \note: if you don't use template files simply use constructors
+/// inherited from parent class and isc::dhcp::Option type instead
+///
+class PerfPkt4 : public dhcp::Pkt4 {
+public:
+
+    /// Localized option pointer type.
+    typedef boost::shared_ptr<LocalizedOption> LocalizedOptionPtr;
+
+    /// \brief Constructor, used for outgoing DHCP messages.
+    ///
+    /// Creates new DHCPv4 message using provided buffer.
+    /// Transaction id and its offset are specified through this
+    /// constructor so as they are stored in outgoing message
+    /// when client class calls \ref PerfPkt4::rawPack.
+    ///
+    /// \note This constructor should be used only for outgoing
+    /// messages that are created from raw buffer (e.g. read from
+    /// template files).
+    ///
+    /// \param buf buffer holiding contents of the message (this can
+    /// be directly read from template file).
+    /// \param len length of the data in the buffer.
+    /// \param transid_offset transaction id offset in outgoing message.
+    /// \param transid transaction id to be stored in outgoing message.
+    PerfPkt4(const uint8_t* buf,
+             uint32_t len,
+             size_t transid_offset,
+             uint32_t transid);
+
+    /// Constructor, used for incoming DHCP messages.
+    ///
+    /// Creates new DHCPv6 message using provided buffer. New object
+    /// will keep copy of contents of provided buffer. If buffer contains
+    /// options at custom offsets (e.g. if packet was read from
+    /// template file) additional information about options'
+    /// offsets has to be provided - see
+    /// \ref isc::perfdhcp::LocalizedOption for details.
+    ///
+    /// Transaction id offset point to location of raw data where
+    /// transaction id field is stored. The transaction id will
+    /// be read from this location when PerfPkt4::rawUnpack is
+    /// called. The transid_ class member will be updated accordingly.
+    ///
+    /// \note use this constructor only in case you want to create
+    /// incoming DHCPv6 object from the raw buffer
+    /// and you know options offsets. Options offsets are
+    /// specified from perfdhcp command line by the user.
+    ///
+    /// \param buf pointer to a buffer of received packet content.
+    /// \param len size of buffer of packet content.
+    /// \param transid_offset transaction id offset in a message.
+    PerfPkt4(const uint8_t* buf,
+             uint32_t len,
+             size_t transid_offset);
+
+    /// \brief Returns transaction id offset in packet buffer
+    ///
+    /// return transaction id offset in packet buffer
+    size_t getTransIdOffset() const { return transid_offset_; };
+
+    /// \brief Prepares on-wire format from raw buffer
+    ///
+    /// The method copies user buffer to output buffer and
+    /// extracts transaction id from it based on transaction id
+    /// offset provided in constructor.
+    ///
+    /// \note: Use this method to prepare on-wire DHCPv6 message
+    /// when you use template packets that require replacement
+    /// of selected options' contents before sending.
+    ///
+    /// \retrun false, id pack operation failed.
+    bool rawPack();
+
+    /// \brief Handles limited binary packet parsing for packets with
+    /// custom offsets of options and transaction id
+    ///
+    /// Function handles reception of packets that have non-default values
+    /// of options or transaction id offsets. Use
+    /// \ref isc::dhcp::Pkt4::addOption to specify which options to parse.
+    /// Each option should be of the: isc::perfdhcp::LocalizedOption
+    /// type with offset value indicated.
+    ///
+    /// \return false, if unpack operation failed.
+    bool rawUnpack();
+
+    /// \brief Update packet timestamp with current time
+    ///
+    /// \throw isc::Unexpected if timestamp update failed
+    void updateTimestamp();
+
+private:
+    size_t transid_offset_;      ///< transaction id offset
+
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __PERF_PKT4_H

+ 220 - 0
tests/tools/perfdhcp/pkt_transform.cc

@@ -0,0 +1,220 @@
+// Copyright (C) 2011  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 <iostream>
+
+#include <exceptions/exceptions.h>
+#include <dhcp/option.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/dhcp6.h>
+
+#include "pkt_transform.h"
+#include "localized_option.h"
+
+using namespace std;
+using namespace isc;
+using namespace dhcp;
+
+namespace isc {
+namespace perfdhcp {
+
+bool
+PktTransform::pack(const Option::Universe universe,
+                   const OptionBuffer& in_buffer,
+                   const Option::OptionCollection& options,
+                   const size_t transid_offset,
+                   const uint32_t transid,
+                   util::OutputBuffer& out_buffer) {
+
+    // Always override the packet if function is called.
+    out_buffer.clear();
+    // Write whole buffer to output buffer.
+    out_buffer.writeData(&in_buffer[0], in_buffer.size());
+
+    uint8_t transid_len = (universe == Option::V6) ? 3 : 4;
+
+    if ((transid_offset + transid_len + 1 > in_buffer.size()) ||
+        (transid_offset == 0)) {
+        cout << "Failed to build packet: provided transaction id offset: "
+             << transid_offset <<  " is out of bounds (expected 1.."
+             << in_buffer.size()-1 << ")." << endl;
+        return (false);
+    }
+
+    // Seek to the transaction id position in output buffer.
+    out_buffer.clear();
+    out_buffer.skip(transid_offset);
+
+    try {
+        if (universe == Option::V6) {
+            out_buffer.writeUint8(transid >> 24 & 0xFF);
+        }
+        out_buffer.writeUint8(transid >> 16 & 0xFF);
+        out_buffer.writeUint8(transid >> 8 & 0xFF);
+        out_buffer.writeUint8(transid & 0xFF);
+
+        // Buffer pointer is at the end of transaction id.
+        // We have to seek to the end of buffer so as data don't
+        // get truncated.
+        out_buffer.skip(in_buffer.size() - out_buffer.getLength());
+
+        // We already have packet template stored in output buffer
+        // but still some options have to be updated if client
+        // specified them along with their offsets in the buffer.
+        PktTransform::packOptions(in_buffer, out_buffer, options);
+    } catch (const isc::BadValue& e) {
+        cout << "Building packet failed: " << e.what() << endl;
+        return (false);
+    }
+    return (true);
+}
+
+bool
+PktTransform::unpack(const Option::Universe universe,
+                     const OptionBuffer& in_buffer,
+                     const Option::OptionCollection& options,
+                     const size_t transid_offset,
+                     uint32_t& transid) {
+
+    uint8_t transid_len = (universe == Option::V6) ? 3 : 4;
+
+    // Validate transaction id offset.
+    if ((transid_offset + transid_len + 1 > in_buffer.size()) ||
+        (transid_offset == 0)) {
+        cout << "Failed to parse packet: provided transaction id offset: "
+             << transid_offset <<  " is out of bounds (expected 1.."
+             << in_buffer.size()-1 << ")." << endl;
+        return (false);
+    }
+
+    if (universe == Option::V6) {
+        transid = ((in_buffer[transid_offset] << 16) +
+                   (in_buffer[transid_offset + 1] << 8) +
+                   (in_buffer[transid_offset + 2]))
+            & 0xFFFFFF;
+    } else {
+        transid = ((in_buffer[transid_offset] << 24) +
+                   (in_buffer[transid_offset +1 ] << 16) +
+                   (in_buffer[transid_offset + 2] << 8) +
+                   (in_buffer[transid_offset + 3]));
+    }
+
+    try {
+        PktTransform::unpackOptions(universe, in_buffer, options);
+    } catch (const isc::BadValue& e) {
+        cout << "Packet parsing failed: " << e.what() << endl;
+        return (false);
+    }
+
+    return (true);
+}
+
+void
+PktTransform::packOptions(const OptionBuffer& in_buffer,
+                          util::OutputBuffer& out_buffer,
+                          const Option::OptionCollection& options) {
+    try {
+        // If there are any options on the list, we will use provided
+        // options offsets to override them in the output buffer
+        // with new contents.
+        for (Option::OptionCollection::const_iterator it = options.begin();
+             it != options.end(); ++it) {
+            // Get options with their position (offset).
+            boost::shared_ptr<LocalizedOption> option =
+                boost::dynamic_pointer_cast<LocalizedOption>(it->second);
+            uint32_t offset = option->getOffset();
+            if ((offset == 0) ||
+                (offset + option->len() > in_buffer.size())) {
+                isc_throw(isc::BadValue,
+                          "option offset for option: " << option->getType()
+                          << " is out of bounds (expected 1.."
+                          << in_buffer.size() - option->len() << ")");
+            }
+            out_buffer.clear();
+            out_buffer.skip(offset);
+
+            // Replace existing option with new value.
+            option->pack(out_buffer);
+        }
+        // Seek to the end of the buffer to make sure its size is correct.
+        out_buffer.skip(in_buffer.size() - out_buffer.getLength());
+    }
+    catch (const Exception&) {
+        isc_throw(isc::BadValue, "failed to pack options into buffer.");
+    }
+}
+
+void
+PktTransform::unpackOptions(const Option::Universe universe,
+                            const OptionBuffer& in_buffer,
+                            const Option::OptionCollection& options) {
+    for (Option::OptionCollection::const_iterator it = options.begin();
+         it != options.end(); ++it) {
+
+        boost::shared_ptr<LocalizedOption> option =
+            boost::dynamic_pointer_cast<LocalizedOption>(it->second);
+        size_t opt_pos = option->getOffset();
+        if (opt_pos == 0) {
+            isc_throw(isc::BadValue, "failed to unpack packet from raw buffer "
+                      "(Option position not specified)");
+        } else if (opt_pos + 4 > in_buffer.size()) {
+            isc_throw(isc::BadValue,
+                      "failed to unpack options from from raw buffer "
+                      "(Option position out of bounds)");
+        }
+
+        size_t offset = opt_pos;
+        size_t offset_step = 1;
+        uint16_t opt_type = 0;
+        if (universe == Option::V6) {
+            offset_step = 2;
+            // For DHCPv6 option type is in first two octets.
+            opt_type = in_buffer[offset] * 256 + in_buffer[offset + 1];
+        } else {
+            // For DHCPv4 option type is in first octet.
+            opt_type = in_buffer[offset];
+        }
+        // Check if we got expected option type.
+        if (opt_type != option->getType()) {
+            isc_throw(isc::BadValue,
+                      "failed to unpack option from raw buffer "
+                      "(option type mismatch)");
+        }
+
+        // Get option length which is supposed to be after option type.
+        offset += offset_step;
+        uint16_t opt_len = in_buffer[offset] * 256 + in_buffer[offset + 1];
+        if (universe == Option::V6) {
+            opt_len = in_buffer[offset] * 256 + in_buffer[offset + 1];
+        } else {
+            opt_len = in_buffer[offset];
+        }
+        
+        // Check if packet is not truncated.
+        if (offset + opt_len > in_buffer.size()) {
+            isc_throw(isc::BadValue,
+                      "failed to unpack option from raw buffer "
+                      "(option truncated)");
+        }
+
+        // Seek to actual option data and replace it.
+        offset += offset_step;
+        option->setData(in_buffer.begin() + offset,
+                        in_buffer.begin() + offset + opt_len);
+    }
+}
+
+
+} // namespace perfdhcp
+} // namespace isc

+ 150 - 0
tests/tools/perfdhcp/pkt_transform.h

@@ -0,0 +1,150 @@
+// Copyright (C) 2011  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 __PKT_TRANSFORM_H
+#define __PKT_TRANSFORM_H
+
+#include <boost/shared_ptr.hpp>
+#include <dhcp/option.h>
+
+#include "localized_option.h"
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Read and write raw data to DHCP packets.
+///
+/// This class provides static functions to read raw
+/// data from packet buffer and write raw data to packet
+/// buffer. When reading data with unpack() method, the 
+/// corresponding options objects are updated. 
+/// When writting to the packet buffer with pack() nethod, 
+/// options objects carry input data to be written.
+/// This class is used both by \ref PerfPkt4 and 
+/// \ref PerfPkt6 classes in case DHCP packets are created
+/// from template files. In this case, some of the template
+/// packet's options are replaced before sending it to 
+/// server. Offset of specific options are provided from
+/// command line by perfdhcp tool user and passed in
+/// options collection.
+/// This class also provide mechanism to read some data
+/// from incoming packet buffer and update options 
+/// in options collection with these data. 
+/// It is assumed either in case of writting or reading
+/// that all options have to be added to options collection
+/// and their offset have to be specified in buffer
+/// (\see LocalizedOption).
+class PktTransform {
+public:
+
+    /// \brief Prepares on-wire format from raw buffer
+    ///
+    /// The method copies input buffer and options contents
+    /// to output buffer. Input buffer must contain whole
+    /// initial packet data. Parts of this data will be
+    /// overriden by options data specified in options
+    /// collection. Such options must have their offsets in 
+    /// a packet specified (\see LocalizedOption to find out how
+    /// to specify options offset).
+    ///
+    /// \note Specified options must fit into size of the 
+    /// initial packet data. Call to this function will fail
+    /// if option's offset + its size is out of bounds.
+    ///
+    /// \param universe universe used, V4 or V6
+    /// \param in_buffer input buffer holiding intial packet
+    /// data, this can be directly read from template file
+    /// \param options options collection with offsets
+    /// \param transid_offset offset of transaction id in a packet,
+    /// transatcion id will be written to output buffer at this
+    /// offset
+    /// \param transid transaction id value
+    /// \param out_buffer output buffer holding "packed" data
+    /// 
+    /// \retrun false, if pack operation failed.
+    static bool pack(const dhcp::Option::Universe universe,
+                     const dhcp::OptionBuffer& in_buffer,
+                     const dhcp::Option::OptionCollection& options,
+                     const size_t transid_offset,
+                     const uint32_t transid,
+                     util::OutputBuffer& out_buffer);
+
+    /// \brief Handles limited binary packet parsing for packets with
+    /// custom offsets of options and transaction id.
+    ///
+    /// Function handles parsing of packet that have non-default values
+    /// of options or transaction id offsets. Use
+    /// \ref isc::dhcp::Pkt6::addOption to specify which options to parse.
+    /// Each option should be of the \ref isc::perfdhcp::LocalizedOption
+    /// type with offset value indicated.
+    /// Transaction id offset is specified as separate argument and 
+    /// is used to read transaction id value from buffer. 
+    ///
+    /// \param universe universe used, V4 or V6
+    /// \param in_buffer input buffer to be parsed
+    /// \param options options collection with options offsets
+    /// \param transid_offset offset of transaction id in input buffer
+    /// \param transid transaction id value read from input buffer
+    /// \return false, if unpack operation failed.
+    static bool unpack(const dhcp::Option::Universe universe, 
+                       const dhcp::OptionBuffer& in_buffer,
+                       const dhcp::Option::OptionCollection& options,
+                       const size_t transid_offset,
+                       uint32_t& transid);
+
+private:
+    /// \brief Replaces options contents of options in a buffer
+    ///
+    /// The method uses localized options collection added to 
+    /// replace parts of initial packet data (e.g. read from 
+    /// template file). 
+    /// This private method is called from \ref PktTransform::pack
+    ///
+    /// \param in_buffer input buffer holding initial packet data.
+    /// \param out_buffer output buffer with "packed" options.
+    /// \param options options collection with actual data and offsets.
+    /// \throw isc::Unexpected if options update failed.
+    static void packOptions(const dhcp::OptionBuffer& in_buffer,
+                            util::OutputBuffer& out_buffer,
+                            const dhcp::Option::OptionCollection& options);
+
+    /// \brief Reads contents of specified options from buffer
+    ///
+    /// The method reads options data from the input buffer
+    /// and stores read data in options objects. Options
+    /// must have their offsets in a buffer specified 
+    /// (\see LocalizedOption to find out how to specify
+    /// option offset).
+    /// This private method is called by \ref PktTransform::unpack.
+    ///
+    /// \note This method iterates through all options in 
+    /// options collection, checks offset of the option
+    /// in input buffer and reads data from the buffer to
+    /// update option's buffer. If provided options collection
+    /// is empty, call to this function will have no effect.
+    /// 
+    /// \param universe universe used, V4 or V6
+    /// \param in_buffer input buffer to be parsed.
+    /// \param options oprions collection with their offsets 
+    /// in input buffer specified.
+    /// \throw isc::Unexpected if options unpack failed.
+    static void unpackOptions(const dhcp::Option::Universe universe,
+                              const dhcp::OptionBuffer& in_buffer,
+                              const dhcp::Option::OptionCollection& options);
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __PKT_TRANSFORM_H

+ 4 - 0
tests/tools/perfdhcp/tests/Makefile.am

@@ -17,9 +17,12 @@ TESTS += run_unittests
 run_unittests_SOURCES  = run_unittests.cc
 run_unittests_SOURCES += command_options_unittest.cc
 run_unittests_SOURCES += perf_pkt6_unittest.cc
+run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
@@ -27,6 +30,7 @@ run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
 run_unittests_LDADD  = $(GTEST_LDADD)
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libutil.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 endif

+ 344 - 0
tests/tools/perfdhcp/tests/perf_pkt4_unittest.cc

@@ -0,0 +1,344 @@
+// Copyright (C) 2011-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 <iostream>
+#include <sstream>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcp/dhcp4.h>
+
+#include "../localized_option.h"
+#include "../perf_pkt4.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::perfdhcp;
+
+typedef PerfPkt4::LocalizedOptionPtr LocalizedOptionPtr;
+
+namespace {
+
+// a sample data
+const uint8_t dummyOp = BOOTREQUEST;
+const uint8_t dummyHtype = 6;
+const uint8_t dummyHlen = 6;
+const uint8_t dummyHops = 13;
+const uint32_t dummyTransid = 0x12345678;
+const uint16_t dummySecs = 42;
+const uint16_t dummyFlags = BOOTP_BROADCAST;
+
+const IOAddress dummyCiaddr("192.0.2.1");
+const IOAddress dummyYiaddr("1.2.3.4");
+const IOAddress dummySiaddr("192.0.2.255");
+const IOAddress dummyGiaddr("255.255.255.255");
+
+// a dummy MAC address
+const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
+
+// a dummy MAC address, padded with 0s
+const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
+                                 0, 0, 0, 0, 0, 0, 0, 0 };
+
+// let's use some creative test content here (128 chars + \0)
+const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
+    "adipiscing elit. Proin mollis placerat metus, at "
+    "lacinia orci ornare vitae. Mauris amet.";
+
+// yet another type of test content (64 chars + \0)
+const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
+    "adipiscing elit posuere.";
+
+class PerfPkt4Test : public ::testing::Test {
+public:
+    PerfPkt4Test() {
+    }
+
+    /// \brief Returns captured SOLICIT packet.
+    ///
+    /// Captured SOLICIT packet with transid=0x3d79fb and options: client-id,
+    /// in_na, dns-server, elapsed-time, option-request
+    /// This code was autogenerated
+    /// (see src/bin/dhcp6/tests/iface_mgr_unittest.c),
+    /// but we spent some time to make is less ugly than it used to be.
+    ///
+    /// \return pointer to Pkt6 that represents received SOLICIT
+    std::vector<uint8_t> capture() {
+
+        // That is only part of the header. It contains all "short" fields,
+        // larger fields are constructed separately.
+        uint8_t hdr[] = {
+            1, 6, 6, 13,            // op, htype, hlen, hops,
+            0x12, 0x34, 0x56, 0x78, // transaction-id
+            0, 42, 0x80, 0x00,      // 42 secs, BROADCAST flags
+            192, 0, 2, 1,           // ciaddr
+            1, 2, 3, 4,             // yiaddr
+            192, 0, 2, 255,         // siaddr
+            255, 255, 255, 255,     // giaddr
+        };
+
+        uint8_t v4Opts[] = {
+            12,  3, 0,   1,  2, // Host name option.
+            13,  3, 10, 11, 12, // Boot file size option
+            14,  3, 20, 21, 22, // Merit dump file
+            53, 1, 1,           // DHCP message type. 
+            128, 3, 30, 31, 32, 
+            254, 3, 40, 41, 42,
+        };
+
+        // Initialize the vector with the header fields defined above.
+        vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+        
+        // Append the large header fields.
+        std::copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
+        std::copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
+        std::copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
+
+        // Append magic cookie.
+        buf.push_back(0x63); 
+        buf.push_back(0x82);
+        buf.push_back(0x53);
+        buf.push_back(0x63);
+
+        // Append options.
+        std::copy(v4Opts, v4Opts + sizeof(v4Opts), back_inserter(buf));
+
+        return buf;
+    }
+};
+
+TEST_F(PerfPkt4Test, Constructor) {
+    // Initialize some dummy payload.
+    uint8_t data[250];
+    for (int i = 0; i < 250; i++) {
+        data[i]=i;
+    }
+
+    // Test constructor to be used for incoming messages.
+    // Use default (1) offset value and don't specify transaction id.
+    boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(data, sizeof(data), 1));
+    EXPECT_EQ(1, pkt1->getTransIdOffset());
+
+    // Test constructor to be used for outgoing messages.
+    // Use non-zero offset and specify transaction id.
+    boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(data, sizeof(data), 10, 0x010203));
+    EXPECT_EQ(0x010203, pkt2->getTransid());
+    EXPECT_EQ(10, pkt2->getTransIdOffset());
+}
+
+TEST_F(PerfPkt4Test, RawPack) {
+    // Create new packet.
+    std::vector<uint8_t> buf = capture();
+    boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size(), 0x1, 1));
+
+    // Initialize options data.
+    uint8_t buf_hostname[] = { 12, 3, 4, 5, 6 };
+    uint8_t buf_boot_filesize[] = { 13,  3, 1, 2, 3 };
+    OptionBuffer vec_hostname(buf_hostname + 2, buf_hostname + 5);
+    OptionBuffer vec_boot_filesize(buf_boot_filesize + 2, buf_boot_filesize + 5);
+
+    // Create options objects.
+    LocalizedOptionPtr pkt_hostname(new LocalizedOption(Option::V4, 
+                                                        DHO_HOST_NAME,
+                                                        vec_hostname,
+                                                        240));
+    LocalizedOptionPtr pkt_boot_filesize(new LocalizedOption(Option::V4, 
+                                                             DHO_BOOT_SIZE,
+                                                             vec_boot_filesize,
+                                                             245));
+
+    // Try to add options to packet.
+    ASSERT_NO_THROW(pkt->addOption(pkt_boot_filesize));
+    ASSERT_NO_THROW(pkt->addOption(pkt_hostname));
+
+    // We have valid options addedwith valid offsets so 
+    // pack operation should succeed.
+    ASSERT_TRUE(pkt->rawPack());
+
+    // Buffer should now contain new values of DHO_HOST_NAME and
+    // DHO_BOOT_SIZE options.
+    util::OutputBuffer pkt_output = pkt->getBuffer();
+    ASSERT_EQ(buf.size(), pkt_output.getLength());
+    const uint8_t* out_buf_data = static_cast<const uint8_t*>(pkt_output.getData());
+
+    // Check if options we read from buffer is valid.
+    EXPECT_EQ(0, memcmp(buf_hostname, out_buf_data + 240, 5));
+    EXPECT_EQ(0, memcmp(buf_boot_filesize, out_buf_data + 245, 5));
+}
+
+TEST_F(PerfPkt4Test, RawUnpack) {
+    // Create new packet.
+    std::vector<uint8_t> buf = capture();
+    boost::scoped_ptr<PerfPkt4> pkt(new PerfPkt4(&buf[0], buf.size(), 0x1, 1));    
+
+    // Create options (existing in the packet) and specify their offsets.
+    LocalizedOptionPtr opt_merit(new LocalizedOption(Option::V4,
+                                                     DHO_MERIT_DUMP,
+                                                     OptionBuffer(),
+                                                     250));
+    
+    LocalizedOptionPtr opt_msg_type(new LocalizedOption(Option::V4,
+                                                        DHO_DHCP_MESSAGE_TYPE,
+                                                        OptionBuffer(), 
+                                                        255));
+    // Addition should be successful
+    ASSERT_NO_THROW(pkt->addOption(opt_merit));
+    ASSERT_NO_THROW(pkt->addOption(opt_msg_type));
+    
+    // Option fit to packet boundaries and offsets are valid,
+    // so this should unpack successfully.
+    ASSERT_TRUE(pkt->rawUnpack());
+
+    // At this point we should have updated options data (read from buffer).
+    // Let's try to retrieve them.
+    opt_merit = boost::dynamic_pointer_cast<LocalizedOption>(pkt->getOption(DHO_MERIT_DUMP));
+    opt_msg_type = boost::dynamic_pointer_cast<LocalizedOption>(pkt->getOption(DHO_DHCP_MESSAGE_TYPE));
+    ASSERT_TRUE(opt_merit);
+    ASSERT_TRUE(opt_msg_type);
+
+    // Get first option payload.
+    OptionBuffer opt_merit_data = opt_merit->getData();
+
+    // Define reference data. 
+    uint8_t buf_merit[] = { 20, 21, 22 }; 
+
+    // Validate first option data.
+    ASSERT_EQ(sizeof(buf_merit), opt_merit_data.size());
+    EXPECT_TRUE(std::equal(opt_merit_data.begin(), opt_merit_data.end(), buf_merit));
+    
+    // Get second option payload.
+    OptionBuffer opt_msg_type_data = opt_msg_type->getData();
+
+    // Expect one byte of message type payload.
+    ASSERT_EQ(1, opt_msg_type_data.size());
+    EXPECT_EQ(1, opt_msg_type_data[0]);
+}
+
+    /*
+TEST_F(PerfPkt4Test, InvalidOptions) {
+    // Create packet.
+    boost::scoped_ptr<PerfPkt4> pkt1(capture());
+    OptionBuffer vec_server_id;
+    vec_server_id.resize(10);
+    // Testing invalid offset of the option (greater than packet size)
+    LocalizedOptionPtr pkt1_serverid(new LocalizedOption(Option::V6,
+                                                         D6O_SERVERID,
+                                                         vec_server_id,
+                                                         150));
+    pkt1->addOption(pkt1_serverid);
+    // Pack has to fail due to invalid offset.
+    EXPECT_FALSE(pkt1->rawPack());
+
+    // Create packet.
+    boost::scoped_ptr<PerfPkt4> pkt2(capture());
+    // Testing offset of the option (lower than pakcet size but
+    // tail of the option out of bounds).
+    LocalizedOptionPtr pkt2_serverid(new LocalizedOption(Option::V6,
+                                                         D6O_SERVERID,
+                                                         vec_server_id,
+                                                         85));
+    pkt2->addOption(pkt2_serverid);
+    // Pack must fail due to invalid offset.
+    EXPECT_FALSE(pkt2->rawPack());
+}
+
+
+TEST_F(PerfPkt4Test, TruncatedPacket) {
+    cout << "Testing parsing options from truncated packet."
+         << "This may produce spurious errors" << endl;
+
+    // Create truncated (in the middle of duid options)
+    boost::scoped_ptr<PerfPkt4> pkt1(captureTruncated());
+    OptionBuffer vec_duid;
+    vec_duid.resize(30);
+    LocalizedOptionPtr pkt1_duid(new LocalizedOption(Option::V6,
+                                                     D6O_CLIENTID,
+                                                     vec_duid,
+                                                     4));
+    pkt1->addOption(pkt1_duid);
+    // Pack/unpack must fail because length of the option read from buffer
+    // will extend over the actual packet length.
+    EXPECT_FALSE(pkt1->rawUnpack());
+    EXPECT_FALSE(pkt1->rawPack());
+}
+
+TEST_F(PerfPkt4Test, PackTransactionId) {
+    uint8_t data[100] = { 0 };
+
+    // Create dummy packet that is simply filled with zeros.
+    boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(data,
+                                                  sizeof(data),
+                                                  0x010203,
+                                                  PerfPkt4::Offset(50)));
+
+    // Reference data are non zero so we can detect them in dummy packet.
+    uint8_t ref_data[3] = { 1, 2, 3 };
+
+    // This will store given transaction id in the packet data at
+    // offset of 50.
+    ASSERT_TRUE(pkt1->rawPack());
+
+    // Get the output buffer so we can validate it.
+    util::OutputBuffer out_buf = pkt1->getBuffer();
+    ASSERT_EQ(sizeof(data), out_buf.getLength());
+    const uint8_t *out_buf_data = static_cast<const uint8_t*>
+        (out_buf.getData());
+
+    // Validate transaction id.
+    EXPECT_EQ(0, memcmp(out_buf_data + 50, ref_data, 3));
+
+    // Out of bounds transaction id offset.
+    boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(data,
+                                                  sizeof(data),
+                                                  0x010202,
+                                                  PerfPkt4::Offset(100)));
+    cout << "Testing out of bounds offset. "
+        "This may produce spurious errors ..." << endl;
+    EXPECT_FALSE(pkt2->rawPack());
+}
+
+TEST_F(PerfPkt4Test, UnpackTransactionId) {
+    // Initialize data for dummy packet (zeros only).
+    uint8_t data[100] = { 0 };
+
+    // Generate transaction id = 0x010203 and inject at offset = 50.
+    for (int i = 50; i <  53; ++i) {
+        data[i] = i - 49;
+    }
+    // Create packet and point out that transaction id is at offset 50.
+    boost::scoped_ptr<PerfPkt4> pkt1(new PerfPkt4(data,
+                                                  sizeof(data),
+                                                  PerfPkt4::Offset(50)));
+
+    // Get transaction id out of buffer and store in class member.
+    ASSERT_TRUE(pkt1->rawUnpack());
+    // Test value of transaction id.
+    EXPECT_EQ(0x010203, pkt1->getTransid());
+
+    // Out of bounds transaction id offset.
+    boost::scoped_ptr<PerfPkt4> pkt2(new PerfPkt4(data,
+                                                  sizeof(data),
+                                                  PerfPkt4::Offset(300)));
+    cout << "Testing out of bounds offset. "
+        "This may produce spurious errors ..." << endl;
+    EXPECT_FALSE(pkt2->rawUnpack());
+
+    } */
+
+}