Browse Source

[master] Merge branch 'trac3180'

Marcin Siodelski 11 years ago
parent
commit
f73fba3cde

+ 2 - 0
doc/devel/mainpage.dox

@@ -53,12 +53,14 @@
  *   - @subpage dhcpv4Session
  *   - @subpage dhcpv4Session
  *   - @subpage dhcpv4ConfigParser
  *   - @subpage dhcpv4ConfigParser
  *   - @subpage dhcpv4ConfigInherit
  *   - @subpage dhcpv4ConfigInherit
+ *   - @subpage dhcpv4OptionsParse
  *   - @subpage dhcpv4Other
  *   - @subpage dhcpv4Other
  * - @subpage dhcp6
  * - @subpage dhcp6
  *   - @subpage dhcpv6Session
  *   - @subpage dhcpv6Session
  *   - @subpage dhcpv6ConfigParser
  *   - @subpage dhcpv6ConfigParser
  *   - @subpage dhcpv6ConfigInherit
  *   - @subpage dhcpv6ConfigInherit
  *   - @subpage dhcpv6DDNSIntegration
  *   - @subpage dhcpv6DDNSIntegration
+ *   - @subpage dhcpv6OptionsParse
  *   - @subpage dhcpv6Other
  *   - @subpage dhcpv6Other
  * - @subpage libdhcp
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIntro

+ 6 - 1
src/bin/dhcp4/dhcp4.dox

@@ -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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -79,6 +79,11 @@ See \ref dhcpv6ConfigParser.
 Configuration inheritance in DHCPv4 follows exactly the same logic as its DHCPv6
 Configuration inheritance in DHCPv4 follows exactly the same logic as its DHCPv6
 counterpart. See \ref dhcpv6ConfigInherit.
 counterpart. See \ref dhcpv6ConfigInherit.
 
 
+@section dhcpv4OptionsParse Custom functions to parse message options
+
+The DHCPv4 server uses the same logic to supply custom callback function to
+parse message option as DHCPv6 server implementation. See \ref dhcpv6OptionsParse.
+
 @section dhcpv4Other Other DHCPv4 topics
 @section dhcpv4Other Other DHCPv4 topics
 
 
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.

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

@@ -35,6 +35,7 @@
 #include <hooks/hooks_manager.h>
 #include <hooks/hooks_manager.h>
 
 
 #include <boost/algorithm/string/erase.hpp>
 #include <boost/algorithm/string/erase.hpp>
+#include <boost/bind.hpp>
 
 
 #include <iomanip>
 #include <iomanip>
 #include <fstream>
 #include <fstream>
@@ -197,6 +198,15 @@ Dhcpv4Srv::run() {
             continue;
             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;
         bool skip_unpack = false;
 
 
         // The packet has just been received so contains the uninterpreted wire
         // The packet has just been received so contains the uninterpreted wire
@@ -1161,6 +1171,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 == "dhcp4") {
+        // Get the list of stdandard option definitions.
+        option_defs = LibDHCP::getOptionDefs(Option::V4);
+    } 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 dhcp
 }   // namespace isc
 }   // namespace isc

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

@@ -349,6 +349,18 @@ protected:
     /// simulates transmission of a packet. For that purpose it is protected.
     /// simulates transmission of a packet. For that purpose it is protected.
     virtual void sendPacket(const Pkt4Ptr& pkt);
     virtual void sendPacket(const Pkt4Ptr& 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.
+    /// @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);
+
 private:
 private:
 
 
     /// @brief Constructs netmask option based on subnet4
     /// @brief Constructs netmask option based on subnet4

+ 87 - 12
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -22,6 +22,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter.h>
 #include <dhcp/pkt_filter_inet.h>
 #include <dhcp/pkt_filter_inet.h>
@@ -147,6 +148,7 @@ public:
     using Dhcpv4Srv::writeServerID;
     using Dhcpv4Srv::writeServerID;
     using Dhcpv4Srv::sanityCheck;
     using Dhcpv4Srv::sanityCheck;
     using Dhcpv4Srv::srvidToString;
     using Dhcpv4Srv::srvidToString;
+    using Dhcpv4Srv::unpackOptions;
 };
 };
 
 
 static const char* SRVID_FILE = "server-id-test.txt";
 static const char* SRVID_FILE = "server-id-test.txt";
@@ -1658,21 +1660,94 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
     EXPECT_TRUE(hook_index_buffer4_send > 0);
     EXPECT_TRUE(hook_index_buffer4_send > 0);
 }
 }
 
 
-    // a dummy MAC address
-    const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
+// 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')
+// @todo Add more thorough unit tests for unpackOptions.
+TEST_F(Dhcpv4SrvTest, 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.
+        0x01,                   // option code = 1
+        0x0B,                   // option length = 11
+        0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
+        // Sub option starts here.
+        0x01,                   // option code = 1
+        0x05,                   // option length = 5
+        0x01, 0x02,             // this option carries uint16 value
+        // Last option starts here.
+        0x01,                   // option code = 1
+        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.
+    NakedDhcpv4Srv srv(0);
+    OptionCollection options;
+    ASSERT_NO_THROW(srv.unpackOptions(buf, "space-foobar", options));
+
+    // 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());
+}
+
+// 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 };
+// 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.";
+// 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.";
+// Yet another type of test content (64 chars + \0)
+const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
+    "adipiscing elit posuere.";
 
 
 /// @brief a class dedicated to Hooks testing in DHCPv4 server
 /// @brief a class dedicated to Hooks testing in DHCPv4 server
 ///
 ///

+ 23 - 0
src/bin/dhcp6/dhcp6.dox

@@ -167,6 +167,29 @@ Once the configuration is implemented, these constants will be removed.
 
 
  @todo Add section about setting up options and their definitions with bindctl.
  @todo Add section about setting up options and their definitions with bindctl.
 
 
+@section dhcpv6OptionsParse Custom functions to parse message options
+
+The DHCPv6 server implementation provides a generic support to define option
+formats and set option values. A number of options formats have been defined
+for standard options in libdhcp++. However, the formats for vendor specific
+options are dynamically configured by the server's administrator and thus can't
+be stored in libdhcp++. Such option formats are stored in the
+@c isc::dhcp::CfgMgr. The libdhcp++ provides functions for recursive parsing
+of options which may be encapsulated by other options up to the any level of
+encapsulation but these functions are unaware of the option formats defined
+in the @c isc::dhcp::CfgMgr because they belong to a different library.
+Therefore, the generic functions @c isc::dhcp::LibDHCP::unpackOptions4 and
+@c isc::dhcp::LibDHCP::unpackOptions6 are only useful to parse standard
+options which definitions are provided in the libdhcp++. In order to overcome
+this problem a callback mechanism has been implemented in @c Option and @c Pkt6
+classes. By installing a callback function on the instance of the @c Pkt6 the
+server may provide a custom implementation of the options parsing algorithm.
+This callback function will take precedence over the @c LibDHCP::unpackOptions6
+and @c LibDHCP::unpackOptions4 functions. With this approach, the callback is
+implemented within the context of the server and it has access to all objects
+which define its configuration (including dynamically created option
+definitions).
+
  @section dhcpv6Other Other DHCPv6 topics
  @section dhcpv6Other Other DHCPv6 topics
 
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.

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

