Browse Source

[3180] Implemented support for callback functions to parse options.

Marcin Siodelski 11 years ago
parent
commit
d06b0d68f9

+ 99 - 0
src/bin/dhcp4/dhcp4_srv.cc

@@ -35,6 +35,7 @@
 #include <hooks/hooks_manager.h>
 
 #include <boost/algorithm/string/erase.hpp>
+#include <boost/bind.hpp>
 
 #include <iomanip>
 #include <fstream>
@@ -197,6 +198,15 @@ Dhcpv4Srv::run() {
             continue;
         }
 
+        // In order to parse the DHCP options, the server needs to use some
+        // configuration information such as: existing option spaces, option
+        // definitions etc. This is the kind of information which is not
+        // available in the libdhcp, so we need to supply our own implementation
+        // of the option parsing function here, which would rely on the
+        // configuration data.
+        query->setCallback(boost::bind(&Dhcpv4Srv::unpackOptions, this,
+                                       _1, _2, _3));
+
         bool skip_unpack = false;
 
         // The packet has just been received so contains the uninterpreted wire
@@ -1164,6 +1174,95 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
     }
 }
 
+size_t
+Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
+                          const std::string& option_space,
+                          isc::dhcp::OptionCollection& options) {
+    size_t offset = 0;
+
+    OptionDefContainer option_defs;
+    if (option_space == "dhcp6") {
+        // Get the list of stdandard option definitions.
+        option_defs = LibDHCP::getOptionDefs(Option::V6);
+    } else if (!option_space.empty()) {
+        OptionDefContainerPtr option_defs_ptr =
+            CfgMgr::instance().getOptionDefs(option_space);
+        if (option_defs_ptr != NULL) {
+            option_defs = *option_defs_ptr;
+        }
+    }
+    // Get the search index #1. It allows to search for option definitions
+    // using option code.
+    const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+
+    // The buffer being read comprises a set of options, each starting with
+    // a one-byte type code and a one-byte length field.
+    while (offset + 1 <= buf.size()) {
+        uint8_t opt_type = buf[offset++];
+
+        // DHO_END is a special, one octet long option
+        if (opt_type == DHO_END)
+            return (offset); // just return. Don't need to add DHO_END option
+
+        // DHO_PAD is just a padding after DHO_END. Let's continue parsing
+        // in case we receive a message without DHO_END.
+        if (opt_type == DHO_PAD)
+            continue;
+
+        if (offset + 1 >= buf.size()) {
+            // opt_type must be cast to integer so as it is not treated as
+            // unsigned char value (a number is presented in error message).
+            isc_throw(OutOfRange, "Attempt to parse truncated option "
+                      << static_cast<int>(opt_type));
+        }
+
+        uint8_t opt_len =  buf[offset++];
+        if (offset + opt_len > buf.size()) {
+            isc_throw(OutOfRange, "Option parse failed. Tried to parse "
+                      << offset + opt_len << " bytes from " << buf.size()
+                      << "-byte long buffer.");
+        }
+
+        // Get all definitions with the particular option code. Note that option code
+        // is non-unique within this container however at this point we expect
+        // to get one option definition with the particular code. If more are
+        // returned we report an error.
+        const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
+        // Get the number of returned option definitions for the option code.
+        size_t num_defs = distance(range.first, range.second);
+
+        OptionPtr opt;
+        if (num_defs > 1) {
+            // Multiple options of the same code are not supported right now!
+            isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
+                      " for option type " << static_cast<int>(opt_type)
+                      << " returned. Currently it is not supported to initialize"
+                      << " multiple option definitions for the same option code."
+                      << " This will be supported once support for option spaces"
+                      << " is implemented");
+        } else if (num_defs == 0) {
+            opt = OptionPtr(new Option(Option::V4, opt_type,
+                                       buf.begin() + offset,
+                                       buf.begin() + offset + opt_len));
+            opt->setEncapsulatedSpace("dhcp4");
+        } else {
+            // The option definition has been found. Use it to create
+            // the option instance from the provided buffer chunk.
+            const OptionDefinitionPtr& def = *(range.first);
+            assert(def);
+            opt = def->optionFactory(Option::V4, opt_type,
+                                     buf.begin() + offset,
+                                     buf.begin() + offset + opt_len,
+                                     boost::bind(&Dhcpv4Srv::unpackOptions,
+                                                 this, _1, _2, _3));
+        }
+
+        options.insert(std::make_pair(opt_type, opt));
+        offset += opt_len;
+    }
+    return (offset);
+}
+
 
 }   // namespace dhcp
 }   // namespace isc

+ 12 - 0
src/bin/dhcp4/dhcp4_srv.h

@@ -351,6 +351,18 @@ protected:
 
 private:
 
+    /// @brief Implements a callback function to parse options in the message.
+    ///
+    /// @param buf a A buffer holding options in on-wire format.
+    /// @param option_space A name of the option space which holds definitions
+    /// of to be used to parse options in the packets.
+    /// @param [out] options A reference to the collection where parsed options
+    /// will be stored.
+    /// @return An offset to the first byte after last parsed option.
+    size_t unpackOptions(const OptionBuffer& buf,
+                         const std::string& option_space,
+                         isc::dhcp::OptionCollection& options);
+
     /// @brief Constructs netmask option based on subnet4
     /// @param subnet subnet for which the netmask will be calculated
     ///

+ 110 - 7
src/bin/dhcp6/dhcp6_srv.cc

@@ -229,6 +229,15 @@ bool Dhcpv6Srv::run() {
             continue;
         }
 
+        // In order to parse the DHCP options, the server needs to use some
+        // configuration information such as: existing option spaces, option
+        // definitions etc. This is the kind of information which is not
+        // available in the libdhcp, so we need to supply our own implementation
+        // of the option parsing function here, which would rely on the
+        // configuration data.
+        query->setCallback(boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2,
+                                       _3, _4, _5));
+
         bool skip_unpack = false;
 
         // The packet has just been received so contains the uninterpreted wire
