Browse Source

[4088] Finished merge of trac4091 from master

Francis Dupont 9 years ago
parent
commit
11bc4b3cfc
46 changed files with 1576 additions and 483 deletions
  1. 4 1
      AUTHORS
  2. 9 0
      ChangeLog
  3. 8 8
      doc/guide/dhcp4-srv.xml
  4. 12 1
      doc/guide/dhcp6-srv.xml
  5. 10 0
      doc/guide/logging.xml
  6. 45 2
      src/bin/dhcp4/json_config_parser.cc
  7. 272 156
      src/bin/dhcp4/tests/config_parser_unittest.cc
  8. 2 2
      src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
  9. 1 4
      src/bin/dhcp4/tests/decline_unittest.cc
  10. 9 36
      src/bin/dhcp4/tests/dora_unittest.cc
  11. 2 8
      src/bin/dhcp4/tests/fqdn_unittest.cc
  12. 8 32
      src/bin/dhcp4/tests/inform_unittest.cc
  13. 1 1
      src/bin/dhcp4/tests/kea_controller_unittest.cc
  14. 1 4
      src/bin/dhcp4/tests/release_unittest.cc
  15. 2 2
      src/bin/dhcp6/json_config_parser.cc
  16. 41 165
      src/bin/dhcp6/tests/config_parser_unittest.cc
  17. 2 2
      src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
  18. 4 18
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  19. 1 1
      src/bin/dhcp6/tests/kea_controller_unittest.cc
  20. 1 0
      src/lib/dhcp/Makefile.am
  21. 15 8
      src/lib/dhcp/dhcp6.h
  22. 11 0
      src/lib/dhcp/libdhcp++.cc
  23. 3 0
      src/lib/dhcp/libdhcp++.h
  24. 4 4
      src/lib/dhcp/pkt.cc
  25. 8 0
      src/lib/dhcp/pkt4.h
  26. 60 0
      src/lib/dhcp/pkt4o6.cc
  27. 83 0
      src/lib/dhcp/pkt4o6.h
  28. 10 0
      src/lib/dhcp/pkt6.cc
  29. 16 0
      src/lib/dhcp/std_option_defs.h
  30. 1 0
      src/lib/dhcp/tests/Makefile.am
  31. 6 0
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  32. 106 0
      src/lib/dhcp/tests/pkt4o6_unittest.cc
  33. 8 0
      src/lib/dhcp/tests/pkt6_unittest.cc
  34. 1 1
      src/lib/dhcpsrv/parsers/dhcp_parsers.cc
  35. 2 1
      src/lib/dhcpsrv/parsers/dhcp_parsers.h
  36. 88 0
      src/lib/dhcpsrv/subnet.h
  37. 109 18
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  38. 10 2
      src/lib/eval/Makefile.am
  39. 26 0
      src/lib/eval/eval_log.cc
  40. 49 0
      src/lib/eval/eval_log.h
  41. 5 0
      src/lib/eval/eval_messages.mes
  42. 5 2
      src/lib/eval/tests/Makefile.am
  43. 0 0
      src/lib/eval/tests/run_unittests.cc
  44. 320 0
      src/lib/eval/tests/token_unittest.cc
  45. 111 0
      src/lib/eval/token.cc
  46. 84 4
      src/lib/eval/token.h

+ 4 - 1
AUTHORS

@@ -76,12 +76,15 @@ We have received the following contributions:
  - David Gutierrez Rueda, CERN
    2014-12: Support for client link-address option in DHCPv6 (RFC6939)
 
- - Adam Kalmus, Gdank University of Technology
+ - Adam Kalmus, Gdansk University of Technology
    2014-12: Extract MAC address from DUID-LL and DUID-LLT types
    2015-01: Extract MAC address from remote-id
    2015-05: MySQL schema extended to cover host reservation
    2015-10: Common MySQL Connector Pool
 
+ - Jinmei Tatuya
+   2015-10: Pkt4o6 class improvements
+
 Kea uses log4cplus (http://sourceforge.net/projects/log4cplus/) for logging,
 Boost (http://www.boost.org/) library for almost everything, and can use Botan
 (http://botan.randombit.net/) or OpenSSL (https://www.openssl.org/) for

+ 9 - 0
ChangeLog

@@ -1,3 +1,12 @@
+1043.	[func]		fdupont
+	Implemented support for hex strings in client classification.
+	(Trac #4091, git 406153af95404adb96296df09ec6033b484586e3)
+
+1042.	[doc]		fdupont
+	User Guide: parameters having default values may be omitted in the
+	option definitions.
+	(Trac #3927, git c7460e849258ec77cf1215a2baf840d98f1ab77b)
+
 1041.	[func]		tomek
 	A new library, libkea-eval has been edded. It is not functional
 	yet, but its purpose is to provide a generic expression

+ 8 - 8
doc/guide/dhcp4-srv.xml

@@ -1160,14 +1160,14 @@ It is merely echoed by the server
       should be left blank.  Note that the above set of comments define the
       format of the new option and do not set its values.
       </para>
-      <note>
-        <para>
-          In the current release the default values are not propagated to the
-          parser when the new configuration is being set. Therefore, all
-          parameters must be specified at all times, even if their values are
-          left blank.
-        </para>
-      </note>
+
+      <para>The <command>name</command>, <command>code</command> and
+      <command>type</command> parameters are required, all others are
+      optional. The <command>array</command> default value is
+      <command>false</command>. The <command>record-types</command>
+      and <command>encapsulate</command> default values are blank
+      (i.e. ""). The default <command>space</command> is "dhcp4".
+      </para>
 
       <para>Once the new option format is defined, its value is set
       in the same way as for a standard option. For example the following

+ 12 - 1
doc/guide/dhcp6-srv.xml

@@ -961,6 +961,8 @@ temporarily override a list of interface names and listen on all interfaces.
 <row><entry>erp-local-domain-name</entry><entry>65</entry><entry>fqdn</entry><entry>false</entry></row>
 <row><entry>rsoo</entry><entry>66</entry><entry>empty</entry><entry>false</entry></row>
 <row><entry>client-linklayer-addr</entry><entry>79</entry><entry>binary</entry><entry>false</entry></row>
+<!-- <row><entry>dhcpv4-message</entry><entry>87</entry><entry>binary</entry><entry>false</entry></row> -->
+<row><entry>dhcp4o6-server-addr</entry><entry>88</entry><entry>ipv6-address</entry><entry>true</entry></row>
         </tbody>
         </tgroup>
       </table>
@@ -1022,6 +1024,15 @@ temporarily override a list of interface names and listen on all interfaces.
       set of comments define the format of the new option and do not set its
       values.
       </para>
+
+      <para>The <command>name</command>, <command>code</command> and
+      <command>type</command> parameters are required, all others are
+      optional. The <command>array</command> default value is
+      <command>false</command>. The <command>record-types</command>
+      and <command>encapsulate</command> default values are blank
+      (i.e. ""). The default <command>space</command> is "dhcp6".
+      </para>
+
       <para>Once the new option format is defined, its value is set
       in the same way as for a standard option. For example the following
       commands set a global value that applies to all subnets.
@@ -1059,7 +1070,7 @@ temporarily override a list of interface names and listen on all interfaces.
             "space": "dhcp6",
             "type": "record",
             "array": false,
-            "record-types": "ipv4-address, uint16, boolean, string",
+            "record-types": "ipv6-address, uint16, boolean, string",
             "encapsulate": ""</userinput>
         }, ...
     ],

+ 10 - 0
doc/guide/logging.xml

@@ -220,6 +220,11 @@
             logger for the libdhcpsrv library.</simpara>
           </listitem>
           <listitem>
+            <simpara><command>kea-dhcp4.eval</command> - this logger is used
+            to log messages relating to the client classification expression
+            evaluation code.</simpara>
+          </listitem>
+          <listitem>
             <simpara><command>kea-dhcp4.hooks</command> - this logger is used
             to log messages related to management of hooks libraries, e.g.
             registration and deregistration of the libraries, and to the
@@ -303,6 +308,11 @@
             logger for the libdhcpsrv library.</simpara>
           </listitem>
           <listitem>
+            <simpara><command>kea-dhcp6.eval</command> - this logger is used
+            to log messages relating to the client classification expression
+            evaluation code.</simpara>
+          </listitem>
+          <listitem>
             <simpara><command>kea-dhcp6.hooks</command> - this logger is used
             to log messages related to management of hooks libraries, e.g.
             registration and deregistration of the libraries, and to the

+ 45 - 2
src/bin/dhcp4/json_config_parser.cc

@@ -200,6 +200,12 @@ protected:
             parser = new OptionDataListParser(config_id, options_, AF_INET);
         } else if (config_id.compare("match-client-id") == 0) {
             parser = new BooleanParser(config_id, boolean_values_);
+        } else if (config_id.compare("4o6-subnet") == 0) {
+            parser = new StringParser(config_id, string_values_);
+        } else if (config_id.compare("4o6-interface") == 0) {
+            parser = new StringParser(config_id, string_values_);
+        } else if (config_id.compare("4o6-interface-id") == 0) {
+            parser = new StringParser(config_id, string_values_);
         } else {
             isc_throw(NotImplemented, "unsupported parameter: " << config_id);
         }
@@ -305,6 +311,43 @@ protected:
                       << ")");
         }
 