@@ -229,6 +229,15 @@ bool Dhcpv6Srv::run() {
             continue;
             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;
         bool skip_unpack = false;
 
 
         // The packet has just been received so contains the uninterpreted wire
         // 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
 void
 Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
 Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
                        RequirementLevel serverid) {
                        RequirementLevel serverid) {
-    Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
+    OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
     switch (clientid) {
     switch (clientid) {
     case MANDATORY:
     case MANDATORY:
         if (client_ids.size() != 1) {
         if (client_ids.size() != 1) {
@@ -724,7 +733,7 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
         break;
         break;
     }
     }
 
 
-    Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
+    OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
     switch (serverid) {
     switch (serverid) {
     case FORBIDDEN:
     case FORBIDDEN:
         if (!server_ids.empty()) {
         if (!server_ids.empty()) {
@@ -869,7 +878,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
     //
     //
     // @todo: expand this to cover IA_PD and IA_TA once we implement support for
     // @todo: expand this to cover IA_PD and IA_TA once we implement support for
     // prefix delegation and temporary addresses.
     // 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) {
          opt != question->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
         case D6O_IA_NA: {
@@ -1059,8 +1068,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
 
 
     // Get all IAs from the answer. For each IA, holding an address we will
     // Get all IAs from the answer. For each IA, holding an address we will
     // create a corresponding NameChangeRequest.
     // 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) {
              answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) {
         // @todo IA_NA may contain multiple addresses. We should process
         // @todo IA_NA may contain multiple addresses. We should process
         // each address individually. Currently we get only one.
         // each address individually. Currently we get only one.
@@ -1702,7 +1711,7 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
     }
     }
     DuidPtr duid(new DUID(opt_duid->getData()));
     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) {
          opt != renew->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         switch (opt->second->getType()) {
 
 
@@ -1769,7 +1778,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
     // handled properly. Therefore the releaseIA_NA and releaseIA_PD options
     // handled properly. Therefore the releaseIA_NA and releaseIA_PD options
     // may turn the status code to some error, but can't turn it back to success.
     // may turn the status code to some error, but can't turn it back to success.
     int general_status = STATUS_Success;
     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) {
          opt != release->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
         case D6O_IA_NA: {
@@ -2243,5 +2252,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

@@ -506,6 +506,24 @@ protected:
     /// simulates transmission of a packet. For that purpose it is protected.
     /// simulates transmission of a packet. For that purpose it is protected.
     virtual void sendPacket(const Pkt6Ptr& pkt);
     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:
 private:
     /// @brief Allocation Engine.
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using
     /// 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_client_fqdn.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_ia.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp6/config_parser.h>
 #include <dhcp6/config_parser.h>
@@ -1994,7 +1995,7 @@ TEST_F(Dhcpv6SrvTest, portsRelayedTraffic) {
 // Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
 // Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
 // @todo Uncomment this test as part of #3180 work.
 // @todo Uncomment this test as part of #3180 work.
 // Kea code currently fails to handle docsis traffic.
 // Kea code currently fails to handle docsis traffic.
-TEST_F(Dhcpv6SrvTest, DISABLED_docsisTraffic) {
+TEST_F(Dhcpv6SrvTest, docsisTraffic) {
 
 
     NakedDhcpv6Srv srv(0);
     NakedDhcpv6Srv srv(0);
 
 
@@ -2018,6 +2019,78 @@ TEST_F(Dhcpv6SrvTest, DISABLED_docsisTraffic) {
     /// that is relayed properly, etc.
     /// 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
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.
 /// to call processX() methods.
 
 

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

@@ -114,6 +114,7 @@ public:
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::writeServerID;
     using Dhcpv6Srv::writeServerID;
+    using Dhcpv6Srv::unpackOptions;
     using Dhcpv6Srv::name_change_reqs_;
     using Dhcpv6Srv::name_change_reqs_;
 
 
     /// @brief packets we pretend to receive
     /// @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,
 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_offset /* = 0 */,
                                size_t* relay_msg_len /* = 0 */) {
                                size_t* relay_msg_len /* = 0 */) {
     size_t offset = 0;
     size_t offset = 0;
@@ -206,7 +206,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
 }
 }
 
 
 size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
 size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
-                               isc::dhcp::Option::OptionCollection& options) {
+                               isc::dhcp::OptionCollection& options) {
     size_t offset = 0;
     size_t offset = 0;
 
 
     // Get the list of stdandard option definitions.
     // Get the list of stdandard option definitions.
@@ -282,8 +282,8 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
 
 
 void
 void
 LibDHCP::packOptions(isc::util::OutputBuffer& buf,
 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 != options.end(); ++it) {
         it->second->pack(buf);
         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 buf output buffer (assembled options will be stored here)
     /// @param options collection of options to store to
     /// @param options collection of options to store to
     static void packOptions(isc::util::OutputBuffer& buf,
     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.
     /// @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
     /// @param options Reference to option container. Options will be
     ///        put here.
     ///        put here.
     static size_t unpackOptions4(const OptionBuffer& buf,
     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.
     /// @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.
     ///        length of the relay_msg option will be stored in it.
     /// @return offset to the first byte after last parsed option
     /// @return offset to the first byte after last parsed option
     static size_t unpackOptions6(const OptionBuffer& buf,
     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_offset = 0,
                                  size_t* relay_msg_len = 0);
                                  size_t* relay_msg_len = 0);
 
 

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

@@ -126,6 +126,13 @@ void Option::unpack(OptionBufferConstIter begin,
 
 
 void
 void
 Option::unpackOptions(const OptionBuffer& buf) {
 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_) {
     switch (universe_) {
     case V4:
     case V4:
         LibDHCP::unpackOptions4(buf, options_);
         LibDHCP::unpackOptions4(buf, options_);
@@ -146,7 +153,7 @@ uint16_t Option::len() {
     int length = getHeaderLen() + data_.size();
     int length = getHeaderLen() + data_.size();
 
 
     // ... and sum of lengths of all suboptions
     // ... and sum of lengths of all suboptions
-    for (Option::OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::iterator it = options_.begin();
          it != options_.end();
          it != options_.end();
          ++it) {
          ++it) {
         length += (*it).second->len();
         length += (*it).second->len();
@@ -169,7 +176,7 @@ Option::valid() {
 }
 }
 
 
 OptionPtr Option::getOption(uint16_t opt_type) {
 OptionPtr Option::getOption(uint16_t opt_type) {
-    isc::dhcp::Option::OptionCollection::const_iterator x =
+    isc::dhcp::OptionCollection::const_iterator x =
         options_.find(opt_type);
         options_.find(opt_type);
     if ( x != options_.end() ) {
     if ( x != options_.end() ) {
         return (*x).second;
         return (*x).second;
@@ -178,7 +185,7 @@ OptionPtr Option::getOption(uint16_t opt_type) {
 }
 }
 
 
 bool Option::delOption(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() ) {
     if ( x != options_.end() ) {
         options_.erase(x);
         options_.erase(x);
         return true; // delete successful
         return true; // delete successful

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

@@ -17,6 +17,7 @@
 
 
 #include <util/buffer.h>
 #include <util/buffer.h>
 
 
+#include <boost/function.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 
 
 #include <map>
 #include <map>
@@ -44,6 +45,34 @@ typedef boost::shared_ptr<OptionBuffer> OptionBufferPtr;
 class Option;
 class Option;
 typedef boost::shared_ptr<Option> OptionPtr;
 typedef boost::shared_ptr<Option> OptionPtr;
 
 
+/// A collection of DHCP (v4 or v6) options
+typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
+
+/// @brief This type describes a callback function to parse options from buffer.
+///
+/// @note The last two parameters should be specified in the callback function
+/// parameters list only if DHCPv6 options are parsed. Exclude these parameters
+/// from the callback function defined to parse DHCPv4 options.
+///
+/// @param buffer A buffer holding options to be parsed.
+/// @param encapsulated_space A name of the option space to which options being
+/// parsed belong.
+/// @param [out] options A container to which parsed options should be appended.
+/// @param relay_msg_offset A pointer to a size_t value. It indicates the
+/// offset to beginning of relay_msg option. This parameter should be specified
+/// for DHCPv6 options only.
+/// @param relay_msg_len A pointer to a size_t value. It holds the length of
+/// of the relay_msg option. This parameter should be specified for DHCPv6
+/// options only.
+///
+/// @return An offset to the first byte after last parsed option.
+typedef boost::function< size_t(const OptionBuffer& buffer,
+                                const std::string encapsulated_space,
+                                OptionCollection& options,
+                                size_t* relay_msg_offset,
+                                size_t* relay_msg_len)
+                         > UnpackOptionsCallback;
+
 
 
 class Option {
 class Option {
 public:
 public:
@@ -56,8 +85,6 @@ public:
     /// defines option universe DHCPv4 or DHCPv6
     /// defines option universe DHCPv4 or DHCPv6
     enum Universe { V4, V6 };
     enum Universe { V4, V6 };
 
 
-    /// a collection of DHCPv6 options
-    typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
 
 
     /// @brief a factory function prototype
     /// @brief a factory function prototype
     ///
     ///
@@ -290,6 +317,29 @@ public:
         data_.assign(first, last);
         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
     /// just to force that every option has virtual dtor
     virtual ~Option();
     virtual ~Option();
 
 
@@ -372,6 +422,12 @@ protected:
     /// collection for storing suboptions
     /// collection for storing suboptions
     OptionCollection options_;
     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
     /// @todo probably 2 different containers have to be used for v4 (unique
     /// options) and v6 (options with the same type can repeat)
     /// 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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 "
         isc_throw(BadValue, "Can't use Option6IA for IA_TA as it has "
                   "a different layout");
                   "a different layout");
     }
     }
+
+    setEncapsulatedSpace("dhcp6");
 }
 }
 
 
 Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
 Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
@@ -48,6 +50,8 @@ Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin,
                   "a different layout");
                   "a different layout");
     }
     }
 
 
+    setEncapsulatedSpace("dhcp6");
+
     unpack(begin, end);
     unpack(begin, end);
 }
 }
 
 
@@ -113,7 +117,7 @@ uint16_t Option6IA::len() {
         OPTION6_IA_LEN  /* option content (12) */;
         OPTION6_IA_LEN  /* option content (12) */;
 
 
     // length of all suboptions
     // length of all suboptions
-    for (Option::OptionCollection::iterator it = options_.begin();
+    for (OptionCollection::iterator it = options_.begin();
          it != options_.end();
          it != options_.end();
          ++it) {
          ++it) {
         length += (*it).second->len();
         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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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)
                              uint32_t pref, uint32_t valid)
     :Option(V6, type), addr_(addr), preferred_(pref),
     :Option(V6, type), addr_(addr), preferred_(pref),
      valid_(valid) {
      valid_(valid) {
+    setEncapsulatedSpace("dhcp6");
     if (!addr.isV6()) {
     if (!addr.isV6()) {
         isc_throw(isc::BadValue, addr_.toText() << " is not an IPv6 address");
         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,
 Option6IAAddr::Option6IAAddr(uint32_t type, OptionBuffer::const_iterator begin,
                              OptionBuffer::const_iterator end)
                              OptionBuffer::const_iterator end)
     :Option(V6, type), addr_("::") {
     :Option(V6, type), addr_("::") {
+    setEncapsulatedSpace("dhcp6");
     unpack(begin, end);
     unpack(begin, end);
 }
 }
 
 
@@ -110,7 +112,7 @@ uint16_t Option6IAAddr::len() {
     // length of all suboptions
     // length of all suboptions
     // TODO implement:
     // TODO implement:
     // protected: unsigned short Option::lenHelper(int header_size);
     // 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 != options_.end();
          ++it) {
          ++it) {
         length += (*it).second->len();
         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,
 Option6IAPrefix::Option6IAPrefix(uint16_t type, const isc::asiolink::IOAddress& prefix,
                                  uint8_t prefix_len, uint32_t pref, uint32_t valid)
                                  uint8_t prefix_len, uint32_t pref, uint32_t valid)
     :Option6IAAddr(type, prefix, pref, valid), prefix_len_(prefix_len) {
     :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
     // Option6IAAddr will check if prefix is IPv6 and will throw if it is not
     if (prefix_len > 128) {
     if (prefix_len > 128) {
         isc_throw(BadValue, prefix_len << " is not a valid prefix length. "
         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,
 Option6IAPrefix::Option6IAPrefix(uint32_t type, OptionBuffer::const_iterator begin,
                              OptionBuffer::const_iterator end)
                              OptionBuffer::const_iterator end)
     :Option6IAAddr(type, begin, end) {
     :Option6IAAddr(type, begin, end) {
+    setEncapsulatedSpace("dhcp6");
     unpack(begin, end);
     unpack(begin, end);
 }
 }
 
 
@@ -113,7 +115,7 @@ uint16_t Option6IAPrefix::len() {
     uint16_t length = OPTION6_HDR_LEN + OPTION6_IAPREFIX_LEN;
     uint16_t length = OPTION6_HDR_LEN + OPTION6_IAPREFIX_LEN;
 
 
     // length of all suboptions
     // length of all suboptions
-    for (Option::OptionCollection::const_iterator it = options_.begin();
+    for (OptionCollection::const_iterator it = options_.begin();
          it != options_.end(); ++it) {
          it != options_.end(); ++it) {
         length += (*it).second->len();
         length += (*it).second->len();
     }
     }

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

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

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

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

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

@@ -310,12 +310,17 @@ public:
     /// @param type option type.
     /// @param type option type.
     /// @param begin beginning of the option buffer.
     /// @param begin beginning of the option buffer.
     /// @param end end of the option buffer.
     /// @param end end of the option buffer.
+    /// @param callback An instance of the function which parses packet options.
+    /// If this is set to non NULL value this function will be used instead of
+    /// @c isc::dhcp::LibDHCP::unpackOptions6 and
+    /// isc::dhcp::LibDHCP::unpackOptions4.
     ///
     ///
     /// @return instance of the DHCP option.
     /// @return instance of the DHCP option.
     /// @throw InvalidOptionValue if data for the option is invalid.
     /// @throw InvalidOptionValue if data for the option is invalid.
     OptionPtr optionFactory(Option::Universe u, uint16_t type,
     OptionPtr optionFactory(Option::Universe u, uint16_t type,
                             OptionBufferConstIter begin,
                             OptionBufferConstIter begin,
-                            OptionBufferConstIter end) const;
+                            OptionBufferConstIter end,
+                            UnpackOptionsCallback callback = NULL) const;
 
 
     /// @brief Option factory.
     /// @brief Option factory.
     ///
     ///
@@ -330,11 +335,16 @@ public:
     /// @param u option universe (V4 or V6).
     /// @param u option universe (V4 or V6).
     /// @param type option type.
     /// @param type option type.
     /// @param buf option buffer.
     /// @param buf option buffer.
+    /// @param callback An instance of the function which parses packet options.
+    /// If this is set to non NULL value this function will be used instead of
+    /// @c isc::dhcp::LibDHCP::unpackOptions6 and
+    /// isc::dhcp::LibDHCP::unpackOptions4.
     ///
     ///
     /// @return instance of the DHCP option.
     /// @return instance of the DHCP option.
     /// @throw InvalidOptionValue if data for the option is invalid.
     /// @throw InvalidOptionValue if data for the option is invalid.
     OptionPtr optionFactory(Option::Universe u, uint16_t type,
     OptionPtr optionFactory(Option::Universe u, uint16_t type,
-                            const OptionBuffer& buf = OptionBuffer()) const;
+                            const OptionBuffer& buf = OptionBuffer(),
+                            UnpackOptionsCallback callback = NULL) const;
 
 
     /// @brief Option factory.
     /// @brief Option factory.
     ///
     ///
@@ -437,16 +447,28 @@ public:
     ///
     ///
     /// @param u universe (V4 or V6).
     /// @param u universe (V4 or V6).
     /// @param type option type.
     /// @param type option type.
+    /// @param encapsulated_space An option space being encapsulated by the
+    /// options created by this factory function. The options which belong to
+    /// encapsulated option space are sub options of this option.
     /// @param begin iterator pointing to the beginning of the buffer.
     /// @param begin iterator pointing to the beginning of the buffer.
     /// @param end iterator pointing to the end of the buffer.
     /// @param end iterator pointing to the end of the buffer.
+    /// @param callback An instance of the function which parses packet options.
+    /// If this is set to non NULL value this function will be used instead of
+    /// @c isc::dhcp::LibDHCP::unpackOptions6 and
+    /// isc::dhcp::LibDHCP::unpackOptions4.
     /// @tparam T type of the data field (must be one of the uintX_t or intX_t).
     /// @tparam T type of the data field (must be one of the uintX_t or intX_t).
     ///
     ///
     /// @throw isc::OutOfRange if provided option buffer length is invalid.
     /// @throw isc::OutOfRange if provided option buffer length is invalid.
     template<typename T>
     template<typename T>
     static OptionPtr factoryInteger(Option::Universe u, uint16_t type,
     static OptionPtr factoryInteger(Option::Universe u, uint16_t type,
+                                    const std::string& encapsulated_space,
                                     OptionBufferConstIter begin,
                                     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);
         return (option);
     }
     }
 
 

+ 6 - 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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -47,11 +47,13 @@ public:
     ///
     ///
     /// @throw isc::dhcp::InvalidDataType if data field type provided
     /// @throw isc::dhcp::InvalidDataType if data field type provided
     /// as template parameter is not a supported integer type.
     /// as template parameter is not a supported integer type.
+    /// @todo Extend constructor to set encapsulated option space name.
     OptionInt(Option::Universe u, uint16_t type, T value)
     OptionInt(Option::Universe u, uint16_t type, T value)
         : Option(u, type), value_(value) {
         : Option(u, type), value_(value) {
         if (!OptionDataTypeTraits<T>::integer_type) {
         if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
         }
+        setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
     }
     }
 
 
     /// @brief Constructor.
     /// @brief Constructor.
@@ -68,12 +70,14 @@ public:
     /// @throw isc::OutOfRange if provided buffer is shorter than data size.
     /// @throw isc::OutOfRange if provided buffer is shorter than data size.
     /// @throw isc::dhcp::InvalidDataType if data field type provided
     /// @throw isc::dhcp::InvalidDataType if data field type provided
     /// as template parameter is not a supported integer type.
     /// as template parameter is not a supported integer type.
+    /// @todo Extend constructor to set encapsulated option space name.
     OptionInt(Option::Universe u, uint16_t type, OptionBufferConstIter begin,
     OptionInt(Option::Universe u, uint16_t type, OptionBufferConstIter begin,
                OptionBufferConstIter end)
                OptionBufferConstIter end)
         : Option(u, type) {
         : Option(u, type) {
         if (!OptionDataTypeTraits<T>::integer_type) {
         if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
         }
+        setEncapsulatedSpace(u == Option::V4 ? "dhcp4" : "dhcp6");
         unpack(begin, end);
         unpack(begin, end);
     }
     }
 
 
@@ -175,7 +179,7 @@ public:
         // The data length is equal to size of T.
         // The data length is equal to size of T.
         length += sizeof(T);;
         length += sizeof(T);;
         // length of all suboptions
         // length of all suboptions
-        for (Option::OptionCollection::iterator it = options_.begin();
+        for (OptionCollection::iterator it = options_.begin();
              it != options_.end();
              it != options_.end();
              ++it) {
              ++it) {
             length += (*it).second->len();
             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
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // 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;
         uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
         length += values_.size() * sizeof(T);
         length += values_.size() * sizeof(T);
         // length of all suboptions
         // length of all suboptions
-        for (Option::OptionCollection::iterator it = options_.begin();
+        for (OptionCollection::iterator it = options_.begin();
              it != options_.end();
              it != options_.end();
              ++it) {
              ++it) {
             length += (*it).second->len();
             length += (*it).second->len();

+ 38 - 29
src/lib/dhcp/pkt4.cc

@@ -93,7 +93,7 @@ Pkt4::len() {
     size_t length = DHCPV4_PKT_HDR_LEN; // DHCPv4 header
     size_t length = DHCPV4_PKT_HDR_LEN; // DHCPv4 header
 
 
     // ... and sum of lengths of all options
     // ... 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 != options_.end();
          ++it) {
          ++it) {
         length += (*it).second->len();
         length += (*it).second->len();
@@ -162,58 +162,67 @@ void
 Pkt4::unpack() {
 Pkt4::unpack() {
 
 
     // input buffer (used during message reception)
     // input buffer (used during message reception)
-    isc::util::InputBuffer bufferIn(&data_[0], data_.size());
+    isc::util::InputBuffer buffer_in(&data_[0], data_.size());
 
 
-    if (bufferIn.getLength() < DHCPV4_PKT_HDR_LEN) {
+    if (buffer_in.getLength() < DHCPV4_PKT_HDR_LEN) {
         isc_throw(OutOfRange, "Received truncated DHCPv4 packet (len="
         isc_throw(OutOfRange, "Received truncated DHCPv4 packet (len="
-                  << bufferIn.getLength() << " received, at least "
+                  << buffer_in.getLength() << " received, at least "
                   << DHCPV4_PKT_HDR_LEN << "is expected");
                   << DHCPV4_PKT_HDR_LEN << "is expected");
     }
     }
 
 
-    op_ = bufferIn.readUint8();
-    uint8_t htype = bufferIn.readUint8();
-    uint8_t hlen = bufferIn.readUint8();
-    hops_ = bufferIn.readUint8();
-    transid_ = bufferIn.readUint32();
-    secs_ = bufferIn.readUint16();
-    flags_ = bufferIn.readUint16();
-    ciaddr_ = IOAddress(bufferIn.readUint32());
-    yiaddr_ = IOAddress(bufferIn.readUint32());
-    siaddr_ = IOAddress(bufferIn.readUint32());
-    giaddr_ = IOAddress(bufferIn.readUint32());
+    op_ = buffer_in.readUint8();
+    uint8_t htype = buffer_in.readUint8();
+    uint8_t hlen = buffer_in.readUint8();
+    hops_ = buffer_in.readUint8();
+    transid_ = buffer_in.readUint32();
+    secs_ = buffer_in.readUint16();
+    flags_ = buffer_in.readUint16();
+    ciaddr_ = IOAddress(buffer_in.readUint32());
+    yiaddr_ = IOAddress(buffer_in.readUint32());
+    siaddr_ = IOAddress(buffer_in.readUint32());
+    giaddr_ = IOAddress(buffer_in.readUint32());
 
 
     vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0);
     vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0);
-    bufferIn.readVector(hw_addr, MAX_CHADDR_LEN);
-    bufferIn.readData(sname_, MAX_SNAME_LEN);
-    bufferIn.readData(file_, MAX_FILE_LEN);
+    buffer_in.readVector(hw_addr, MAX_CHADDR_LEN);
+    buffer_in.readData(sname_, MAX_SNAME_LEN);
+    buffer_in.readData(file_, MAX_FILE_LEN);
 
 
     hw_addr.resize(hlen);
     hw_addr.resize(hlen);
 
 
     hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype));
     hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype));
 
 
-    if (bufferIn.getLength() == bufferIn.getPosition()) {
+    if (buffer_in.getLength() == buffer_in.getPosition()) {
         // this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
         // this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
         // particular, it does not have magic cookie, a 4 byte sequence that
         // particular, it does not have magic cookie, a 4 byte sequence that
         // differentiates between DHCP and BOOTP packets.
         // differentiates between DHCP and BOOTP packets.
         isc_throw(InvalidOperation, "Received BOOTP packet. BOOTP is not supported.");
         isc_throw(InvalidOperation, "Received BOOTP packet. BOOTP is not supported.");
     }
     }
 
 
-    if (bufferIn.getLength() - bufferIn.getPosition() < 4) {
+    if (buffer_in.getLength() - buffer_in.getPosition() < 4) {
       // there is not enough data to hold magic DHCP cookie
       // there is not enough data to hold magic DHCP cookie
       isc_throw(Unexpected, "Truncated or no DHCP packet.");
       isc_throw(Unexpected, "Truncated or no DHCP packet.");
     }
     }
 
 
-    uint32_t magic = bufferIn.readUint32();
+    uint32_t magic = buffer_in.readUint32();
     if (magic != DHCP_OPTIONS_COOKIE) {
     if (magic != DHCP_OPTIONS_COOKIE) {
       isc_throw(Unexpected, "Invalid or missing DHCP magic cookie");
       isc_throw(Unexpected, "Invalid or missing DHCP magic cookie");
     }
     }
 
 
-    size_t opts_len = bufferIn.getLength() - bufferIn.getPosition();
-    vector<uint8_t> optsBuffer;
+    size_t opts_len = buffer_in.getLength() - buffer_in.getPosition();
+    vector<uint8_t> opts_buffer;
 
 
-    // First use of readVector.
-    bufferIn.readVector(optsBuffer, opts_len);
-    LibDHCP::unpackOptions4(optsBuffer, options_);
+    // Use readVector because a function which parses option requires
+    // a vector as an input.
+    buffer_in.readVector(opts_buffer, opts_len);
+    if (callback_.empty()) {
+        LibDHCP::unpackOptions4(opts_buffer, options_);
+    } else {
+        // The last two arguments are set to NULL because they are
+        // specific to DHCPv6 options parsing. They are unused for
+        // DHCPv4 case. In DHCPv6 case they hold are the relay message
+        // offset and length.
+        callback_(opts_buffer, "dhcp4", options_, NULL, NULL);
+    }
 
 
     // @todo check will need to be called separately, so hooks can be called
     // @todo check will need to be called separately, so hooks can be called
     // after the packet is parsed, but before its content is verified
     // after the packet is parsed, but before its content is verified
@@ -270,7 +279,7 @@ Pkt4::toText() {
         << ":" << remote_port_ << ", msgtype=" << static_cast<int>(getType())
         << ":" << remote_port_ << ", msgtype=" << static_cast<int>(getType())
         << ", transid=0x" << hex << transid_ << dec << endl;
         << ", 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 != options_.end();
          ++opt) {
          ++opt) {
         tmp << "  " << opt->second->toText() << std::endl;
         tmp << "  " << opt->second->toText() << std::endl;
@@ -428,7 +437,7 @@ Pkt4::addOption(boost::shared_ptr<Option> opt) {
 
 
 boost::shared_ptr<isc::dhcp::Option>
 boost::shared_ptr<isc::dhcp::Option>
 Pkt4::getOption(uint8_t type) const {
 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()) {
     if (x != options_.end()) {
         return (*x).second;
         return (*x).second;
     }
     }
@@ -437,7 +446,7 @@ Pkt4::getOption(uint8_t type) const {
 
 
 bool
 bool
 Pkt4::delOption(uint8_t type) {
 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()) {
     if (x != options_.end()) {
         options_.erase(x);
         options_.erase(x);
         return (true); // delete successful
         return (true); // delete successful

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

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

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

@@ -14,6 +14,7 @@
 
 
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
 #include <exceptions/exceptions.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.");
                   << " 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) {
          it != relay_info_[relay_level].options_.end(); ++it) {
         if ((*it).second->getType() == opt_type) {
         if ((*it).second->getType() == opt_type) {
             return (it->second);
             return (it->second);
@@ -148,7 +149,7 @@ uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const {
     uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
     uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
         + Option::OPTION6_HDR_LEN; // header of the relay-msg option
         + 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) {
          opt != relay.options_.end(); ++opt) {
         len += (opt->second)->len();
         len += (opt->second)->len();
     }
     }
@@ -171,7 +172,7 @@ uint16_t Pkt6::calculateRelaySizes() {
 uint16_t Pkt6::directLen() const {
 uint16_t Pkt6::directLen() const {
     uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
     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 != options_.end();
          ++it) {
          ++it) {
         length += (*it).second->len();
         length += (*it).second->len();
@@ -226,7 +227,7 @@ Pkt6::packUDP() {
                 // present here as well (vendor-opts for Cable modems,
                 // present here as well (vendor-opts for Cable modems,
                 // subscriber-id, remote-id, options echoed back from Echo
                 // subscriber-id, remote-id, options echoed back from Echo
                 // Request Option, etc.)
                 // Request Option, etc.)
-                for (Option::OptionCollection::const_iterator opt =
+                for (OptionCollection::const_iterator opt =
                          relay->options_.begin();
                          relay->options_.begin();
                      opt != relay->options_.end(); ++opt) {
                      opt != relay->options_.end(); ++opt) {
                     (opt->second)->pack(bufferOut_);
                     (opt->second)->pack(bufferOut_);
@@ -324,7 +325,16 @@ Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
     try {
     try {
         OptionBuffer opt_buffer(begin, end);
         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 {
+            // The last two arguments hold the DHCPv6 Relay message offset and
+            // length. Setting them to NULL because we are dealing with the
+            // not-relayed message.
+            callback_(opt_buffer, "dhcp6", options_, NULL, NULL);
+        }
     } catch (const Exception& e) {
     } catch (const Exception& e) {
         // @todo: throw exception here once we turn this function to void.
         // @todo: throw exception here once we turn this function to void.
         return (false);
         return (false);
@@ -361,8 +371,16 @@ Pkt6::unpackRelayMsg() {
         try {
         try {
             // parse the rest as options
             // parse the rest as options
             OptionBuffer opt_buffer(&data_[offset], &data_[offset+bufsize]);
             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
             /// @todo: check that each option appears at most once
             //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
             //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
@@ -438,7 +456,7 @@ Pkt6::toText() {
         << "]:" << remote_port_ << endl;
         << "]:" << remote_port_ << endl;
     tmp << "msgtype=" << static_cast<int>(msg_type_) << ", transid=0x" <<
     tmp << "msgtype=" << static_cast<int>(msg_type_) << ", transid=0x" <<
         hex << transid_ << dec << endl;
         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 != options_.end();
          ++opt) {
          ++opt) {
         tmp << opt->second->toText() << std::endl;
         tmp << opt->second->toText() << std::endl;
@@ -448,18 +466,18 @@ Pkt6::toText() {
 
 
 OptionPtr
 OptionPtr
 Pkt6::getOption(uint16_t opt_type) {
 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()) {
     if (x!=options_.end()) {
         return (*x).second;
         return (*x).second;
     }
     }
     return OptionPtr(); // NULL
     return OptionPtr(); // NULL
 }
 }
 
 
-isc::dhcp::Option::OptionCollection
+isc::dhcp::OptionCollection
 Pkt6::getOptions(uint16_t opt_type) {
 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) {
          x != options_.end(); ++x) {
         if (x->first == opt_type) {
         if (x->first == opt_type) {
             found.insert(make_pair(opt_type, x->second));
             found.insert(make_pair(opt_type, x->second));
@@ -475,7 +493,7 @@ Pkt6::addOption(const OptionPtr& opt) {
 
 
 bool
 bool
 Pkt6::delOption(uint16_t type) {
 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()) {
     if (x!=options_.end()) {
         options_.erase(x);
         options_.erase(x);
         return (true); // delete successful
         return (true); // delete successful

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

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

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

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

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

@@ -15,10 +15,12 @@
 #include <config.h>
 #include <config.h>
 
 
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 #include <util/buffer.h>
 
 
+#include <boost/bind.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
@@ -35,6 +37,66 @@ using namespace isc::util;
 using boost::scoped_ptr;
 using boost::scoped_ptr;
 
 
 namespace {
 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 {
 class OptionTest : public ::testing::Test {
 public:
 public:
     OptionTest(): buf_(255), outBuf_(255) {
     OptionTest(): buf_(255), outBuf_(255) {
@@ -505,4 +567,73 @@ TEST_F(OptionTest, equal) {
 
 
     EXPECT_TRUE(opt2->equal(opt5));
     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 = 2
+        0x00, 0x01,  // sub option data (2 bytes)
+        0x00, 0x02,  // sub option code = 2
+        0x00, 0x02,  // sub option length = 2
+        0x01, 0x01   // sub option data (2 bytes)
+    };
+    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;
+    // Parameters from _1 to _5 are placeholders for the actual values
+    // to be passed to the callback function. See: boost::bind documentation
+    // at http://www.boost.org/doc/libs/1_54_0/libs/bind/bind.html.
+    // Also, see UnpackOptionsCallback in option.h for description of the
+    // parameter values.
+    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 <asiolink/io_address.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp4.h>
+#include <dhcp/libdhcp++.h>
 #include <dhcp/option_string.h>
 #include <dhcp/option_string.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/pkt4.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
@@ -42,40 +43,58 @@ using boost::scoped_ptr;
 
 
 namespace {
 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
 // Sample data
 const uint8_t dummyOp = BOOTREQUEST;
 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(dummyFile)  == Pkt4::MAX_FILE_LEN + 1);
 BOOST_STATIC_ASSERT(sizeof(dummySname) == Pkt4::MAX_SNAME_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();
     boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
 
 
@@ -215,7 +331,7 @@ TEST(Pkt4Test, fixedFields) {
     EXPECT_EQ(DHCPDISCOVER, pkt->getType());
     EXPECT_EQ(DHCPDISCOVER, pkt->getType());
 }
 }
 
 
-TEST(Pkt4Test, fixedFieldsPack) {
+TEST_F(Pkt4Test, fixedFieldsPack) {
     boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
     boost::shared_ptr<Pkt4> pkt = generateTestPacket1();
     vector<uint8_t> expectedFormat = generateTestPacket2();
     vector<uint8_t> expectedFormat = generateTestPacket2();
 
 
@@ -235,7 +351,7 @@ TEST(Pkt4Test, fixedFieldsPack) {
 }
 }
 
 
 /// TODO Uncomment when ticket #1226 is implemented
 /// TODO Uncomment when ticket #1226 is implemented
-TEST(Pkt4Test, fixedFieldsUnpack) {
+TEST_F(Pkt4Test, fixedFieldsUnpack) {
     vector<uint8_t> expectedFormat = generateTestPacket2();
     vector<uint8_t> expectedFormat = generateTestPacket2();
 
 
     expectedFormat.push_back(0x63); // magic cookie
     expectedFormat.push_back(0x63); // magic cookie
@@ -282,7 +398,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
 }
 }
 
 
 // This test is for hardware addresses (htype, hlen and chaddr fields)
 // This test is for hardware addresses (htype, hlen and chaddr fields)
-TEST(Pkt4Test, hwAddr) {
+TEST_F(Pkt4Test, hwAddr) {
 
 
     vector<uint8_t> mac;
     vector<uint8_t> mac;
     uint8_t expectedChaddr[Pkt4::MAX_CHADDR_LEN];
     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
     /// longer than 16 bytes should be stored in client-identifier option
 }
 }
 
 
-TEST(Pkt4Test, msgTypes) {
+TEST_F(Pkt4Test, msgTypes) {
 
 
     struct msgType {
     struct msgType {
         uint8_t dhcp;
         uint8_t dhcp;
@@ -366,7 +482,7 @@ TEST(Pkt4Test, msgTypes) {
 }
 }
 
 
 // This test verifies handling of sname field
 // This test verifies handling of sname field
-TEST(Pkt4Test, sname) {
+TEST_F(Pkt4Test, sname) {
 
 
     uint8_t sname[Pkt4::MAX_SNAME_LEN];
     uint8_t sname[Pkt4::MAX_SNAME_LEN];
 
 
@@ -404,7 +520,7 @@ TEST(Pkt4Test, sname) {
     EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter);
     EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter);
 }
 }
 
 
-TEST(Pkt4Test, file) {
+TEST_F(Pkt4Test, file) {
 
 
     uint8_t file[Pkt4::MAX_FILE_LEN];
     uint8_t file[Pkt4::MAX_FILE_LEN];
 
 
@@ -442,20 +558,7 @@ TEST(Pkt4Test, file) {
     EXPECT_THROW(pkt4.setFile(NULL, 0), InvalidParameter);
     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));
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 0));
 
 
     vector<uint8_t> payload[5];
     vector<uint8_t> payload[5];
@@ -496,10 +599,10 @@ TEST(Pkt4Test, options) {
     );
     );
 
 
     const OutputBuffer& buf = pkt->getBuffer();
     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)
     // DHCP magic cookie (4 bytes), and OPTION_END added (just one byte)
     ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) +
     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());
               buf.getLength());
 
 
     // That that this extra data actually contain our options
     // That that this extra data actually contain our options
@@ -508,8 +611,8 @@ TEST(Pkt4Test, options) {
     // Rewind to end of fixed part.
     // Rewind to end of fixed part.
     ptr += Pkt4::DHCPV4_PKT_HDR_LEN + sizeof(DHCP_OPTIONS_COOKIE);
     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
     // delOption() checks
     EXPECT_TRUE(pkt->getOption(12));  // Sanity check: option 12 is still there
     EXPECT_TRUE(pkt->getOption(12));  // Sanity check: option 12 is still there
@@ -520,7 +623,8 @@ TEST(Pkt4Test, options) {
     EXPECT_NO_THROW(pkt.reset());
     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();
     vector<uint8_t> expectedFormat = generateTestPacket2();
 
 
@@ -529,8 +633,8 @@ TEST(Pkt4Test, unpackOptions) {
     expectedFormat.push_back(0x53);
     expectedFormat.push_back(0x53);
     expectedFormat.push_back(0x63);
     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
     // now expectedFormat contains fixed format and 5 options
@@ -542,59 +646,53 @@ TEST(Pkt4Test, unpackOptions) {
         pkt->unpack()
         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
 // This test verifies methods that are used for manipulating meta fields
 // i.e. fields that are not part of DHCPv4 (e.g. interface name).
 // 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));
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     pkt->setIface("loooopback");
     pkt->setIface("loooopback");
@@ -608,7 +706,7 @@ TEST(Pkt4Test, metaFields) {
     EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
     EXPECT_EQ("4.3.2.1", pkt->getLocalAddr().toText());
 }
 }
 
 
-TEST(Pkt4Test, Timestamp) {
+TEST_F(Pkt4Test, Timestamp) {
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
 
 
     // Just after construction timestamp is invalid
     // Just after construction timestamp is invalid
@@ -634,7 +732,7 @@ TEST(Pkt4Test, Timestamp) {
     EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
     EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
 }
 }
 
 
-TEST(Pkt4Test, hwaddr) {
+TEST_F(Pkt4Test, hwaddr) {
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
     const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
     const uint8_t hw_type = 123; // hardware type
     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
 // This test verifies that the packet remte and local HW address can
 // be set and returned.
 // be set and returned.
-TEST(Pkt4Test, hwaddrSrcRemote) {
+TEST_F(Pkt4Test, hwaddrSrcRemote) {
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
     const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
     const uint8_t src_hw[] = { 1, 2, 3, 4, 5, 6 };
     const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };
     const uint8_t dst_hw[] = { 7, 8, 9, 10, 11, 12 };

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

@@ -24,6 +24,7 @@
 #include <dhcp/pkt6.h>
 #include <dhcp/pkt6.h>
 #include <util/range_utilities.h>
 #include <util/range_utilities.h>
 
 
+#include <boost/bind.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <util/encode/hex.h>
 #include <util/encode/hex.h>
@@ -41,7 +42,54 @@ using namespace isc::dhcp;
 using boost::scoped_ptr;
 using boost::scoped_ptr;
 
 
 namespace {
 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 {
 class Pkt6Test : public ::testing::Test {
 public:
 public:
     Pkt6Test() {
     Pkt6Test() {
@@ -58,6 +106,51 @@ public:
         util::fillRandom(data.begin(), data.end());
         util::fillRandom(data.begin(), data.end());
         return OptionPtr(new Option(Option::V6, code, data));
         return OptionPtr(new Option(Option::V6, code, data));
     }
     }
+
+    /// @brief Create a wire representation of the test packet and clone it.
+    ///
+    /// The purpose of this function is to create a packet to be used to
+    /// check that packet parsing works correctly. The unpack() function
+    /// requires that the data_ field of the object holds the data to be
+    /// parsed. This function creates an on-wire representation of the
+    /// packet by calling pack(). But, the pack() function stores the
+    /// on-wire representation into the output buffer (not the data_ field).
+    /// For this reason, it is not enough to return the packet on which
+    /// pack() is called. This function returns a clone of this packet
+    /// which is created using a constructor taking a buffer and buffer
+    /// length as an input. This constructor is normally used to parse
+    /// received packets. It stores the packet in a data_ field and
+    /// therefore unpack() can be called to parse 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) {
 TEST_F(Pkt6Test, constructor) {
@@ -204,44 +297,62 @@ TEST_F(Pkt6Test, unpack_solicit1) {
 }
 }
 
 
 TEST_F(Pkt6Test, packUnpack) {
 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
     // 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
     // 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_EQ(DHCPV6_SOLICIT, clone->getType());
 
 
     EXPECT_TRUE(clone->getOption(1));
     EXPECT_TRUE(clone->getOption(1));
     EXPECT_TRUE(clone->getOption(2));
     EXPECT_TRUE(clone->getOption(2));
     EXPECT_TRUE(clone->getOption(100));
     EXPECT_TRUE(clone->getOption(100));
     EXPECT_FALSE(clone->getOption(4));
     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
 // This test verifies that options can be added (addOption()), retrieved
@@ -266,12 +377,12 @@ TEST_F(Pkt6Test, addGetDelOptions) {
     // Now there are 2 options of type 2
     // Now there are 2 options of type 2
     parent->addOption(opt3);
     parent->addOption(opt3);
 
 
-    Option::OptionCollection options = parent->getOptions(2);
+    OptionCollection options = parent->getOptions(2);
     EXPECT_EQ(2, options.size()); // there should be 2 instances
     EXPECT_EQ(2, options.size()); // there should be 2 instances
 
 
     // Both options must be of type 2 and there must not be
     // Both options must be of type 2 and there must not be
     // any other type returned
     // any other type returned
-    for (Option::OptionCollection::const_iterator x= options.begin();
+    for (OptionCollection::const_iterator x= options.begin();
          x != options.end(); ++x) {
          x != options.end(); ++x) {
         EXPECT_EQ(2, x->second->getType());
         EXPECT_EQ(2, x->second->getType());
     }
     }

+ 6 - 6
tests/tools/perfdhcp/pkt_transform.cc

@@ -32,7 +32,7 @@ namespace perfdhcp {
 bool
 bool
 PktTransform::pack(const Option::Universe universe,
 PktTransform::pack(const Option::Universe universe,
                    const OptionBuffer& in_buffer,
                    const OptionBuffer& in_buffer,
-                   const Option::OptionCollection& options,
+                   const OptionCollection& options,
                    const size_t transid_offset,
                    const size_t transid_offset,
                    const uint32_t transid,
                    const uint32_t transid,
                    util::OutputBuffer& out_buffer) {
                    util::OutputBuffer& out_buffer) {
@@ -75,7 +75,7 @@ PktTransform::pack(const Option::Universe universe,
 bool
 bool
 PktTransform::unpack(const Option::Universe universe,
 PktTransform::unpack(const Option::Universe universe,
                      const OptionBuffer& in_buffer,
                      const OptionBuffer& in_buffer,
-                     const Option::OptionCollection& options,
+                     const OptionCollection& options,
                      const size_t transid_offset,
                      const size_t transid_offset,
                      uint32_t& transid) {
                      uint32_t& transid) {
 
 
@@ -113,13 +113,13 @@ PktTransform::unpack(const Option::Universe universe,
 
 
 void
 void
 PktTransform::packOptions(const OptionBuffer& in_buffer,
 PktTransform::packOptions(const OptionBuffer& in_buffer,
-                          const Option::OptionCollection& options,
+                          const OptionCollection& options,
                           util::OutputBuffer& out_buffer) {
                           util::OutputBuffer& out_buffer) {
     try {
     try {
         // If there are any options on the list, we will use provided
         // If there are any options on the list, we will use provided
         // options offsets to override them in the output buffer
         // options offsets to override them in the output buffer
         // with new contents.
         // with new contents.
-        for (Option::OptionCollection::const_iterator it = options.begin();
+        for (OptionCollection::const_iterator it = options.begin();
              it != options.end(); ++it) {
              it != options.end(); ++it) {
             // Get options with their position (offset).
             // Get options with their position (offset).
             boost::shared_ptr<LocalizedOption> option =
             boost::shared_ptr<LocalizedOption> option =
@@ -157,8 +157,8 @@ PktTransform::packOptions(const OptionBuffer& in_buffer,
 
 
 void
 void
 PktTransform::unpackOptions(const OptionBuffer& in_buffer,
 PktTransform::unpackOptions(const OptionBuffer& in_buffer,
-                            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 != options.end(); ++it) {
 
 
         boost::shared_ptr<LocalizedOption> option =
         boost::shared_ptr<LocalizedOption> option =

+ 4 - 4
tests/tools/perfdhcp/pkt_transform.h

@@ -66,7 +66,7 @@ public:
     /// \return false, if pack operation failed.
     /// \return false, if pack operation failed.
     static bool pack(const dhcp::Option::Universe universe,
     static bool pack(const dhcp::Option::Universe universe,
                      const dhcp::OptionBuffer& in_buffer,
                      const dhcp::OptionBuffer& in_buffer,
-                     const dhcp::Option::OptionCollection& options,
+                     const dhcp::OptionCollection& options,
                      const size_t transid_offset,
                      const size_t transid_offset,
                      const uint32_t transid,
                      const uint32_t transid,
                      util::OutputBuffer& out_buffer);
                      util::OutputBuffer& out_buffer);
@@ -88,7 +88,7 @@ public:
     /// \return false, if unpack operation failed.
     /// \return false, if unpack operation failed.
     static bool unpack(const dhcp::Option::Universe universe,
     static bool unpack(const dhcp::Option::Universe universe,
                        const dhcp::OptionBuffer& in_buffer,
                        const dhcp::OptionBuffer& in_buffer,
-                       const dhcp::Option::OptionCollection& options,
+                       const dhcp::OptionCollection& options,
                        const size_t transid_offset,
                        const size_t transid_offset,
                        uint32_t& transid);
                        uint32_t& transid);
 
 
@@ -135,7 +135,7 @@ private:
     ///
     ///
     /// \throw isc::Unexpected if options update failed.
     /// \throw isc::Unexpected if options update failed.
     static void packOptions(const dhcp::OptionBuffer& in_buffer,
     static void packOptions(const dhcp::OptionBuffer& in_buffer,
-                            const dhcp::Option::OptionCollection& options,
+                            const dhcp::OptionCollection& options,
                             util::OutputBuffer& out_buffer);
                             util::OutputBuffer& out_buffer);
 
 
     /// \brief Reads contents of specified options from buffer.
     /// \brief Reads contents of specified options from buffer.
@@ -159,7 +159,7 @@ private:
     ///
     ///
     /// \throw isc::Unexpected if options unpack failed.
     /// \throw isc::Unexpected if options unpack failed.
     static void unpackOptions(const dhcp::OptionBuffer& in_buffer,
     static void unpackOptions(const dhcp::OptionBuffer& in_buffer,
-                              const dhcp::Option::OptionCollection& options);
+                              const dhcp::OptionCollection& options);
 
 
 };
 };