@@ -703,7 +712,7 @@ Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
 void
 Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                        RequirementLevel serverid) {
-    Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
+    OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
     switch (clientid) {
     case MANDATORY:
         if (client_ids.size() != 1) {
@@ -724,7 +733,7 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
         break;
     }
 
-    Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
+    OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
     switch (serverid) {
     case FORBIDDEN:
         if (!server_ids.empty()) {
@@ -870,7 +879,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
     //
     // @todo: expand this to cover IA_PD and IA_TA once we implement support for
     // prefix delegation and temporary addresses.
-    for (Option::OptionCollection::iterator opt = question->options_.begin();
+    for (OptionCollection::iterator opt = question->options_.begin();
          opt != question->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
@@ -1052,8 +1061,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
 
     // Get all IAs from the answer. For each IA, holding an address we will
     // create a corresponding NameChangeRequest.
-    Option::OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
-    for (Option::OptionCollection::const_iterator answer_ia =
+    OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
+    for (OptionCollection::const_iterator answer_ia =
              answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) {
         // @todo IA_NA may contain multiple addresses. We should process
         // each address individually. Currently we get only one.
@@ -1493,7 +1502,7 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
     }
     DuidPtr duid(new DUID(opt_duid->getData()));
 
-    for (Option::OptionCollection::iterator opt = renew->options_.begin();
+    for (OptionCollection::iterator opt = renew->options_.begin();
          opt != renew->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
@@ -1543,7 +1552,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
     DuidPtr duid(new DUID(opt_duid->getData()));
 
     int general_status = STATUS_Success;
-    for (Option::OptionCollection::iterator opt = release->options_.begin();
+    for (OptionCollection::iterator opt = release->options_.begin();
          opt != release->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
@@ -1868,5 +1877,99 @@ Dhcpv6Srv::openActiveSockets(const uint16_t port) {
     }
 }
 
+size_t
+Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
+                         const std::string& option_space,
+                         isc::dhcp::OptionCollection& options,
+                         size_t* relay_msg_offset,
+                         size_t* relay_msg_len) {
+    size_t offset = 0;
+    size_t length = buf.size();
+
+    OptionDefContainer option_defs;
+    if (option_space == "dhcp6") {
+        // Get the list of stdandard option definitions.
+        option_defs = LibDHCP::getOptionDefs(Option::V6);
+    } else if (!option_space.empty()) {
+        OptionDefContainerPtr option_defs_ptr =
+            CfgMgr::instance().getOptionDefs(option_space);
+        if (option_defs_ptr != NULL) {
+            option_defs = *option_defs_ptr;
+        }
+    }
+
+    // Get the search index #1. It allows to search for option definitions
+    // using option code.
+    const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+
+    // The buffer being read comprises a set of options, each starting with
+    // a two-byte type code and a two-byte length field.
+    while (offset + 4 <= length) {
+        uint16_t opt_type = isc::util::readUint16(&buf[offset]);
+        offset += 2;
+
+        uint16_t opt_len = isc::util::readUint16(&buf[offset]);
+        offset += 2;
+
+        if (offset + opt_len > length) {
+            // @todo: consider throwing exception here.
+            return (offset);
+        }
+
+        if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
+            // remember offset of the beginning of the relay-msg option
+            *relay_msg_offset = offset;
+            *relay_msg_len = opt_len;
+
+            // do not create that relay-msg option
+            offset += opt_len;
+            continue;
+        }
+
+        // Get all definitions with the particular option code. Note that option
+        // code is non-unique within this container however at this point we
+        // expect to get one option definition with the particular code. If more
+        // are returned we report an error.
+        const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
+        // Get the number of returned option definitions for the option code.
+        size_t num_defs = distance(range.first, range.second);
+
+        OptionPtr opt;
+        if (num_defs > 1) {
+            // Multiple options of the same code are not supported right now!
+            isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
+                      " for option type " << opt_type << " returned. Currently it is not"
+                      " supported to initialize multiple option definitions"
+                      " for the same option code. This will be supported once"
+                      " support for option spaces is implemented");
+        } else if (num_defs == 0) {
+            // @todo Don't crash if definition does not exist because only a few
+            // option definitions are initialized right now. In the future
+            // we will initialize definitions for all options and we will
+            // remove this elseif. For now, return generic option.
+            opt = OptionPtr(new Option(Option::V6, opt_type,
+                                       buf.begin() + offset,
+                                       buf.begin() + offset + opt_len));
+            opt->setEncapsulatedSpace("dhcp6");
+        } else {
+            // The option definition has been found. Use it to create
+            // the option instance from the provided buffer chunk.
+            const OptionDefinitionPtr& def = *(range.first);
+            assert(def);
+            opt = def->optionFactory(Option::V6, opt_type,
+                                     buf.begin() + offset,
+                                     buf.begin() + offset + opt_len,
+                                     boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2,
+                                                 _3, _4, _5));
+        }
+        // add option to options
+        options.insert(std::make_pair(opt_type, opt));
+        offset += opt_len;
+    }
+
+    return (offset);
+}
+
+
 };
 };

+ 18 - 0
src/bin/dhcp6/dhcp6_srv.h

@@ -459,6 +459,24 @@ protected:
     /// simulates transmission of a packet. For that purpose it is protected.
     virtual void sendPacket(const Pkt6Ptr& pkt);
 
+    /// @brief Implements a callback function to parse options in the message.
+    ///
+    /// @param buf a A buffer holding options in on-wire format.
+    /// @param option_space A name of the option space which holds definitions
+    /// of to be used to parse options in the packets.
+    /// @param [out] options A reference to the collection where parsed options
+    /// will be stored.
+    /// @param relay_msg_offset Reference to a size_t structure. If specified,
+    /// offset to beginning of relay_msg option will be stored in it.
+    /// @param relay_msg_len reference to a size_t structure. If specified,
+    /// length of the relay_msg option will be stored in it.
+    /// @return An offset to the first byte after last parsed option.
+    size_t unpackOptions(const OptionBuffer& buf,
+                         const std::string& option_space,
+                         isc::dhcp::OptionCollection& options,
+                         size_t* relay_msg_offset,
+                         size_t* relay_msg_len);
+
 private:
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using

+ 74 - 1
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -24,6 +24,7 @@
 #include <dhcp/option6_client_fqdn.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp6/config_parser.h>
@@ -2157,7 +2158,7 @@ TEST_F(Dhcpv6SrvTest, portsRelayedTraffic) {
 // Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
 // @todo Uncomment this test as part of #3180 work.
 // Kea code currently fails to handle docsis traffic.
-TEST_F(Dhcpv6SrvTest, DISABLED_docsisTraffic) {
+TEST_F(Dhcpv6SrvTest, docsisTraffic) {
 
     NakedDhcpv6Srv srv(0);
 
@@ -2181,6 +2182,78 @@ TEST_F(Dhcpv6SrvTest, DISABLED_docsisTraffic) {
     /// that is relayed properly, etc.
 }
 
+// This test verifies that the following option structure can be parsed:
+// - option (option space 'foobar')
+//   - sub option (option space 'foo')
+//      - sub option (option space 'bar')
+TEST_F(Dhcpv6SrvTest, unpackOptions) {
+    // Create option definition for each level of encapsulation. Each option
+    // definition is for the option code 1. Options may have the same
+    // option code because they belong to different option spaces.
+
+    // Top level option encapsulates options which belong to 'space-foo'.
+    OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, "uint32",
+                                                      "space-foo"));\
+    // Middle option encapsulates options which belong to 'space-bar'
+    OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, "uint16",
+                                                      "space-bar"));
+    // Low level option doesn't encapsulate any option space.
+    OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1,
+                                                      "uint8"));
+
+    // Add option definitions to the Configuration Manager. Each goes under
+    // different option space.
+    CfgMgr& cfgmgr = CfgMgr::instance();
+    ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def, "space-foobar"));
+    ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def2, "space-foo"));
+    ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def3, "space-bar"));
+
+    // Create the buffer holding the structure of options.
+    const char raw_data[] = {
+        // First option starts here.
+        0x00, 0x01,   // option code = 1
+        0x00, 0x0F,   // option length = 15
+        0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
+        // Sub option starts here.
+        0x00, 0x01,  // option code = 1
+        0x00, 0x07,  // option length = 7
+        0x01, 0x02,  // this option carries uint16 value
+        // Last option starts here.
+        0x00, 0x01,  // option code = 1
+        0x00, 0x01,  // option length = 1
+        0x00 // This option carries a single uint8 value and has no sub options.
+    };
+    OptionBuffer buf(raw_data, raw_data + sizeof(raw_data));
+
+    // Parse options.
+    NakedDhcpv6Srv srv(0);
+    OptionCollection options;
+    ASSERT_NO_THROW(srv.unpackOptions(buf, "space-foobar", options, 0, 0));
+
+    // There should be one top level option.
+    ASSERT_EQ(1, options.size());
+    boost::shared_ptr<OptionInt<uint32_t> > option_foobar =
+        boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()->
+                                                          second);
+    ASSERT_TRUE(option_foobar);
+    EXPECT_EQ(1, option_foobar->getType());
+    EXPECT_EQ(0x00010203, option_foobar->getValue());
+    // There should be a middle level option held in option_foobar.
+    boost::shared_ptr<OptionInt<uint16_t> > option_foo =
+        boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar->
+                                                          getOption(1));
+    ASSERT_TRUE(option_foo);
+    EXPECT_EQ(1, option_foo->getType());
+    EXPECT_EQ(0x0102, option_foo->getValue());
+    // Finally, there should be a low level option under option_foo.
+    boost::shared_ptr<OptionInt<uint8_t> > option_bar =
+        boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1));
+    ASSERT_TRUE(option_bar);
+    EXPECT_EQ(1, option_bar->getType());
+    EXPECT_EQ(0x0, option_bar->getValue());
+}
+
+
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 

+ 1 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -110,6 +110,7 @@ public:
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::writeServerID;
+    using Dhcpv6Srv::unpackOptions;
     using Dhcpv6Srv::name_change_reqs_;
 
     /// @brief packets we pretend to receive

+ 4 - 4
src/lib/dhcp/libdhcp++.cc

@@ -128,7 +128,7 @@ LibDHCP::optionFactory(Option::Universe u,
 
 
 size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
-                               isc::dhcp::Option::OptionCollection& options,
+                               isc::dhcp::OptionCollection& options,
                                size_t* relay_msg_offset /* = 0 */,
                                size_t* relay_msg_len /* = 0 */) {
     size_t offset = 0;
@@ -206,7 +206,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
 }
 
 size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