+        // Try 4o6 specific parameter: 4o6-interface
+        string iface4o6 = string_values_->getOptionalParam("4o6-interface", "");
+        if (!iface4o6.empty()) {
+            subnet4->get4o6().setIface4o6(iface4o6);
+            subnet4->get4o6().enabled(true);
+        }
+
+        // Try 4o6 specific parameter: 4o6-subnet
+        string subnet4o6 = string_values_->getOptionalParam("4o6-subnet", "");
+        if (!subnet4o6.empty()) {
+            size_t slash = subnet4o6.find("/");
+            if (slash == std::string::npos) {
+                isc_throw(DhcpConfigError, "Missing / in the 4o6-subnet parameter:"
+                          + subnet4o6 +", expected format: prefix6/length");
+            }
+            string prefix = subnet4o6.substr(0, slash);
+            string lenstr = subnet4o6.substr(slash + 1);
+
+            uint8_t len = 128;
+            try {
+                len = boost::lexical_cast<unsigned int>(lenstr.c_str());
+            } catch (const boost::bad_lexical_cast &) {
+                isc_throw(DhcpConfigError, "Invalid prefix length specified in "
+                          "4o6-subnet parameter: " + subnet4o6 + ", expected 0..128 value");
+            }
+            subnet4->get4o6().setSubnet4o6(IOAddress(prefix), len);
+            subnet4->get4o6().enabled(true);
+        }
+
+        // Try 4o6 specific paramter: 4o6-interface-id
+        std::string ifaceid = string_values_->getOptionalParam("4o6-interface-id", "");
+        if (!ifaceid.empty()) {
+            OptionBuffer tmp(ifaceid.begin(), ifaceid.end());
+            OptionPtr opt(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+            subnet4->get4o6().setInterfaceId(opt);
+            subnet4->get4o6().enabled(true);
+        }
 
         // Try setting up client class (if specified)
         try {
@@ -373,8 +416,8 @@ namespace dhcp {
 /// @return parser for specified global DHCPv4 parameter
 /// @throw NotImplemented if trying to create a parser for unknown
 /// config element
-    DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
-                                                    ConstElementPtr element) {
+DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
+                                                ConstElementPtr element) {
     DhcpConfigParser* parser = NULL;
     if ((config_id.compare("valid-lifetime") == 0)  ||
         (config_id.compare("renew-timer") == 0)  ||

+ 272 - 156
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -1330,10 +1330,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1372,10 +1369,8 @@ TEST_F(Dhcp4ParserTest, optionDefRecord) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"record\","
-        "      \"array\": False,"
         "      \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1422,19 +1417,13 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
         "      \"code\": 101,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1488,19 +1477,13 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
         "      \"code\": 100,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1529,9 +1512,7 @@ TEST_F(Dhcp4ParserTest, optionDefArray) {
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
         "      \"array\": True,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1571,8 +1552,6 @@ TEST_F(Dhcp4ParserTest, optionDefEncapsulate) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"sub-opts-space\""
         "  } ]"
@@ -1613,10 +1592,7 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
         "      \"name\": \"invalid%name\","
         "      \"code\": 100,"
         "      \"type\": \"string\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1640,10 +1616,7 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"sting\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1667,10 +1640,8 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"record\","
-        "      \"array\": False,"
         "      \"record-types\": \"uint32,uint8,sting\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1694,8 +1665,6 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidEncapsulatedSpace) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"invalid%space%name\""
         "  } ]"
@@ -1724,7 +1693,6 @@ TEST_F(Dhcp4ParserTest, optionDefEncapsulatedSpaceAndArray) {
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
         "      \"array\": True,"
-        "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"valid-space-name\""
         "  } ]"
@@ -1750,8 +1718,6 @@ TEST_F(Dhcp4ParserTest, optionDefEncapsulateOwnSpace) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"isc\""
         "  } ]"
@@ -1781,10 +1747,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "      \"name\": \"foo\","
         "      \"code\": 109,"
         "      \"type\": \"string\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp4\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"dhcp4\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1818,10 +1781,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "      \"name\": \"routers\","
         "      \"code\": 3,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp4\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"dhcp4\""
         "  } ]"
         "}";
     json = Element::fromJSON(config);
@@ -1843,10 +1803,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "      \"name\": \"nis-server-addr\","
         "      \"code\": 65,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp4\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"dhcp4\""
         "  } ]"
         "}";
     json = Element::fromJSON(config);
@@ -1879,15 +1836,11 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"dhcp-message\","
-        "    \"space\": \"dhcp4\","
-        "    \"code\": 56,"
         "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
         "    \"name\": \"default-ip-ttl\","
-        "    \"space\": \"dhcp4\","
-        "    \"code\": 23,"
         "    \"data\": \"01\","
         "    \"csv-format\": False"
         " } ],"
@@ -1952,26 +1905,19 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"dhcp-message\","
-        "    \"space\": \"dhcp4\","
-        "    \"code\": 56,"
         "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
         "    \"name\": \"foo\","
         "    \"space\": \"isc\","
-        "    \"code\": 56,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 56,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " } ],"
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
@@ -2033,34 +1979,24 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
         "\"option-data\": [ {"
         "    \"name\": \"foo\","
         "    \"space\": \"isc\","
-        "    \"code\": 1,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"space\": \"isc\","
-        "    \"code\": 2,"
-        "    \"data\": \"192.168.2.1\","
-        "    \"csv-format\": True"
+        "    \"data\": \"192.168.2.1\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 1,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 2,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " } ]"
         "}";
 
@@ -2084,31 +2020,22 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"base-option\","
-        "    \"space\": \"dhcp4\","
-        "    \"code\": 222,"
-        "    \"data\": \"11\","
-        "    \"csv-format\": True"
+        "    \"data\": \"11\""
         " },"
         " {"
         "    \"name\": \"foo\","
         "    \"space\": \"isc\","
-        "    \"code\": 1,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"space\": \"isc\","
-        "    \"code\": 2,"
-        "    \"data\": \"192.168.2.1\","
-        "    \"csv-format\": True"
+        "    \"data\": \"192.168.2.1\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"base-option\","
         "    \"code\": 222,"
         "    \"type\": \"uint8\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
         "    \"space\": \"dhcp4\","
         "    \"encapsulate\": \"isc\""
         "},"
@@ -2116,19 +2043,13 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
         "    \"name\": \"foo\","
         "    \"code\": 1,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 2,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " } ],"
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
@@ -2180,8 +2101,6 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
         "\"renew-timer\": 1000, "
         "\"option-data\": [ {"
         "      \"name\": \"dhcp-message\","
-        "      \"space\": \"dhcp4\","
-        "      \"code\": 56,"
         "      \"data\": \"AB\","
         "      \"csv-format\": False"
         " } ],"
@@ -2190,15 +2109,11 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"option-data\": [ {"
         "          \"name\": \"dhcp-message\","
-        "          \"space\": \"dhcp4\","
-        "          \"code\": 56,"
         "          \"data\": \"ABCDEF0105\","
         "          \"csv-format\": False"
         "        },"
         "        {"
         "          \"name\": \"default-ip-ttl\","
-        "          \"space\": \"dhcp4\","
-        "          \"code\": 23,"
         "          \"data\": \"01\","
         "          \"csv-format\": False"
         "        } ]"
@@ -2337,8 +2252,6 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"option-data\": [ {"
         "          \"name\": \"dhcp-message\","
-        "          \"space\": \"dhcp4\","
-        "          \"code\": 56,"
         "          \"data\": \"0102030405060708090A\","
         "          \"csv-format\": False"
         "        } ]"
@@ -2348,8 +2261,6 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
         "    \"subnet\": \"192.0.3.0/24\", "
         "    \"option-data\": [ {"
         "          \"name\": \"default-ip-ttl\","
-        "          \"space\": \"dhcp4\","
-        "          \"code\": 23,"
         "          \"data\": \"FF\","
         "          \"csv-format\": False"
         "        } ]"
@@ -2610,34 +2521,24 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
         "\"option-data\": [ {"
         "    \"name\": \"foo\","
         "    \"space\": \"vendor-encapsulated-options-space\","
-        "    \"code\": 1,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"space\": \"vendor-encapsulated-options-space\","
-        "    \"code\": 2,"
-        "    \"data\": \"192.168.2.1\","
-        "    \"csv-format\": True"
+        "    \"data\": \"192.168.2.1\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 1,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-encapsulated-options-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-encapsulated-options-space\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 2,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-encapsulated-options-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-encapsulated-options-space\""
         " } ]"
         "}";
 
@@ -2665,17 +2566,12 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"vendor-encapsulated-options\","
-        "    \"space\": \"dhcp4\","
-        "    \"code\": 43,"
-        "    \"data\": \"\","
         "    \"csv-format\": False"
         " },"
         " {"
         "    \"name\": \"foo\","
         "    \"space\": \"vendor-encapsulated-options-space\","
-        "    \"code\": 1,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
@@ -2688,19 +2584,13 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
         "    \"name\": \"foo\","
         "    \"code\": 1,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-encapsulated-options-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-encapsulated-options-space\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 2,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-encapsulated-options-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-encapsulated-options-space\""
         " } ],"
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
@@ -2834,17 +2724,13 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) {
         "    \"name\": \"foo\","
         "    \"space\": \"vendor-4491\","
         "    \"code\": 100,"
-        "    \"data\": \"this is a string vendor-opt\","
-        "    \"csv-format\": True"
+        "    \"data\": \"this is a string vendor-opt\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 100,"
         "    \"type\": \"string\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-4491\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-4491\""
         " } ],"
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\":  \"192.0.2.1 - 192.0.2.100\" } ],"
@@ -2911,26 +2797,19 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"dhcp-message\","
-        "    \"space\": \"dhcp4\","
-        "    \"code\": 56,"
         "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
         "    \"name\": \"foo\","
         "    \"space\": \"isc\","
-        "    \"code\": 56,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 56,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " } ],"
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
@@ -3782,4 +3661,241 @@ TEST_F(Dhcp4ParserTest, expiredLeasesProcessingError) {
     EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
+
+// Checks if the DHCPv4 is able to parse the configuration without 4o6 parameters
+// and does not set 4o6 fields at all.
+TEST_F(Dhcp4ParserTest, 4o6default) {
+
+    ConstElementPtr status;
+
+    // Just a plain v4 config (no 4o6 parameters)
+    string config = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected pool configured.
+    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+
+    Cfg4o6& dhcp4o6 = subnet->get4o6();
+    EXPECT_FALSE(dhcp4o6.enabled());
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with 4o6 subnet
+// defined.
+TEST_F(Dhcp4ParserTest, 4o6subnet) {
+
+    ConstElementPtr status;
+
+    // Just a plain v4 config (no 4o6 parameters)
+    string config = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\","
+        "    \"4o6-subnet\": \"2001:db8::123/45\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected pool configured.
+    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+
+    Cfg4o6& dhcp4o6 = subnet->get4o6();
+    EXPECT_TRUE(dhcp4o6.enabled());
+    EXPECT_EQ(IOAddress("2001:db8::123"), dhcp4o6.getSubnet4o6().first);
+    EXPECT_EQ(45, dhcp4o6.getSubnet4o6().second);
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with 4o6 subnet
+// defined.
+TEST_F(Dhcp4ParserTest, 4o6subnetBogus) {
+
+    ConstElementPtr status;
+
+    // Just a plain v4 config (no 4o6 parameters)
+    string config[] = {
+        // Bogus configuration 1: missing / in subnet
+        "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\","
+        "    \"4o6-subnet\": \"2001:db8::123\" } ],"
+        "\"valid-lifetime\": 4000 }",
+
+        // Bogus configuration 2: incorrect address
+                "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\","
+        "    \"4o6-subnet\": \"2001:db8:bogus/45\" } ],"
+        "\"valid-lifetime\": 4000 }",
+
+        // Bogus configuration 3: incorrect prefix lenght
+        "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\","
+        "    \"4o6-subnet\": \"2001:db8::123/200\" } ],"
+        "\"valid-lifetime\": 4000 }"
+    };
+
+    ElementPtr json1 = Element::fromJSON(config[0]);
+    ElementPtr json2 = Element::fromJSON(config[0]);
+    ElementPtr json3 = Element::fromJSON(config[0]);
+
+    // Check that the first config is rejected.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json1));
+    checkResult(status, 1);
+
+    // Check that the second config is rejected.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json2));
+    checkResult(status, 1);
+
+    // Check that the third config is rejected.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json3));
+    checkResult(status, 1);
+}
+
+
+// Checks if the DHCPv4 is able to parse the configuration with 4o6 network
+// interface defined.
+TEST_F(Dhcp4ParserTest, 4o6iface) {
+
+    ConstElementPtr status;
+
+    // Just a plain v4 config (no 4o6 parameters)
+    string config = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\","
+        "    \"4o6-interface\": \"ethX\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected pool configured.
+    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+
+    Cfg4o6& dhcp4o6 = subnet->get4o6();
+    EXPECT_TRUE(dhcp4o6.enabled());
+    EXPECT_EQ("ethX", dhcp4o6.getIface4o6());
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with both 4o6 network
+// interface and v6 subnet defined.
+TEST_F(Dhcp4ParserTest, 4o6subnetIface) {
+
+    ConstElementPtr status;
+
+    // Just a plain v4 config (no 4o6 parameters)
+    string config = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\","
+        "    \"4o6-subnet\": \"2001:db8::543/21\","
+        "    \"4o6-interface\": \"ethX\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected subnet configured...
+    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+
+    // ... and that subnet has 4o6 network interface specified.
+    Cfg4o6& dhcp4o6 = subnet->get4o6();
+    EXPECT_TRUE(dhcp4o6.enabled());
+    EXPECT_EQ(IOAddress("2001:db8::543"), dhcp4o6.getSubnet4o6().first);
+    EXPECT_EQ(21, dhcp4o6.getSubnet4o6().second);
+    EXPECT_EQ("ethX", dhcp4o6.getIface4o6());
+}
+
+// Checks if the DHCPv4 is able to parse the configuration with 4o6 network
+// interface-id.
+TEST_F(Dhcp4ParserTest, 4o6subnetInterfaceId) {
+
+    ConstElementPtr status;
+
+    // Just a plain v4 config (no 4o6 parameters)
+    string config = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\","
+        "    \"4o6-interface-id\": \"vlan123\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected 4o6-interface-id configured.
+    Subnet4Ptr subnet = CfgMgr::instance().getStagingCfg()->
+        getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+
+    Cfg4o6& dhcp4o6 = subnet->get4o6();
+    EXPECT_TRUE(dhcp4o6.enabled());
+    OptionPtr ifaceid = dhcp4o6.getInterfaceId();
+    ASSERT_TRUE(ifaceid);
+
+    vector<uint8_t> data = ifaceid->getData();
+    EXPECT_EQ(7, data.size());
+    const char *exp = "vlan123";
+    EXPECT_EQ(0, memcmp(&data[0], exp, data.size()));
+}
+
 }

+ 2 - 2
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -361,7 +361,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, controlLeasesReclaim) {
                                 time(NULL) - 100, SubnetID(1)));
 
     // Add leases to the database.
-    LeaseMgr& lease_mgr = LeaseMgrFactory().instance();
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
     ASSERT_NO_THROW(lease_mgr.addLease(lease0));
     ASSERT_NO_THROW(lease_mgr.addLease(lease1));
 
@@ -419,7 +419,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, controlLeasesReclaimRemove) {
                                 time(NULL) - 100, SubnetID(1)));
 
     // Add leases to the database.