-                               isc::dhcp::Option::OptionCollection& options) {
+                               isc::dhcp::OptionCollection& options) {
     size_t offset = 0;
 
     // Get the list of stdandard option definitions.
@@ -282,8 +282,8 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
 
 void
 LibDHCP::packOptions(isc::util::OutputBuffer& buf,
-                     const Option::OptionCollection& options) {
-    for (Option::OptionCollection::const_iterator it = options.begin();
+                     const OptionCollection& options) {
+    for (OptionCollection::const_iterator it = options.begin();
          it != options.end(); ++it) {
         it->second->pack(buf);
     }

+ 3 - 3
src/lib/dhcp/libdhcp++.h

@@ -100,7 +100,7 @@ public:
     /// @param buf output buffer (assembled options will be stored here)
     /// @param options collection of options to store to
     static void packOptions(isc::util::OutputBuffer& buf,
-                            const isc::dhcp::Option::OptionCollection& options);
+                            const isc::dhcp::OptionCollection& options);
 
     /// @brief Parses provided buffer as DHCPv4 options and creates Option objects.
     ///
@@ -111,7 +111,7 @@ public:
     /// @param options Reference to option container. Options will be
     ///        put here.
     static size_t unpackOptions4(const OptionBuffer& buf,
-                                 isc::dhcp::Option::OptionCollection& options);
+                                 isc::dhcp::OptionCollection& options);
 
     /// @brief Parses provided buffer as DHCPv6 options and creates Option objects.
     ///
@@ -133,7 +133,7 @@ public:
     ///        length of the relay_msg option will be stored in it.
     /// @return offset to the first byte after last parsed option
     static size_t unpackOptions6(const OptionBuffer& buf,
-                                 isc::dhcp::Option::OptionCollection& options,
+                                 isc::dhcp::OptionCollection& options,
                                  size_t* relay_msg_offset = 0,
                                  size_t* relay_msg_len = 0);
 

+ 10 - 3
src/lib/dhcp/option.cc

@@ -126,6 +126,13 @@ void Option::unpack(OptionBufferConstIter begin,
 
 void
 Option::unpackOptions(const OptionBuffer& buf) {
+    // If custom option parsing function has been set, use this function
+    // to parse options. Otherwise, use standard function from libdhcp++.
+    if (!callback_.empty()) {
+        callback_(buf, getEncapsulatedSpace(), options_, 0, 0);
+        return;
+    }
+
     switch (universe_) {
     case V4:
         LibDHCP::unpackOptions4(buf, options_);
@@ -146,7 +153,7 @@ uint16_t Option::len() {
     int length = getHeaderLen() + data_.size();
 
     // ... and sum of lengths of all suboptions
-    for (Option::OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();
@@ -169,7 +176,7 @@ Option::valid() {
 }
 
 OptionPtr Option::getOption(uint16_t opt_type) {
-    isc::dhcp::Option::OptionCollection::const_iterator x =
+    isc::dhcp::OptionCollection::const_iterator x =
         options_.find(opt_type);
     if ( x != options_.end() ) {
         return (*x).second;
@@ -178,7 +185,7 @@ OptionPtr Option::getOption(uint16_t opt_type) {
 }
 
 bool Option::delOption(uint16_t opt_type) {
-    isc::dhcp::Option::OptionCollection::iterator x = options_.find(opt_type);
+    isc::dhcp::OptionCollection::iterator x = options_.find(opt_type);
     if ( x != options_.end() ) {
         options_.erase(x);
         return true; // delete successful

+ 38 - 2
src/lib/dhcp/option.h

@@ -17,6 +17,7 @@
 
 #include <util/buffer.h>
 
+#include <boost/function.hpp>
 #include <boost/shared_ptr.hpp>
 
 #include <map>
@@ -44,6 +45,14 @@ typedef boost::shared_ptr<OptionBuffer> OptionBufferPtr;
 class Option;
 typedef boost::shared_ptr<Option> OptionPtr;
 
+/// A collection of DHCPv6 options
+typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
+
+/// This type describes a callback function to parse options from buffer.
+typedef boost::function< size_t(const OptionBuffer&, const std::string,
+                                OptionCollection&, size_t*, size_t*)
+                         > UnpackOptionsCallback;
+
 
 class Option {
 public:
@@ -56,8 +65,6 @@ public:
     /// defines option universe DHCPv4 or DHCPv6
     enum Universe { V4, V6 };
 
-    /// a collection of DHCPv6 options
-    typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
 
     /// @brief a factory function prototype
     ///
@@ -290,6 +297,29 @@ public:
         data_.assign(first, last);
     }
 
+    /// @brief Sets the name of the option space encapsulated by this option.
+    ///
+    /// @param encapsulated_space name of the option space encapsulated by
+    /// this option.
+    void setEncapsulatedSpace(const std::string& encapsulated_space) {
+        encapsulated_space_ = encapsulated_space;
+    }
+
+    /// @brief Returns the name of the option space encapsulated by this option.
+    ///
+    /// @return name of the option space encapsulated by this option.
+    std::string getEncapsulatedSpace() const {
+        return (encapsulated_space_);
+    }
+
+    /// @brief Set callback function to be used to parse options.
+    ///
+    /// @param callback An instance of the callback function or NULL to
+    /// uninstall callback.
+    void setCallback(UnpackOptionsCallback callback) {
+        callback_ = callback;
+    }
+
     /// just to force that every option has virtual dtor
     virtual ~Option();
 
@@ -372,6 +402,12 @@ protected:
     /// collection for storing suboptions
     OptionCollection options_;
 
+    /// Name of the option space being encapsulated by this option.
+    std::string encapsulated_space_;
+
+    /// A callback to be called to unpack options from the packet.
+    UnpackOptionsCallback callback_;
+
     /// @todo probably 2 different containers have to be used for v4 (unique
     /// options) and v6 (options with the same type can repeat)
 };

+ 6 - 2
src/lib/dhcp/option6_ia.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 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
@@ -36,6 +36,8 @@ Option6IA::Option6IA(uint16_t type, uint32_t iaid)
         isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has "
                   "a different layout");
     }
+
+    setEncapsulatedSpace("dhcp6");
 }
 
 Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
@@ -48,6 +50,8 @@ Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
                   "a different layout");
     }
 
+    setEncapsulatedSpace("dhcp6");
+
     unpack(begin, end);
 }
 
@@ -113,7 +117,7 @@ uint16_t Option6IA::len() {
         OPTION6_IA_LEN  /* option content (12) */;
 
     // length of all suboptions
-    for (Option::OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();

+ 4 - 2
src/lib/dhcp/option6_iaaddr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 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
@@ -35,6 +35,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr
                              uint32_t pref, uint32_t valid)
     :Option(V6, type), addr_(addr), preferred_(pref),
      valid_(valid) {
+    setEncapsulatedSpace("dhcp6");
     if (!addr.isV6()) {
         isc_throw(isc::BadValue, addr_.toText() << " is not an IPv6 address");
     }
@@ -43,6 +44,7 @@ Option6IAAddr::Option6IAAddr(uint16_t type, const isc::asiolink::IOAddress& addr
 Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
                              OptionBuffer::const_iterator end)
     :Option(V6, type), addr_("::") {
+    setEncapsulatedSpace("dhcp6");
     unpack(begin, end);
 }
 
@@ -110,7 +112,7 @@ uint16_t Option6IAAddr::len() {
     // length of all suboptions
     // TODO implement:
     // protected: unsigned short Option::lenHelper(int header_size);
-    for (Option::OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();

+ 3 - 1
src/lib/dhcp/option6_iaprefix.cc

@@ -34,6 +34,7 @@ namespace dhcp {
 Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& prefix,
                                  uint8_t prefix_len, uint32_t pref, uint32_t valid)
     :Option6IAAddr(type, prefix, pref, valid), prefix_len_(prefix_len) {
+    setEncapsulatedSpace("dhcp6");
     // Option6IAAddr will check if prefix is IPv6 and will throw if it is not
     if (prefix_len > 128) {
         isc_throw(BadValue, prefix_len << " is not a valid prefix length. "
@@ -44,6 +45,7 @@ Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress&
 Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
                              OptionBuffer::const_iterator end)
     :Option6IAAddr(type, begin, end) {
+    setEncapsulatedSpace("dhcp6");
     unpack(begin, end);
 }
 
@@ -113,7 +115,7 @@ uint16_t Option6IAPrefix::len() {
     uint16_t length = OPTION6_HDR_LEN + OPTION6_IAPREFIX_LEN;
 
     // length of all suboptions
-    for (Option::OptionCollection::const_iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end(); ++it) {
         length += (*it).second->len();
     }

+ 4 - 1
src/lib/dhcp/option_custom.cc

@@ -24,6 +24,7 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
                            Universe u)
     : Option(u, def.getCode(), OptionBuffer()),
       definition_(def) {
+    setEncapsulatedSpace(def.getEncapsulatedSpace());
     createBuffers();
 }
 
@@ -32,6 +33,7 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
                            const OptionBuffer& data)
     : Option(u, def.getCode(), data.begin(), data.end()),
       definition_(def) {
+    setEncapsulatedSpace(def.getEncapsulatedSpace());
     createBuffers(getData());
 }
 
@@ -41,6 +43,7 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
                            OptionBufferConstIter last)
     : Option(u, def.getCode(), first, last),
       definition_(def) {
+    setEncapsulatedSpace(def.getEncapsulatedSpace());
     createBuffers(getData());
 }
 
@@ -522,7 +525,7 @@ OptionCustom::len() {
     }
 
     // ... and lengths of all suboptions
-    for (OptionCustom::OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();

+ 17 - 9
src/lib/dhcp/option_definition.cc

@@ -113,7 +113,8 @@ OptionDefinition::addRecordField(const OptionDataType data_type) {
 OptionPtr
 OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                                 OptionBufferConstIter begin,
-                                OptionBufferConstIter end) const {
+                                OptionBufferConstIter end,
+                                UnpackOptionsCallback callback) const {
     try {
         switch(type_) {
         case OPT_EMPTY_TYPE:
@@ -124,31 +125,37 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
 
         case OPT_UINT8_TYPE:
             return (array_type_ ? factoryGeneric(u, type, begin, end) :
-                    factoryInteger<uint8_t>(u, type, begin, end));
+                    factoryInteger<uint8_t>(u, type, getEncapsulatedSpace(),
+                                            begin, end, callback));
 
         case OPT_INT8_TYPE:
             return (array_type_ ? factoryGeneric(u, type, begin, end) :
-                    factoryInteger<int8_t>(u, type, begin, end));
+                    factoryInteger<int8_t>(u, type, getEncapsulatedSpace(),
+                                           begin, end, callback));
 
         case OPT_UINT16_TYPE:
             return (array_type_ ?
                     factoryIntegerArray<uint16_t>(u, type, begin, end) :
-                    factoryInteger<uint16_t>(u, type, begin, end));
+                    factoryInteger<uint16_t>(u, type, getEncapsulatedSpace(),
+                                             begin, end, callback));
 
         case OPT_INT16_TYPE:
             return (array_type_ ?
                     factoryIntegerArray<uint16_t>(u, type, begin, end) :
-                    factoryInteger<int16_t>(u, type, begin, end));
+                    factoryInteger<int16_t>(u, type, getEncapsulatedSpace(),
+                                            begin, end, callback));
 
         case OPT_UINT32_TYPE:
             return (array_type_ ?
                     factoryIntegerArray<uint32_t>(u, type, begin, end) :
-                    factoryInteger<uint32_t>(u, type, begin, end));
+                    factoryInteger<uint32_t>(u, type, getEncapsulatedSpace(),
+                                             begin, end, callback));
 
         case OPT_INT32_TYPE:
             return (array_type_ ?
                     factoryIntegerArray<uint32_t>(u, type, begin, end) :
-                    factoryInteger<int32_t>(u, type, begin, end));
+                    factoryInteger<int32_t>(u, type, getEncapsulatedSpace(),
+                                            begin, end, callback));
 
         case OPT_IPV4_ADDRESS_TYPE:
             // If definition specifies that an option is an array
@@ -211,8 +218,9 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
 
 OptionPtr
 OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
-                                const OptionBuffer& buf) const {
-    return (optionFactory(u, type, buf.begin(), buf.end()));
+                                const OptionBuffer& buf,
+                                UnpackOptionsCallback callback) const {
+    return (optionFactory(u, type, buf.begin(), buf.end(), callback));
 }
 
 OptionPtr

+ 11 - 4
src/lib/dhcp/option_definition.h

@@ -315,7 +315,8 @@ public:
     /// @throw InvalidOptionValue if data for the option is invalid.
     OptionPtr optionFactory(Option::Universe u, uint16_t type,
                             OptionBufferConstIter begin,
-                            OptionBufferConstIter end) const;
+                            OptionBufferConstIter end,
+                            UnpackOptionsCallback callback = NULL) const;
 
     /// @brief Option factory.
     ///
@@ -334,7 +335,8 @@ public:
     /// @return instance of the DHCP option.
     /// @throw InvalidOptionValue if data for the option is invalid.
     OptionPtr optionFactory(Option::Universe u, uint16_t type,
-                            const OptionBuffer& buf = OptionBuffer()) const;
+                            const OptionBuffer& buf = OptionBuffer(),
+                            UnpackOptionsCallback callback = NULL) const;
 
     /// @brief Option factory.
     ///
@@ -444,9 +446,14 @@ public:
     /// @throw isc::OutOfRange if provided option buffer length is invalid.
     template<typename T>
     static OptionPtr factoryInteger(Option::Universe u, uint16_t type,
+                                    const std::string& encapsulated_space,
                                     OptionBufferConstIter begin,
-                                    OptionBufferConstIter end) {
-        OptionPtr option(new OptionInt<T>(u, type, begin, end));
+                                    OptionBufferConstIter end,
+                                    UnpackOptionsCallback callback) {
+        OptionPtr option(new OptionInt<T>(u, type, 0));
+        option->setEncapsulatedSpace(encapsulated_space);
+        option->setCallback(callback);
+        option->unpack(begin, end);
         return (option);
     }
 

+ 4 - 2
src/lib/dhcp/option_int.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -52,6 +52,7 @@ public:
         if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
+        setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
     }
 
     /// @brief Constructor.
@@ -74,6 +75,7 @@ public:
         if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
+        setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
         unpack(begin, end);
     }
 
@@ -175,7 +177,7 @@ public:
         // The data length is equal to size of T.
         length += sizeof(T);;
         // length of all suboptions
-        for (Option::OptionCollection::iterator it = options_.begin();
+        for (OptionCollection::iterator it = options_.begin();
              it != options_.end();
              ++it) {
             length += (*it).second->len();

+ 2 - 2
src/lib/dhcp/option_int_array.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 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
@@ -239,7 +239,7 @@ public:
         uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
         length += values_.size() * sizeof(T);
         // length of all suboptions
-        for (Option::OptionCollection::iterator it = options_.begin();
+        for (OptionCollection::iterator it = options_.begin();
              it != options_.end();
              ++it) {
             length += (*it).second->len();

+ 11 - 7
src/lib/dhcp/pkt4.cc

@@ -93,7 +93,7 @@ Pkt4::len() {
     size_t length = DHCPV4_PKT_HDR_LEN; // DHCPv4 header
 
     // ... and sum of lengths of all options
-    for (Option::OptionCollection::const_iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();
@@ -209,11 +209,15 @@ Pkt4::unpack() {
     }
 
     size_t opts_len = bufferIn.getLength() - bufferIn.getPosition();
-    vector<uint8_t> optsBuffer;
+    vector<uint8_t> opts_buffer;
 
     // First use of readVector.
-    bufferIn.readVector(optsBuffer, opts_len);
-    LibDHCP::unpackOptions4(optsBuffer, options_);
+    bufferIn.readVector(opts_buffer, opts_len);
+    if (callback_.empty()) {
+        LibDHCP::unpackOptions4(opts_buffer, options_);
+    } else {
+        callback_(opts_buffer, "dhcp4", options_, 0, 0);
+    }
 
     // @todo check will need to be called separately, so hooks can be called
     // after the packet is parsed, but before its content is verified
@@ -270,7 +274,7 @@ Pkt4::toText() {
         << ":" << remote_port_ << ", msgtype=" << static_cast<int>(getType())
         << ", transid=0x" << hex << transid_ << dec << endl;
 
-    for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
+    for (isc::dhcp::OptionCollection::iterator opt=options_.begin();
          opt != options_.end();
          ++opt) {
         tmp << "  " << opt->second->toText() << std::endl;
@@ -428,7 +432,7 @@ Pkt4::addOption(boost::shared_ptr<Option> opt) {
 
 boost::shared_ptr<isc::dhcp::Option>
 Pkt4::getOption(uint8_t type) const {
-    Option::OptionCollection::const_iterator x = options_.find(type);
+    OptionCollection::const_iterator x = options_.find(type);
     if (x != options_.end()) {
         return (*x).second;
     }
@@ -437,7 +441,7 @@ Pkt4::getOption(uint8_t type) const {
 
 bool
 Pkt4::delOption(uint8_t type) {
-    isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
+    isc::dhcp::OptionCollection::iterator x = options_.find(type);
     if (x != options_.end()) {
         options_.erase(x);
         return (true); // delete successful

+ 13 - 1
src/lib/dhcp/pkt4.h

@@ -16,6 +16,7 @@
 #define PKT4_H
 
 #include <asiolink/io_address.h>
+#include <dhcp/option.h>
 #include <util/buffer.h>
 #include <dhcp/option.h>
 #include <dhcp/hwaddr.h>
@@ -482,6 +483,14 @@ public:
     /// @return remote port
     uint16_t getRemotePort() const { return (remote_port_); }
 
+    /// @brief Set callback function to be used to parse options.
+    ///
+    /// @param callback An instance of the callback function or NULL to
+    /// uninstall callback.
+    void setCallback(UnpackOptionsCallback callback) {
+        callback_ = callback;
+    }
+
     /// @brief Update packet timestamp.
     ///
     /// Updates packet timestamp. This method is invoked
@@ -632,11 +641,14 @@ protected:
     /// behavior must be taken into consideration before making
     /// changes to this member such as access scope restriction or
     /// data format change etc.
-    isc::dhcp::Option::OptionCollection options_;
+    isc::dhcp::OptionCollection options_;
 
     /// packet timestamp
     boost::posix_time::ptime timestamp_;
 
+    /// A callback to be called to unpack options from the packet.
+    UnpackOptionsCallback callback_;
+
 }; // Pkt4 class
 
 typedef boost::shared_ptr<Pkt4> Pkt4Ptr;

+ 28 - 13
src/lib/dhcp/pkt6.cc

@@ -14,6 +14,7 @@
 
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
 #include <dhcp/pkt6.h>
 #include <exceptions/exceptions.h>
 
@@ -134,7 +135,7 @@ OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) {
                   << " There is no info about " << relay_level + 1 << " relay.");
     }
 
-    for (Option::OptionCollection::iterator it = relay_info_[relay_level].options_.begin();
+    for (OptionCollection::iterator it = relay_info_[relay_level].options_.begin();
          it != relay_info_[relay_level].options_.end(); ++it) {
         if ((*it).second->getType() == opt_type) {
             return (it->second);
@@ -148,7 +149,7 @@ uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const {
     uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
         + Option::OPTION6_HDR_LEN; // header of the relay-msg option
 
-    for (Option::OptionCollection::const_iterator opt = relay.options_.begin();
+    for (OptionCollection::const_iterator opt = relay.options_.begin();
          opt != relay.options_.end(); ++opt) {
         len += (opt->second)->len();
     }
@@ -171,7 +172,7 @@ uint16_t Pkt6::calculateRelaySizes() {
 uint16_t Pkt6::directLen() const {
     uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
 
-    for (Option::OptionCollection::const_iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end();
          ++it) {
         length += (*it).second->len();
@@ -226,7 +227,7 @@ Pkt6::packUDP() {
                 // present here as well (vendor-opts for Cable modems,
                 // subscriber-id, remote-id, options echoed back from Echo
                 // Request Option, etc.)
-                for (Option::OptionCollection::const_iterator opt =
+                for (OptionCollection::const_iterator opt =
                          relay->options_.begin();
                      opt != relay->options_.end(); ++opt) {
                     (opt->second)->pack(bufferOut_);
@@ -324,7 +325,13 @@ Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
     try {
         OptionBuffer opt_buffer(begin, end);
 
-        LibDHCP::unpackOptions6(opt_buffer, options_);
+        // If custom option parsing function has been set, use this function
+        // to parse options. Otherwise, use standard function from libdhcp.
+        if (callback_.empty()) {
+            LibDHCP::unpackOptions6(opt_buffer, options_);
+        } else {
+            callback_(opt_buffer, "dhcp6", options_, 0, 0);
+        }
     } catch (const Exception& e) {
         // @todo: throw exception here once we turn this function to void.
         return (false);
@@ -361,8 +368,16 @@ Pkt6::unpackRelayMsg() {
         try {
             // parse the rest as options
             OptionBuffer opt_buffer(&data_[offset], &data_[offset+bufsize]);
-            LibDHCP::unpackOptions6(opt_buffer, relay.options_, &relay_msg_offset,
-                                    &relay_msg_len);
+
+            // If custom option parsing function has been set, use this function
+            // to parse options. Otherwise, use standard function from libdhcp.
+            if (callback_.empty()) {
+                LibDHCP::unpackOptions6(opt_buffer, relay.options_,
+                                        &relay_msg_offset, &relay_msg_len);
+            } else {
+                callback_(opt_buffer, "dhcp6", relay.options_,
+                          &relay_msg_offset, &relay_msg_len);
+            }
 
             /// @todo: check that each option appears at most once
             //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
@@ -438,7 +453,7 @@ Pkt6::toText() {
         << "]:" << remote_port_ << endl;
     tmp << "msgtype=" << static_cast<int>(msg_type_) << ", transid=0x" <<
         hex << transid_ << dec << endl;
-    for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
+    for (isc::dhcp::OptionCollection::iterator opt=options_.begin();
          opt != options_.end();
          ++opt) {
         tmp << opt->second->toText() << std::endl;
@@ -448,18 +463,18 @@ Pkt6::toText() {
 
 OptionPtr
 Pkt6::getOption(uint16_t opt_type) {
-    isc::dhcp::Option::OptionCollection::const_iterator x = options_.find(opt_type);
+    isc::dhcp::OptionCollection::const_iterator x = options_.find(opt_type);
     if (x!=options_.end()) {
         return (*x).second;
     }
     return OptionPtr(); // NULL
 }
 
-isc::dhcp::Option::OptionCollection
+isc::dhcp::OptionCollection
 Pkt6::getOptions(uint16_t opt_type) {
-    isc::dhcp::Option::OptionCollection found;
+    isc::dhcp::OptionCollection found;
 
-    for (Option::OptionCollection::const_iterator x = options_.begin();
+    for (OptionCollection::const_iterator x = options_.begin();
          x != options_.end(); ++x) {
         if (x->first == opt_type) {
             found.insert(make_pair(opt_type, x->second));
@@ -475,7 +490,7 @@ Pkt6::addOption(const OptionPtr& opt) {
 
 bool
 Pkt6::delOption(uint16_t type) {
-    isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
+    isc::dhcp::OptionCollection::iterator x = options_.find(type);
     if (x!=options_.end()) {
         options_.erase(x);
         return (true); // delete successful

+ 15 - 3
src/lib/dhcp/pkt6.h

@@ -88,7 +88,7 @@ public:
         uint16_t  relay_msg_len_;
 
         /// options received from a specified relay, except relay-msg option
-        isc::dhcp::Option::OptionCollection options_;
+        isc::dhcp::OptionCollection options_;
     };
 
     /// Constructor, used in replying to a message
@@ -242,7 +242,7 @@ public:
     ///
     /// @param type option type we are looking for
     /// @return instance of option collection with requested options
-    isc::dhcp::Option::OptionCollection getOptions(uint16_t type);
+    isc::dhcp::OptionCollection getOptions(uint16_t type);
 
     /// Attempts to delete first suboption of requested type
     ///
@@ -350,7 +350,7 @@ public:
     /// behavior must be taken into consideration before making
     /// changes to this member such as access scope restriction or
     /// data format change etc.
-    isc::dhcp::Option::OptionCollection options_;
+    isc::dhcp::OptionCollection options_;
 
     /// @brief Update packet timestamp.
     ///
@@ -388,6 +388,14 @@ public:
     ///         be freed by the caller.
     const char* getName() const;
 
+    /// @brief Set callback function to be used to parse options.
+    ///
+    /// @param callback An instance of the callback function or NULL to
+    /// uninstall callback.
+    void setCallback(UnpackOptionsCallback callback) {
+        callback_ = callback;
+    }
+
     /// @brief copies relay information from client's packet to server's response
     ///
     /// This information is not simply copied over. Some parameter are
@@ -534,6 +542,10 @@ protected:
 
     /// packet timestamp
     boost::posix_time::ptime timestamp_;
+
+    /// A callback to be called to unpack options from the packet.
+    UnpackOptionsCallback callback_;
+
 }; // Pkt6 class
 
 } // isc::dhcp namespace

+ 6 - 6
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -254,7 +254,7 @@ TEST_F(LibDhcpTest, optionFactory) {
 
 TEST_F(LibDhcpTest, packOptions6) {
     OptionBuffer buf(512);
-    isc::dhcp::Option::OptionCollection opts; // list of options
+    isc::dhcp::OptionCollection opts; // list of options
 
     // generate content for options
     for (int i = 0; i < 64; i++) {
@@ -286,7 +286,7 @@ TEST_F(LibDhcpTest, unpackOptions6) {
     // Option is used as a simple option implementation
     // More advanced uses are validated in tests dedicated for
     // specific derived classes.
-    isc::dhcp::Option::OptionCollection options; // list of options
+    isc::dhcp::OptionCollection options; // list of options
 
     OptionBuffer buf(512);
     memcpy(&buf[0], v6packed, sizeof(v6packed));
@@ -298,7 +298,7 @@ TEST_F(LibDhcpTest, unpackOptions6) {
 
     EXPECT_EQ(options.size(), 5); // there should be 5 options
 
-    isc::dhcp::Option::OptionCollection::const_iterator x = options.find(1);
+    isc::dhcp::OptionCollection::const_iterator x = options.find(1);
     ASSERT_FALSE(x == options.end()); // option 1 should exist
     EXPECT_EQ(1, x->second->getType());  // this should be option 1
     ASSERT_EQ(9, x->second->len()); // it should be of length 9
@@ -399,7 +399,7 @@ TEST_F(LibDhcpTest, packOptions4) {
     OptionPtr opt4(new Option(Option::V4,254, payload[3]));
     OptionPtr opt5(new Option(Option::V4,128, payload[4]));
 
-    isc::dhcp::Option::OptionCollection opts; // list of options
+    isc::dhcp::OptionCollection opts; // list of options
     opts.insert(make_pair(opt1->getType(), opt1));
     opts.insert(make_pair(opt1->getType(), opt2));
     opts.insert(make_pair(opt1->getType(), opt3));
@@ -418,13 +418,13 @@ TEST_F(LibDhcpTest, packOptions4) {
 TEST_F(LibDhcpTest, unpackOptions4) {
 
     vector<uint8_t> v4packed(v4_opts, v4_opts + sizeof(v4_opts));
-    isc::dhcp::Option::OptionCollection options; // list of options
+    isc::dhcp::OptionCollection options; // list of options
 
     ASSERT_NO_THROW(
         LibDHCP::unpackOptions4(v4packed, options);
     );
 
-    isc::dhcp::Option::OptionCollection::const_iterator x = options.find(12);
+    isc::dhcp::OptionCollection::const_iterator x = options.find(12);
     ASSERT_FALSE(x == options.end()); // option 1 should exist
     // Option 12 holds a string so let's cast it to an appropriate type.
     OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x->second);

+ 2 - 2
src/lib/dhcp/tests/option_definition_unittest.cc

@@ -951,8 +951,8 @@ TEST_F(OptionDefinitionTest, integerInvalidType) {
     // see if it rejects it.
     OptionBuffer buf(1);
     EXPECT_THROW(
-        OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE,
-                                               buf.begin(), buf.end()),
+        OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, "dhcp6",
+                                               buf.begin(), buf.end(), NULL),
         isc::dhcp::InvalidDataType
     );
 }

+ 126 - 0
src/lib/dhcp/tests/option_unittest.cc

@@ -15,10 +15,12 @@
 #include <config.h>
 
 #include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/option.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 
+#include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
@@ -35,6 +37,66 @@ using namespace isc::util;
 using boost::scoped_ptr;
 
 namespace {
+
+/// @brief A class which contains a custom callback function to unpack options.
+///
+/// This is a class used by the tests which verify that the custom callback
+/// functions can be installed to unpack options from a message. When the
+/// callback function is called, the executed_ member is set to true to allow
+/// verification that the callback was really called. Internally, this class
+/// uses libdhcp++ to unpack options so the options parsing algorithm remains
+/// unchanged after installation of the callback.
+class CustomUnpackCallback {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Marks that callback hasn't been called.
+    CustomUnpackCallback()
+        : executed_(false) {
+    }
+
+    /// @brief A callback
+    ///
+    /// Contains custom implementation of the callback.
+    ///
+    /// @param buf a A buffer holding options in on-wire format.
+    /// @param [out] options A reference to the collection where parsed options
+    /// will be stored.
+    /// @param relay_msg_offset Reference to a size_t structure. If specified,
+    /// offset to beginning of relay_msg option will be stored in it.
+    /// @param relay_msg_len reference to a size_t structure. If specified,
+    /// length of the relay_msg option will be stored in it.
+    /// @return An offset to the first byte after last parsed option.
+    size_t execute(const OptionBuffer& buf,
+                   const std::string&,
+                   isc::dhcp::OptionCollection& options,
+                   size_t* relay_msg_offset,
+                   size_t* relay_msg_len) {
+        // Set the executed_ member to true to allow verification that the
+        // callback has been actually called.
+        executed_ = true;
+        // Use default implementation of the unpack algorithm to parse options.
+        return (LibDHCP::unpackOptions6(buf, options, relay_msg_offset,
+                                        relay_msg_len));
+    }
+
+    /// A flag which indicates if callback function has been called.
+    bool executed_;
+};
+
+/// @brief A class which derives from option and exposes protected members.
+class NakedOption : public Option {
+public:
+    /// @brief Constructor
+    ///
+    /// Sets the universe and option type to arbitrary test values.
+    NakedOption() : Option(Option::V6, 258) {
+    }
+
+    using Option::unpackOptions;
+};
+
 class OptionTest : public ::testing::Test {
 public:
     OptionTest(): buf_(255), outBuf_(255) {
@@ -505,4 +567,68 @@ TEST_F(OptionTest, equal) {
 
     EXPECT_TRUE(opt2->equal(opt5));
 }
+
+// This test verifies that the name of the option space being encapsulated by
+// the particular option can be set.
+TEST_F(OptionTest, setEncapsulatedSpace) {
+    Option optv6(Option::V6, 258);
+    EXPECT_TRUE(optv6.getEncapsulatedSpace().empty());
+
+    optv6.setEncapsulatedSpace("dhcp6");
+    EXPECT_EQ("dhcp6", optv6.getEncapsulatedSpace());
+
+    Option optv4(Option::V4, 125);
+    EXPECT_TRUE(optv4.getEncapsulatedSpace().empty());
+
+    optv4.setEncapsulatedSpace("dhcp4");
+    EXPECT_EQ("dhcp4", optv4.getEncapsulatedSpace());
+
+}
+
+// This test verifies that it is possible to specify custom implementation of
+// the option parsing algorithm by installing a callback function.
+TEST_F(OptionTest, unpackCallback) {
+    // Create a buffer which holds two sub options.
+    const char opt_data[] = {
+        0x00, 0x01,  // sub option code  = 1
+        0x00, 0x02,  // sub option length = 1
+        0x00, 0x01,  // sub option data
+        0x00, 0x02,  // sub option code = 2
+        0x00, 0x02,  // sub option length
+        0x00, 0x01   // sub option data
+    };
+    OptionBuffer opt_buf(opt_data, opt_data + sizeof(opt_data));
+
+    // Make sure that the flag which indicates if the callback function has
+    // been called is not set. Otherwise, our test doesn't make sense.
+    CustomUnpackCallback cb;
+    ASSERT_FALSE(cb.executed_);
+    // Create an option and install a callback.
+    NakedOption option;
+    option.setCallback(boost::bind(&CustomUnpackCallback::execute, &cb,
+                                   _1, _2, _3, _4, _5));
+    // Parse options. It should result in a call to our callback function.
+    // This function uses LibDHCP to parse options so they should be parsed
+    // correctly.
+    ASSERT_NO_THROW(option.unpackOptions(opt_buf));
+    EXPECT_TRUE(option.getOption(1));
+    EXPECT_TRUE(option.getOption(2));
+    EXPECT_FALSE(option.getOption(3));
+    // The callback should have been registered.
+    EXPECT_TRUE(cb.executed_);
+    // Reset the flag because now we are going to uninstall the callback and
+    // verify that it was NOT called.
+    cb.executed_ = false;
+    // Uninstall the callback.
+    option.setCallback(NULL);
+    ASSERT_NO_THROW(option.unpackOptions(opt_buf));
+    // Options should still get unpacked...
+    EXPECT_TRUE(option.getOption(1));
+    EXPECT_TRUE(option.getOption(2));
+    EXPECT_FALSE(option.getOption(3));
+    // ... but not via callback.
+    EXPECT_FALSE(cb.executed_);
+}
+
+
 }

+ 275 - 177
src/lib/dhcp/tests/pkt4_unittest.cc

@@ -16,6 +16,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/option_string.h>
 #include <dhcp/pkt4.h>
 #include <exceptions/exceptions.h>
@@ -42,40 +43,58 @@ using boost::scoped_ptr;
 
 namespace {
 
-TEST(Pkt4Test, constructor) {
-
-    ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
-    scoped_ptr<Pkt4> pkt;
-
-    // Just some dummy payload.
-    uint8_t testData[250];
-    for (int i = 0; i < 250; i++) {
-        testData[i] = i;
+/// @brief A class which contains a custom callback function to unpack options.
+///
+/// This is a class used by the tests which verify that the custom callback
+/// functions can be installed to unpack options from a message. When the
+/// callback function is called, the executed_ member is set to true to allow
+/// verification that the callback was really called. Internally, this class
+/// uses libdhcp++ to unpack options so the options parsing algorithm remains
+/// unchanged after installation of the callback.
+class CustomUnpackCallback {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Marks that callback hasn't been called.
+    CustomUnpackCallback()
+        : executed_(false) {
     }
 
-    // Positive case1. Normal received packet.
-    EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
-
-    EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
-
-    EXPECT_NO_THROW(pkt.reset());
-
-    // Positive case2. Normal outgoing packet.
-    EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
+    /// @brief A callback
+    ///
+    /// Contains custom implementation of the callback.
+    ///
+    /// @param buf a A buffer holding options in on-wire format.
+    /// @param [out] options A reference to the collection where parsed options
+    /// will be stored.
+    /// @return An offset to the first byte after last parsed option.
+    size_t execute(const OptionBuffer& buf,
+                   const std::string&,
+                   isc::dhcp::OptionCollection& options) {
+        // Set the executed_ member to true to allow verification that the
+        // callback has been actually called.
+        executed_ = true;
+        // Use default implementation of the unpack algorithm to parse options.
+        return (LibDHCP::unpackOptions4(buf, options));
+    }
 
-    // DHCPv4 packet must be at least 236 bytes long, with Message Type
-    // Option taking extra 3 bytes it is 239
-    EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
-    EXPECT_EQ(DHCPDISCOVER, pkt->getType());
-    EXPECT_EQ(0xffffffff, pkt->getTransid());
-    EXPECT_NO_THROW(pkt.reset());
+    /// A flag which indicates if callback function has been called.
+    bool executed_;
+};
 
-    // Negative case. Should drop truncated messages.
-    EXPECT_THROW(
-        pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
-        OutOfRange
-    );
-}
+/// V4 Options being used for pack/unpack testing.
+/// For test simplicity, all selected options have
+/// variable length data so as there are no restrictions
+/// on a length of their data.
+static uint8_t v4_opts[] = {
+    12,  3, 0,   1,  2, // Hostname
+    14,  3, 10, 11, 12, // Merit Dump File
+    53, 1, 2, // Message Type (required to not throw exception during unpack)
+    60,  3, 20, 21, 22, // Class Id
+    128, 3, 30, 31, 32, // Vendor specific
+    254, 3, 40, 41, 42, // Reserved
+};
 
 // Sample data
 const uint8_t dummyOp = BOOTREQUEST;
@@ -110,82 +129,179 @@ const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
 BOOST_STATIC_ASSERT(sizeof(dummyFile)  == Pkt4::MAX_FILE_LEN + 1);
 BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_LEN + 1);
 
-/// @brief Generates test packet.
-///
-/// Allocates and generates test packet, with all fixed fields set to non-zero
-/// values. Content is not always reasonable.
-///
-/// See generateTestPacket2() function that returns exactly the same packet in
-/// on-wire format.
-///
-/// @return pointer to allocated Pkt4 object.
-boost::shared_ptr<Pkt4>
-generateTestPacket1() {
-
-    boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, dummyTransid));
-
-    vector<uint8_t> vectorMacAddr(dummyMacAddr, dummyMacAddr
-                                  +sizeof(dummyMacAddr));
-
-    // hwType = 6(ETHERNET), hlen = 6(MAC address len)
-    pkt->setHWAddr(dummyHtype, dummyHlen, vectorMacAddr);
-    pkt->setHops(dummyHops); // 13 relays. Wow!
-    // Transaction-id is already set.
-    pkt->setSecs(dummySecs);
-    pkt->setFlags(dummyFlags); // all flags set
-    pkt->setCiaddr(dummyCiaddr);
-    pkt->setYiaddr(dummyYiaddr);
-    pkt->setSiaddr(dummySiaddr);
-    pkt->setGiaddr(dummyGiaddr);
-    // Chaddr already set with setHWAddr().
-    pkt->setSname(dummySname, 64);
-    pkt->setFile(dummyFile, 128);
-
-    return (pkt);
-}
 
-/// @brief Generates test packet.
-///
-/// Allocates and generates on-wire buffer that represents test packet, with all
-/// fixed fields set to non-zero values.  Content is not always reasonable.
-///
-/// See generateTestPacket1() function that returns exactly the same packet as
-/// Pkt4 object.
-///
-/// @return pointer to allocated Pkt4 object
-// Returns a vector containing a DHCPv4 packet header.
-vector<uint8_t>
-generateTestPacket2() {
-
-    // 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
-    };
+class Pkt4Test : public ::testing::Test {
+public:
+    Pkt4Test() {
+    }
+
+    /// @brief Generates test packet.
+    ///
+    /// Allocates and generates test packet, with all fixed fields set to non-zero
+    /// values. Content is not always reasonable.
+    ///
+    /// See generateTestPacket2() function that returns exactly the same packet in
+    /// on-wire format.
+    ///
+    /// @return pointer to allocated Pkt4 object.
+    Pkt4Ptr generateTestPacket1() {
+
+        boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDISCOVER, dummyTransid));
+
+        vector<uint8_t> vectorMacAddr(dummyMacAddr, dummyMacAddr
+                                      + sizeof(dummyMacAddr));
+
+        // hwType = 6(ETHERNET), hlen = 6(MAC address len)
+        pkt->setHWAddr(dummyHtype, dummyHlen, vectorMacAddr);
+        pkt->setHops(dummyHops); // 13 relays. Wow!
+        // Transaction-id is already set.
+        pkt->setSecs(dummySecs);
+        pkt->setFlags(dummyFlags); // all flags set
+        pkt->setCiaddr(dummyCiaddr);
+        pkt->setYiaddr(dummyYiaddr);
+        pkt->setSiaddr(dummySiaddr);
+        pkt->setGiaddr(dummyGiaddr);
+        // Chaddr already set with setHWAddr().
+        pkt->setSname(dummySname, 64);
+        pkt->setFile(dummyFile, 128);
+
+        return (pkt);
+    }
+
+    /// @brief Generates test packet.
+    ///
+    /// Allocates and generates on-wire buffer that represents test packet, with all
+    /// fixed fields set to non-zero values.  Content is not always reasonable.
+    ///
+    /// See generateTestPacket1() function that returns exactly the same packet as
+    /// Pkt4 object.
+    ///
+    /// @return pointer to allocated Pkt4 object
+    // Returns a vector containing a DHCPv4 packet header.
+    vector<uint8_t> generateTestPacket2() {
+
+        // 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
+        };
+
+        // Initialize the vector with the header fields defined above.
+        vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+
+        // Append the large header fields.
+        copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
+        copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
+        copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
+
+        // Should now have all the header, so check.  The "static_cast" is used
+        // to get round an odd bug whereby the linker appears not to find the
+        // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
+        EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
+
+        return (buf);
+    }
+
+    /// @brief Verify that the options are correct after parsing.
+    ///
+    /// @param pkt A packet holding parsed options.
+    void verifyParsedOptions(const Pkt4Ptr& pkt) {
+        EXPECT_TRUE(pkt->getOption(12));
+        EXPECT_TRUE(pkt->getOption(60));
+        EXPECT_TRUE(pkt->getOption(14));
+        EXPECT_TRUE(pkt->getOption(128));
+        EXPECT_TRUE(pkt->getOption(254));
+
+        boost::shared_ptr<Option> x = pkt->getOption(12);
+        ASSERT_TRUE(x); // option 1 should exist
+        // Option 12 is represented by the OptionString class so let's do
+        // the appropriate conversion.
+        OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
+        ASSERT_TRUE(option12);
+        EXPECT_EQ(12, option12->getType());  // this should be option 12
+        ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
+        EXPECT_EQ(5, option12->len()); // total option length 5
+        EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4_opts + 2, 3)); // data len=3
+
+        x = pkt->getOption(14);
+        ASSERT_TRUE(x); // option 14 should exist
+        // Option 14 is represented by the OptionString class so let's do
+        // the appropriate conversion.
+        OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x);
+        ASSERT_TRUE(option14);
+        EXPECT_EQ(14, option14->getType());  // this should be option 14
+        ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
+        EXPECT_EQ(5, option14->len()); // total option length 5
+        EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4_opts + 7, 3)); // data len=3
+
+        x = pkt->getOption(60);
+        ASSERT_TRUE(x); // option 60 should exist
+        EXPECT_EQ(60, x->getType());  // this should be option 60
+        ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+        EXPECT_EQ(5, x->len()); // total option length 5
+        EXPECT_EQ(0, memcmp(&x->getData()[0], v4_opts + 15, 3)); // data len=3
+
+        x = pkt->getOption(128);
+        ASSERT_TRUE(x); // option 3 should exist
+        EXPECT_EQ(128, x->getType());  // this should be option 254
+        ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+        EXPECT_EQ(5, x->len()); // total option length 5
+        EXPECT_EQ(0, memcmp(&x->getData()[0], v4_opts + 20, 3)); // data len=3
+
+        x = pkt->getOption(254);
+        ASSERT_TRUE(x); // option 3 should exist
+        EXPECT_EQ(254, x->getType());  // this should be option 254
+        ASSERT_EQ(3, x->getData().size()); // it should be of length 3
+        EXPECT_EQ(5, x->len()); // total option length 5
+        EXPECT_EQ(0, memcmp(&x->getData()[0], v4_opts + 25, 3)); // data len=3
+    }
+
+};
+
+
+TEST_F(Pkt4Test, constructor) {
+
+    ASSERT_EQ(236U, static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) );
+    scoped_ptr<Pkt4> pkt;
+
+    // Just some dummy payload.
+    uint8_t testData[250];
+    for (int i = 0; i < 250; i++) {
+        testData[i] = i;
+    }
+
+    // Positive case1. Normal received packet.
+    EXPECT_NO_THROW(pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN)));
+
+    EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
 
-    // Initialize the vector with the header fields defined above.
-    vector<uint8_t> buf(hdr, hdr + sizeof(hdr));
+    EXPECT_NO_THROW(pkt.reset());
 
-    // Append the large header fields.
-    copy(dummyChaddr, dummyChaddr + Pkt4::MAX_CHADDR_LEN, back_inserter(buf));
-    copy(dummySname, dummySname + Pkt4::MAX_SNAME_LEN, back_inserter(buf));
-    copy(dummyFile, dummyFile + Pkt4::MAX_FILE_LEN, back_inserter(buf));
+    // Positive case2. Normal outgoing packet.
+    EXPECT_NO_THROW(pkt.reset(new Pkt4(DHCPDISCOVER, 0xffffffff)));
 
-    // Should now have all the header, so check.  The "static_cast" is used
-    // to get round an odd bug whereby the linker appears not to find the
-    // definition of DHCPV4_PKT_HDR_LEN if it appears within an EXPECT_EQ().
-    EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), buf.size());
+    // DHCPv4 packet must be at least 236 bytes long, with Message Type
+    // Option taking extra 3 bytes it is 239
+    EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
+    EXPECT_EQ(DHCPDISCOVER, pkt->getType());
+    EXPECT_EQ(0xffffffff, pkt->getTransid());
+    EXPECT_NO_THROW(pkt.reset());
 
-    return (buf);
+    // Negative case. Should drop truncated messages.
+    EXPECT_THROW(
+        pkt.reset(new Pkt4(testData, Pkt4::DHCPV4_PKT_HDR_LEN - 1)),
+        OutOfRange
+    );
 }
 
-TEST(Pkt4Test, fixedFields) {
+
+TEST_F(Pkt4Test, fixedFields) {
 
     boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
 
@@ -215,7 +331,7 @@ TEST(Pkt4Test, fixedFields) {
     EXPECT_EQ(DHCPDISCOVER, pkt->getType());
 }
 
-TEST(Pkt4Test, fixedFieldsPack) {
+TEST_F(Pkt4Test, fixedFieldsPack) {
     boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
     vector<uint8_t> expectedFormat = generateTestPacket2();
 
@@ -235,7 +351,7 @@ TEST(Pkt4Test, fixedFieldsPack) {
 }
 
 /// TODO Uncomment when ticket #1226 is implemented
-TEST(Pkt4Test, fixedFieldsUnpack) {
+TEST_F(Pkt4Test, fixedFieldsUnpack) {
     vector<uint8_t> expectedFormat = generateTestPacket2();
 
     expectedFormat.push_back(0x63); // magic cookie
@@ -282,7 +398,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
 }
 
 // This test is for hardware addresses (htype, hlen and chaddr fields)
-TEST(Pkt4Test, hwAddr) {
+TEST_F(Pkt4Test, hwAddr) {
 
     vector<uint8_t> mac;
     uint8_t expectedChaddr[Pkt4::MAX_CHADDR_LEN];
@@ -329,7 +445,7 @@ TEST(Pkt4Test, hwAddr) {
     /// longer than 16 bytes should be stored in client-identifier option
 }
 
-TEST(Pkt4Test, msgTypes) {
+TEST_F(Pkt4Test, msgTypes) {
 
     struct msgType {
         uint8_t dhcp;
@@ -366,7 +482,7 @@ TEST(Pkt4Test, msgTypes) {
 }
 
 // This test verifies handling of sname field
-TEST(Pkt4Test, sname) {
+TEST_F(Pkt4Test, sname) {
 
     uint8_t sname[Pkt4::MAX_SNAME_LEN];
 
@@ -404,7 +520,7 @@ TEST(Pkt4Test, sname) {
     EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter);
 }
 
-TEST(Pkt4Test, file) {
+TEST_F(Pkt4Test, file) {
 
     uint8_t file[Pkt4::MAX_FILE_LEN];
 
@@ -442,20 +558,7 @@ TEST(Pkt4Test, file) {
     EXPECT_THROW(pkt4.setFile(NULL, 0), InvalidParameter);
 }
 
-/// V4 Options being used for pack/unpack testing.
-/// For test simplicity, all selected options have
-/// variable length data so as there are no restrictions
-/// on a length of their data.
-static uint8_t v4Opts[] = {
-    12,  3, 0,   1,  2, // Hostname
-    14,  3, 10, 11, 12, // Merit Dump File
-    53, 1, 2, // Message Type (required to not throw exception during unpack)
-    60,  3, 20, 21, 22, // Class Id
-    128, 3, 30, 31, 32, // Vendor specific
-    254, 3, 40, 41, 42, // Reserved
-};
-
-TEST(Pkt4Test, options) {
+TEST_F(Pkt4Test, options) {
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
 
     vector<uint8_t> payload[5];
@@ -496,10 +599,10 @@ TEST(Pkt4Test, options) {
     );
 
     const OutputBuffer& buf = pkt->getBuffer();
-    // Check that all options are stored, they should take sizeof(v4Opts),
+    // Check that all options are stored, they should take sizeof(v4_opts),
     // DHCP magic cookie (4 bytes), and OPTION_END added (just one byte)
     ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) +
-              sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4Opts) + 1,
+              sizeof(DHCP_OPTIONS_COOKIE) + sizeof(v4_opts) + 1,
               buf.getLength());
 
     // That that this extra data actually contain our options
@@ -508,8 +611,8 @@ TEST(Pkt4Test, options) {
     // Rewind to end of fixed part.
     ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE);
 
-    EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
-    EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
+    EXPECT_EQ(0, memcmp(ptr, v4_opts, sizeof(v4_opts)));
+    EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4_opts))));
 
     // delOption() checks
     EXPECT_TRUE(pkt->getOption(12));  // Sanity check: option 12 is still there
@@ -520,7 +623,8 @@ TEST(Pkt4Test, options) {
     EXPECT_NO_THROW(pkt.reset());
 }
 
-TEST(Pkt4Test, unpackOptions) {
+// This test verifies that the options are unpacked from the packet correctly.
+TEST_F(Pkt4Test, unpackOptions) {
 
     vector<uint8_t> expectedFormat = generateTestPacket2();
 
@@ -529,8 +633,8 @@ TEST(Pkt4Test, unpackOptions) {
     expectedFormat.push_back(0x53);
     expectedFormat.push_back(0x63);
 
-    for (int i = 0; i < sizeof(v4Opts); i++) {
-        expectedFormat.push_back(v4Opts[i]);
+    for (int i = 0; i < sizeof(v4_opts); i++) {
+        expectedFormat.push_back(v4_opts[i]);
     }
 
     // now expectedFormat contains fixed format and 5 options
@@ -542,59 +646,53 @@ TEST(Pkt4Test, unpackOptions) {
         pkt->unpack()
     );
 
-    EXPECT_TRUE(pkt->getOption(12));
-    EXPECT_TRUE(pkt->getOption(60));
-    EXPECT_TRUE(pkt->getOption(14));
-    EXPECT_TRUE(pkt->getOption(128));
-    EXPECT_TRUE(pkt->getOption(254));
+    verifyParsedOptions(pkt);
+}
+
+// This test verifies that it is possible to specify custom implementation of
+// the option parsing algorithm by installing a callback function.
+TEST_F(Pkt4Test, unpackOptionsWithCallback) {
+    vector<uint8_t> expectedFormat = generateTestPacket2();
+
+    expectedFormat.push_back(0x63);
+    expectedFormat.push_back(0x82);
+    expectedFormat.push_back(0x53);
+    expectedFormat.push_back(0x63);
+
+    for (int i = 0; i < sizeof(v4_opts); i++) {
+        expectedFormat.push_back(v4_opts[i]);
+    }
+
+    // now expectedFormat contains fixed format and 5 options
+
+    boost::shared_ptr<Pkt4> pkt(new Pkt4(&expectedFormat[0],
+                                expectedFormat.size()));
+
+    CustomUnpackCallback cb;
+    pkt->setCallback(boost::bind(&CustomUnpackCallback::execute, &cb,
+                                 _1, _2, _3));
+
+    ASSERT_FALSE(cb.executed_);
+
+    EXPECT_NO_THROW(pkt->unpack());
+
+    EXPECT_TRUE(cb.executed_);
+    verifyParsedOptions(pkt);
+
+    // Reset the indicator to perform another check: uninstall the callback.
+    cb.executed_ = false;
+    // By setting the callback to NULL we effectively uninstall the callback.
+    pkt->setCallback(NULL);
+    // Do another unpack.
+    EXPECT_NO_THROW(pkt->unpack());
+    // Callback should not be executed.
+    EXPECT_FALSE(cb.executed_);
 
-    boost::shared_ptr<Option> x = pkt->getOption(12);
-    ASSERT_TRUE(x); // option 1 should exist
-    // Option 12 is represented by the OptionString class so let's do
-    // the appropriate conversion.
-    OptionStringPtr option12 = boost::static_pointer_cast<OptionString>(x);
-    ASSERT_TRUE(option12);
-    EXPECT_EQ(12, option12->getType());  // this should be option 12
-    ASSERT_EQ(3, option12->getValue().length()); // it should be of length 3
-    EXPECT_EQ(5, option12->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&option12->getValue()[0], v4Opts + 2, 3)); // data len=3
-
-    x = pkt->getOption(14);
-    ASSERT_TRUE(x); // option 14 should exist
-    // Option 14 is represented by the OptionString class so let's do
-    // the appropriate conversion.
-    OptionStringPtr option14 = boost::static_pointer_cast<OptionString>(x);
-    ASSERT_TRUE(option14);
-    EXPECT_EQ(14, option14->getType());  // this should be option 14
-    ASSERT_EQ(3, option14->getValue().length()); // it should be of length 3
-    EXPECT_EQ(5, option14->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&option14->getValue()[0], v4Opts + 7, 3)); // data len=3
-
-    x = pkt->getOption(60);
-    ASSERT_TRUE(x); // option 60 should exist
-    EXPECT_EQ(60, x->getType());  // this should be option 60
-    ASSERT_EQ(3, x->getData().size()); // it should be of length 3
-    EXPECT_EQ(5, x->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 15, 3)); // data len=3
-
-    x = pkt->getOption(128);
-    ASSERT_TRUE(x); // option 3 should exist
-    EXPECT_EQ(128, x->getType());  // this should be option 254
-    ASSERT_EQ(3, x->getData().size()); // it should be of length 3
-    EXPECT_EQ(5, x->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 20, 3)); // data len=3
-
-    x = pkt->getOption(254);
-    ASSERT_TRUE(x); // option 3 should exist
-    EXPECT_EQ(254, x->getType());  // this should be option 254
-    ASSERT_EQ(3, x->getData().size()); // it should be of length 3
-    EXPECT_EQ(5, x->len()); // total option length 5
-    EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts + 25, 3)); // data len=3
 }
 
 // This test verifies methods that are used for manipulating meta fields
 // i.e. fields that are not part of DHCPv4 (e.g. interface name).
-TEST(Pkt4Test, metaFields) {
+TEST_F(Pkt4Test, metaFields) {
 
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     pkt->setIface("loooopback");
@@ -608,7 +706,7 @@ TEST(Pkt4Test, metaFields) {
     EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
 }
 
-TEST(Pkt4Test, Timestamp) {
+TEST_F(Pkt4Test, Timestamp) {
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
 
     // Just after construction timestamp is invalid
@@ -634,7 +732,7 @@ TEST(Pkt4Test, Timestamp) {
     EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
 }
 
-TEST(Pkt4Test, hwaddr) {
+TEST_F(Pkt4Test, hwaddr) {
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
     const uint8_t hw_type = 123; // hardware type
@@ -655,7 +753,7 @@ TEST(Pkt4Test, hwaddr) {
 
 // This test verifies that the packet remte and local HW address can
 // be set and returned.
-TEST(Pkt4Test, hwaddrSrcRemote) {
+TEST_F(Pkt4Test, hwaddrSrcRemote) {
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
     const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };

+ 125 - 27
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -24,6 +24,7 @@
 #include <dhcp/pkt6.h>
 #include <util/range_utilities.h>
 
+#include <boost/bind.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <util/encode/hex.h>
@@ -41,7 +42,54 @@ using namespace isc::dhcp;
 using boost::scoped_ptr;
 
 namespace {
-// empty class for now, but may be extended once Addr6 becomes bigger
+
+/// @brief A class which contains a custom callback function to unpack options.
+///
+/// This is a class used by the tests which verify that the custom callback
+/// functions can be installed to unpack options from a message. When the
+/// callback function is called, the executed_ member is set to true to allow
+/// verification that the callback was really called. Internally, this class
+/// uses libdhcp++ to unpack options so the options parsing algorithm remains
+/// unchanged after installation of the callback.
+class CustomUnpackCallback {
+public:
+
+    /// @brief Constructor
+    ///
+    /// Marks that callback hasn't been called.
+    CustomUnpackCallback()
+        : executed_(false) {
+    }
+
+    /// @brief A callback
+    ///
+    /// Contains custom implementation of the callback.
+    ///
+    /// @param buf a A buffer holding options in on-wire format.
+    /// @param [out] options A reference to the collection where parsed options
+    /// will be stored.
+    /// @param relay_msg_offset Reference to a size_t structure. If specified,
+    /// offset to beginning of relay_msg option will be stored in it.
+    /// @param relay_msg_len reference to a size_t structure. If specified,
+    /// length of the relay_msg option will be stored in it.
+    /// @return An offset to the first byte after last parsed option.
+    size_t execute(const OptionBuffer& buf,
+                   const std::string&,
+                   isc::dhcp::OptionCollection& options,
+                   size_t* relay_msg_offset,
+                   size_t* relay_msg_len) {
+        // Set the executed_ member to true to allow verification that the
+        // callback has been actually called.
+        executed_ = true;
+        // Use default implementation of the unpack algorithm to parse options.
+        return (LibDHCP::unpackOptions6(buf, options, relay_msg_offset,
+                                        relay_msg_len));
+    }
+
+    /// A flag which indicates if callback function has been called.
+    bool executed_;
+};
+
 class Pkt6Test : public ::testing::Test {
 public:
     Pkt6Test() {
@@ -58,6 +106,38 @@ public:
         util::fillRandom(data.begin(), data.end());
         return OptionPtr(new Option(Option::V6, code, data));
     }
+
+    /// @brief Create a wire representation of the test packet and clone it.
+    Pkt6Ptr packAndClone() {
+        Pkt6Ptr parent(new Pkt6(DHCPV6_SOLICIT, 0x020304));
+
+        OptionPtr opt1(new Option(Option::V6, 1));
+        OptionPtr opt2(new Option(Option::V6, 2));
+        OptionPtr opt3(new Option(Option::V6, 100));
+        // Let's not use zero-length option type 3 as it is IA_NA
+
+        parent->addOption(opt1);
+        parent->addOption(opt2);
+        parent->addOption(opt3);
+
+        EXPECT_EQ(DHCPV6_SOLICIT, parent->getType());
+
+        // Calculated length should be 16
+        EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
+                  parent->len());
+
+        EXPECT_NO_THROW(parent->pack());
+
+        EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
+                  parent->len());
+
+        // Create second packet,based on assembled data from the first one
+        Pkt6Ptr clone(new Pkt6(static_cast<const uint8_t*>
+                               (parent->getBuffer().getData()),
+                               parent->getBuffer().getLength()));
+        return (clone);
+
+    }
 };
 
 TEST_F(Pkt6Test, constructor) {
@@ -204,44 +284,62 @@ TEST_F(Pkt6Test, unpack_solicit1) {
 }
 
 TEST_F(Pkt6Test, packUnpack) {
-    scoped_ptr<Pkt6> parent(new Pkt6(DHCPV6_SOLICIT, 0x020304));
+    // Create an on-wire representation of the test packet and clone it.
+    Pkt6Ptr clone = packAndClone();
 
-    OptionPtr opt1(new Option(Option::V6, 1));
-    OptionPtr opt2(new Option(Option::V6, 2));
-    OptionPtr opt3(new Option(Option::V6, 100));
-    // Let's not use zero-length option type 3 as it is IA_NA
-
-    parent->addOption(opt1);
-    parent->addOption(opt2);
-    parent->addOption(opt3);
-
-    EXPECT_EQ(DHCPV6_SOLICIT, parent->getType());
-
-    // Calculated length should be 16
-    EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
-              parent->len());
+    // Now recreate options list
+    EXPECT_TRUE(clone->unpack());
 
-    EXPECT_NO_THROW(parent->pack());
+    // transid, message-type should be the same as before
+    EXPECT_EQ(0x020304, clone->getTransid());
+    EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
 
-    EXPECT_EQ(Pkt6::DHCPV6_PKT_HDR_LEN + 3 * Option::OPTION6_HDR_LEN,
-              parent->len());
+    EXPECT_TRUE(clone->getOption(1));
+    EXPECT_TRUE(clone->getOption(2));
+    EXPECT_TRUE(clone->getOption(100));
+    EXPECT_FALSE(clone->getOption(4));
+}
 
-    // Create second packet,based on assembled data from the first one
-    scoped_ptr<Pkt6> clone(new Pkt6(
-        static_cast<const uint8_t*>(parent->getBuffer().getData()),
-                                    parent->getBuffer().getLength()));
+// This test verifies that it is possible to specify custom implementation of
+// the option parsing algorithm by installing a callback function.
+TEST_F(Pkt6Test, packUnpackWithCallback) {
+    // Create an on-wire representation of the test packet and clone it.
+    Pkt6Ptr clone = packAndClone();
+
+    // Install the custom callback function. We expect that this function
+    // will be called to parse options in the packet instead of
+    // LibDHCP::unpackOptions6.
+    CustomUnpackCallback cb;
+    clone->setCallback(boost::bind(&CustomUnpackCallback::execute, &cb,
+                                   _1, _2, _3, _4, _5));
+    // Make sure that the flag which indicates if the callback function has
+    // been called is not set. Otherwise, our test doesn't make sense.
+    ASSERT_FALSE(cb.executed_);
 
     // Now recreate options list
-    EXPECT_TRUE( clone->unpack() );
+    EXPECT_TRUE(clone->unpack());
+
+    // An object which holds a callback should now have a flag set which
+    // indicates that callback has been called.
+    EXPECT_TRUE(cb.executed_);
 
     // transid, message-type should be the same as before
-    EXPECT_EQ(parent->getTransid(), parent->getTransid());
+    EXPECT_EQ(0x020304, clone->getTransid());
     EXPECT_EQ(DHCPV6_SOLICIT, clone->getType());
 
     EXPECT_TRUE(clone->getOption(1));
     EXPECT_TRUE(clone->getOption(2));
     EXPECT_TRUE(clone->getOption(100));
     EXPECT_FALSE(clone->getOption(4));
+
+    // Reset the indicator to perform another check: uninstall the callback.
+    cb.executed_ = false;
+    // By setting the callback to NULL we effectively uninstall the callback.
+    clone->setCallback(NULL);
+    // Do another unpack.
+    EXPECT_TRUE(clone->unpack());
+    // Callback should not be executed.
+    EXPECT_FALSE(cb.executed_);
 }
 
 // This test verifies that options can be added (addOption()), retrieved
@@ -266,12 +364,12 @@ TEST_F(Pkt6Test, addGetDelOptions) {
     // Now there are 2 options of type 2
     parent->addOption(opt3);
 
-    Option::OptionCollection options = parent->getOptions(2);
+    OptionCollection options = parent->getOptions(2);
     EXPECT_EQ(2, options.size()); // there should be 2 instances
 
     // Both options must be of type 2 and there must not be
     // any other type returned
-    for (Option::OptionCollection::const_iterator x= options.begin();
+    for (OptionCollection::const_iterator x= options.begin();
          x != options.end(); ++x) {
         EXPECT_EQ(2, x->second->getType());
     }