-    LeaseMgr& lease_mgr = LeaseMgrFactory().instance();
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
     ASSERT_NO_THROW(lease_mgr.addLease(lease0));
     ASSERT_NO_THROW(lease_mgr.addLease(lease1));
 

+ 1 - 4
src/bin/dhcp4/tests/decline_unittest.cc

@@ -53,10 +53,7 @@ const char* DECLINE_CONFIGS[] = {
         "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    } ]"
         " } ]"
     "}"

+ 9 - 36
src/bin/dhcp4/tests/dora_unittest.cc

@@ -77,31 +77,19 @@ const char* DORA_CONFIGS[] = {
         "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    },"
         "    {"
         "        \"name\": \"domain-name-servers\","
-        "        \"code\": 6,"
-        "        \"data\": \"10.0.0.202,10.0.0.203\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
         "    },"
         "    {"
         "        \"name\": \"log-servers\","
-        "        \"code\": 7,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    },"
         "    {"
         "        \"name\": \"cookie-servers\","
-        "        \"code\": 8,"
-        "        \"data\": \"10.0.0.202,10.0.0.203\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
         "    } ]"
         " } ]"
     "}",
@@ -116,31 +104,19 @@ const char* DORA_CONFIGS[] = {
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"192.0.2.200,192.0.2.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"192.0.2.200,192.0.2.201\""
         "    },"
         "    {"
         "        \"name\": \"domain-name-servers\","
-        "        \"code\": 6,"
-        "        \"data\": \"192.0.2.202,192.0.2.203\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"192.0.2.202,192.0.2.203\""
         "    },"
         "    {"
         "        \"name\": \"log-servers\","
-        "        \"code\": 7,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    },"
         "    {"
         "        \"name\": \"cookie-servers\","
-        "        \"code\": 8,"
-        "        \"data\": \"10.0.0.202,10.0.0.203\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
         "    } ]"
         " } ]"
     "}",
@@ -174,10 +150,7 @@ const char* DORA_CONFIGS[] = {
         "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    } ]"
         " } ]"
     "}",

+ 2 - 8
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -47,10 +47,7 @@ const char* CONFIGS[] = {
         "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    } ],"
         "    \"reservations\": ["
         "       {"
@@ -74,10 +71,7 @@ const char* CONFIGS[] = {
         "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    } ],"
         "    \"reservations\": ["
         "       {"

+ 8 - 32
src/bin/dhcp4/tests/inform_unittest.cc

@@ -58,31 +58,19 @@ const char* INFORM_CONFIGS[] = {
         "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    },"
         "    {"
         "        \"name\": \"domain-name-servers\","
-        "        \"code\": 6,"
-        "        \"data\": \"10.0.0.202,10.0.0.203\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
         "    },"
         "    {"
         "        \"name\": \"log-servers\","
-        "        \"code\": 7,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    },"
         "    {"
         "        \"name\": \"cookie-servers\","
-        "        \"code\": 8,"
-        "        \"data\": \"10.0.0.202,10.0.0.203\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
         "    } ]"
         " } ]"
     "}",
@@ -96,31 +84,19 @@ const char* INFORM_CONFIGS[] = {
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"192.0.2.200,192.0.2.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"192.0.2.200,192.0.2.201\""
         "    },"
         "    {"
         "        \"name\": \"domain-name-servers\","
-        "        \"code\": 6,"
-        "        \"data\": \"192.0.2.202,192.0.2.203\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"192.0.2.202,192.0.2.203\""
         "    },"
         "    {"
         "        \"name\": \"log-servers\","
-        "        \"code\": 7,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    },"
         "    {"
         "        \"name\": \"cookie-servers\","
-        "        \"code\": 8,"
-        "        \"data\": \"10.0.0.202,10.0.0.203\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.202,10.0.0.203\""
         "    } ]"
         " } ]"
     "}"

+ 1 - 1
src/bin/dhcp4/tests/kea_controller_unittest.cc

@@ -377,7 +377,7 @@ TEST_F(JSONFileBackendTest, timers) {
     lease_reclaimed->state_ = Lease4::STATE_EXPIRED_RECLAIMED;
 
     // Add leases to the database.
-    LeaseMgr& lease_mgr = LeaseMgrFactory().instance();
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
     ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
     ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
 

+ 1 - 4
src/bin/dhcp4/tests/release_unittest.cc

@@ -53,10 +53,7 @@ const char* RELEASE_CONFIGS[] = {
         "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
         "    \"option-data\": [ {"
         "        \"name\": \"routers\","
-        "        \"code\": 3,"
-        "        \"data\": \"10.0.0.200,10.0.0.201\","
-        "        \"csv-format\": true,"
-        "        \"space\": \"dhcp4\""
+        "        \"data\": \"10.0.0.200,10.0.0.201\""
         "    } ]"
         " } ]"
     "}"

+ 2 - 2
src/bin/dhcp6/json_config_parser.cc

@@ -665,8 +665,8 @@ namespace dhcp {
 /// @return parser for specified global DHCPv6 parameter
 /// @throw NotImplemented if trying to create a parser for unknown config
 /// element
-    DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
-                                                    ConstElementPtr element) {
+DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
+                                                ConstElementPtr element) {
     DhcpConfigParser* parser = NULL;
     if ((config_id.compare("preferred-lifetime") == 0)  ||
         (config_id.compare("valid-lifetime") == 0)  ||

+ 41 - 165
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -205,10 +205,7 @@ public:
             "  \"name\": \"bool-option\","
             "  \"code\": 1000,"
             "  \"type\": \"boolean\","
-            "  \"array\": False,"
-            "  \"record-types\": \"\","
-            "  \"space\": \"dhcp6\","
-            "  \"encapsulate\": \"\""
+            "  \"space\": \"dhcp6\""
             "} ],"
             "\"subnet6\": [ { "
             "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
@@ -1572,10 +1569,7 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"ipv6-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1612,10 +1606,8 @@ TEST_F(Dhcp6ParserTest, optionDefRecord) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"record\","
-        "      \"array\": False,"
         "      \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1661,19 +1653,13 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
         "      \"code\": 101,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1725,19 +1711,13 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
         "      \"code\": 100,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1766,9 +1746,7 @@ TEST_F(Dhcp6ParserTest, optionDefArray) {
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
         "      \"array\": True,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1806,8 +1784,6 @@ TEST_F(Dhcp6ParserTest, optionDefEncapsulate) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"sub-opts-space\""
         "  } ]"
@@ -1847,10 +1823,7 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
         "      \"name\": \"invalid%name\","
         "      \"code\": 100,"
         "      \"type\": \"string\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1874,10 +1847,7 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"sting\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1901,10 +1871,8 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"record\","
-        "      \"array\": False,"
         "      \"record-types\": \"uint32,uint8,sting\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -1928,8 +1896,6 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidEncapsulatedSpace) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"invalid%space%name\""
         "  } ]"
@@ -1958,7 +1924,6 @@ TEST_F(Dhcp6ParserTest, optionDefEncapsulatedSpaceAndArray) {
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
         "      \"array\": True,"
-        "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"valid-space-name\""
         "  } ]"
@@ -1984,8 +1949,6 @@ TEST_F(Dhcp6ParserTest, optionDefEncapsulateOwnSpace) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"uint32\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
         "      \"space\": \"isc\","
         "      \"encapsulate\": \"isc\""
         "  } ]"
@@ -2016,10 +1979,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"string\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp6\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"dhcp6\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -2053,10 +2013,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
         "      \"name\": \"foo\","
         "      \"code\": 3,"
         "      \"type\": \"string\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp6\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"dhcp6\""
         "  } ]"
         "}";
     json = Element::fromJSON(config);
@@ -2078,10 +2035,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
         "      \"name\": \"geolocation\","
         "      \"code\": 63,"
         "      \"type\": \"string\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp6\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"dhcp6\""
         "  } ]"
         "}";
     json = Element::fromJSON(config);
@@ -2114,17 +2068,12 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"subscriber-id\","
-        "    \"space\": \"dhcp6\","
-        "    \"code\": 38,"
         "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
         "    \"name\": \"preference\","
-        "    \"space\": \"dhcp6\","
-        "    \"code\": 7,"
-        "    \"data\": \"01\","
-        "    \"csv-format\": True"
+        "    \"data\": \"01\""
         " } ],"
         "\"subnet6\": [ { "
         "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
@@ -2196,26 +2145,19 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"subscriber-id\","
-        "    \"space\": \"dhcp6\","
-        "    \"code\": 38,"
         "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
         "    \"name\": \"foo\","
         "    \"space\": \"isc\","
-        "    \"code\": 38,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 38,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " } ],"
         "\"subnet6\": [ { "
         "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
@@ -2278,34 +2220,24 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
         "\"option-data\": [ {"
         "    \"name\": \"foo\","
         "    \"space\": \"isc\","
-        "    \"code\": 110,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"space\": \"isc\","
-        "    \"code\": 111,"
-        "    \"data\": \"192.168.2.1\","
-        "    \"csv-format\": True"
+        "    \"data\": \"192.168.2.1\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 110,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 111,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " } ]"
         "}";
 
@@ -2330,31 +2262,22 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"base-option\","
-        "    \"space\": \"dhcp6\","
-        "    \"code\": 100,"
-        "    \"data\": \"11\","
-        "    \"csv-format\": True"
+        "    \"data\": \"11\""
         " },"
         " {"
         "    \"name\": \"foo\","
         "    \"space\": \"isc\","
-        "    \"code\": 110,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"space\": \"isc\","
-        "    \"code\": 111,"
-        "    \"data\": \"192.168.2.1\","
-        "    \"csv-format\": True"
+        "    \"data\": \"192.168.2.1\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"base-option\","
         "    \"code\": 100,"
         "    \"type\": \"uint8\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
         "    \"space\": \"dhcp6\","
         "    \"encapsulate\": \"isc\""
         "},"
@@ -2362,19 +2285,13 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
         "    \"name\": \"foo\","
         "    \"code\": 110,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 111,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"isc\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"isc\""
         " } ],"
         "\"subnet6\": [ { "
         "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
@@ -2429,8 +2346,6 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
         "    \"subnet\": \"2001:db8:1::/64\", "
         "    \"option-data\": [ {"
         "          \"name\": \"subscriber-id\","
-        "          \"space\": \"dhcp6\","
-        "          \"code\": 38,"
         "          \"data\": \"0102030405060708090A\","
         "          \"csv-format\": False"
         "        } ]"
@@ -2440,8 +2355,6 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
         "    \"subnet\": \"2001:db8:2::/64\", "
         "    \"option-data\": [ {"
         "          \"name\": \"user-class\","
-        "          \"space\": \"dhcp6\","
-        "          \"code\": 15,"
         "          \"data\": \"FFFEFDFCFB\","
         "          \"csv-format\": False"
         "        } ]"
@@ -2804,17 +2717,13 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
         "    \"name\": \"foo\","
         "    \"space\": \"vendor-4491\","
         "    \"code\": 100,"
-        "    \"data\": \"this is a string vendor-opt\","
-        "    \"csv-format\": True"
+        "    \"data\": \"this is a string vendor-opt\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 100,"
         "    \"type\": \"string\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-4491\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-4491\""
         " } ],"
         "\"subnet6\": [ { "
         "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
@@ -2869,34 +2778,24 @@ TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) {
         "\"option-data\": [ {"
         "    \"name\": \"foo\","
         "    \"space\": \"vendor-opts-space\","
-        "    \"code\": 110,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"space\": \"vendor-opts-space\","
-        "    \"code\": 111,"
-        "    \"data\": \"192.168.2.1\","
-        "    \"csv-format\": True"
+        "    \"data\": \"192.168.2.1\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 110,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-opts-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-opts-space\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 111,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-opts-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-opts-space\""
         " } ]"
         "}";
 
@@ -2923,42 +2822,29 @@ TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) {
         "\"renew-timer\": 1000,"
         "\"option-data\": [ {"
         "    \"name\": \"vendor-opts\","
-        "    \"space\": \"dhcp6\","
-        "    \"code\": 17,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo\","
         "    \"space\": \"vendor-opts-space\","
-        "    \"code\": 110,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"space\": \"vendor-opts-space\","
-        "    \"code\": 111,"
-        "    \"data\": \"192.168.2.1\","
-        "    \"csv-format\": True"
+        "    \"data\": \"192.168.2.1\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 110,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-opts-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-opts-space\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 111,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-opts-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-opts-space\""
         " } ],"
         "\"subnet6\": [ { "
         "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
@@ -3049,34 +2935,24 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
         "\"option-data\": [ {"
         "    \"name\": \"foo\","
         "    \"space\": \"vendor-opts-space\","
-        "    \"code\": 110,"
-        "    \"data\": \"1234\","
-        "    \"csv-format\": True"
+        "    \"data\": \"1234\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"space\": \"vendor-opts-space\","
-        "    \"code\": 111,"
-        "    \"data\": \"192.168.2.1\","
-        "    \"csv-format\": True"
+        "    \"data\": \"192.168.2.1\""
         " } ],"
         "\"option-def\": [ {"
         "    \"name\": \"foo\","
         "    \"code\": 110,"
         "    \"type\": \"uint32\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-opts-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-opts-space\""
         " },"
         " {"
         "    \"name\": \"foo2\","
         "    \"code\": 111,"
         "    \"type\": \"ipv4-address\","
-        "    \"array\": False,"
-        "    \"record-types\": \"\","
-        "    \"space\": \"vendor-opts-space\","
-        "    \"encapsulate\": \"\""
+        "    \"space\": \"vendor-opts-space\""
         " } ]"
         "}");
 

+ 2 - 2
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -436,7 +436,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, controlLeasesReclaim) {
     lease1->cltt_ = time(NULL) - 100;
 
     // Add leases to the database.
-    LeaseMgr& lease_mgr = LeaseMgrFactory().instance();
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
     ASSERT_NO_THROW(lease_mgr.addLease(lease0));
     ASSERT_NO_THROW(lease_mgr.addLease(lease1));
 
@@ -498,7 +498,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, controlLeasesReclaimRemove) {
     lease1->cltt_ = time(NULL) - 100;
 
     // Add leases to the database.
-    LeaseMgr& lease_mgr = LeaseMgrFactory().instance();
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
     ASSERT_NO_THROW(lease_mgr.addLease(lease0));
     ASSERT_NO_THROW(lease_mgr.addLease(lease1));
 

+ 4 - 18
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -300,15 +300,10 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
         "    \"interface\": \"eth0\", "
         "    \"option-data\": [ {"
         "          \"name\": \"dns-servers\","
-        "          \"space\": \"dhcp6\","
-        "          \"code\": 23,"
-        "          \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\","
-        "          \"csv-format\": True"
+        "          \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\""
         "        },"
         "        {"
         "          \"name\": \"subscriber-id\","
-        "          \"space\": \"dhcp6\","
-        "          \"code\": 38,"
         "          \"data\": \"1234\","
         "          \"csv-format\": False"
         "        } ]"
@@ -1615,17 +1610,12 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
         "        \"name\": \"config-file\","
         "        \"code\": 33,"
         "        \"type\": \"string\","
-        "        \"array\": False,"
-        "        \"record-types\": \"\","
-        "        \"space\": \"vendor-4491\","
-        "        \"encapsulate\": \"\""
+        "        \"space\": \"vendor-4491\""
         "     } ],"
         "    \"option-data\": [ {"
         "          \"name\": \"config-file\","
         "          \"space\": \"vendor-4491\","
-        "          \"code\": 33,"
-        "          \"data\": \"normal_erouter_v6.cm\","
-        "          \"csv-format\": True"
+        "          \"data\": \"normal_erouter_v6.cm\""
         "        }],"
         "\"subnet6\": [ { "
         "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
@@ -2305,11 +2295,7 @@ TEST_F(Dhcpv6SrvTest, rsooOverride) {
         "    \"option-def\": [ {"
         "      \"name\": \"foo\","
         "      \"code\": 120,"
-        "      \"type\": \"binary\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp6\","
-        "      \"encapsulate\": \"\""
+        "      \"type\": \"binary\""
         "    } ],"
         "    \"option-data\": [ {"
         "      \"code\": 120,"

+ 1 - 1
src/bin/dhcp6/tests/kea_controller_unittest.cc

@@ -321,7 +321,7 @@ TEST_F(JSONFileBackendTest, timers) {
     lease_reclaimed->state_ = Lease6::STATE_EXPIRED_RECLAIMED;
 
     // Add leases to the database.
-    LeaseMgr& lease_mgr = LeaseMgrFactory().instance();
+    LeaseMgr& lease_mgr = LeaseMgrFactory::instance();
     ASSERT_NO_THROW(lease_mgr.addLease(lease_expired));
     ASSERT_NO_THROW(lease_mgr.addLease(lease_reclaimed));
 

+ 1 - 0
src/lib/dhcp/Makefile.am

@@ -48,6 +48,7 @@ libkea_dhcp___la_SOURCES += protocol_util.cc protocol_util.h
 libkea_dhcp___la_SOURCES += pkt.cc pkt.h
 libkea_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libkea_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libkea_dhcp___la_SOURCES += pkt4o6.cc pkt4o6.h
 libkea_dhcp___la_SOURCES += pkt_filter.h pkt_filter.cc
 libkea_dhcp___la_SOURCES += pkt_filter6.h pkt_filter6.cc
 libkea_dhcp___la_SOURCES += pkt_filter_inet.cc pkt_filter_inet.h

+ 15 - 8
src/lib/dhcp/dhcp6.h

@@ -110,8 +110,8 @@
 //#define D6O_ADDRSEL                             84 /* RFC7078 */
 //#define D6O_ADDRSEL_TABLE                       85 /* RFC7078 */
 //#define D6O_V6_PCP_SERVER                       86 /* RFC7291 */
-//#define D6O_DHCPV4_MSG                          87 /* RFC7341 */
-//#define D6O_DHCPV4_O_DHCPV6_SERVER              88 /* RFC7341 */
+#define D6O_DHCPV4_MSG                          87 /* RFC7341 */
+#define D6O_DHCPV4_O_DHCPV6_SERVER              88 /* RFC7341 */
 //#define D6O_S46_RULE                            89 /* RFC7598 */
 //#define D6O_S46_BR                              90 /* RFC7598 */
 //#define D6O_S46_DMR                             91 /* RFC7598 */
@@ -123,10 +123,9 @@
 //#define D6O_4RD                                 97 /* RFC7600 */
 //#define D6O_4RD_MAP_RULE                        98 /* RFC7600 */
 //#define D6O_4RD_NON_MAP_RULE                    99 /* RFC7600 */
-/* draft-ietf-dhc-dhcpv6-active-leasequery-04 */
-//#define D6O_LQ_BASE_TIME                       100
-//#define D6O_LQ_START_TIME                      101
-//#define D6O_LQ_END_TIME                        102
+//#define D6O_LQ_BASE_TIME                       100 /* RFC7653 */
+//#define D6O_LQ_START_TIME                      101 /* RFC7653 */
+//#define D6O_LQ_END_TIME                        102 /* RFC7653 */
 /* 103-142 unassigned */
 //#define D6O_IPV6_ADDRESS_ANDSF                 143 /* RFC6153 */
 
@@ -195,8 +194,8 @@
 //#define DHCPV6_RECONFIGURE_REQUEST 18
 //#define DHCPV6_RECONFIGURE_REPLY   19
 /* RFC 7341 */
-//#define DHCPV6_DHCPV4_QUERY        20
-//#define DHCPV6_DHCPV4_RESPONSE     21
+#define DHCPV6_DHCPV4_QUERY        20
+#define DHCPV6_DHCPV4_RESPONSE     21
 /* draft-ietf-dhc-dhcpv6-active-leasequery-04 */
 //#define DHCPV6_ACTIVELEASEQUERY    22
 //#define DHCPV6_STARTTLS            23
@@ -223,6 +222,11 @@ extern const int dhcpv6_type_name_max;
 // Taken from http://www.iana.org/assignments/enterprise-numbers
 #define ENTERPRISE_ID_ISC 2495
 
+/* DHCPv4-over-DHCPv6 (RFC 7341) inter-process communication. These are option
+   codes for the ISC vendor specific options used in 4o6 */
+#define ISC_V6_4O6_INTERFACE                 60000
+#define ISC_V6_4O6_SRC_ADDRESS               60001
+
 /* Offsets into IA_*'s where Option spaces commence.  */
 #define IA_NA_OFFSET 12 /* IAID, T1, T2, all 4 octets each */
 #define IA_TA_OFFSET  4 /* IAID only, 4 octets */
@@ -298,4 +302,7 @@ extern const int dhcpv6_type_name_max;
 #define IRT_DEFAULT     86400
 #define IRT_MINIMUM     600
 
+/* DHCPv4-query message flags (see RFC7341) */
+#define DHCPV4_QUERY_FLAGS_UNICAST (1 << 23)
+
 #endif /* DHCP6_H */

+ 11 - 0
src/lib/dhcp/libdhcp++.cc

@@ -110,6 +110,11 @@ LibDHCP::getVendorOption6Defs(const uint32_t vendor_id) {
         initVendorOptsDocsis6();
     }
 
+    if (vendor_id == ENTERPRISE_ID_ISC &&
+        vendor6_defs_.find(ENTERPRISE_ID_ISC) == vendor6_defs_.end()) {
+        initVendorOptsIsc6();
+    }
+
     VendorOptionDefContainers::const_iterator def = vendor6_defs_.find(vendor_id);
     if (def == vendor6_defs_.end()) {
         // No such vendor-id space
@@ -737,6 +742,12 @@ LibDHCP::initVendorOptsDocsis6() {
     initOptionSpace(vendor6_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V6_DEFS, DOCSIS3_V6_DEFS_SIZE);
 }
 
+void
+LibDHCP::initVendorOptsIsc6() {
+    vendor6_defs_[ENTERPRISE_ID_ISC] = OptionDefContainer();
+    initOptionSpace(vendor6_defs_[ENTERPRISE_ID_ISC], ISC_V6_DEFS, ISC_V6_DEFS_SIZE);
+}
+
 void initOptionSpace(OptionDefContainer& defs,
                      const OptionDefParams* params,
                      size_t params_size) {

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

@@ -281,6 +281,9 @@ private:
 
     static void initVendorOptsDocsis6();
 
+    /// Initialize private DHCPv6 option definitions.
+    static void initVendorOptsIsc6();
+
     /// pointers to factories that produce DHCPv6 options
     static FactoryMap v4factories_;
 

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

@@ -50,10 +50,10 @@ Pkt::Pkt(const uint8_t* buf, uint32_t len, const isc::asiolink::IOAddress& local
 {
 
     if (len != 0) {
-	if (buf == NULL) {
-	    isc_throw(InvalidParameter, "data buffer passed to Pkt is NULL");
-	}
-	data_.resize(len);
+        if (buf == NULL) {
+            isc_throw(InvalidParameter, "data buffer passed to Pkt is NULL");
+        }
+        data_.resize(len);
         memcpy(&data_[0], buf, len);
     }
 }

+ 8 - 0
src/lib/dhcp/pkt4.h

@@ -375,6 +375,14 @@ public:
     /// (true) or non-relayed (false).
     bool isRelayed() const;
 
+    /// @brief Checks if a DHCPv4 message has beeb transported over DHCPv6
+    ///
+    /// @return Boolean value which indicates whether the message is
+    /// transported over DHCPv6 (true) or native DHCPv4 (false)
+    virtual bool isDhcp4o6() const {
+        return (false);
+    }
+
 private:
 
     /// @brief Generic method that validates and sets HW address.

+ 60 - 0
src/lib/dhcp/pkt4o6.cc

@@ -0,0 +1,60 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt4o6.h>
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace std;
+
+namespace isc {
+namespace dhcp {
+
+Pkt4o6::Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6)
+    :Pkt4(&pkt4[0], pkt4.size()), pkt6_(pkt6)
+{
+    static_cast<void>(pkt6->delOption(D6O_DHCPV4_MSG));
+    setIface(pkt6->getIface());
+    setIndex(pkt6->getIndex());
+    setRemoteAddr(pkt6->getRemoteAddr());
+}
+
+Pkt4o6::Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6)
+    :Pkt4(*pkt4), pkt6_(pkt6) {
+}
+
+void Pkt4o6::pack() {
+    // Convert wire-format Pkt4 data in the form of OptionBuffer.
+    Pkt4::pack();
+    OutputBuffer& buf = getBuffer();
+    const uint8_t* ptr = static_cast<const uint8_t*>(buf.getData());
+    OptionBuffer msg(ptr, ptr + buf.getLength());
+
+    // Build the DHCPv4 Message option for the DHCPv6 message, and pack the
+    // entire stuff.
+    OptionPtr dhcp4_msg(new Option(Option::V6, D6O_DHCPV4_MSG, msg));
+    pkt6_->addOption(dhcp4_msg);
+    pkt6_->pack();
+}
+
+} // end of namespace isc::dhcp
+
+} // end of namespace isc

+ 83 - 0
src/lib/dhcp/pkt4o6.h

@@ -0,0 +1,83 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PKT4O6_H
+#define PKT4O6_H
+
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+
+namespace dhcp {
+
+/// @brief Represents DHCPv4-over-DHCPv6 packet
+///
+/// This class derives from @c Pkt4 in order to be handled by
+/// the DHCPv4 server code. It includes a shared pointer to the
+/// DHCPv6 message too.
+///
+/// This is an implementation of the DHCPv4-query/response DHCPv6 messages
+/// defined in RFC 7341 (http://ietf.org/rfc/rfc7341.txt).
+/// See also http://kea.isc.org/wiki/Dhcp4o6Design for design discussions.
+class Pkt4o6 : public Pkt4 {
+public:
+
+    /// @brief Constructor, used in message reception.
+    ///
+    /// @param pkt4 Content of the DHCPv4-message option
+    /// @param pkt6 encapsulating unpacked DHCPv6 message
+    /// the DHCPv4 message option will be removed
+    Pkt4o6(const OptionBuffer& pkt4, const Pkt6Ptr& pkt6);
+
+    /// @brief Constructor, used in replying to a message
+    ///
+    /// @param pkt4 DHCPv4 message
+    /// @param pkt6 DHCPv6 message
+    Pkt4o6(const Pkt4Ptr& pkt4, const Pkt6Ptr& pkt6);
+
+    /// @brief Returns encapsulating DHCPv6 message
+    Pkt6Ptr getPkt6() const { return (pkt6_); }
+
+    /// @brief Prepares on-wire format of DHCPv4-over-DHCPv6 packet.
+    ///
+    /// Calls pack() on both DHCPv4 and DHCPv6 parts
+    /// Inserts the DHCPv4-message option
+    /// @ref Pkt4::pack and @ref Pkt6::pack
+    virtual void pack();
+
+    /// @brief Checks if a DHCPv4 message has been transported over DHCPv6
+    ///
+    /// @return Boolean value which indicates whether the message is
+    /// transported over DHCPv6 (true) or native DHCPv4 (false)
+    virtual bool isDhcp4o6() const {
+        return (true);
+    }
+
+private:
+    /// Encapsulating DHCPv6 message
+    Pkt6Ptr pkt6_;
+
+}; // Pkt4o6 class
+
+/// @brief A pointer to Pkt4o6 object.
+typedef boost::shared_ptr<Pkt4o6> Pkt4o6Ptr;
+
+} // isc::dhcp namespace
+
+} // isc namespace
+
+#endif

+ 10 - 0
src/lib/dhcp/pkt6.cc

@@ -289,6 +289,8 @@ Pkt6::unpackUDP() {
     case DHCPV6_DECLINE:
     case DHCPV6_RECONFIGURE:
     case DHCPV6_INFORMATION_REQUEST:
+    case DHCPV6_DHCPV4_QUERY:
+    case DHCPV6_DHCPV4_RESPONSE:
     default: // assume that uknown messages are not using relay format
         {
             return (unpackMsg(data_.begin(), data_.end()));
@@ -586,6 +588,8 @@ Pkt6::getName(const uint8_t type) {
     static const char* REPLY = "REPLY";
     static const char* REQUEST = "REQUEST";
     static const char* SOLICIT = "SOLICIT";
+    static const char* DHCPV4_QUERY = "DHCPV4_QUERY";
+    static const char* DHCPV4_RESPONSE = "DHCPV4_RESPONSE";
     static const char* UNKNOWN = "UNKNOWN";
 
     switch (type) {
@@ -634,6 +638,12 @@ Pkt6::getName(const uint8_t type) {
     case DHCPV6_SOLICIT:
         return (SOLICIT);
 
+    case DHCPV6_DHCPV4_QUERY:
+        return (DHCPV4_QUERY);
+
+    case DHCPV6_DHCPV4_RESPONSE:
+        return (DHCPV4_RESPONSE);
+
     default:
         ;
     }

+ 16 - 0
src/lib/dhcp/std_option_defs.h

@@ -353,6 +353,9 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
     { "rsoo", D6O_RSOO, OPT_EMPTY_TYPE, false, NO_RECORD_DEF, "rsoo-opts" },
     { "client-linklayer-addr", D6O_CLIENT_LINKLAYER_ADDR, OPT_BINARY_TYPE, false,
       NO_RECORD_DEF, "" },
+    { "dhcpv4-message", D6O_DHCPV4_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF, "" },
+    { "dhcp4o6-server-addr", D6O_DHCPV4_O_DHCPV6_SERVER, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF, "" },
     { "public-key", D6O_PUBLIC_KEY, OPT_BINARY_TYPE, false,
       NO_RECORD_DEF, "" },
     { "certificate", D6O_CERTIFICATE, OPT_BINARY_TYPE, false,
@@ -371,6 +374,19 @@ const OptionDefParams OPTION_DEF_PARAMS6[] = {
 const int OPTION_DEF_PARAMS_SIZE6  =
     sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
 
+/// @brief Definitions of vendor-specific DHCPv6 options, defined by ISC.
+/// 4o6-* options are used for inter-process communication. For details, see
+/// http://kea.isc.org/wiki/Dhcp4o6Design
+///
+/// @todo: As those options are defined by ISC, they do not belong in std_option_defs.h.
+///        We need to move them to a separate file, e.g. isc_option_defs.h
+const OptionDefParams ISC_V6_DEFS[] = {
+    { "4o6-interface", ISC_V6_4O6_INTERFACE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+    { "4o6-source-address", ISC_V6_4O6_SRC_ADDRESS, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF, "" }
+};
+
+const int ISC_V6_DEFS_SIZE = sizeof(ISC_V6_DEFS) / sizeof(OptionDefParams);
+
 } // unnamed namespace
 
 } // namespace dhcp

+ 1 - 0
src/lib/dhcp/tests/Makefile.am

@@ -74,6 +74,7 @@ libdhcp___unittests_SOURCES += option_vendor_class_unittest.cc
 libdhcp___unittests_SOURCES  += pkt_captures4.cc pkt_captures6.cc pkt_captures.h
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
+libdhcp___unittests_SOURCES += pkt4o6_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet_unittest.cc
 libdhcp___unittests_SOURCES += pkt_filter_inet6_unittest.cc

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

@@ -1200,6 +1200,12 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
                                     fqdn_buf.begin(), fqdn_buf.end(),
                                     typeid(OptionCustom));
 
+    LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_MSG, begin, end,
+                                    typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_DHCPV4_O_DHCPV6_SERVER, begin, end,
+                                    typeid(Option6AddrLst));
+
     LibDhcpTest::testStdOptionDefs6(D6O_PUBLIC_KEY, begin, end,
                                     typeid(Option));
 

+ 106 - 0
src/lib/dhcp/tests/pkt4o6_unittest.cc

@@ -0,0 +1,106 @@
+// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/pkt4o6.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief A Fixture class dedicated to testing of the Pkt4o6 class that
+///        represents a DHCPv4-over-DHCPv6 packet.
+class Pkt4o6Test : public ::testing::Test {
+protected:
+    Pkt4o6Test() :
+        data6_(6, 0),
+        pkt6_(new Pkt6(&data6_[0], data6_.size())),
+        pkt4_(new Pkt4(DHCPDISCOVER, 0x12345678))
+    {
+        pkt4_->pack();
+        const uint8_t* cp = static_cast<const uint8_t*>(
+            pkt4_->getBuffer().getData());
+        buffer4_.assign(cp, cp + pkt4_->getBuffer().getLength());
+    }
+
+protected:
+    // commonly used test data
+    const std::vector<uint8_t> data6_; // data for Pkt6 (content unimportant)
+    Pkt6Ptr pkt6_;                     // DHCPv6 message for 4o6
+    Pkt4Ptr pkt4_;                     // DHCPv4 message for 4o6
+    OptionBuffer buffer4_;             // wire-format data buffer of pkt4_
+};
+
+// This test verifies that the constructors are working as expected.
+TEST_F(Pkt4o6Test, construct) {
+    // Construct 4o6 packet, unpack the data to examine it
+    boost::scoped_ptr<Pkt4o6> pkt4o6(new Pkt4o6(buffer4_, pkt6_));
+    pkt4o6->unpack();
+    // Inspect its internal to confirm it's built as expected.  We also test
+    // isDhcp4o6() here.
+    EXPECT_TRUE(pkt4o6->isDhcp4o6());
+    EXPECT_EQ(pkt6_, pkt4o6->getPkt6());
+    EXPECT_EQ(DHCPDISCOVER, pkt4o6->getType());
+
+    // Same check for the other constructor.  It relies on the internal
+    // behavior of Pkt4's copy constructor, so we need to first unpack pkt4.
+    pkt4_.reset(new Pkt4(&buffer4_[0], buffer4_.size()));
+    pkt4_->unpack();
+    pkt4o6.reset(new Pkt4o6(pkt4_, pkt6_));
+    EXPECT_TRUE(pkt4o6->isDhcp4o6());
+    EXPECT_EQ(pkt6_, pkt4o6->getPkt6());
+    EXPECT_EQ(DHCPDISCOVER, pkt4o6->getType());
+}
+
+// This test verifies that the pack() method handles the building
+// process correctly.
+TEST_F(Pkt4o6Test, pack) {
+    // prepare unpacked DHCPv4 packet (see the note in constructor test)
+    pkt4_.reset(new Pkt4(&buffer4_[0], buffer4_.size()));
+    pkt4_->unpack();
+
+    // Construct 4o6 packet to be tested and pack the data.
+    Pkt4o6 pkt4o6(pkt4_, pkt6_);
+    pkt4o6.pack();
+
+    // The packed data should be:
+    // 4-byte DHCPv6 message header
+    // 4-byte header part of DHCPv4 message option
+    // Raw DHCPv4 message (data stored in buffer4_)
+    EXPECT_EQ(4 + 4 + buffer4_.size(),
+              pkt4o6.getPkt6()->getBuffer().getLength());
+
+    // Check the DHCPv4 message option content (Pkt4o6 class is not responsible
+    // for making it valid, so we won't examine it)
+    const uint8_t* cp = static_cast<const uint8_t*>(
+        pkt4o6.getPkt6()->getBuffer().getData());
+    EXPECT_EQ(0, cp[4]);
+    EXPECT_EQ(D6O_DHCPV4_MSG, cp[5]);
+    EXPECT_EQ((buffer4_.size() >> 8) & 0xff, cp[6]);
+    EXPECT_EQ(buffer4_.size() & 0xff, cp[7]);
+    EXPECT_EQ(0, memcmp(&cp[8], &buffer4_[0], buffer4_.size()));
+}
+
+/// @todo: Add a test that handles actual DHCP4o6 traffic capture
+///        once we get it. We should add the capture to pkt_captures{4,6}.cc
+}

+ 8 - 0
src/lib/dhcp/tests/pkt6_unittest.cc

@@ -594,6 +594,14 @@ TEST_F(Pkt6Test, getName) {
             EXPECT_STREQ("DECLINE", Pkt6::getName(type));
             break;
 
+        case DHCPV6_DHCPV4_QUERY:
+            EXPECT_STREQ("DHCPV4_QUERY", Pkt6::getName(type));
+            break;
+
+        case DHCPV6_DHCPV4_RESPONSE:
+            EXPECT_STREQ("DHCPV4_RESPONSE", Pkt6::getName(type));
+            break;
+
         case DHCPV6_INFORMATION_REQUEST:
             EXPECT_STREQ("INFORMATION_REQUEST",
                          Pkt6::getName(type));

+ 1 - 1
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -593,7 +593,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
             // The decodeHex function expects that the string contains an
             // even number of digits. If we don't meet this requirement,
             // we have to insert a leading 0.
-            if (!data_param.empty() && data_param.length() % 2) {
+            if (!data_param.empty() && ((data_param.length() % 2) != 0)) {
                 data_param = data_param.insert(0, "0");
             }
             util::encode::decodeHex(data_param, binary);

+ 2 - 1
src/lib/dhcpsrv/parsers/dhcp_parsers.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -745,6 +745,7 @@ private:
 
     /// Instance of option definition being created by this parser.
     OptionDefinitionPtr option_definition_;
+
     /// Name of the space the option definition belongs to.
     std::string option_space_name_;
 

+ 88 - 0
src/lib/dhcpsrv/subnet.h

@@ -507,6 +507,83 @@ private:
 /// @brief A generic pointer to either Subnet4 or Subnet6 object
 typedef boost::shared_ptr<Subnet> SubnetPtr;
 
+/// @brief This structure contains information about DHCP4o6 (RFC7341)
+///
+/// DHCP4o6 is completely optional. If it is not enabled, this structure
+/// does not contain any information.
+struct Cfg4o6 {
+
+    /// the default constructor.
+    ///
+    /// Initializes fields to their default value.
+    Cfg4o6()
+    :enabled_(false), subnet4o6_(std::make_pair(asiolink::IOAddress("::"), 128u)) {
+    }
+
+    /// @brief Returns whether the DHCP4o6 is enabled or not.
+    /// @return true if enabled
+    bool enabled() const {
+        return (enabled_);
+    }
+
+    /// @brief Sets the DHCP4o6 enabled status.
+    /// @param enabled specifies if the DHCP4o6 should be enabled or not
+    void enabled(bool enabled) {
+        enabled_ = enabled;
+    }
+
+    /// @brief Returns the DHCP4o6 interface.
+    /// @return value of the 4o6-interface parameter.
+    std::string getIface4o6() const {
+        return (iface4o6_);
+    }
+
+    /// @brief Sets the 4o6-interface.
+    /// @param iface name of the network interface the 4o6 traffic is received on
+    void setIface4o6(const std::string& iface) {
+        iface4o6_ = iface;
+    }
+
+    /// @brief Returns prefix/len for the IPv6 subnet.
+    /// @return prefix/length pair
+    std::pair<asiolink::IOAddress, uint8_t> getSubnet4o6() const {
+        return (subnet4o6_);
+    }
+
+    /// @brief Sets the prefix/length information (content of the 4o6-subnet).
+    /// @param subnet IOAddress that represents a prefix
+    /// @param prefix specifies prefix length
+    void setSubnet4o6(const asiolink::IOAddress& subnet, uint8_t prefix) {
+        subnet4o6_ = std::make_pair(subnet, prefix);
+    }
+
+    /// @brief Returns the interface-id.
+    /// @return the option representing interface-id (or NULL)
+    OptionPtr getInterfaceId() const {
+        return (interface_id_);
+    }
+
+    /// @brief Sets the interface-id
+    /// @param opt option to be used as interface-id match
+    void setInterfaceId(const OptionPtr& opt) {
+        interface_id_ = opt;
+    }
+
+private:
+
+    /// Specifies if 4o6 is enabled on this subnet.
+    bool enabled_;
+
+    /// Specifies the network interface used as v4 subnet selector.
+    std::string iface4o6_;
+
+    /// Specifies the IPv6 subnet used for v4 subnet selection.
+    std::pair<asiolink::IOAddress, uint8_t> subnet4o6_;
+
+    /// Specifies the v6 interface-id used for v4 subnet selection.
+    OptionPtr interface_id_;
+};
+
 /// @brief A configuration holder for IPv4 subnet.
 ///
 /// This class represents an IPv4 subnet.
@@ -559,6 +636,14 @@ public:
         return (match_client_id_);
     }
 
+    /// @brief Returns DHCP4o6 configuration parameters.
+    ///
+    /// This structure is always available. If the 4o6 is not enabled, its
+    /// enabled_ field will be set to false.
+    Cfg4o6& get4o6() {
+        return (dhcp4o6_);
+    }
+
 private:
 
     /// @brief Returns default address for pool selection
@@ -581,6 +666,9 @@ private:
     /// @brief Should server use client identifiers for client lease
     /// lookup.
     bool match_client_id_;
+
+    /// @brief All the information related to DHCP4o6
+    Cfg4o6 dhcp4o6_;
 };
 
 /// @brief A pointer to a @c Subnet4 object

+ 109 - 18
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -458,10 +458,10 @@ public:
     std::string error_text_;
 };
 
-/// @brief Check Basic parsing of option definitions.
+/// @brief Check basic parsing of option definitions.
 ///
 /// Note that this tests basic operation of the OptionDefinitionListParser and
-/// OptionDefinitionParser.  It uses a simple configuration consisting of one
+/// OptionDefinitionParser.  It uses a simple configuration consisting of
 /// one definition and verifies that it is parsed and committed to storage
 /// correctly.
 TEST_F(ParseConfigTest, basicOptionDefTest) {
@@ -481,7 +481,7 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
 
     // Verify that the configuration string parses.
     int rcode = parseConfiguration(config);
-    ASSERT_TRUE(rcode == 0);
+    ASSERT_EQ(0, rcode);
 
 
     // Verify that the option definition can be retrieved.
@@ -497,7 +497,73 @@ TEST_F(ParseConfigTest, basicOptionDefTest) {
     EXPECT_TRUE(def->getEncapsulatedSpace().empty());
 }
 
-/// @brief Check Basic parsing of options.
+/// @brief Check minimal parsing of option definitions.
+///
+/// Same than basic but without optional parameters set to their default.
+TEST_F(ParseConfigTest, minimalOptionDefTest) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"ipv4-address\","
+        "      \"space\": \"isc\""
+        "  } ]"
+        "}";
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config);
+    ASSERT_EQ(0, rcode);
+
+
+    // Verify that the option definition can be retrieved.
+    OptionDefinitionPtr def =
+        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Verify that the option definition is correct.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_FALSE(def->getArrayType());
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+/// @brief Check parsing of option definitions using default dhcp6 space.
+///
+/// Same than minimal but using the fact the default universe is V6
+/// so the default space is dhcp6
+TEST_F(ParseConfigTest, defaultSpaceOptionDefTest) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 10000,"
+        "      \"type\": \"ipv6-address\""
+        "  } ]"
+        "}";
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config);
+    ASSERT_EQ(0, rcode);
+
+
+    // Verify that the option definition can be retrieved.
+    OptionDefinitionPtr def =
+        CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get("dhcp6", 10000);
+    ASSERT_TRUE(def);
+
+    // Verify that the option definition is correct.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(10000, def->getCode());
+    EXPECT_FALSE(def->getArrayType());
+    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+/// @brief Check basic parsing of options.
 ///
 /// Note that this tests basic operation of the OptionDataListParser and
 /// OptionDataParser.  It uses a simple configuration consisting of one
@@ -511,10 +577,7 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
         "      \"name\": \"foo\","
         "      \"code\": 100,"
         "      \"type\": \"ipv4-address\","
-        "      \"array\": False,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"isc\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"isc\""
         " } ], "
         " \"option-data\": [ {"
         "    \"name\": \"foo\","
@@ -527,13 +590,47 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
 
     // Verify that the configuration string parses.
     int rcode = parseConfiguration(config);
-    ASSERT_TRUE(rcode == 0);
+    ASSERT_EQ(0, rcode);
 
     // Verify that the option can be retrieved.
     OptionPtr opt_ptr = getOptionPtr("isc", 100);
     ASSERT_TRUE(opt_ptr);
 
-    // Verify that the option definition is correct.
+    // Verify that the option data is correct.
+    std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
+
+    EXPECT_EQ(val, opt_ptr->toText());
+}
+
+/// @brief Check minimal parsing of options.
+///
+/// Same than basic but without optional parameters set to their default.
+TEST_F(ParseConfigTest, minimalOptionDataTest) {
+
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"ipv4-address\","
+        "      \"space\": \"isc\""
+        " } ], "
+        " \"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"data\": \"192.0.2.0\""
+        " } ]"
+        "}";
+
+    // Verify that the configuration string parses.
+    int rcode = parseConfiguration(config);
+    ASSERT_EQ(0, rcode);
+
+    // Verify that the option can be retrieved.
+    OptionPtr opt_ptr = getOptionPtr("isc", 100);
+    ASSERT_TRUE(opt_ptr);
+
+    // Verify that the option data is correct.
     std::string val = "type=00100, len=00004: 192.0.2.0 (ipv4-address)";
 
     EXPECT_EQ(val, opt_ptr->toText());
@@ -690,7 +787,6 @@ TEST_F(ParseConfigTest, optionDataNoName) {
         "{ \"option-data\": [ {"
         "    \"space\": \"dhcp6\","
         "    \"code\": 23,"
-        "    \"csv-format\": True,"
         "    \"data\": \"2001:db8:1::1\""
         " } ]"
         "}";
@@ -711,7 +807,6 @@ TEST_F(ParseConfigTest, optionDataNoCode) {
         "{ \"option-data\": [ {"
         "    \"space\": \"dhcp6\","
         "    \"name\": \"dns-servers\","
-        "    \"csv-format\": True,"
         "    \"data\": \"2001:db8:1::1\""
         " } ]"
         "}";
@@ -772,9 +867,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
         "      \"code\": 2345,"
         "      \"type\": \"ipv6-address\","
         "      \"array\": True,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp6\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"dhcp6\""
         "  } ],"
         "  \"option-data\": [ {"
         "    \"name\": \"foo-name\","
@@ -800,9 +893,7 @@ TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
         "      \"code\": 2345,"
         "      \"type\": \"ipv6-address\","
         "      \"array\": True,"
-        "      \"record-types\": \"\","
-        "      \"space\": \"dhcp6\","
-        "      \"encapsulate\": \"\""
+        "      \"space\": \"dhcp6\""
         "  } ],"
         "  \"option-data\": [ {"
         "    \"code\": 2345,"

+ 10 - 2
src/lib/eval/Makefile.am

@@ -12,18 +12,25 @@ AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
 
 lib_LTLIBRARIES = libkea-eval.la
 libkea_eval_la_SOURCES  =
+libkea_eval_la_SOURCES += eval_log.cc eval_log.h
 libkea_eval_la_SOURCES += token.cc token.h
 
 libkea_eval_la_SOURCES += parser.cc parser.h
 libkea_eval_la_SOURCES += lexer.cc
 libkea_eval_la_SOURCES += eval_context.cc
 
+nodist_libkea_eval_la_SOURCES = eval_messages.h eval_messages.cc
+
 libkea_eval_la_CXXFLAGS = $(AM_CXXFLAGS)
 libkea_eval_la_CPPFLAGS = $(AM_CPPFLAGS)
 libkea_eval_la_LIBADD   = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 libkea_eval_la_LIBADD  += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+libkea_eval_la_LIBADD  += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_eval_la_LIBADD  += $(top_builddir)/src/lib/util/libkea-util.la
+libkea_eval_la_LIBADD  += $(LOG4CPLUS_LIBS) $(CRYPTO_LIBS)
+
 libkea_eval_la_LDFLAGS  = -no-undefined -version-info 3:0:0
-libkea_eval_la_LDFLAGS += $(LOG4CPLUS_LIBS) $(CRYPTO_LDFLAGS)
+libkea_eval_la_LDFLAGS += $(CRYPTO_LDFLAGS)
 
 EXTRA_DIST  = eval.dox
 EXTRA_DIST += eval_messages.mes
@@ -33,6 +40,7 @@ eval_messages.h eval_messages.cc: s-messages
 
 s-messages: eval_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/eval/eval_messages.mes
+	touch $@
 
 # Tell Automake that the eval_messages.{cc,h} source files are created in the
 # build process, so it must create these before doing anything else. Although
@@ -43,7 +51,7 @@ s-messages: eval_messages.mes
 # first.
 BUILT_SOURCES = eval_messages.h eval_messages.cc
 
-CLEANFILES = eval_messages.h eval_messages.cc
+CLEANFILES = eval_messages.h eval_messages.cc s-messages
 
 # If we want to get rid of all flex/bison generated files, we need to use
 # make maintainer-clean. The proper way to introduce custom commands for

+ 26 - 0
src/lib/eval/eval_log.cc

@@ -0,0 +1,26 @@
+// Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the Eval (classification) code
+
+#include <eval/eval_log.h>
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger eval_logger("eval");
+
+} // namespace dhcp
+} // namespace isc
+

+ 49 - 0
src/lib/eval/eval_log.h

@@ -0,0 +1,49 @@
+// Copyright (C) 2015  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef EVAL_LOG_H
+#define EVAL_LOG_H
+
+#include <log/macros.h>
+#include <eval/eval_messages.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Eval debug Logging levels
+///
+/// Defines the levels used to output debug messages in the eval (classification) code.
+/// Note that higher numbers equate to more verbose (and detailed) output.
+
+// The first level traces normal operations,
+const int EVAL_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+// The next level traces each call to hook code.
+const int EVAL_DBG_CALLS = DBGLVL_TRACE_BASIC_DATA;
+
+// Additional information on the calls.  Report each call to a callout (even
+// if there are multiple callouts on a hook) and each status return.
+const int EVAL_DBG_EXTENDED_CALLS = DBGLVL_TRACE_DETAIL_DATA;
+
+/// @brief Eval Logger
+///
+/// Define the logger used to log messages.  We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger eval_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // EVAL_LOG_H

+ 5 - 0
src/lib/eval/eval_messages.mes

@@ -18,3 +18,8 @@ $NAMESPACE isc::dhcp
 This debug message indicates that the expression has been evaluated
 to said value. This message is mostly useful during debugging of the
 client classification expressions.
+
+% EVAL_SUBSTRING_BAD_PARAM_CONVERSION starting %1, length %2
+This debug message indicates that the parameter for the starting postion
+or length of the substring couldn't be converted to an integer.  In this
+case the substring routine returns an empty string.

+ 5 - 2
src/lib/eval/tests/Makefile.am

@@ -2,6 +2,8 @@ SUBDIRS = .
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DLOGGING_SPEC_FILE=\"$(abs_top_srcdir)/src/lib/dhcpsrv/logging.spec\"
+
 AM_CXXFLAGS = $(KEA_CXXFLAGS)
 
 # Some versions of GCC warn about some versions of Boost regarding
@@ -24,8 +26,9 @@ if HAVE_GTEST
 
 TESTS += libeval_unittests
 
-libeval_unittests_SOURCES  = token_unittest.cc main.cc
-libeval_unittests_SOURCES += context_unittest.cc
+libeval_unittests_SOURCES  = context_unittest.cc
+libeval_unittests_SOURCES += token_unittest.cc
+libeval_unittests_SOURCES += run_unittests.cc
 libeval_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 libeval_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 libeval_unittests_LDFLAGS  = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)

src/lib/eval/tests/main.cc → src/lib/eval/tests/run_unittests.cc


+ 320 - 0
src/lib/eval/tests/token_unittest.cc

@@ -24,6 +24,8 @@
 #include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 
+#include <arpa/inet.h>
+
 using namespace std;
 using namespace isc::dhcp;
 
@@ -60,6 +62,40 @@ public:
     OptionPtr option_str4_; ///< A string option for DHCPv4
     OptionPtr option_str6_; ///< A string option for DHCPv6
 
+
+    /// @brief Verify that the substring eval works properly
+    ///
+    /// This function takes the parameters and sets up the value
+    /// stack then executes the eval and checks the results.
+    ///
+    /// @param test_string The string to operate on
+    /// @param test_start The postion to start when getting a substring
+    /// @param test_length The length of the substring to get
+    /// @param result_string The expected result of the eval
+    void verifySubstringEval(const std::string& test_string,
+                             const std::string& test_start,
+                             const std::string& test_length,
+                             const std::string& result_string) {
+
+        // create the token
+        ASSERT_NO_THROW(t_.reset(new TokenSubstring()));
+
+        // push values on stack
+        values_.push(test_string);
+        values_.push(test_start);
+        values_.push(test_length);
+
+        // evaluate the token
+        EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+        // verify results
+        ASSERT_EQ(1, values_.size());
+        EXPECT_EQ(result_string, values_.top());
+
+        // remove result
+        values_.pop();
+    }
+
     /// @todo: Add more option types here
 };
 
@@ -93,6 +129,116 @@ TEST_F(TokenTest, string6) {
     EXPECT_EQ("foo", values_.top());
 }
 
+// This simple test checks that a TokenHexString, representing a constant
+// string coded in hexadecimal, can be used in Pkt4 evaluation.
+// (The actual packet is not used)
+TEST_F(TokenTest, hexstring4) {
+    TokenPtr empty;
+    TokenPtr bad;
+    TokenPtr nodigit;
+    TokenPtr baddigit;
+    TokenPtr bell;
+    TokenPtr foo;
+    TokenPtr cookie;
+
+    // Store constant empty hexstring "" ("") in the TokenHexString object.
+    ASSERT_NO_THROW(empty.reset(new TokenHexString("")));
+    // Store bad encoded hexstring "0abc" ("").
+    ASSERT_NO_THROW(bad.reset(new TokenHexString("0abc")));
+    // Store hexstring with no digits "0x" ("").
+    ASSERT_NO_THROW(nodigit.reset(new TokenHexString("0x")));
+    // Store hexstring with a bad hexdigit "0xxabc" ("").
+    ASSERT_NO_THROW(baddigit.reset(new TokenHexString("0xxabc")));
+    // Store hexstring with an odd number of hexdigits "0x7" ("\a").
+    ASSERT_NO_THROW(bell.reset(new TokenHexString("0x7")));
+    // Store constant hexstring "0x666f6f" ("foo").
+    ASSERT_NO_THROW(foo.reset(new TokenHexString("0x666f6f")));
+    // Store constant hexstring "0x63825363" (DHCP_OPTIONS_COOKIE).
+    ASSERT_NO_THROW(cookie.reset(new TokenHexString("0x63825363")));
+
+    // Make sure that tokens can be evaluated without exceptions.
+    ASSERT_NO_THROW(empty->evaluate(*pkt4_, values_));
+    ASSERT_NO_THROW(bad->evaluate(*pkt4_, values_));
+    ASSERT_NO_THROW(nodigit->evaluate(*pkt4_, values_));
+    ASSERT_NO_THROW(baddigit->evaluate(*pkt4_, values_));
+    ASSERT_NO_THROW(bell->evaluate(*pkt4_, values_));
+    ASSERT_NO_THROW(foo->evaluate(*pkt4_, values_));
+    ASSERT_NO_THROW(cookie->evaluate(*pkt4_, values_));
+
+    // Check that the evaluation put its value on the values stack.
+    ASSERT_EQ(7, values_.size());
+    uint32_t expected = htonl(DHCP_OPTIONS_COOKIE);
+    EXPECT_EQ(4, values_.top().size());
+    EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4));
+    values_.pop();
+    EXPECT_EQ("foo", values_.top());
+    values_.pop();
+    EXPECT_EQ("\a", values_.top());
+    values_.pop();
+    EXPECT_EQ("", values_.top());
+    values_.pop();
+    EXPECT_EQ("", values_.top());
+    values_.pop();
+    EXPECT_EQ("", values_.top());
+    values_.pop();
+    EXPECT_EQ("", values_.top());
+}
+
+// This simple test checks that a TokenHexString, representing a constant
+// string coded in hexadecimal, can be used in Pkt6 evaluation.
+// (The actual packet is not used)
+TEST_F(TokenTest, hexstring6) {
+    TokenPtr empty;
+    TokenPtr bad;
+    TokenPtr nodigit;
+    TokenPtr baddigit;
+    TokenPtr bell;
+    TokenPtr foo;
+    TokenPtr cookie;
+
+    // Store constant empty hexstring "" ("") in the TokenHexString object.
+    ASSERT_NO_THROW(empty.reset(new TokenHexString("")));
+    // Store bad encoded hexstring "0abc" ("").
+    ASSERT_NO_THROW(bad.reset(new TokenHexString("0abc")));
+    // Store hexstring with no digits "0x" ("").
+    ASSERT_NO_THROW(nodigit.reset(new TokenHexString("0x")));
+    // Store hexstring with a bad hexdigit "0xxabc" ("").
+    ASSERT_NO_THROW(baddigit.reset(new TokenHexString("0xxabc")));
+    // Store hexstring with an odd number of hexdigits "0x7" ("\a").
+    ASSERT_NO_THROW(bell.reset(new TokenHexString("0x7")));
+    // Store constant hexstring "0x666f6f" ("foo").
+    ASSERT_NO_THROW(foo.reset(new TokenHexString("0x666f6f")));
+    // Store constant hexstring "0x63825363" (DHCP_OPTIONS_COOKIE).
+    ASSERT_NO_THROW(cookie.reset(new TokenHexString("0x63825363")));
+
+    // Make sure that tokens can be evaluated without exceptions.
+    ASSERT_NO_THROW(empty->evaluate(*pkt6_, values_));
+    ASSERT_NO_THROW(bad->evaluate(*pkt6_, values_));
+    ASSERT_NO_THROW(nodigit->evaluate(*pkt6_, values_));
+    ASSERT_NO_THROW(baddigit->evaluate(*pkt6_, values_));
+    ASSERT_NO_THROW(bell->evaluate(*pkt6_, values_));
+    ASSERT_NO_THROW(foo->evaluate(*pkt6_, values_));
+    ASSERT_NO_THROW(cookie->evaluate(*pkt6_, values_));
+
+    // Check that the evaluation put its value on the values stack.
+    ASSERT_EQ(7, values_.size());
+    uint32_t expected = htonl(DHCP_OPTIONS_COOKIE);
+    EXPECT_EQ(4, values_.top().size());
+    EXPECT_EQ(0, memcmp(&expected, &values_.top()[0], 4));
+    values_.pop();
+    EXPECT_EQ("foo", values_.top());
+    values_.pop();
+    EXPECT_EQ("\a", values_.top());
+    values_.pop();
+    EXPECT_EQ("", values_.top());
+    values_.pop();
+    EXPECT_EQ("", values_.top());
+    values_.pop();
+    EXPECT_EQ("", values_.top());
+    values_.pop();
+    EXPECT_EQ("", values_.top());
+}
+
 // This test checks if a token representing an option value is able to extract
 // the option from an IPv4 packet and properly store the option's value.
 TEST_F(TokenTest, optionString4) {
@@ -197,3 +343,177 @@ TEST_F(TokenTest, optionEqualTrue) {
 }
 
 };
+
+// This test checks if an a token representing a substring request
+// throws an exception if there aren't enough values on the stack.
+// The stack from the top is: length, start, string.
+// The actual packet is not used.
+TEST_F(TokenTest, substringNotEnoughValues) {
+    ASSERT_NO_THROW(t_.reset(new TokenSubstring()));
+
+    // Subsring requires three values on the stack, try
+    // with 0, 1 and 2 all should thorw an exception
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+    values_.push("");
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+    values_.push("0");
+    EXPECT_THROW(t_->evaluate(*pkt4_, values_), EvalBadStack);
+
+    // Three should work
+    values_.push("0");
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // As we had an empty string to start with we should have an empty
+    // one after the evaluate
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("", values_.top());
+}
+
+// Test getting the whole string in different ways
+TEST_F(TokenTest, substringWholeString) {
+    // Get the whole string
+    verifySubstringEval("foobar", "0", "6", "foobar");
+
+    // Get the whole string with "all"
+    verifySubstringEval("foobar", "0", "all", "foobar");
+
+    // Get the whole string with an extra long number
+    verifySubstringEval("foobar", "0", "123456", "foobar");
+
+    // Get the whole string counting from the back
+    verifySubstringEval("foobar", "-6", "all", "foobar");
+}
+
+// Test getting a suffix, in this case the last 3 characters
+TEST_F(TokenTest, substringTrailer) {
+    verifySubstringEval("foobar", "3", "3", "bar");
+    verifySubstringEval("foobar", "3", "all", "bar");
+    verifySubstringEval("foobar", "-3", "all", "bar");
+    verifySubstringEval("foobar", "-3", "123", "bar");
+}
+
+// Test getting the middle of the string in different ways
+TEST_F(TokenTest, substringMiddle) {
+    verifySubstringEval("foobar", "1", "4", "ooba");
+    verifySubstringEval("foobar", "-5", "4", "ooba");
+    verifySubstringEval("foobar", "-1", "-4", "ooba");
+    verifySubstringEval("foobar", "5", "-4", "ooba");
+}
+
+// Test getting the last letter in different ways
+TEST_F(TokenTest, substringLastLetter) {
+    verifySubstringEval("foobar", "5", "all", "r");
+    verifySubstringEval("foobar", "5", "1", "r");
+    verifySubstringEval("foobar", "5", "5", "r");
+    verifySubstringEval("foobar", "-1", "all", "r");
+    verifySubstringEval("foobar", "-1", "1", "r");
+    verifySubstringEval("foobar", "-1", "5", "r");
+}
+
+// Test we get only what is available if we ask for a longer string
+TEST_F(TokenTest, substringLength) {
+    // Test off the front
+    verifySubstringEval("foobar", "0", "-4", "");
+    verifySubstringEval("foobar", "1", "-4", "f");
+    verifySubstringEval("foobar", "2", "-4", "fo");
+    verifySubstringEval("foobar", "3", "-4", "foo");
+
+    // and the back
+    verifySubstringEval("foobar", "3", "4", "bar");
+    verifySubstringEval("foobar", "4", "4", "ar");
+    verifySubstringEval("foobar", "5", "4", "r");
+    verifySubstringEval("foobar", "6", "4", "");
+}
+
+// Test that we get nothing if the starting postion is out of the string
+TEST_F(TokenTest, substringStartingPosition) {
+    // Off the front
+    verifySubstringEval("foobar", "-7", "1", "");
+    verifySubstringEval("foobar", "-7", "-11", "");
+    verifySubstringEval("foobar", "-7", "all", "");
+
+    // and the back
+    verifySubstringEval("foobar", "6", "1", "");
+    verifySubstringEval("foobar", "6", "-11", "");
+    verifySubstringEval("foobar", "6", "all", "");
+}
+
+// Check what happens if we use strings that aren't numbers for start or length
+// We should return the empty string
+TEST_F(TokenTest, substringBadParams) {
+    verifySubstringEval("foobar", "0ick", "all", "");
+    verifySubstringEval("foobar", "ick0", "all", "");
+    verifySubstringEval("foobar", "ick", "all", "");
+    verifySubstringEval("foobar", "0", "ick", "");
+    verifySubstringEval("foobar", "0", "0ick", "");
+    verifySubstringEval("foobar", "0", "ick0", "");
+    verifySubstringEval("foobar", "0", "allaboard", "");
+}
+
+// lastly check that we don't get anything if the string is empty or
+// we don't ask for any characters from it.
+TEST_F(TokenTest, substringReturnEmpty) {
+    verifySubstringEval("", "0", "all", "");
+    verifySubstringEval("foobar", "0", "0", "");
+}
+
+// Check if we can use the substring and equal tokens together
+// We put the result on the stack first then the substring values
+// then evaluate the substring which should leave the original
+// result on the bottom with the substring result on next.
+// Evaulating the equals should produce true for the first
+// and false for the second.
+// throws an exception if there aren't enough values on the stack.
+// The stack from the top is: length, start, string.
+// The actual packet is not used.
+TEST_F(TokenTest, substringEquals) {
+    TokenPtr tequal;
+
+    ASSERT_NO_THROW(t_.reset(new TokenSubstring()));
+    ASSERT_NO_THROW(tequal.reset(new TokenEqual()));
+
+    // The final expected value
+    values_.push("ooba");
+
+    // The substring values
+    // Subsring requires three values on the stack, try
+    // with 0, 1 and 2 all should thorw an exception
+    values_.push("foobar");
+    values_.push("1");
+    values_.push("4");
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // we should have two values on the stack
+    ASSERT_EQ(2, values_.size());
+
+    // next the equals eval
+    EXPECT_NO_THROW(tequal->evaluate(*pkt4_, values_));
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("true", values_.top());
+
+    // get rid of the result
+    values_.pop();
+
+    // and try it again but with a bad final value
+    // The final expected value
+    values_.push("foob");
+
+    // The substring values
+    // Subsring requires three values on the stack, try
+    // with 0, 1 and 2 all should thorw an exception
+    values_.push("foobar");
+    values_.push("1");
+    values_.push("4");
+    EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+    // we should have two values on the stack
+    ASSERT_EQ(2, values_.size());
+
+    // next the equals eval
+    EXPECT_NO_THROW(tequal->evaluate(*pkt4_, values_));
+    ASSERT_EQ(1, values_.size());
+    EXPECT_EQ("false", values_.top());
+
+}

+ 111 - 0
src/lib/eval/token.cc

@@ -13,6 +13,10 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <eval/token.h>
+#include <eval/eval_log.h>
+#include <util/encode/hex.h>
+#include <boost/lexical_cast.hpp>
+#include <cstring>
 #include <string>
 
 using namespace isc::dhcp;
@@ -24,6 +28,40 @@ TokenString::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
     values.push(value_);
 }
 
+TokenHexString::TokenHexString(const string& str) : value_("") {
+    // Check string starts "0x" or "0x" and has at least one additional character.
+    if ((str.size() < 3) ||
+        (str[0] != '0') ||
+        ((str[1] != 'x') && (str[1] != 'X'))) {
+        return;
+    }
+    string digits = str.substr(2);
+
+    // Transform string of hexadecimal digits into binary format
+    vector<uint8_t> binary;
+    try {
+        // The decodeHex function expects that the string contains an
+        // even number of digits. If we don't meet this requirement,
+        // we have to insert a leading 0.
+        if ((digits.length() % 2) != 0) {
+            digits = digits.insert(0, "0");
+        }
+        util::encode::decodeHex(digits, binary);
+    } catch (...) {
+        return;
+    }
+
+    // Convert to a string (note that binary.size() cannot be 0)
+    value_.resize(binary.size());
+    memmove(&value_[0], &binary[0], binary.size());
+}
+
+void
+TokenHexString::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+    // Literals only push, nothing to pop
+    values.push(value_);
+}
+
 void
 TokenOption::evaluate(const Pkt& pkt, ValueStack& values) {
     OptionPtr opt = pkt.getOption(option_code_);
@@ -53,3 +91,76 @@ TokenEqual::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
     else
         values.push("false");
 }
+
+void
+TokenSubstring::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
+
+    if (values.size() < 3) {
+        isc_throw(EvalBadStack, "Incorrect stack order. Expected at least "
+                  "3 values for substring operator, got " << values.size());
+    }
+
+    string len_str = values.top();
+    values.pop();
+    string start_str = values.top();
+    values.pop();
+    string string_str = values.top();
+    values.pop();
+
+    // If we have no string to start with we push an empty string and leave
+    if (string_str.empty()) {
+        values.push("");
+        return;
+    }
+
+    // Convert the starting position and length from strings to numbers
+    // the length may also be "all" in which case simply make it the
+    // length of the string.
+    // If we have a problem push an empty string and leave
+    int start_pos;
+    int length;
+    try {
+        start_pos = boost::lexical_cast<int>(start_str);
+        if (len_str == "all") {
+            length = string_str.length();
+        } else {
+            length = boost::lexical_cast<int>(len_str);
+        }
+    } catch (const boost::bad_lexical_cast&) {
+        LOG_DEBUG(eval_logger, EVAL_DBG_TRACE,
+                  EVAL_SUBSTRING_BAD_PARAM_CONVERSION)
+            .arg(start_str)
+            .arg(len_str);
+
+        values.push("");
+        return;
+    }
+
+    const int string_length = string_str.length();
+    // If the starting postion is outside of the string push an
+    // empty string and leave
+    if ((start_pos < -string_length) || (start_pos >= string_length)) {
+        values.push("");
+        return;
+    }
+
+    // Adjust the values to be something for substr.  We first figure out
+    // the starting postion, then update it and the length to get the
+    // characters before or after it depending on the sign of length
+    if (start_pos < 0) {
+        start_pos = string_length + start_pos;
+    }
+
+    if (length < 0) {
+        length = -length;
+        if (length <=  start_pos){
+            start_pos -= length;
+        } else {
+            length = start_pos;
+            start_pos = 0;
+        }
+    }
+
+    // and finally get the substring
+    values.push(string_str.substr(start_pos, length));
+}

+ 84 - 4
src/lib/eval/token.h

@@ -56,7 +56,7 @@ public:
 /// - option[123] (a token that extracts value of option 123)
 /// - == (an operator that compares two other tokens)
 /// - substring(a,b,c) (an operator that takes three arguments: a string,
-///   first and last character)
+///   first character and length)
 class Token {
 public:
 
@@ -101,6 +101,31 @@ protected:
     std::string value_; ///< Constant value
 };
 
+/// @brief Token representing a constant string in hexadecimal format
+///
+/// This token holds value of a constant string giving in an hexadecimal
+/// format, for instance 0x666f6f is "foo"
+class TokenHexString : public Token {
+public:
+    /// Value is set during token construction.
+    ///
+    /// @param str constant string to be represented
+    /// (must be "0x" or "0X" followed by a string of hexadecimal digits
+    /// or decoding will fail)
+    TokenHexString(const std::string& str);
+
+    /// @brief Token evaluation (puts value of the constant string on
+    /// the stack after decoding or an empty string if decoding fails
+    /// (note it should not if the parser is correct)
+    ///
+    /// @param pkt (ignored)
+    /// @param values (represented string will be pushed here)
+    void evaluate(const Pkt& pkt, ValueStack& values);
+
+protected:
+    std::string value_; ///< Constant value
+};
+
 /// @brief Token that represents a value of an option
 ///
 /// This represents a reference to a given option, e.g. in the expression
@@ -160,10 +185,65 @@ public:
     /// either "true" or "false". It requires at least two parameters to be
     /// present on stack.
     ///
-    /// @throw EvalBadStack if there's less than 2 values on stack
+    /// @throw EvalBadStack if there are less than 2 values on stack
+    ///
+    /// @param pkt (unused)
+    /// @param values - stack of values (2 arguments will be popped, 1 result
+    ///        will be pushed)
+    void evaluate(const Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents the substring operator (returns a portion
+/// of the supplied string)
+///
+/// This token represents substring(str, start, len)  An operator that takes three
+/// arguments: a string, the first character and the length.
+class TokenSubstring : public Token {
+public:
+    /// @brief Constructor (does nothing)
+    TokenSubstring() {}
+
+    /// @brief Extract a substring from a string
+    ///
+    /// Evaluation does not use packet information.  It requires at least
+    /// three values to be present on the stack.  It will consume the top
+    /// three values on the stack as parameters and push the resulting substring
+    /// onto the stack.  From the top it expects the values on the stack as:
+    /// -  len
+    /// -  start
+    /// -  str
+    ///
+    /// str is the string to extract a substring from.  If it is empty, an empty
+    /// string is pushed onto the value stack.
+    ///
+    /// start is the postion from which the code starts extracting the substring.
+    /// 0 is the first character and a negative number starts from the end, with
+    /// -1 being the last character.  If the starting point is outside of the
+    /// original string an empty string is pushed onto the value stack.
+    ///
+    /// length is the number of characters from the string to extract.
+    /// "all" means all remaining characters from start to the end of string.
+    /// A negative number means to go from start towards the beginning of
+    /// the string, but doesn't include start.
+    /// If length is longer than the remaining portion of string
+    /// then the entire remaining portion is placed on the value stack.
+    ///
+    /// The following examples all use the base string "foobar", the first number
+    /// is the starting position and the second is the length.  Note that
+    /// a negative length only selects which characters to extract it does not
+    /// indicate an attempt to reverse the string.
+    /// -  0, all => "foobar"
+    /// -  0,  6  => "foobar"
+    /// -  0,  4  => "foob"
+    /// -  2, all => "obar"
+    /// -  2,  6  => "obar"
+    /// - -1, all => "r"
+    /// - -1, -4  => "ooba"
+    ///
+    /// @throw EvalBadStack if there are less than 3 values on stack
     ///
-    /// @brief pkt (unused)
-    /// @brief values - stack of values (2 arguments will be poped, 1 result
+    /// @param pkt (unused)
+    /// @param values - stack of values (3 arguments will be popped, 1 result
     ///        will be pushed)
     void evaluate(const Pkt& pkt, ValueStack& values);
 };