Browse Source

Merge branch 'master' into trac2450

Mukund Sivaraman 12 years ago
parent
commit
5bb4db417e
100 changed files with 6771 additions and 919 deletions
  1. 51 0
      ChangeLog
  2. 7 0
      Makefile.am
  3. 4 2
      README
  4. 0 1
      configure.ac
  5. 6 5
      doc/guide/bind10-guide.xml
  6. 12 4
      src/bin/bindctl/tests/bindctl_test.py
  7. 1 0
      src/bin/cfgmgr/local_plugins/.gitignore
  8. 2 0
      src/bin/cmdctl/.gitignore
  9. 20 4
      src/bin/cmdctl/Makefile.am
  10. 429 0
      src/bin/cmdctl/b10-certgen.cc
  11. 214 0
      src/bin/cmdctl/b10-certgen.xml
  12. 0 15
      src/bin/cmdctl/cmdctl-keyfile.pem
  13. 7 2
      src/bin/cmdctl/tests/Makefile.am
  14. 254 0
      src/bin/cmdctl/tests/b10-certgen_test.py
  15. 0 0
      src/bin/cmdctl/tests/testdata/expired-certfile.pem
  16. 21 0
      src/bin/cmdctl/tests/testdata/mangled-certfile.pem
  17. 19 0
      src/bin/cmdctl/tests/testdata/noca-certfile.pem
  18. 51 30
      src/bin/dhcp4/tests/dhcp4_test.py
  19. 1 4
      src/bin/dhcp6/config_parser.cc
  20. 0 11
      src/bin/dhcp6/dhcp6_srv.cc
  21. 0 11
      src/bin/dhcp6/dhcp6_srv.h
  22. 0 5
      src/bin/dhcp6/tests/config_parser_unittest.cc
  23. 49 27
      src/bin/dhcp6/tests/dhcp6_test.py
  24. 53 27
      src/bin/msgq/msgq.py.in
  25. 214 3
      src/bin/msgq/tests/msgq_test.py
  26. 1 1
      src/lib/asiolink/io_address.cc
  27. 1 1
      src/lib/asiolink/io_address.h
  28. 3 3
      src/lib/asiolink/tests/io_address_unittest.cc
  29. 2 0
      src/lib/datasrc/Makefile.am
  30. 12 0
      src/lib/datasrc/datasrc_messages.mes
  31. 73 0
      src/lib/datasrc/master_loader_callbacks.cc
  32. 67 0
      src/lib/datasrc/master_loader_callbacks.h
  33. 1 0
      src/lib/datasrc/memory/Makefile.am
  34. 2 2
      src/lib/datasrc/memory/rdataset.cc
  35. 22 9
      src/lib/datasrc/memory/rdataset.h
  36. 57 0
      src/lib/datasrc/memory/util_internal.h
  37. 1 1
      src/lib/datasrc/memory/zone_data.cc
  38. 7 24
      src/lib/datasrc/memory/zone_data_loader.cc
  39. 64 30
      src/lib/datasrc/memory/zone_data_updater.cc
  40. 31 6
      src/lib/datasrc/memory/zone_data_updater.h
  41. 12 1
      src/lib/datasrc/memory/zone_finder.cc
  42. 2 0
      src/lib/datasrc/tests/Makefile.am
  43. 124 0
      src/lib/datasrc/tests/master_loader_callbacks_test.cc
  44. 2 0
      src/lib/datasrc/tests/memory/Makefile.am
  45. 0 10
      src/lib/datasrc/tests/memory/memory_client_unittest.cc
  46. 66 9
      src/lib/datasrc/tests/memory/rdataset_unittest.cc
  47. 65 0
      src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
  48. 208 0
      src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
  49. 81 0
      src/lib/datasrc/tests/memory/zone_finder_unittest.cc
  50. 1 1
      src/lib/datasrc/tests/memory_datasrc_unittest.cc
  51. 85 0
      src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone
  52. 7 0
      src/lib/datasrc/tests/testdata/contexttest.zone
  53. 12 0
      src/lib/datasrc/tests/zone_finder_context_unittest.cc
  54. 2 1
      src/lib/dhcp/Makefile.am
  55. 2 2
      src/lib/dhcp/iface_mgr.cc
  56. 1 1
      src/lib/dhcp/iface_mgr_linux.cc
  57. 62 54
      src/lib/dhcp/libdhcp++.cc
  58. 9 17
      src/lib/dhcp/libdhcp++.h
  59. 52 24
      src/lib/dhcp/option.cc
  60. 41 5
      src/lib/dhcp/option.h
  61. 1 1
      src/lib/dhcp/option6_addrlst.cc
  62. 2 2
      src/lib/dhcp/option6_ia.cc
  63. 4 3
      src/lib/dhcp/option6_iaaddr.cc
  64. 10 10
      src/lib/dhcp/option6_int.h
  65. 9 9
      src/lib/dhcp/option6_int_array.h
  66. 370 0
      src/lib/dhcp/option_custom.cc
  67. 235 0
      src/lib/dhcp/option_custom.h
  68. 227 0
      src/lib/dhcp/option_data_types.cc
  69. 310 8
      src/lib/dhcp/option_data_types.h
  70. 307 120
      src/lib/dhcp/option_definition.cc
  71. 160 120
      src/lib/dhcp/option_definition.h
  72. 1 0
      src/lib/dhcp/tests/Makefile.am
  73. 89 66
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  74. 1 1
      src/lib/dhcp/tests/option6_ia_unittest.cc
  75. 906 0
      src/lib/dhcp/tests/option_custom_unittest.cc
  76. 431 157
      src/lib/dhcp/tests/option_definition_unittest.cc
  77. 2 2
      src/lib/dhcpsrv/addr_utilities.cc
  78. 1 1
      src/lib/dhcpsrv/alloc_engine.cc
  79. 1 0
      src/lib/dhcpsrv/tests/.gitignore
  80. 1 0
      src/lib/dns/Makefile.am
  81. 224 15
      src/lib/dns/master_lexer.cc
  82. 85 6
      src/lib/dns/master_lexer.h
  83. 2 1
      src/lib/dns/master_lexer_inputsource.cc
  84. 2 10
      src/lib/dns/master_lexer_inputsource.h
  85. 12 8
      src/lib/dns/master_lexer_state.h
  86. 122 0
      src/lib/dns/master_loader_callbacks.h
  87. 1 0
      src/lib/dns/tests/Makefile.am
  88. 347 6
      src/lib/dns/tests/master_lexer_state_unittest.cc
  89. 15 2
      src/lib/dns/tests/master_lexer_token_unittest.cc
  90. 160 0
      src/lib/dns/tests/master_lexer_unittest.cc
  91. 84 0
      src/lib/dns/tests/master_loader_callbacks_test.cc
  92. 1 1
      src/lib/log/logger_manager_impl.cc
  93. 7 0
      src/lib/log/tests/Makefile.am
  94. 16 3
      src/lib/log/tests/destination_test.sh.in
  95. 14 7
      src/lib/log/tests/init_logger_test.sh.in
  96. 6 2
      src/lib/log/tests/local_file_test.sh.in
  97. 2 1
      src/lib/log/tests/logger_lock_test.sh.in
  98. 9 3
      src/lib/log/tests/severity_test.sh.in
  99. 1 1
      src/lib/python/isc/log/tests/check_output.sh
  100. 0 0
      tests/tools/perfdhcp/tests/test_control_unittest.cc

+ 51 - 0
ChangeLog

@@ -1,3 +1,54 @@
+515.	[bug]		jinmei
+	The in-memory data source now accepts an RRSIG provided without
+	a covered RRset in loading.  A subsequent query for its owner name
+	of the covered type would generally result in NXRRSET; if the
+	covered RRset is of type NSEC3, the corresponding NSEC3 processing
+	would result in SERVFAIL.
+	(Trac #2420, git 6744c100953f6def5500bcb4bfc330b9ffba0f5f)
+
+514.	[bug]		jelte
+	b10-msgq now handles socket errors more gracefully when sending data
+	to clients. It no longer exits with 'broken pipe' errors, and is
+	also better at resending data on temporary error codes from send().
+	(Trac #2398, git 9f6b45ee210a253dca608848a58c824ff5e0d234)
+
+513.	[func]		marcin
+	Implemented the OptionCustom class for DHCPv4 and DHCPv6.
+	This class represents an option which has a defined
+	structure: a set of data fields of specific types and order.
+	It is used to represent those options that can't be
+	represented by any other specialized class.
+	(Trac #2312, git 28d885b457dda970d9aecc5de018ec1120143a10)
+
+512.	[func]		jelte
+	Added a new tool b10-certgen, to check and update the self-signed
+	SSL certificate used by b10-cmdctl. The original certificate
+	provided has been removed, and a fresh one is generated upon first
+	build. See the b10-certgen manpage for information on how to update
+	existing installed certificates.
+	(Trac #1044, git 510773dd9057ccf6caa8241e74a7a0b34ca971ab)
+
+511.	[bug]		stephen
+	Fixed a race condition in the DHCP tests whereby the test program
+	spawned a subprocess and attempted to read (without waiting) from
+	the interconnecting pipe before the subprocess had written
+	anything.  The lack of output was being interpreted as a test
+	failure.
+	(Trac #2410, git f53e65cdceeb8e6da4723730e4ed0a17e4646579)
+
+510.	[func]		marcin
+	DHCP option instances can be created using a collection of strings.
+	Each string represents a value of a particular data field within
+	an option. The data field values, given as strings, are validated
+	against the actual types of option fields specified in the options
+	definitions.
+	(Trac #2490, git 56cfd6612fcaeae9acec4a94e1e5f1a88142c44d)
+
+509.	[func]		muks
+	Log messages now include the pid of the process that logged the
+	message.
+	(Trac #1745, git fc8bbf3d438e8154e7c2bdd322145a7f7854dc6a)
+
 508.	[bug]		stephen
 	Split the DHCP library into two directories, each with its own
 	Makefile.  This properly solves the problem whereby a "make"

+ 7 - 0
Makefile.am

@@ -20,6 +20,13 @@ dist_doc_DATA = AUTHORS COPYING ChangeLog README
 
 .PHONY: check-valgrind check-valgrind-suppress
 
+install-exec-hook:
+	-@echo -e "\033[1;33m" # switch to yellow color text
+	@echo "NOTE: BIND 10 does not automatically start DNS services when it is run"
+	@echo "      in its default configuration. Please see the Guide for information"
+	@echo "      on how to configure these services to be started automatically."
+	-@echo -e "\033[m" # switch back to normal
+
 check-valgrind:
 if HAVE_VALGRIND
 	@VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --track-origins=yes --num-callers=48 --leak-check=full --fullpath-after=" \

+ 4 - 2
README

@@ -58,5 +58,7 @@ For operating system specific tips see the wiki at:
 
 Please see the wiki and the doc/ directory for various documentation.
 
-The BIND 10 suite is started by running "bind10". Note that the
-default configuration does not run any DNS or DHCP servers.
+The BIND 10 suite is started by running "bind10". Note that the default
+configuration does not start any DNS or DHCP services.  Please see the
+Guide for information on how to configure these services to be started
+automatically.

+ 0 - 1
configure.ac

@@ -705,7 +705,6 @@ fi
 AC_SUBST(BOTAN_LDFLAGS)
 AC_SUBST(BOTAN_LIBS)
 AC_SUBST(BOTAN_INCLUDES)
-
 # Even though chances are high we already performed a real compilation check
 # in the search for the right (pkg)config data, we try again here, to
 # be sure.

+ 6 - 5
doc/guide/bind10-guide.xml

@@ -425,11 +425,12 @@ var/
         </listitem>
 
         <listitem>
-          <para>In another console, enable the authoritative DNS service
-            (by using the <command>bindctl</command> utility to configure
-            the <command>b10-auth</command> component to run):
-            <screen>$ <userinput>bindctl</userinput></screen>
-            (Login with the provided default username and password.)
+          <para>DNS and DHCP components are not started in the default
+	    configuration.  In another console, enable the authoritative
+	    DNS service (by using the <command>bindctl</command> utility
+	    to configure the <command>b10-auth</command> component to
+	    run): <screen>$ <userinput>bindctl</userinput></screen>
+	    (Login with the provided default username and password.)
             <screen>
 &gt; <userinput>config add Boss/components b10-auth</userinput>
 &gt; <userinput>config set Boss/components/b10-auth/special auth</userinput>

+ 12 - 4
src/bin/bindctl/tests/bindctl_test.py

@@ -364,16 +364,24 @@ class TestConfigCommands(unittest.TestCase):
         socket_err_output = io.StringIO()
         sys.stdout = socket_err_output
         self.assertEqual(1, self.tool.run())
-        self.assertEqual("Failed to send request, the connection is closed\n",
-                         socket_err_output.getvalue())
+
+        # First few lines may be some kind of heading, or a warning that
+        # Python readline is unavailable, so we do a sub-string check.
+        self.assertIn("Failed to send request, the connection is closed",
+                      socket_err_output.getvalue())
+
         socket_err_output.close()
 
         # validate log message for http.client.CannotSendRequest
         cannot_send_output = io.StringIO()
         sys.stdout = cannot_send_output
         self.assertEqual(1, self.tool.run())
-        self.assertEqual("Can not send request, the connection is busy\n",
-                         cannot_send_output.getvalue())
+
+        # First few lines may be some kind of heading, or a warning that
+        # Python readline is unavailable, so we do a sub-string check.
+        self.assertIn("Can not send request, the connection is busy",
+                      cannot_send_output.getvalue())
+
         cannot_send_output.close()
 
     def test_apply_cfg_command_int(self):

+ 1 - 0
src/bin/cfgmgr/local_plugins/.gitignore

@@ -0,0 +1 @@
+/datasrc.spec

+ 2 - 0
src/bin/cmdctl/.gitignore

@@ -4,3 +4,5 @@
 /cmdctl.spec.pre
 /run_b10-cmdctl.sh
 /b10-cmdctl.8
+/cmdctl-keyfile.pem
+/cmdctl-certfile.pem

+ 20 - 4
src/bin/cmdctl/Makefile.am

@@ -4,6 +4,8 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 pkglibexec_SCRIPTS = b10-cmdctl
 
+bin_PROGRAMS = b10-certgen
+
 nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 pylogmessagedir = $(pyexecdir)/isc/log_messages/
 
@@ -25,15 +27,18 @@ CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.pyc
 
-man_MANS = b10-cmdctl.8
-DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST += $(man_MANS) b10-cmdctl.xml cmdctl_messages.mes
+man_MANS = b10-cmdctl.8 b10-certgen.1
+DISTCLEANFILES = $(man_MANS) cmdctl-certfile.pem cmdctl-keyfile.pem
+EXTRA_DIST += $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
 
 if GENERATE_DOCS
 
 b10-cmdctl.8: b10-cmdctl.xml
 	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-cmdctl.xml
 
+b10-certgen.1: b10-certgen.xml
+	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-certgen.xml
+
 else
 
 $(man_MANS):
@@ -54,12 +59,23 @@ b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
 	$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
 	chmod a+x $@
 
+b10_certgen_SOURCES = b10-certgen.cc
+b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES)
+b10_certgen_LDFLAGS = $(BOTAN_LIBS)
+
+# Generate the initial certificates immediately
+cmdctl-certfile.pem: b10-certgen
+	./b10-certgen -q -w
+
+cmdctl-keyfile.pem: b10-certgen
+	./b10-certgen -q -w
+
 if INSTALL_CONFIGURATIONS
 
 # Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA)
 # because these file will contain sensitive information.
 install-data-local:
-	$(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@   
+	$(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
 	for f in $(CMDCTL_CONFIGURATIONS) ; do	\
 	  if test ! -f $(DESTDIR)$(sysconfdir)/@PACKAGE@/$$f; then	\
 	    ${INSTALL} -m 640 $(srcdir)/$$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ;	\

+ 429 - 0
src/bin/cmdctl/b10-certgen.cc

@@ -0,0 +1,429 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <botan/botan.h>
+#include <botan/x509self.h>
+#include <botan/x509stor.h>
+#include <botan/rsa.h>
+#include <botan/dsa.h>
+#include <botan/data_src.h>
+using namespace Botan;
+
+#include <iostream>
+#include <fstream>
+#include <memory>
+#include <getopt.h>
+
+// For cleaner 'does not exist or is not readable' output than
+// botan provides
+#include <unistd.h>
+#include <errno.h>
+
+// This is a simple tool that creates a self-signed PEM certificate
+// for use with BIND 10. It creates a simple certificate for initial
+// setup. Currently, all values are hardcoded defaults. For future
+// versions, we may want to add more options for administrators.
+
+// It will create a PEM file containing a certificate with the following
+// values:
+// common name: localhost
+// organization: BIND10
+// country code: US
+
+// Additional error return codes; these are specifically
+// chosen to be distinct from validation error codes as
+// provided by Botan. Their main use is to distinguish
+// error cases in the unit tests.
+const int DECODING_ERROR = 100;
+const int BAD_OPTIONS = 101;
+const int READ_ERROR = 102;
+const int WRITE_ERROR = 103;
+const int UNKNOWN_ERROR = 104;
+const int NO_SUCH_FILE = 105;
+const int FILE_PERMISSION_ERROR = 106;
+
+void
+usage() {
+    std::cout << "Usage: b10-certgen [OPTION]..." << std::endl;
+    std::cout << "Validate, create, or update a self-signed certificate for "
+                 "use with b10-cmdctl" << std::endl;
+    std::cout << "" << std::endl;
+    std::cout << "Options:" << std::endl;
+    std::cout << "-c, --certfile=FILE\t\tfile to read or store the certificate"
+              << std::endl;
+    std::cout << "-f, --force\t\t\toverwrite existing certficate even if it"
+              << std::endl <<"\t\t\t\tis valid" << std::endl;
+    std::cout << "-h, --help\t\t\tshow this help" << std::endl;
+    std::cout << "-k, --keyfile=FILE\t\tfile to store the generated private key"
+              << std::endl;
+    std::cout << "-w, --write\t\t\tcreate a new certificate if the given file"
+              << std::endl << "\t\t\t\tdoes not exist, or if is is not valid"
+              << std::endl;
+    std::cout << "-q, --quiet\t\t\tprint no output when creating or validating"
+              << std::endl;
+}
+
+/// \brief Returns true if the given file exists
+///
+/// \param filename The file to check
+/// \return true if file exists
+bool
+fileExists(const std::string& filename) {
+    return (access(filename.c_str(), F_OK) == 0);
+}
+
+/// \brief Returns true if the given file exists and is readable
+///
+/// \param filename The file to check
+/// \return true if file exists and is readable
+bool
+fileIsReadable(const std::string& filename) {
+    return (access(filename.c_str(), R_OK) == 0);
+}
+
+/// \brief Returns true if the given file exists and is writable
+///
+/// \param filename The file to check
+/// \return true if file exists and is writable
+bool
+fileIsWritable(const std::string& filename) {
+    return (access(filename.c_str(), W_OK) == 0);
+}
+
+/// \brief Helper function for readable error output;
+///
+/// Returns string representation of X509 result code
+/// This does not appear to be provided by Botan itself
+///
+/// \param code An \c X509_Code instance
+/// \return A human-readable c string
+const char*
+X509CodeToString(const X509_Code& code) {
+    // note that this list provides more than we would
+    // need in this context, it is just the enum from
+    // the source code of Botan.
+    switch (code) {
+    case VERIFIED:
+        return ("verified");
+    case UNKNOWN_X509_ERROR:
+        return ("unknown x509 error");
+    case CANNOT_ESTABLISH_TRUST:
+        return ("cannot establish trust");
+    case CERT_CHAIN_TOO_LONG:
+        return ("certificate chain too long");
+    case SIGNATURE_ERROR:
+        return ("signature error");
+    case POLICY_ERROR:
+        return ("policy error");
+    case INVALID_USAGE:
+        return ("invalid usage");
+    case CERT_FORMAT_ERROR:
+        return ("certificate format error");
+    case CERT_ISSUER_NOT_FOUND:
+        return ("certificate issuer not found");
+    case CERT_NOT_YET_VALID:
+        return ("certificate not yet valid");
+    case CERT_HAS_EXPIRED:
+        return ("certificate has expired");
+    case CERT_IS_REVOKED:
+        return ("certificate has been revoked");
+    case CRL_FORMAT_ERROR:
+        return ("crl format error");
+    case CRL_NOT_YET_VALID:
+        return ("crl not yet valid");
+    case CRL_HAS_EXPIRED:
+        return ("crl has expired");
+    case CA_CERT_CANNOT_SIGN:
+        return ("CA cert cannot sign");
+    case CA_CERT_NOT_FOR_CERT_ISSUER:
+        return ("CA certificate not for certificate issuer");
+    case CA_CERT_NOT_FOR_CRL_ISSUER:
+        return ("CA certificate not for crl issuer");
+    default:
+        return ("Unknown X509 code");
+    }
+}
+
+class CertificateTool {
+public:
+    CertificateTool(bool quiet) : quiet_(quiet) {}
+
+    int
+    createKeyAndCertificate(const std::string& key_file_name,
+                            const std::string& cert_file_name) {
+        try {
+            AutoSeeded_RNG rng;
+
+            // Create and store a private key
+            print("Creating key file " + key_file_name);
+            RSA_PrivateKey key(rng, 2048);
+            std::ofstream key_file(key_file_name.c_str());
+            if (!key_file.good()) {
+                print(std::string("Error writing to ") + key_file_name +
+                      ": " + std::strerror(errno));
+                return (WRITE_ERROR);
+            }
+            key_file << PKCS8::PEM_encode(key, rng, "");
+            if (!key_file.good()) {
+                print(std::string("Error writing to ") + key_file_name +
+                      ": " + std::strerror(errno));
+                return (WRITE_ERROR);
+            }
+            key_file.close();
+
+            // Certificate options, currently hardcoded.
+            // For a future version we may want to make these
+            // settable.
+            X509_Cert_Options opts;
+            opts.common_name = "localhost";
+            opts.organization = "UNKNOWN";
+            opts.country = "XX";
+
+            opts.CA_key();
+
+            print("Creating certificate file " + cert_file_name);
+
+            // The exact call changed aftert 1.8, adding the
+            // hash function option
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
+            X509_Certificate cert =
+            X509::create_self_signed_cert(opts, key, "SHA-256", rng);
+#else
+            X509_Certificate cert =
+            X509::create_self_signed_cert(opts, key, rng);
+#endif
+
+            std::ofstream cert_file(cert_file_name.c_str());
+            if (!cert_file.good()) {
+                print(std::string("Error writing to ") + cert_file_name +
+                      ": " + std::strerror(errno));
+                return (WRITE_ERROR);
+            }
+            cert_file << cert.PEM_encode();
+            if (!cert_file.good()) {
+                print(std::string("Error writing to ") + cert_file_name +
+                      ": " + std::strerror(errno));
+                return (WRITE_ERROR);
+            }
+            cert_file.close();
+        } catch(std::exception& e) {
+            std::cout << "Error creating key or certificate: " << e.what()
+                      << std::endl;
+            return (UNKNOWN_ERROR);
+        }
+        return (0);
+    }
+
+    int
+    validateCertificate(const std::string& certfile) {
+        // Since we are dealing with a self-signed certificate here, we
+        // also use the certificate to check itself; i.e. we add it
+        // as a trusted certificate, then validate the certficate itself.
+        //const X509_Certificate cert(certfile);
+        try {
+            X509_Store store;
+            DataSource_Stream in(certfile);
+            store.add_trusted_certs(in);
+
+            const X509_Code result = store.validate_cert(certfile);
+
+            if (result == VERIFIED) {
+                print(certfile + " is valid");
+            } else {
+                print(certfile + " failed to verify: " +
+                      X509CodeToString(result));
+            }
+            return (result);
+        } catch (const Botan::Decoding_Error& bde) {
+            print(certfile + " failed to verify: " + bde.what());
+            return (DECODING_ERROR);
+        } catch (const Botan::Stream_IO_Error& bsie) {
+            print(certfile + " not read: " + bsie.what());
+            return (READ_ERROR);
+        }
+    }
+
+    /// \brief Runs the tool
+    ///
+    /// \param create_cert  Create certificate if true, validate if false.
+    ///                     Does nothing if certificate exists and is valid.
+    /// \param force_create Create new certificate even if it is valid.
+    /// \param certfile     Certificate file to read to or write from.
+    /// \param keyfile      Key file to write if certificate is created.
+    ///                     Ignored if create_cert is false
+    /// \return zero on success, non-zero on failure
+    int
+    run(bool create_cert, bool force_create, const std::string& certfile,
+        const std::string& keyfile)
+    {
+        if (create_cert) {
+            // Unless force is given, only create it if the current
+            // one is not OK
+
+            // First do some basic permission checks; both files
+            // should either not exist, or be both readable
+            // and writable
+            // The checks are done one by one so all errors can
+            // be enumerated in one go
+            if (fileExists(certfile)) {
+                if (!fileIsReadable(certfile)) {
+                    print(certfile + " not readable: " + std::strerror(errno));
+                    create_cert = false;
+                }
+                if (!fileIsWritable(certfile)) {
+                    print(certfile + " not writable: " + std::strerror(errno));
+                    create_cert = false;
+                }
+            }
+            // The key file really only needs write permissions (for
+            // b10-certgen that is)
+            if (fileExists(keyfile)) {
+                if (!fileIsWritable(keyfile)) {
+                    print(keyfile + " not writable: " + std::strerror(errno));
+                    create_cert = false;
+                }
+            }
+            if (!create_cert) {
+                print("Not creating new certificate, "
+                      "check file permissions");
+                return (FILE_PERMISSION_ERROR);
+            }
+
+            // If we reach this, we know that if they exist, we can both
+            // read and write them, so now it's up to content checking
+            // and/or force_create
+
+            if (force_create || !fileExists(certfile) ||
+                validateCertificate(certfile) != VERIFIED) {
+                return (createKeyAndCertificate(keyfile, certfile));
+            } else {
+                print("Not creating a new certificate (use -f to force)");
+            }
+        } else {
+            if (!fileExists(certfile)) {
+                print(certfile + ": " + std::strerror(errno));
+                return (NO_SUCH_FILE);
+            }
+            if (!fileIsReadable(certfile)) {
+                print(certfile + " not readable: " + std::strerror(errno));
+                return (FILE_PERMISSION_ERROR);
+            }
+            int result = validateCertificate(certfile);
+            if (result != 0) {
+                print("Running with -w would overwrite the certificate");
+            }
+            return (result);
+        }
+        return (0);
+    }
+private:
+    /// Prints the message to stdout unless quiet_ is true
+    void print(const std::string& msg) {
+        if (!quiet_) {
+            std::cout << msg << std::endl;
+        }
+    }
+
+    bool quiet_;
+};
+
+int
+main(int argc, char* argv[])
+{
+    Botan::LibraryInitializer init;
+
+    // create or check certificate
+    bool create_cert = false;
+    // force creation even if not necessary
+    bool force_create = false;
+    // don't print any output
+    bool quiet = false;
+
+    // default certificate file
+    std::string certfile("cmdctl-certfile.pem");
+    // default key file
+    std::string keyfile("cmdctl-keyfile.pem");
+
+    // whether or not the above values have been
+    // overridden (used in command line checking)
+    bool certfile_default = true;
+    bool keyfile_default = true;
+
+    // It would appear some environments insist on
+    // char* here (Sunstudio on Solaris), so we const_cast
+    // them to get rid of compiler warnings.
+    const struct option long_options[] = {
+        { const_cast<char*>("certfile"), required_argument, NULL, 'c' },
+        { const_cast<char*>("force"), no_argument, NULL, 'f' },
+        { const_cast<char*>("help"), no_argument, NULL, 'h' },
+        { const_cast<char*>("keyfile"), required_argument, NULL, 'k' },
+        { const_cast<char*>("write"), no_argument, NULL, 'w' },
+        { const_cast<char*>("quiet"), no_argument, NULL, 'q' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt, option_index;
+    while ((opt = getopt_long(argc, argv, "c:fhk:wq", long_options,
+                              &option_index)) != -1) {
+        switch (opt) {
+            case 'c':
+                certfile = optarg;
+                certfile_default = false;
+                break;
+            case 'f':
+                force_create = true;
+                break;
+            case 'h':
+                usage();
+                return (0);
+                break;
+            case 'k':
+                keyfile = optarg;
+                keyfile_default = false;
+                break;
+            case 'w':
+                create_cert = true;
+                break;
+            case 'q':
+                quiet = true;
+                break;
+            default:
+                // A message will have already been output about the error.
+                return (BAD_OPTIONS);
+        }
+    }
+
+    if (optind < argc) {
+        std::cout << "Error: extraneous arguments" << std::endl << std::endl;
+        usage();
+        return (BAD_OPTIONS);
+    }
+
+    // Some sanity checks on option combinations
+    if (create_cert && (certfile_default ^ keyfile_default)) {
+        std::cout << "Error: keyfile and certfile must both be specified "
+                     "if one of them is when calling b10-certgen in write "
+                     "mode." << std::endl;
+        return (BAD_OPTIONS);
+    }
+    if (!create_cert && !keyfile_default) {
+        std::cout << "Error: keyfile is not used when not in write mode"
+                  << std::endl;
+        return (BAD_OPTIONS);
+    }
+
+    // Initialize the tool and perform the appropriate action(s)
+    CertificateTool tool(quiet);
+    return (tool.run(create_cert, force_create, certfile, keyfile));
+}

+ 214 - 0
src/bin/cmdctl/b10-certgen.xml

@@ -0,0 +1,214 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+
+  <refentryinfo>
+    <date>November 15, 2012</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-certgen</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-certgen</refname>
+    <refpurpose>X509 Certificate generation tool for use with b10-cmdctl</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2012</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-certgen</command>
+        <group choice="opt">
+          <arg choice="[OPTION]..."><option>-</option></arg>
+        </group>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+    <para>The <command>b10-certgen</command> tool validates, creates, or
+      updates a self-signed X509 certificate for use in b10-cmdctl.
+    </para>
+
+    <para>
+      The connection between <command>bindctl</command> and
+      <command>b10-cmdctl</command> is done over HTTPS, and therefore
+      <command>b10-cmdctl</command> needs a certificate. Since these
+      certificates have expiry dates, they also need to be regenerated at
+      some point.
+
+      There are many tools to do so, but for ease of use, <command>
+      b10-certgen</command> can create a simple self-signed certificate.
+
+      By default, it will not create anything, but it will merely check an
+      existing certificate (if not specified, cmdctl-certfile.pem, in the
+      current working directory). And print whether it is valid, and
+      whether it would update if the option '-w' is given.
+
+      With that option, the certificate could then be replaced by a newly
+      created one. If the certificate is still valid, it would still not
+      be overwritten (however, if it is found to be invalid, for example
+      because it has expired, it would create a new one).
+
+      A new certificate is always created if the certificate file does
+      not exist, or if creation is forced (with the -f option).
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>ARGUMENTS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term>
+          <option>-c <replaceable>file</replaceable></option>,
+          <option>--certfile=<replaceable>file</replaceable></option>
+        </term>
+        <listitem>
+          <para>
+            File to read the certificate from, or write the certificate to.
+            If <option>-w</option> and <option>-c</option> are used,
+            <option>-k</option> is mandatory as well.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-f</option>,
+          <option>--force</option>
+        </term>
+        <listitem>
+          <para>
+            Force updating of certificate when <option>-w</option> is used,
+            even if the existing certificate is still valid.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-h</option>,
+          <option>--help</option>
+        </term>
+        <listitem>
+          <para>
+            Print the command line arguments and exit.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-k <replaceable>file</replaceable></option>,
+          <option>--keyfile=<replaceable>file</replaceable></option>
+        </term>
+        <listitem>
+          <para>
+            File to write the private key to. This option is only valid when <option>-w</option> is used, and if this option is used, <option>-c</option> is mandatory as well.
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-w</option>,
+          <option>--write</option>
+        </term>
+        <listitem>
+          <para>
+            Check the given certificate file. If it does not exist, a new
+            private key and certificate are created. If it does exist, the
+            certificate is validated. If it is not valid (for instance
+            because it has expired), it is overwritten with a newly created
+            certificate. If it is valid, nothing happens (use
+            <option>-f</option> to force an update in that case).
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term>
+          <option>-q</option>,
+          <option>--quiet</option>
+        </term>
+        <listitem>
+          <para>
+            Don't print informational messages (only command-line errors are
+            printed). Useful in scripts when only the return code is needed.
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>HISTORY</title>
+    <para>
+      The <command>b10-certgen</command> tool was first implemented
+      in November 2012 for the ISC BIND 10 project.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>EXAMPLE</title>
+    <para>
+      To update an expired certificate in BIND 10 that has been installed to
+      /usr/local:
+      <screen>
+$> cd /usr/local/etc/bind10-devel/
+
+$> b10-certgen
+cmdctl-certfile.pem failed to verify: certificate has expired
+Running with -w would overwrite the certificate
+
+$> b10-certgen --write
+cmdctl-certfile.pem failed to verify: certificate has expired
+Creating key file cmdctl-keyfile.pem
+Creating certificate file cmdctl-certfile.pem
+
+$> b10-certgen --write
+cmdctl-certfile.pem is valid
+Not creating a new certificate (use -f to force)
+      </screen>
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

+ 0 - 15
src/bin/cmdctl/cmdctl-keyfile.pem

@@ -1,15 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDpICWxJGKMvUhLFPbf5n8ZWogqjYcQqqoHqHVRHYjyiey6FZdt
-ZkY2s1gYh0G0NXtimlIgic+vEcFe7vdmyKntW7DYDaqAj0KrED7RKAj8324jNbSJ
-HtLP4evvJep3vsoNtTvNuceQJ46vukxyxgg3DuC9kVqPuD8CZ1Rq4ATyiwIDAQAB
-AoGBAOJlOtV+DUq6Y2Ou91VXRiU8GzKgAQP5iWgoe84Ljbxkn4XThBxVD2j94Fbp
-u7AjpDCMx6cbzpoo9w6XqaGizAmAehIfTE3eFYs74N/FM09Wg2OSDyxMY0jgyECU
-A4ukjlPwcGDbmgbmlY3i+FVHp+zCgtZEsMC1IAosMac1BoX5AkEA/lrXWaVtH8bo
-mut3GBaXvubZMdaUr0BUd5a9q+tt4dQcKG1kFqgCNKhNhBIcpiMVcz+jGmOuopNA
-8dnUGqv3FQJBAOqiJ54ZvOTWNDpJIe02wIXRxRmc1xhHFCqYP23KxBVrAcTYB19J
-lesov/hEbnGLCbKS/naZJ1zrTImUPNRLqx8CQCzDtA7U7GWhTiKluioFH+O7IRKC
-X1yQh80cPHlbT9VkzSfYSLssCmdWD35k6aHbntTPqFbmoD+AhveJjKi9BxkCQDwX
-1c+/RcrSNcQr0N2hZUOgyztZGRnlsnuKTMyA3yGhK23P6mt0PEpjQG+Ej0jTVGOB
-FF0pspQwy4R9C+tPif8CQH36NNlXBfVNmT7kDtyLmaE6pID0vY9duX56BJbU1R0x
-SQ8/LcfJagk6gvp08OyYCPA+WZ7u/bas9R/nMTCLivc=
------END RSA PRIVATE KEY-----

+ 7 - 2
src/bin/cmdctl/tests/Makefile.am

@@ -1,6 +1,9 @@
 PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
-PYTESTS = cmdctl_test.py
+PYTESTS = cmdctl_test.py b10-certgen_test.py
 EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += testdata/expired-certfile.pem
+EXTRA_DIST += testdata/mangled-certfile.pem
+EXTRA_DIST += testdata/noca-certfile.pem
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
@@ -9,10 +12,12 @@ if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
+CLEANFILES = test-keyfile.pem test-certfile.pem
+
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 if ENABLE_PYTHON_COVERAGE
-	touch $(abs_top_srcdir)/.coverage 
+	touch $(abs_top_srcdir)/.coverage
 	rm -f .coverage
 	${LN_S} $(abs_top_srcdir)/.coverage .coverage
 endif

+ 254 - 0
src/bin/cmdctl/tests/b10-certgen_test.py

@@ -0,0 +1,254 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
+# Note: the main code is in C++, but what we are mostly testing is
+# options and behaviour (output/file creation, etc), which is easier
+# to test in python.
+
+import unittest
+import os
+from subprocess import call
+import subprocess
+import ssl
+import stat
+
+def run(command):
+    """
+    Small helper function that returns a tuple of (rcode, stdout, stderr) after
+    running the given command (an array of command and arguments, as passed on
+    to subprocess).
+    """
+    subp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    (stdout, stderr) = subp.communicate()
+    return (subp.returncode, stdout, stderr)
+
+class FileDeleterContext:
+    """
+    Simple Context Manager that deletes a given set of files when the context
+    is left.
+    """
+    def __init__(self, files):
+        self.files = files
+
+    def __enter__(self):
+        pass
+
+    def __exit__(self, type, value, traceback):
+        for f in self.files:
+            if os.path.exists(f):
+                os.unlink(f)
+
+class FilePermissionContext:
+    """
+    Simple Context Manager that temporarily modifies file permissions for
+    a given file
+    """
+    def __init__(self, f, unset_flags = [], set_flags = []):
+        """
+        Initialize file permission context.
+        See the stat module for possible flags to set or unset.
+        The flags are changed when the context is entered (i.e.
+        you can create the context first without any change)
+        The flags are changed back when the context is left.
+
+        Parameters:
+        f: string, file to change permissions for
+        unset_flags: list of flags to unset
+        set_flags: list of flags to set
+        """
+        self.file = f
+        self.orig_mode = os.stat(f).st_mode
+        new_mode = self.orig_mode
+        for flag in unset_flags:
+            new_mode = new_mode & ~flag
+        for flag in set_flags:
+            new_mode = new_mode | flag
+        self.new_mode = new_mode
+
+    def __enter__(self):
+        os.chmod(self.file, self.new_mode)
+
+    def __exit__(self, type, value, traceback):
+        os.chmod(self.file, self.orig_mode)
+
+def read_file_data(filename):
+    """
+    Simple text file reader that returns its contents as an array
+    """
+    with open(filename) as f:
+        return f.readlines()
+
+class TestCertGenTool(unittest.TestCase):
+    TOOL = '../b10-certgen'
+
+    def run_check(self, expected_returncode, expected_stdout, expected_stderr, command):
+        """
+        Runs the given command, and checks return code, and outputs (if provided).
+        Arguments:
+        expected_returncode, return code of the command
+        expected_stdout, (multiline) string that is checked agains stdout.
+                         May be None, in which case the check is skipped.
+        expected_stderr, (multiline) string that is checked agains stderr.
+                         May be None, in which case the check is skipped.
+        """
+        (returncode, stdout, stderr) = run(command)
+        self.assertEqual(expected_returncode, returncode, " ".join(command))
+        if expected_stdout is not None:
+            self.assertEqual(expected_stdout, stdout.decode())
+        if expected_stderr is not None:
+            self.assertEqual(expected_stderr, stderr.decode())
+
+    def validate_certificate(self, expected_result, certfile):
+        """
+        Validate a certificate, using the quiet option of the tool; it runs
+        the check option (-c) for the given base name of the certificate (-f
+        <certfile>), and compares the return code to the given
+        expected_result value
+        """
+        self.run_check(expected_result, '', '',
+                       [self.TOOL, '-q', '-c', certfile])
+        # Same with long options
+        self.run_check(expected_result, '', '',
+                       [self.TOOL, '--quiet', '--certfile', certfile])
+
+
+    def test_basic_creation(self):
+        """
+        Tests whether basic creation with no arguments (except output
+        file name) successfully creates a key and certificate
+        """
+        keyfile = 'test-keyfile.pem'
+        certfile = 'test-certfile.pem'
+        command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
+        self.creation_helper(command, certfile, keyfile)
+        # Do same with long options
+        command = [ self.TOOL, '--quiet', '--write', '--certfile=' + certfile, '--keyfile=' + keyfile ]
+        self.creation_helper(command, certfile, keyfile)
+
+    def creation_helper(self, command, certfile, keyfile):
+        """
+        Helper method for test_basic_creation.
+        Performs the actual checks
+        """
+        with FileDeleterContext([keyfile, certfile]):
+            self.assertFalse(os.path.exists(keyfile))
+            self.assertFalse(os.path.exists(certfile))
+            self.run_check(0, '', '', command)
+            self.assertTrue(os.path.exists(keyfile))
+            self.assertTrue(os.path.exists(certfile))
+
+            # Validate the certificate that was just created
+            self.validate_certificate(0, certfile)
+
+            # When run with the same options, it should *not* create it again,
+            # as the current certificate should still be valid
+            certdata = read_file_data(certfile)
+            keydata = read_file_data(keyfile)
+
+            self.run_check(0, '', '', command)
+
+            self.assertEqual(certdata, read_file_data(certfile))
+            self.assertEqual(keydata, read_file_data(keyfile))
+
+            # but if we add -f, it should force a new creation
+            command.append('-f')
+            self.run_check(0, '', '', command)
+            self.assertNotEqual(certdata, read_file_data(certfile))
+            self.assertNotEqual(keydata, read_file_data(keyfile))
+
+    def test_check_bad_certificates(self):
+        """
+        Tests a few pre-created certificates with the -c option
+        """
+        if ('CMDCTL_SRC_PATH' in os.environ):
+            path = os.environ['CMDCTL_SRC_PATH'] + "/tests/testdata/"
+        else:
+            path = "testdata/"
+        self.validate_certificate(10, path + 'expired-certfile.pem')
+        self.validate_certificate(100, path + 'mangled-certfile.pem')
+        self.validate_certificate(17, path + 'noca-certfile.pem')
+
+    def test_bad_options(self):
+        """
+        Tests some combinations of commands that should fail.
+        """
+        # specify -c but not -k
+        self.run_check(101,
+                       'Error: keyfile and certfile must both be specified '
+                       'if one of them is when calling b10-certgen in write '
+                       'mode.\n',
+                       '', [self.TOOL, '-w', '-c', 'foo'])
+        self.run_check(101,
+                       'Error: keyfile and certfile must both be specified '
+                       'if one of them is when calling b10-certgen in write '
+                       'mode.\n',
+                       '', [self.TOOL, '-w', '-k', 'foo'])
+        self.run_check(101,
+                       'Error: keyfile is not used when not in write mode\n',
+                       '', [self.TOOL, '-k', 'foo'])
+        # Extraneous argument
+        self.run_check(101, None, None, [self.TOOL, 'foo'])
+        # No such file
+        self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
+
+    def test_permissions(self):
+        """
+        Test some combinations of correct and bad permissions.
+        """
+        keyfile = 'mod-keyfile.pem'
+        certfile = 'mod-certfile.pem'
+        command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
+        # Delete them at the end
+        with FileDeleterContext([keyfile, certfile]):
+            # Create the two files first
+            self.run_check(0, '', '', command)
+            self.validate_certificate(0, certfile)
+
+            # Make the key file unwritable
+            with FilePermissionContext(keyfile, unset_flags = [stat.S_IWUSR]):
+                self.run_check(106, '', '', command)
+                # Should have no effect on validation
+                self.validate_certificate(0, certfile)
+
+            # Make the cert file unwritable
+            with FilePermissionContext(certfile, unset_flags = [stat.S_IWUSR]):
+                self.run_check(106, '', '', command)
+                # Should have no effect on validation
+                self.validate_certificate(0, certfile)
+
+            # Make the key file unreadable (this should not matter)
+            with FilePermissionContext(keyfile, unset_flags = [stat.S_IRUSR]):
+                self.run_check(0, '', '', command)
+
+                # unreadable key file should also not have any effect on
+                # validation
+                self.validate_certificate(0, certfile)
+
+            # Make the cert file unreadable (this should matter)
+            with FilePermissionContext(certfile, unset_flags = [stat.S_IRUSR]):
+                self.run_check(106, '', '', command)
+
+                # Unreadable cert file should also fail validation
+                self.validate_certificate(106, certfile)
+
+        # Not directly a permission problem, but trying to check or create
+        # in a nonexistent directory returns different error codes
+        self.validate_certificate(105, 'fakedir/cert')
+        self.run_check(103, '', '', [ self.TOOL, '-q', '-w', '-c',
+                                      'fakedir/cert', '-k', 'fakedir/key' ])
+
+if __name__== '__main__':
+    unittest.main()
+

src/bin/cmdctl/cmdctl-certfile.pem → src/bin/cmdctl/tests/testdata/expired-certfile.pem


+ 21 - 0
src/bin/cmdctl/tests/testdata/mangled-certfile.pem

@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
+A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
+MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
+NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
+ZWlqaW5nMraWDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
+CxMFY25uaWMxeZaRBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
+YW5nbGlrdW5AY25UAwMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
+JbEkYoy9SEsU9t/mfxLAICqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
+UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
+O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
+tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
+amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
+BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
+Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
+jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
+EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
+-----END CERTIFICATE-----

+ 19 - 0
src/bin/cmdctl/tests/testdata/noca-certfile.pem

@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe6gAwIBAgIRALUIj3nnW5uDE/+fglPvUDwwDQYJKoZIhvcNAQELBQAw
+HjELMAkGA1UEBhMCVVMxDzANBgNVBAMTBkJJTkQxMDAeFw0xMjExMTQxMjQ5MjVa
+Fw0xMzExMTQxMjQ5MjVaMB4xCzAJBgNVBAYTAlVTMQ8wDQYDVQQDEwZCSU5EMTAw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkIOPfs3Aw9kNDu1JqA2w3
+84/n9oUgAwAlHVmuJv7ZDw1MDaIKHjsh3DW09z+nv67GVksI7pFtAw5O4mnTDxpa
+JT0NKzhvYGfe8VdV/hWDogTIdk1QBJNZ2/id8z0h8z5001sARXPf+4mHBJslenH3
+YtZs22BG5RBLULtZ/2Nr7JkdfLlc6D5PCoDG22r1OiFkYVdCWfLDjisVIbSYPBtY
+BlKAIrvbmOtWcaGM+vQAhl0T5N8WRCKhaQH0DEmzQNckkYd7rSECo57KYiuvOdzp
+d+3bWTgGGy2ff0o3LZypv0O5s0TDC2H6hYtN4bUbcChUJbFu9b5sVZaOEVZtUsyD
+AgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgTwMB0GA1UdDgQW
+BBSqGzsEDNs9E7gBL5pD6XVAwUo4DTANBgkqhkiG9w0BAQsFAAOCAQEAMTNB8NCU
+dnLFZ0jNpvecbECkX/OWGlBYU4/CsoNiibwp4CtUYS2A4NFVjWAyuzLSHhRQi0vJ
+CCWLpKL4VTkaDN5Oft42iUhvEXMnriJqpfXHnjCiBwFFSPl5WKfMIaRNK+tF4zbB
+F+FGNEEmYG3t/ni82orDLq4oy+7CoQwzZNzj5yoV6q7O9kLR9OMPNwJrc27A4erB
+7VMRZslSrNA4uA6YhMZl8iEvO1H801ct0zTxawrCihPOZOCSLew35xjztO7d3YH8
+YavOu5kzeu7AgZ2n75H/qU47ZgBjbonn9Osvrct+RIwZuWTB2bDML8JhNaZCq0aA
+TDBC0QWqIYypLg==
+-----END CERTIFICATE-----

+ 51 - 30
src/bin/dhcp4/tests/dhcp4_test.py

@@ -45,11 +45,30 @@ class TestDhcpv4Daemon(unittest.TestCase):
     def tearDown(self):
         pass
 
+    def readPipe(self, pipe_fd):
+        """
+        Reads bytes from a pipe and returns a character string.  If nothing is
+        read, or if there is an error, an empty string is returned.
+
+        pipe_fd - Pipe file descriptor to read
+        """
+        try:
+            data = os.read(pipe_fd, 16384)
+            # Make sure we have a string
+            if (data is None):
+                data = ""
+            else:
+                data = str(data)
+        except OSError:
+            data = ""
+
+        return data
+
     def runCommand(self, params, wait=1):
         """
-        This method runs dhcp4 and returns a tuple: (returncode, stdout, stderr)
+        This method runs a command and returns a tuple: (returncode, stdout, stderr)
         """
-        ## @todo: Convert this into generic method and reuse it in dhcp6
+        ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
 
         print("Running command: %s" % (" ".join(params)))
 
@@ -89,46 +108,48 @@ class TestDhcpv4Daemon(unittest.TestCase):
         fl = fcntl.fcntl(fd, fcntl.F_GETFL)
         fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
 
-        # There's potential problem if b10-dhcp4 prints out more
-        # than 16kB of text
-        try:
-            output = os.read(self.stdout_pipes[0], 16384)
-        except OSError:
-            print("No data available from stdout")
-            output = ""
-
-        # read can return None. Make sure we have a string
-        if (output is None):
-            output = ""
-
-        try:
-            error = os.read(self.stderr_pipes[0], 16384)
-        except OSError:
-            print("No data available on stderr")
-            error = ""
-
-        # read can return None. Make sure we have a string
-        if (error is None):
-            error = ""
-
-
-        try:
-            if (not pi.process.poll()):
-                # let's be nice at first...
+        # As we don't know how long the subprocess will take to start and
+        # produce output, we'll loop and sleep for 250 ms between each
+        # iteration.  To avoid an infinite loop, we'll loop for a maximum
+        # of five seconds: that should be enough.
+        for count in range(20):
+            # Read something from stderr and stdout (these reads don't block).
+            output = self.readPipe(self.stdout_pipes[0])
+            error  = self.readPipe(self.stderr_pipes[0])
+
+            # If the process has already exited, or if it has output something,
+            # quit the loop now.
+            if pi.process.poll() is not None or len(error) > 0 or len(output) > 0:
+                break
+
+            # Process still running, try again in 250 ms.
+            time.sleep(0.25)
+
+        # Exited loop, kill the process if it is still running
+        if pi.process.poll() is None:
+            try:
                 pi.process.terminate()
-        except OSError:
-            print("Ignoring failed kill attempt. Process is dead already.")
+            except OSError:
+                print("Ignoring failed kill attempt. Process is dead already.")
 
         # call this to get returncode, process should be dead by now
         rc = pi.process.wait()
 
         # Clean up our stdout/stderr munging.
         os.dup2(self.stdout_old, sys.stdout.fileno())
+        os.close(self.stdout_old)
         os.close(self.stdout_pipes[0])
 
         os.dup2(self.stderr_old, sys.stderr.fileno())
+        os.close(self.stderr_old)
         os.close(self.stderr_pipes[0])
 
+        # Free up resources (file descriptors) from the ProcessInfo object
+        # TODO: For some reason, this gives an error if the process has ended,
+        #       although it does cause all descriptors still allocated to the
+        #       object to be freed.
+        pi = None
+
         print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
                % (rc, len(output), len(error)) )
 

+ 1 - 4
src/bin/dhcp6/config_parser.cc

@@ -698,11 +698,8 @@ private:
             // We have exactly one option definition for the particular option code
             // use it to create the option instance.
             const OptionDefinitionPtr& def = *(range.first);
-            // getFactory should never return NULL pointer.
-            Option::Factory* factory = def->getFactory();
-            assert(factory != NULL);
             try {
-                OptionPtr option = factory(Option::V6, option_code, binary);
+                OptionPtr option = def->optionFactory(Option::V6, option_code, binary);
                 Subnet::OptionDescriptor desc(option, false);
                 option_descriptor_.option = option;
                 option_descriptor_.persistent = false;

+ 0 - 11
src/bin/dhcp6/dhcp6_srv.cc

@@ -56,12 +56,6 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port, const char* dbconfig)
 
     // Initialize objects required for DHCP server operation.
     try {
-        // Initialize standard DHCPv6 option definitions. This function
-        // may throw bad_alloc if system goes out of memory during the
-        // creation if option definitions. It may also throw isc::Unexpected
-        // if definitions are wrong. This would mean error in implementation.
-        initStdOptionDefs();
-
         // Port 0 is used for testing purposes. It means that the server should
         // not open any sockets at all. Some tests, e.g. configuration parser,
         // require Dhcpv6Srv object, but they don't really need it to do
@@ -622,10 +616,5 @@ Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
     return (UNKNOWN);
 }
 
-void
-Dhcpv6Srv::initStdOptionDefs() {
-    LibDHCP::initStdOptionDefs(Option::V6);
-}
-
 };
 };

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

@@ -241,17 +241,6 @@ protected:
     ///         interfaces for new DUID generation are detected.
     void setServerID();
 
-    /// @brief Initializes option definitions for standard options.
-    ///
-    /// Each standard option's format is described by the
-    /// dhcp::OptionDefinition object. This function creates such objects
-    /// for each standard DHCPv6 option.
-    ///
-    /// @todo list thrown exceptions.
-    /// @todo extend this function to cover all standard options. Currently
-    /// it is limited to critical options only.
-    void initStdOptionDefs();
-
 private:
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using

+ 0 - 5
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -46,11 +46,6 @@ public:
         // srv_(0) means to not open any sockets. We don't want to
         // deal with sockets here, just check if configuration handling
         // is sane.
-
-        // Create instances of option definitions and put them into storage.
-        // This is normally initialized by the server when calling run()
-        // run() function.
-        LibDHCP::initStdOptionDefs(Option::V6);
     }
 
     ~Dhcp6ParserTest() {

+ 49 - 27
src/bin/dhcp6/tests/dhcp6_test.py

@@ -45,6 +45,25 @@ class TestDhcpv6Daemon(unittest.TestCase):
     def tearDown(self):
         pass
 
+    def readPipe(self, pipe_fd):
+        """
+        Reads bytes from a pipe and returns a character string.  If nothing is
+        read, or if there is an error, an empty string is returned.
+
+        pipe_fd - Pipe file descriptor to read
+        """
+        try:
+            data = os.read(pipe_fd, 16384)
+            # Make sure we have a string
+            if (data is None):
+                data = ""
+            else:
+                data = str(data)
+        except OSError:
+            data = ""
+
+        return data
+
     def runCommand(self, params, wait=1):
         """
         This method runs a command and returns a tuple: (returncode, stdout, stderr)
@@ -89,45 +108,48 @@ class TestDhcpv6Daemon(unittest.TestCase):
         fl = fcntl.fcntl(fd, fcntl.F_GETFL)
         fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
 
-        # There's potential problem if b10-dhcp4 prints out more
-        # than 16k of text
-        try:
-            output = os.read(self.stdout_pipes[0], 16384)
-        except OSError:
-            print("No data available from stdout")
-            output = ""
-
-        # read can return None. Make sure we have a string
-        if (output is None):
-            output = ""
-
-        try:
-            error = os.read(self.stderr_pipes[0], 16384)
-        except OSError:
-            print("No data available on stderr")
-            error = ""
-
-        # read can return None. Make sure we have a string
-        if (error is None):
-            error = ""
-
-        try:
-            if (not pi.process.poll()):
-                # let's be nice at first...
+        # As we don't know how long the subprocess will take to start and
+        # produce output, we'll loop and sleep for 250 ms between each
+        # iteration.  To avoid an infinite loop, we'll loop for a maximum
+        # of five seconds: that should be enough.
+        for count in range(20):
+            # Read something from stderr and stdout (these reads don't block).
+            output = self.readPipe(self.stdout_pipes[0])
+            error  = self.readPipe(self.stderr_pipes[0])
+
+            # If the process has already exited, or if it has output something,
+            # quit the loop now.
+            if pi.process.poll() is not None or len(error) > 0 or len(output) > 0:
+                break
+
+            # Process still running, try again in 250 ms.
+            time.sleep(0.25)
+
+        # Exited loop, kill the process if it is still running
+        if pi.process.poll() is None:
+            try:
                 pi.process.terminate()
-        except OSError:
-            print("Ignoring failed kill attempt. Process is dead already.")
+            except OSError:
+                print("Ignoring failed kill attempt. Process is dead already.")
 
         # call this to get returncode, process should be dead by now
         rc = pi.process.wait()
 
         # Clean up our stdout/stderr munging.
         os.dup2(self.stdout_old, sys.stdout.fileno())
+        os.close(self.stdout_old)
         os.close(self.stdout_pipes[0])
 
         os.dup2(self.stderr_old, sys.stderr.fileno())
+        os.close(self.stderr_old)
         os.close(self.stderr_pipes[0])
 
+        # Free up resources (file descriptors) from the ProcessInfo object
+        # TODO: For some reason, this gives an error if the process has ended,
+        #       although it does cause all descriptors still allocated to the
+        #       object to be freed.
+        pi = None
+
         print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
                % (rc, len(output), len(error)) )
 

+ 53 - 27
src/bin/msgq/msgq.py.in

@@ -127,6 +127,7 @@ class MsgQ:
         self.subs = SubscriptionManager()
         self.lnames = {}
         self.sendbuffs = {}
+        self.running = False
 
     def setup_poller(self):
         """Set up the poll thing.  Internal function."""
@@ -315,6 +316,8 @@ class MsgQ:
         elif cmd == 'ping':
             # Command for testing purposes
             self.process_command_ping(sock, routing, data)
+        elif cmd == 'stop':
+            self.stop()
         else:
             sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
 
@@ -336,14 +339,34 @@ class MsgQ:
         self.send_prepared_msg(sock, self.preparemsg(env, msg))
 
     def __send_data(self, sock, data):
+        """
+        Send a piece of data to the given socket.
+        Parameters:
+        sock: The socket to send to
+        data: The list of bytes to send
+        Returns:
+        An integer or None. If an integer (which can be 0), it signals
+        the number of bytes sent. If None, the socket appears to have
+        been closed on the other end, and it has been killed on this
+        side too.
+        """
         try:
             # We set the socket nonblocking, MSG_DONTWAIT doesn't exist
             # on some OSes
             sock.setblocking(0)
             return sock.send(data)
         except socket.error as e:
-            if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
+            if e.errno in [ errno.EAGAIN,
+                            errno.EWOULDBLOCK,
+                            errno.EINTR ]:
                 return 0
+            elif e.errno in [ errno.EPIPE,
+                              errno.ECONNRESET,
+                              errno.ENOBUFS ]:
+                print("[b10-msgq] " + errno.errorcode[e.errno] +
+                      " on send, dropping message and closing connection")
+                self.kill_socket(sock.fileno(), sock)
+                return None
             else:
                 raise e
         finally:
@@ -356,20 +379,12 @@ class MsgQ:
         if fileno in self.sendbuffs:
             amount_sent = 0
         else:
-            try:
-                amount_sent = self.__send_data(sock, msg)
-            except socket.error as sockerr:
-                # in the case the other side seems gone, kill the socket
-                # and drop the send action
-                if sockerr.errno == errno.EPIPE:
-                    print("[b10-msgq] SIGPIPE on send, dropping message " +
-                          "and closing connection")
-                    self.kill_socket(fileno, sock)
-                    return
-                else:
-                    raise
+            amount_sent = self.__send_data(sock, msg)
+            if amount_sent is None:
+                # Socket has been killed, drop the send
+                return
 
-        # Still something to send
+        # Still something to send, add it to outgoing queue
         if amount_sent < len(msg):
             now = time.clock()
             # Append it to buffer (but check the data go away)
@@ -394,17 +409,18 @@ class MsgQ:
         (_, msg) = self.sendbuffs[fileno]
         sock = self.sockets[fileno]
         amount_sent = self.__send_data(sock, msg)
-        # Keep the rest
-        msg = msg[amount_sent:]
-        if len(msg) == 0:
-            # If there's no more, stop requesting for write availability
-            if self.poller:
-                self.poller.register(fileno, select.POLLIN)
+        if amount_sent is not None:
+            # Keep the rest
+            msg = msg[amount_sent:]
+            if len(msg) == 0:
+                # If there's no more, stop requesting for write availability
+                if self.poller:
+                    self.poller.register(fileno, select.POLLIN)
+                else:
+                    self.delete_kqueue_socket(sock, True)
+                del self.sendbuffs[fileno]
             else:
-                self.delete_kqueue_socket(sock, True)
-            del self.sendbuffs[fileno]
-        else:
-            self.sendbuffs[fileno] = (time.clock(), msg)
+                self.sendbuffs[fileno] = (time.clock(), msg)
 
     def newlname(self):
         """Generate a unique connection identifier for this socket.
@@ -458,6 +474,7 @@ class MsgQ:
 
     def run(self):
         """Process messages.  Forever.  Mostly."""
+        self.running = True
 
         if self.poller:
             self.run_poller()
@@ -465,8 +482,10 @@ class MsgQ:
             self.run_kqueue()
 
     def run_poller(self):
-        while True:
+        while self.running:
             try:
+                # Poll with a timeout so that every once in a while,
+                # the loop checks for self.running.
                 events = self.poller.poll()
             except select.error as err:
                 if err.args[0] == errno.EINTR:
@@ -480,11 +499,15 @@ class MsgQ:
                 else:
                     if event & select.POLLOUT:
                         self.__process_write(fd)
-                    if event & select.POLLIN:
+                    elif event & select.POLLIN:
                         self.process_socket(fd)
+                    else:
+                        print("[b10-msgq] Error: Unknown even in run_poller()")
 
     def run_kqueue(self):
-        while True:
+        while self.running:
+            # Check with a timeout so that every once in a while,
+            # the loop checks for self.running.
             events = self.kqueue.control(None, 10)
             if not events:
                 raise RuntimeError('serve: kqueue returned no events')
@@ -502,6 +525,9 @@ class MsgQ:
                         self.kill_socket(event.ident,
                                          self.sockets[event.ident])
 
+    def stop(self):
+        self.running = False
+
     def shutdown(self):
         """Stop the MsgQ master."""
         if self.verbose:

+ 214 - 3
src/bin/msgq/tests/msgq_test.py

@@ -6,7 +6,10 @@ import socket
 import signal
 import sys
 import time
+import errno
+import threading
 import isc.cc
+import collections
 
 #
 # Currently only the subscription part and some sending is implemented...
@@ -112,6 +115,85 @@ class TestSubscriptionManager(unittest.TestCase):
         msgq = MsgQ("/does/not/exist")
         self.assertRaises(socket.error, msgq.setup)
 
+class DummySocket:
+    """
+    Dummy socket class.
+    This one does nothing at all, but some calls are used.
+    It is mainly intended to override the listen socket for msgq, which
+    we do not need in these tests.
+    """
+    def fileno():
+        return -1
+
+    def close():
+        pass
+
+class BadSocket:
+    """
+    Special socket wrapper class. Once given a socket in its constructor,
+    it completely behaves like that socket, except that its send() call
+    will only actually send one byte per call, and optionally raise a given
+    exception at a given time.
+    """
+    def __init__(self, real_socket, raise_on_send=0, send_exception=None):
+        """
+        Parameters:
+        real_socket: The actual socket to wrap
+        raise_on_send: integer. If higher than 0, and send_exception is
+                       not None, send_exception will be raised on the
+                       'raise_on_send'th call to send().
+        send_exception: if not None, this exception will be raised
+                        (if raise_on_send is not 0)
+        """
+        self.socket = real_socket
+        self.send_count = 0
+        self.raise_on_send = raise_on_send
+        self.send_exception = send_exception
+
+    # completely wrap all calls and member access
+    # (except explicitely overridden ones)
+    def __getattr__(self, name, *args):
+        attr = getattr(self.socket, name)
+        if isinstance(attr, collections.Callable):
+            def callable_attr(*args):
+                return attr.__call__(*args)
+            return callable_attr
+        else:
+            return attr
+
+    def send(self, data):
+        self.send_count += 1
+        if self.send_exception is not None and\
+           self.send_count == self.raise_on_send:
+            raise self.send_exception
+
+        if len(data) > 0:
+            return self.socket.send(data[:1])
+        else:
+            return 0
+
+class MsgQThread(threading.Thread):
+    """
+    Very simple thread class that runs msgq.run() when started,
+    and stores the exception that msgq.run() raises, if any.
+    """
+    def __init__(self, msgq):
+        threading.Thread.__init__(self)
+        self.msgq_ = msgq
+        self.caught_exception = None
+        self.lock = threading.Lock()
+
+    def run(self):
+        try:
+            self.msgq_.run()
+        except Exception as exc:
+            # Store the exception to make the test fail if necessary
+            self.caught_exception = exc
+
+    def stop(self):
+        self.msgq_.stop()
+
+
 class SendNonblock(unittest.TestCase):
     """
     Tests that the whole thing will not get blocked if someone does not read.
@@ -191,9 +273,6 @@ class SendNonblock(unittest.TestCase):
         msgq = MsgQ()
         # msgq.run needs to compare with the listen_socket, so we provide
         # a replacement
-        class DummySocket:
-            def fileno():
-                return -1
         msgq.listen_socket = DummySocket
         (queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
         def run():
@@ -245,5 +324,137 @@ class SendNonblock(unittest.TestCase):
             data = data + data
         self.send_many(data)
 
+    def do_send(self, write, read, control_write, control_read,
+                expect_arrive=True, expect_send_exception=None):
+        """
+        Makes a msgq object that is talking to itself,
+        run it in a separate thread so we can use and
+        test run().
+        It is given two sets of connected sockets; write/read, and
+        control_write/control_read. The former may be throwing errors
+        and mangle data to test msgq. The second is mainly used to
+        send msgq the stop command.
+        (Note that the terms 'read' and 'write' are from the msgq
+        point of view, so the test itself writes to 'control_read')
+        Parameters:
+        write: a socket that is used to send the data to
+        read: a socket that is used to read the data from
+        control_write: a second socket for communication with msgq
+        control_read: a second socket for communication with msgq
+        expect_arrive: if True, the read socket is read from, and the data
+                       that is read is expected to be the same as the data
+                       that has been sent to the write socket.
+        expect_send_exception: if not None, this is the exception that is
+                               expected to be raised by msgq
+        """
+
+        # Some message and envelope data to send and check
+        env = b'{"env": "foo"}'
+        msg = b'{"msg": "bar"}'
+
+        msgq = MsgQ()
+        # Don't need a listen_socket
+        msgq.listen_socket = DummySocket
+        msgq.setup_poller()
+        msgq.register_socket(write)
+        msgq.register_socket(control_write)
+        # Queue the message for sending
+        msgq.sendmsg(write, env, msg)
+
+        # Run it in a thread
+        msgq_thread = MsgQThread(msgq)
+        # If we're done, just kill it
+        msgq_thread.start()
+
+        if expect_arrive:
+            (recv_env, recv_msg) = msgq.read_packet(read.fileno(),
+                read)
+            self.assertEqual(env, recv_env)
+            self.assertEqual(msg, recv_msg)
+
+        # Tell msgq to stop
+        msg = msgq.preparemsg({"type" : "stop"})
+        control_read.sendall(msg)
+
+        # Wait for thread to stop if it hasn't already.
+        # Put in a (long) timeout; the thread *should* stop, but if it
+        # does not, we don't want the test to hang forever
+        msgq_thread.join(60)
+        # Fail the test if it didn't stop
+        self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")
+
+        # Check the exception from the thread, if any
+        # First, if we didn't expect it; reraise it (to make test fail and
+        # show the stacktrace for debugging)
+        if expect_send_exception is None:
+            if msgq_thread.caught_exception is not None:
+                raise msgq_thread.caught_exception
+        else:
+            # If we *did* expect it, fail it there was none
+            self.assertIsNotNone(msgq_thread.caught_exception)
+
+    def do_send_with_send_error(self, raise_on_send, send_exception,
+                                expect_answer=True,
+                                expect_send_exception=None):
+        """
+        Sets up two connected sockets, wraps the sender socket into a BadSocket
+        class, then performs a do_send() test.
+        Parameters:
+        raise_on_send: the byte at which send_exception should be raised
+                       (see BadSocket)
+        send_exception: the exception to raise (see BadSocket)
+        expect_answer: whether the send is expected to complete (and hence
+                       the read socket should get the message)
+        expect_send_exception: the exception msgq is expected to raise when
+                               send_exception is raised by BadSocket.
+        """
+        (write, read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        (control_write, control_read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        badwrite = BadSocket(write, raise_on_send, send_exception)
+        self.do_send(badwrite, read, control_write, control_read, expect_answer, expect_send_exception)
+        write.close()
+        read.close()
+        control_write.close()
+        control_read.close()
+
+    def test_send_raise_recoverable(self):
+        """
+        Test whether msgq survices a recoverable socket errors when sending.
+        Two tests are done: one where the error is raised on the 3rd octet,
+                            and one on the 23rd.
+        """
+        sockerr = socket.error
+        for err in [ errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR ]:
+            sockerr.errno = err
+            self.do_send_with_send_error(3, sockerr)
+            self.do_send_with_send_error(23, sockerr)
+
+    def test_send_raise_nonrecoverable(self):
+        """
+        Test whether msgq survives socket errors that are nonrecoverable
+        (for said socket that is, i.e. EPIPE etc).
+        Two tests are done: one where the error is raised on the 3rd octet,
+                            and one on the 23rd.
+        """
+        sockerr = socket.error
+        for err in [ errno.EPIPE, errno.ENOBUFS, errno.ECONNRESET ]:
+            sockerr.errno = err
+            self.do_send_with_send_error(3, sockerr, False)
+            self.do_send_with_send_error(23, sockerr, False)
+
+    def otest_send_raise_crash(self):
+        """
+        Test whether msgq does NOT survive on a general exception.
+        Note, perhaps it should; but we'd have to first discuss and decide
+        how it should recover (i.e. drop the socket and consider the client
+        dead?
+        It may be a coding problem in msgq itself, and we certainly don't
+        want to ignore those.
+        """
+        sockerr = Exception("just some general exception")
+        self.do_send_with_send_error(3, sockerr, False, sockerr)
+        self.do_send_with_send_error(23, sockerr, False, sockerr)
+
+
 if __name__ == '__main__':
     unittest.main()

+ 1 - 1
src/lib/asiolink/io_address.cc

@@ -61,7 +61,7 @@ IOAddress::toText() const {
 }
 
 IOAddress
-IOAddress::from_bytes(short family, const uint8_t* data) {
+IOAddress::fromBytes(short family, const uint8_t* data) {
     if (data == NULL) {
         isc_throw(BadValue, "NULL pointer received.");
     } else

+ 1 - 1
src/lib/asiolink/io_address.h

@@ -111,7 +111,7 @@ public:
     ///
     /// \return Created IOAddress object
     static IOAddress
-    from_bytes(short family, const uint8_t* data);
+    fromBytes(short family, const uint8_t* data);
 
     /// \brief Compare addresses for equality
     ///

+ 3 - 3
src/lib/asiolink/tests/io_address_unittest.cc

@@ -64,7 +64,7 @@ TEST(IOAddressTest, Family) {
     EXPECT_EQ(AF_INET6, IOAddress("2001:0DB8:0:0::0012").getFamily());
 }
 
-TEST(IOAddressTest, from_bytes) {
+TEST(IOAddressTest, fromBytes) {
     // 2001:db8:1::dead:beef
     uint8_t v6[] = {
         0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
@@ -74,12 +74,12 @@ TEST(IOAddressTest, from_bytes) {
 
     IOAddress addr("::");
     EXPECT_NO_THROW({
-        addr = IOAddress::from_bytes(AF_INET6, v6);
+        addr = IOAddress::fromBytes(AF_INET6, v6);
     });
     EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
 
     EXPECT_NO_THROW({
-        addr = IOAddress::from_bytes(AF_INET, v4);
+        addr = IOAddress::fromBytes(AF_INET, v4);
     });
     EXPECT_EQ(addr.toText(), IOAddress("192.0.2.3").toText());
 }

+ 2 - 0
src/lib/datasrc/Makefile.am

@@ -36,6 +36,8 @@ libb10_datasrc_la_SOURCES += database.h database.cc
 libb10_datasrc_la_SOURCES += factory.h factory.cc
 libb10_datasrc_la_SOURCES += client_list.h client_list.cc
 libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
+libb10_datasrc_la_SOURCES += master_loader_callbacks.h
+libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
 nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
 

+ 12 - 0
src/lib/datasrc/datasrc_messages.mes

@@ -315,6 +315,18 @@ An error was found in the zone data when it was being loaded from
 another data source. The zone was not loaded. The specific error is
 shown in the message, and should be addressed.
 
+% DATASRC_MASTER_LOAD_ERROR %1:%2: Zone '%3/%4' contains error: %5
+There's an error in the given master file. The zone won't be loaded for
+this reason. Parsing might follow, so you might get further errors and
+warnings to fix everything at once. But in case the error is serious enough,
+the parser might just give up or get confused and generate false errors
+afterwards.
+
+% DATASRC_MASTER_LOAD_WARN %1:%2: Zone '%3/%4' has a potential problem: %5
+There's something suspicious in the master file. This is a warning only.
+It may be a problem or it may be harmless, but it should be checked.
+This problem does not stop the zone from being loaded.
+
 % DATASRC_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
 Debug information. An RRset is being added to the in-memory data source.
 

+ 73 - 0
src/lib/datasrc/master_loader_callbacks.cc

@@ -0,0 +1,73 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/master_loader_callbacks.h>
+#include <datasrc/zone.h>
+#include <datasrc/logger.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <string>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace datasrc {
+
+namespace {
+
+void
+logError(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+         bool* ok, const std::string& source, size_t line,
+         const std::string& reason)
+{
+    LOG_ERROR(logger, DATASRC_MASTER_LOAD_ERROR).arg(source).arg(line).
+        arg(name).arg(rrclass).arg(reason);
+    if (ok != NULL) {
+        *ok = false;
+    }
+}
+
+void
+logWarning(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+         const std::string& source, size_t line, const std::string& reason)
+{
+    LOG_WARN(logger, DATASRC_MASTER_LOAD_WARN).arg(source).arg(line).
+        arg(name).arg(rrclass).arg(reason);
+}
+
+}
+
+isc::dns::MasterLoaderCallbacks
+createMasterLoaderCallbacks(const isc::dns::Name& name,
+                            const isc::dns::RRClass& rrclass, bool* ok)
+{
+    return (isc::dns::MasterLoaderCallbacks(boost::bind(&logError, name,
+                                                        rrclass, ok, _1, _2,
+                                                        _3),
+                                            boost::bind(&logWarning, name,
+                                                        rrclass, _1, _2, _3)));
+}
+
+isc::dns::AddRRsetCallback
+createMasterLoaderAddCallback(ZoneUpdater& updater) {
+    return (boost::bind(&ZoneUpdater::addRRset, &updater,
+                        // The callback provides a shared pointer, we
+                        // need the object. This bind unpacks the object.
+                        boost::bind(&isc::dns::RRsetPtr::operator*, _1)));
+}
+
+}
+}

+ 67 - 0
src/lib/datasrc/master_loader_callbacks.h

@@ -0,0 +1,67 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MASTER_LOADER_CALLBACKS_H
+#define DATASRC_MASTER_LOADER_CALLBACKS_H
+
+#include <dns/master_loader_callbacks.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+}
+namespace datasrc {
+
+class ZoneUpdater;
+
+/// \brief Create issue callbacks for MasterLoader
+///
+/// This will create set of callbacks for the MasterLoader that
+/// will be used to report any issues found in the zone data.
+///
+/// \param name Name of the zone. Used in logging.
+/// \param rrclass The class of the zone. Used in logging.
+/// \param ok If this is non-NULL and there are any errors during
+///     the loading, it is set to false. Otherwise, it is untouched.
+/// \return Set of callbacks to be passed to the master loader.
+/// \throw std::bad_alloc when allocation fails.
+isc::dns::MasterLoaderCallbacks
+createMasterLoaderCallbacks(const isc::dns::Name& name,
+                            const isc::dns::RRClass& rrclass, bool* ok);
+
+/// \brief Create a callback for MasterLoader to add RRsets.
+///
+/// This creates a callback that can be used by the MasterLoader to add
+/// loaded RRsets into a zone updater.
+///
+/// The zone updater should be opened in the replace mode no changes should
+/// have been done to it yet (but it is not checked). It is not commited
+/// automatically and it is up to the caller to commit the changes (or not).
+/// It must not be destroyed for the whole time of loading.
+///
+/// The function is mostly straightforward packing of the updater.addRRset
+/// into a boost::function, it is defined explicitly due to small technical
+/// annoyences around boost::bind application, so it can be reused.
+///
+/// \param updater The zone updater to use.
+/// \return The callback to be passed to MasterLoader.
+/// \throw std::bad_alloc when allocation fails.
+isc::dns::AddRRsetCallback
+createMasterLoaderAddCallback(ZoneUpdater& updater);
+
+}
+}
+
+#endif

+ 1 - 0
src/lib/datasrc/memory/Makefile.am

@@ -27,6 +27,7 @@ libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
 libdatasrc_memory_la_SOURCES += zone_writer.h
 libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
 libdatasrc_memory_la_SOURCES += load_action.h
+libdatasrc_memory_la_SOURCES += util_internal.h
 
 nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
 

+ 2 - 2
src/lib/datasrc/memory/rdataset.cc

@@ -122,8 +122,8 @@ RdataSet::create(util::MemorySegment& mem_sgmt, RdataEncoder& encoder,
 }
 
 void
-RdataSet::destroy(util::MemorySegment& mem_sgmt, RRClass rrclass,
-                  RdataSet* rdataset)
+RdataSet::destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
+                  RRClass rrclass)
 {
     const size_t data_len =
         RdataReader(rrclass, rdataset->type,

+ 22 - 9
src/lib/datasrc/memory/rdataset.h

@@ -187,12 +187,12 @@ public:
     ///
     /// \param mem_sgmt The \c MemorySegment that allocated memory for
     /// \c node.
-    /// \param rrclass The RR class of the \c RdataSet to be destroyed.
     /// \param rdataset A non NULL pointer to a valid \c RdataSet object
+    /// \param rrclass The RR class of the \c RdataSet to be destroyed.
     /// that was originally created by the \c create() method (the behavior
     /// is undefined if this condition isn't met).
-    static void destroy(util::MemorySegment& mem_sgmt, dns::RRClass rrclass,
-                        RdataSet* rdataset);
+    static void destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
+                        dns::RRClass rrclass);
 
     /// \brief Find \c RdataSet of given RR type from a list (const version).
     ///
@@ -205,6 +205,11 @@ public:
     /// if not found in the entire list, it returns NULL.  The head pointer
     /// can be NULL, in which case this function will simply return NULL.
     ///
+    /// By default, this method ignores an RdataSet that only contains an
+    /// RRSIG (i.e., missing the covered RdataSet); if the optional
+    /// sigonly_ok parameter is explicitly set to true, it matches such
+    /// RdataSet and returns it if found.
+    ///
     /// \note This function is defined as a (static) class method to
     /// clarify its an operation for \c RdataSet objects and to make the
     /// name shorter.  But its implementation does not depend on private
@@ -215,10 +220,14 @@ public:
     /// \param rdata_head A pointer to \c RdataSet from which the search
     /// starts.  It can be NULL.
     /// \param type The RRType of \c RdataSet to find.
+    /// \param sigonly_ok Whether it should find an RdataSet that only has
+    /// RRSIG
     /// \return A pointer to the found \c RdataSet or NULL if none found.
     static const RdataSet*
-    find(const RdataSet* rdataset_head, const dns::RRType& type) {
-        return (find<const RdataSet>(rdataset_head, type));
+    find(const RdataSet* rdataset_head, const dns::RRType& type,
+         bool sigonly_ok = false)
+    {
+        return (find<const RdataSet>(rdataset_head, type, sigonly_ok));
     }
 
     /// \brief Find \c RdataSet of given RR type from a list (non const
@@ -227,8 +236,10 @@ public:
     /// This is similar to the const version, except it takes and returns non
     /// const pointers.
     static RdataSet*
-    find(RdataSet* rdataset_head, const dns::RRType& type) {
-        return (find<RdataSet>(rdataset_head, type));
+    find(RdataSet* rdataset_head, const dns::RRType& type,
+         bool sigonly_ok = false)
+    {
+        return (find<RdataSet>(rdataset_head, type, sigonly_ok));
     }
 
     typedef boost::interprocess::offset_ptr<RdataSet> RdataSetPtr;
@@ -347,12 +358,14 @@ private:
     // Shared by both mutable and immutable versions of find()
     template <typename RdataSetType>
     static RdataSetType*
-    find(RdataSetType* rdataset_head, const dns::RRType& type) {
+    find(RdataSetType* rdataset_head, const dns::RRType& type, bool sigonly_ok)
+    {
         for (RdataSetType* rdataset = rdataset_head;
              rdataset != NULL;
              rdataset = rdataset->getNext()) // use getNext() for efficiency
         {
-            if (rdataset->type == type) {
+            if (rdataset->type == type &&
+                (rdataset->getRdataCount() > 0 || sigonly_ok)) {
                 return (rdataset);
             }
         }

+ 57 - 0
src/lib/datasrc/memory/util_internal.h

@@ -0,0 +1,57 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MEMORY_UTIL_INTERNAL_H
+#define DATASRC_MEMORY_UTIL_INTERNAL_H 1
+
+#include <dns/rdataclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace detail {
+
+/// \brief Return the covered RR type of an RRSIG RRset.
+///
+/// This is a commonly used helper to extract the type covered field of an
+/// RRSIG RRset and return it in the form of an RRType object.
+///
+/// Normally, an empty RRSIG shouldn't be passed to this function, whether
+/// it comes from a master file or another data source iterator, but it could
+/// still happen in some buggy situations.  This function catches and rejects
+/// such cases.
+inline dns::RRType
+getCoveredType(const dns::ConstRRsetPtr& sig_rrset) {
+    dns::RdataIteratorPtr it = sig_rrset->getRdataIterator();
+    if (it->isLast()) {
+        isc_throw(isc::Unexpected,
+                  "Empty RRset is passed in-memory loader, name: "
+                  << sig_rrset->getName());
+    }
+    return (dynamic_cast<const dns::rdata::generic::RRSIG&>(it->getCurrent()).
+            typeCovered());
+}
+
+} // namespace detail
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_UTIL_INTERNAL_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 1
src/lib/datasrc/memory/zone_data.cc

@@ -49,7 +49,7 @@ rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt,
          rdataset = rdataset_next)
     {
         rdataset_next = rdataset->getNext();
-        RdataSet::destroy(*mem_sgmt, rrclass, rdataset);
+        RdataSet::destroy(*mem_sgmt, rdataset, rrclass);
     }
 }
 

+ 7 - 24
src/lib/datasrc/memory/zone_data_loader.cc

@@ -16,6 +16,7 @@
 #include <datasrc/memory/zone_data_updater.h>
 #include <datasrc/memory/logger.h>
 #include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/memory/util_internal.h>
 
 #include <dns/rdataclass.h>
 #include <dns/rrset.h>
@@ -35,6 +36,7 @@ namespace datasrc {
 namespace memory {
 
 using detail::SegmentObjectHolder;
+using detail::getCoveredType;
 
 namespace { // unnamed namespace
 
@@ -75,8 +77,6 @@ private:
     typedef NodeRRsets::value_type NodeRRsetsVal;
 
     // A helper to identify the covered type of an RRSIG.
-    static isc::dns::RRType getCoveredType
-        (const isc::dns::ConstRRsetPtr& sig_rrset);
     const isc::dns::Name& getCurrentName() const;
 
 private:
@@ -126,34 +126,17 @@ ZoneDataLoader::flushNodeRRsets() {
         updater_.add(val.second, sig_rrset);
     }
 
-    // Right now, we don't accept RRSIG without covered RRsets (this
-    // should eventually allowed, but to do so we'll need to update the
-    // finder).
-    if (!node_rrsigsets_.empty()) {
-        isc_throw(ZoneDataUpdater::AddError,
-                  "RRSIG is added without covered RRset for "
-                  << getCurrentName());
+    // Normally rrsigsets map should be empty at this point, but it's still
+    // possible that an RRSIG that don't has covered RRset is added; they
+    // still remain in the map.  We add them to the zone separately.
+    BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) {
+        updater_.add(ConstRRsetPtr(), val.second);
     }
 
     node_rrsets_.clear();
     node_rrsigsets_.clear();
 }
 
-RRType
-ZoneDataLoader::getCoveredType(const ConstRRsetPtr& sig_rrset) {
-    RdataIteratorPtr it = sig_rrset->getRdataIterator();
-    // Empty RRSIG shouldn't be passed either via a master file or
-    // another data source iterator, but it could still happen if the
-    // iterator has a bug.  We catch and reject such cases.
-    if (it->isLast()) {
-        isc_throw(isc::Unexpected,
-                  "Empty RRset is passed in-memory loader, name: "
-                  << sig_rrset->getName());
-    }
-    return (dynamic_cast<const generic::RRSIG&>(it->getCurrent()).
-            typeCovered());
-}
-
 const Name&
 ZoneDataLoader::getCurrentName() const {
     if (!node_rrsets_.empty()) {

+ 64 - 30
src/lib/datasrc/memory/zone_data_updater.cc

@@ -12,12 +12,18 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <exceptions/exceptions.h>
+
 #include <datasrc/memory/zone_data_updater.h>
 #include <datasrc/memory/logger.h>
+#include <datasrc/memory/util_internal.h>
 #include <datasrc/zone.h>
 
 #include <dns/rdataclass.h>
 
+#include <cassert>
+#include <string>
+
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
@@ -25,6 +31,8 @@ namespace isc {
 namespace datasrc {
 namespace memory {
 
+using detail::getCoveredType;
+
 void
 ZoneDataUpdater::addWildcards(const Name& name) {
     Name wname(name);
@@ -99,9 +107,7 @@ ZoneDataUpdater::contextCheck(const AbstractRRset& rrset,
 
 void
 ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
-    if (!rrset) {
-        isc_throw(NullRRset, "The rrset provided is NULL");
-    }
+    assert(rrset);
 
     if (rrset->getRdataCount() == 0) {
         isc_throw(AddError,
@@ -241,31 +247,46 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
 }
 
 void
-ZoneDataUpdater::addNSEC3(const ConstRRsetPtr rrset, const ConstRRsetPtr rrsig)
+ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
+                          const ConstRRsetPtr rrsig)
 {
-    setupNSEC3<generic::NSEC3>(rrset);
+    if (rrset) {
+        setupNSEC3<generic::NSEC3>(rrset);
+    }
 
     NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+    if (nsec3_data == NULL) {
+        // This is some tricky case: an RRSIG for NSEC3 is given without the
+        // covered NSEC3, and we don't even know any NSEC3 related data.
+        // This situation is not necessarily broken, but in our current
+        // implementation it's very difficult to deal with.  So we reject it;
+        // hopefully this case shouldn't happen in practice, at least unless
+        // zone is really broken.
+        assert(!rrset);
+        isc_throw(NotImplemented,
+                  "RRSIG for NSEC3 cannot be added - no known NSEC3 data");
+    }
 
     ZoneNode* node;
-    nsec3_data->insertName(mem_sgmt_, rrset->getName(), &node);
+    nsec3_data->insertName(mem_sgmt_, name, &node);
 
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig);
     RdataSet* old_rdataset = node->setData(rdataset);
     if (old_rdataset != NULL) {
-        RdataSet::destroy(mem_sgmt_, rrclass_, old_rdataset);
+        RdataSet::destroy(mem_sgmt_, old_rdataset, rrclass_);
     }
 }
 
 void
-ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
+ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
+                             const ConstRRsetPtr rrset,
                              const ConstRRsetPtr rrsig)
 {
-    if (rrset->getType() == RRType::NSEC3()) {
-        addNSEC3(rrset, rrsig);
+    if (rrtype == RRType::NSEC3()) {
+        addNSEC3(name, rrset, rrsig);
     } else {
         ZoneNode* node;
-        zone_data_.insertName(mem_sgmt_, rrset->getName(), &node);
+        zone_data_.insertName(mem_sgmt_, name, &node);
 
         RdataSet* rdataset_head = node->getData();
 
@@ -273,13 +294,14 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
         // fails and the exception is thrown, it may break strong
         // exception guarantee.  At the moment we prefer code simplicity
         // and don't bother to introduce complicated recovery code.
-        contextCheck(*rrset, rdataset_head);
+        if (rrset) { // this check is only for covered RRset, not RRSIG
+            contextCheck(*rrset, rdataset_head);
+        }
 
-        if (RdataSet::find(rdataset_head, rrset->getType()) != NULL) {
+        if (RdataSet::find(rdataset_head, rrtype, true) != NULL) {
             isc_throw(AddError,
                       "RRset of the type already exists: "
-                      << rrset->getName() << " (type: "
-                      << rrset->getType() << ")");
+                      << name << " (type: " << rrtype << ")");
         }
 
         RdataSet* rdataset_new = RdataSet::create(mem_sgmt_, encoder_,
@@ -289,23 +311,25 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
 
         // Ok, we just put it in.
 
+        // Convenient (and more efficient) shortcut to check RRsets at origin
+        const bool is_origin = (node == zone_data_.getOriginNode());
+
         // If this RRset creates a zone cut at this node, mark the node
-        // indicating the need for callback in find().
-        if (rrset->getType() == RRType::NS() &&
-            rrset->getName() != zone_name_) {
+        // indicating the need for callback in find().  Note that we do this
+        // only when non RRSIG RRset of that type is added.
+        if (rrset && rrtype == RRType::NS() && !is_origin) {
             node->setFlag(ZoneNode::FLAG_CALLBACK);
             // If it is DNAME, we have a callback as well here
-        } else if (rrset->getType() == RRType::DNAME()) {
+        } else if (rrset && rrtype == RRType::DNAME()) {
             node->setFlag(ZoneNode::FLAG_CALLBACK);
         }
 
         // If we've added NSEC3PARAM at zone origin, set up NSEC3
         // specific data or check consistency with already set up
         // parameters.
-        if (rrset->getType() == RRType::NSEC3PARAM() &&
-            rrset->getName() == zone_name_) {
+        if (rrset && rrtype == RRType::NSEC3PARAM() && is_origin) {
             setupNSEC3<generic::NSEC3PARAM>(rrset);
-        } else if (rrset->getType() == RRType::NSEC()) {
+        } else if (rrset && rrtype == RRType::NSEC() && is_origin) {
             // If it is NSEC signed zone, we mark the zone as signed
             // (conceptually "signed" is a broader notion but our
             // current zone finder implementation regards "signed" as
@@ -319,27 +343,37 @@ void
 ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
                      const ConstRRsetPtr& sig_rrset)
 {
-    // Validate input.  This will cause an exception to be thrown if the
-    // input RRset is empty.
-    validate(rrset);
+    // Validate input.
+    if (!rrset && !sig_rrset) {
+        isc_throw(NullRRset,
+                  "ZoneDataUpdater::add is given 2 NULL pointers");
+    }
+    if (rrset) {
+        validate(rrset);
+    }
     if (sig_rrset) {
         validate(sig_rrset);
     }
 
+    const Name& name = rrset ? rrset->getName() : sig_rrset->getName();
+    const RRType& rrtype = rrset ? rrset->getType() :
+        getCoveredType(sig_rrset);
+
     // OK, can add the RRset.
-    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).
-        arg(rrset->getName()).arg(rrset->getType()).arg(zone_name_);
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).arg(name).
+        arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")").
+        arg(zone_name_);
 
     // Add wildcards possibly contained in the owner name to the domain
     // tree.  This can only happen for the normal (non-NSEC3) tree.
     // Note: this can throw an exception, breaking strong exception
     // guarantee.  (see also the note for the call to contextCheck()
     // above).
-    if (rrset->getType() != RRType::NSEC3()) {
-        addWildcards(rrset->getName());
+    if (rrtype != RRType::NSEC3()) {
+        addWildcards(name);
     }
 
-    addRdataSet(rrset, sig_rrset);
+    addRdataSet(name, rrtype, rrset, sig_rrset);
 }
 
 } // namespace memory

+ 31 - 6
src/lib/datasrc/memory/zone_data_updater.h

@@ -110,10 +110,32 @@ public:
     /// populated with the record data and added to the ZoneData for the
     /// name in the RRset.
     ///
-    /// This method throws an \c NullRRset exception (see above) if
-    /// \c rrset is empty. It throws \c AddError if any of a variety of
-    /// validation checks fail for the \c rrset and its associated
-    /// \c sig_rrset.
+    /// At least one of \c rrset or \c sig_rrset must be non NULL.
+    /// \c sig_rrset can be reasonably NULL when \c rrset is not signed in
+    /// the zone; it's unusual that \c rrset is NULL, but is still possible
+    /// if these RRsets are given separately to the loader, or if even the
+    /// zone is half broken and really contains an RRSIG that doesn't have
+    /// any covered RRset.  This implementation supports these cases (but
+    /// see the note below).
+    ///
+    /// There is one tricky case: Due to a limitation of the current
+    /// implementation, it cannot accept an RRSIG for NSEC3 without the covered
+    /// NSEC3, unless at least one NSEC3 or NSEC3PARAM has been added.
+    /// In this case an isc::NotImplemented exception will be thrown.  It
+    /// should be very rare in practice, and hopefully wouldn't be a real
+    /// issue.
+    ///
+    /// \note Due to limitations of the current implementation, if a
+    /// (non RRSIG) RRset and its RRSIG are added separately in different
+    /// calls to this method, the second attempt will be rejected due to
+    /// an \c AddError exception.  This will be loosened in Trac
+    /// ticket #2441.
+    ///
+    /// \throw NullRRset Both \c rrset and sig_rrset is NULL
+    /// \throw AddError any of a variety of validation checks fail for the
+    /// \c rrset and its associated \c sig_rrset.
+    /// \throw NotImplemented RRSIG for NSEC3 cannot be added due to internal
+    /// restriction.
     ///
     /// \param rrset The RRset to be added.
     /// \param sig_rrset An associated RRSIG RRset for the \c rrset. It
@@ -152,9 +174,12 @@ private:
     const isc::dns::NSEC3Hash* getNSEC3Hash();
     template <typename T>
     void setupNSEC3(const isc::dns::ConstRRsetPtr rrset);
-    void addNSEC3(const isc::dns::ConstRRsetPtr rrset,
+    void addNSEC3(const isc::dns::Name& name,
+                  const isc::dns::ConstRRsetPtr rrset,
                   const isc::dns::ConstRRsetPtr rrsig);
-    void addRdataSet(const isc::dns::ConstRRsetPtr rrset,
+    void addRdataSet(const isc::dns::Name& name,
+                     const isc::dns::RRType& rrtype,
+                     const isc::dns::ConstRRsetPtr rrset,
                      const isc::dns::ConstRRsetPtr rrsig);
 
     util::MemorySegment& mem_sgmt_;

+ 12 - 1
src/lib/datasrc/memory/zone_finder.cc

@@ -216,6 +216,14 @@ createNSEC3RRset(const ZoneNode* node, const RRClass& rrclass) {
      assert(rdataset != NULL);
      assert(rdataset->type == RRType::NSEC3());
 
+     // Check for the rare case of RRSIG-only record; in theory it could exist
+     // but we simply consider it broken for NSEC3.
+     if (rdataset->getRdataCount() == 0) {
+         uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+         isc_throw(DataSourceError, "Broken zone: RRSIG-only NSEC3 record at "
+                   << node->getAbsoluteLabels(labels_buf) << "/" << rrclass);
+     }
+
     // Create the RRset.  Note the DNSSEC flag: NSEC3 implies DNSSEC.
     return (createTreeNodeRRset(node, rdataset, rrclass,
                                 ZoneFinder::FIND_DNSSEC));
@@ -627,7 +635,10 @@ private:
             // This can be a bit more optimized, but unless we have many
             // requested types the effect is probably marginal.  For now we
             // keep it simple.
-            if (std::find(type_beg, type_end, rdset->type) != type_end) {
+            // Check for getRdataCount is necessary not to include RRSIG-only
+            // records accidentally (should be rare, but possible).
+            if (std::find(type_beg, type_end, rdset->type) != type_end &&
+                rdset->getRdataCount() > 0) {
                 result->push_back(createTreeNodeRRset(node, rdset, rrclass_,
                                                       options, real_name));
             }

+ 2 - 0
src/lib/datasrc/tests/Makefile.am

@@ -59,6 +59,7 @@ run_unittests_SOURCES += zonetable_unittest.cc
 run_unittests_SOURCES += zone_finder_context_unittest.cc
 run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
 run_unittests_SOURCES += client_list_unittest.cc
+run_unittests_SOURCES += master_loader_callbacks_test.cc
 
 # We need the actual module implementation in the tests (they are not part
 # of libdatasrc)
@@ -93,6 +94,7 @@ endif
 
 EXTRA_DIST =  testdata/brokendb.sqlite3
 EXTRA_DIST += testdata/contexttest.zone
+EXTRA_DIST += testdata/contexttest-almost-obsolete.zone
 EXTRA_DIST += testdata/diffs.sqlite3
 EXTRA_DIST += testdata/duplicate_rrset.zone
 EXTRA_DIST += testdata/example2.com

+ 124 - 0
src/lib/datasrc/tests/master_loader_callbacks_test.cc

@@ -0,0 +1,124 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/master_loader_callbacks.h>
+#include <datasrc/zone.h>
+
+#include <dns/rrset.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <list>
+#include <string>
+
+using namespace isc::datasrc;
+
+namespace {
+
+// An updater for the tests. Most of the virtual methods throw
+// NotImplemented, as they are not used in the tests.
+class MockUpdater : public ZoneUpdater {
+public:
+    // We do the adding in this test. We currently only check these are
+    // the correct ones, according to a predefined set in a list.
+    virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
+        ASSERT_FALSE(expected_rrsets_.empty());
+        // In our tests, pointer equality is enough.
+        EXPECT_EQ(expected_rrsets_.front().get(), &rrset);
+        // And remove this RRset, as it has been used.
+        expected_rrsets_.pop_front();
+    }
+    // The unused but required methods
+    virtual ZoneFinder& getFinder() {
+        isc_throw(isc::NotImplemented, "Not to be called in this test");
+    }
+    virtual void deleteRRset(const isc::dns::AbstractRRset&) {
+        isc_throw(isc::NotImplemented, "Not to be called in this test");
+    }
+    virtual void commit() {
+        isc_throw(isc::NotImplemented, "Not to be called in this test");
+    }
+    // The RRsets that are expected to appear through addRRset.
+    std::list<isc::dns::RRsetPtr> expected_rrsets_;
+};
+
+class MasterLoaderCallbackTest : public ::testing::Test {
+protected:
+    MasterLoaderCallbackTest() :
+        ok_(true),
+        callbacks_(createMasterLoaderCallbacks(isc::dns::Name("example.org"),
+                                               isc::dns::RRClass::IN(), &ok_))
+    {}
+    // Generate a new RRset, put it to the updater and return it.
+    isc::dns::RRsetPtr generateRRset() {
+        const isc::dns::RRsetPtr
+            result(new isc::dns::RRset(isc::dns::Name("example.org"),
+                                       isc::dns::RRClass::IN(),
+                                       isc::dns::RRType::A(),
+                                       isc::dns::RRTTL(3600)));
+        updater_.expected_rrsets_.push_back(result);
+        return (result);
+    }
+    // An updater to be passed to the context
+    MockUpdater updater_;
+    // Is the loading OK?
+    bool ok_;
+    // The tested context
+    isc::dns::MasterLoaderCallbacks callbacks_;
+};
+
+// Check it doesn't crash if we don't provide the OK
+TEST_F(MasterLoaderCallbackTest, noOkProvided) {
+    createMasterLoaderCallbacks(isc::dns::Name("example.org"),
+                                isc::dns::RRClass::IN(), NULL).
+        error("No source", 1, "No reason");
+}
+
+// Check the callbacks can be called, don't crash and the error one switches
+// to non-OK mode. This, however, does not stop anybody from calling more
+// callbacks.
+TEST_F(MasterLoaderCallbackTest, callbacks) {
+    EXPECT_NO_THROW(callbacks_.warning("No source", 1, "Just for fun"));
+    // The warning does not hurt the OK mode.
+    EXPECT_TRUE(ok_);
+    // Now the error
+    EXPECT_NO_THROW(callbacks_.error("No source", 2, "Some error"));
+    // The OK is turned off once there's at least one error
+    EXPECT_FALSE(ok_);
+
+    // Not being OK does not hurt that much, we can still call the callbacks
+    EXPECT_NO_THROW(callbacks_.warning("No source", 3, "Just for fun"));
+    // The OK is not reset back to true
+    EXPECT_FALSE(ok_);
+    EXPECT_NO_THROW(callbacks_.error("No source", 4, "Some error"));
+}
+
+// Try adding some RRsets.
+TEST_F(MasterLoaderCallbackTest, addRRset) {
+    isc::dns::AddRRsetCallback
+        callback(createMasterLoaderAddCallback(updater_));
+    // Put some of them in.
+    EXPECT_NO_THROW(callback(generateRRset()));
+    EXPECT_NO_THROW(callback(generateRRset()));
+    // They all get pushed there right away, so there are none in the queue
+    EXPECT_TRUE(updater_.expected_rrsets_.empty());
+}
+
+}

+ 2 - 0
src/lib/datasrc/tests/memory/Makefile.am

@@ -32,6 +32,8 @@ run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
 run_unittests_SOURCES += memory_segment_test.h
 run_unittests_SOURCES += segment_object_holder_unittest.cc
 run_unittests_SOURCES += memory_client_unittest.cc
+run_unittests_SOURCES += zone_data_loader_unittest.cc
+run_unittests_SOURCES += zone_data_updater_unittest.cc
 run_unittests_SOURCES += zone_table_segment_test.h
 run_unittests_SOURCES += zone_table_segment_unittest.cc
 run_unittests_SOURCES += zone_writer_unittest.cc

+ 0 - 10
src/lib/datasrc/tests/memory/memory_client_unittest.cc

@@ -576,16 +576,6 @@ TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
     // Teardown checks for memory segment leaks
 }
 
-TEST_F(MemoryClientTest, loadRRSIGFollowsNothing) {
-    // This causes the situation where an RRSIG is added without a covered
-    // RRset.  Such cases are currently rejected.
-    EXPECT_THROW(client_->load(Name("example.org"),
-                               TEST_DATA_DIR
-                               "/example.org-rrsig-follows-nothing.zone"),
-                 ZoneDataUpdater::AddError);
-    // Teardown checks for memory segment leaks
-}
-
 TEST_F(MemoryClientTest, loadRRSIGs) {
     client_->load(Name("example.org"),
                   TEST_DATA_DIR "/example.org-rrsigs.zone");

+ 66 - 9
src/lib/datasrc/tests/memory/rdataset_unittest.cc

@@ -24,6 +24,7 @@
 #include <dns/rrtype.h>
 #include <dns/rrttl.h>
 
+#include <datasrc/memory/segment_object_holder.h>
 #include <datasrc/memory/rdata_serialization.h>
 #include <datasrc/memory/rdataset.h>
 
@@ -39,6 +40,7 @@ using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::datasrc::memory;
 using namespace isc::testutils;
+using isc::datasrc::memory::detail::SegmentObjectHolder;
 using boost::lexical_cast;
 
 namespace {
@@ -112,7 +114,7 @@ TEST_F(RdataSetTest, create) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           ConstRRsetPtr());
     checkRdataSet(*rdataset, true, false);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 TEST_F(RdataSetTest, getNext) {
@@ -131,7 +133,62 @@ TEST_F(RdataSetTest, getNext) {
     rdataset->next = rdataset;
     EXPECT_EQ(rdataset, static_cast<const RdataSet*>(rdataset)->getNext());
 
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
+}
+
+TEST_F(RdataSetTest, find) {
+    // Create some RdataSets and make a chain of them.
+    SegmentObjectHolder<RdataSet, RRClass> holder1(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()),
+        RRClass::IN());
+    ConstRRsetPtr aaaa_rrset =
+        textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1");
+    SegmentObjectHolder<RdataSet, RRClass> holder2(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()),
+        RRClass::IN());
+    ConstRRsetPtr sigonly_rrset =
+        textToRRset("www.example.com. 1076895760 IN RRSIG "
+                    "TXT 5 2 3600 20120814220826 20120715220826 "
+                    "1234 example.com. FAKE");
+    SegmentObjectHolder<RdataSet, RRClass> holder3(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset),
+        RRClass::IN());
+
+    RdataSet* rdataset_a = holder1.get();
+    RdataSet* rdataset_aaaa = holder2.get();
+    RdataSet* rdataset_sigonly = holder3.get();
+    RdataSet* rdataset_null = NULL;
+    rdataset_a->next = rdataset_aaaa;
+    rdataset_aaaa->next = rdataset_sigonly;
+
+    // If a non-RRSIG part of rdataset exists for the given type, it will be
+    // returned regardless of the value of sigonly_ok.  If it's RRSIG-only
+    // rdataset, it returns non NULL iff sigonly_ok is explicitly set to true.
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA()));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), true));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), false));
+
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT()));
+    EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a, RRType::TXT(),
+                                               true));
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT(), false));
+
+    // Same tests for the const version of find().
+    const RdataSet* rdataset_a_const = holder1.get();
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA()));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
+                                            true));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
+                                            false));
+
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT()));
+    EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a_const, RRType::TXT(),
+                                               true));
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT(),
+                                            false));
 }
 
 // A helper function to create an RRset containing the given number of
@@ -154,7 +211,7 @@ TEST_F(RdataSetTest, createManyRRs) {
                                           ConstRRsetPtr());
     EXPECT_EQ(8191, rdataset->getRdataCount());
     EXPECT_EQ(0, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Exceeding that will result in an exception.
     EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_,
@@ -173,7 +230,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           rrsig_rrset_);
     checkRdataSet(*rdataset, true, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Unusual case: TTL doesn't match.  This implementation accepts that,
     // using the TTL of the covered RRset.
@@ -183,7 +240,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
                                    "20120715220826 1234 example.com. FAKE"));
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_badttl);
     checkRdataSet(*rdataset, true, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 // A helper function to create an RRSIG RRset containing the given number of
@@ -218,21 +275,21 @@ TEST_F(RdataSetTest, createManyRRSIGs) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           getRRSIGWithRdataCount(7));
     EXPECT_EQ(7, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // 8 would cause overflow in the normal 3-bit field if there were no extra
     // count field.
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                 getRRSIGWithRdataCount(8));
     EXPECT_EQ(8, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Up to 2^16-1 RRSIGs are allowed (although that would be useless
     // in practice)
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                 getRRSIGWithRdataCount(65535));
     EXPECT_EQ(65535, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Exceeding this limit will result in an exception.
     EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
@@ -250,7 +307,7 @@ TEST_F(RdataSetTest, createWithRRSIGOnly) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(),
                                           rrsig_rrset_);
     checkRdataSet(*rdataset, false, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 TEST_F(RdataSetTest, badCeate) {

+ 65 - 0
src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc

@@ -0,0 +1,65 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/memory/zone_data_loader.h>
+
+#include "memory_segment_test.h"
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class ZoneDataLoaderTest : public ::testing::Test {
+protected:
+    ZoneDataLoaderTest() : zclass_(RRClass::IN()), zone_data_(NULL) {}
+    void TearDown() {
+        if (zone_data_ != NULL) {
+            ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        }
+        EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
+    }
+    const RRClass zclass_;
+    test::MemorySegmentTest mem_sgmt_;
+    ZoneData* zone_data_;
+};
+
+TEST_F(ZoneDataLoaderTest, loadRRSIGFollowsNothing) {
+    // This causes the situation where an RRSIG is added without a covered
+    // RRset.  It will be accepted, and corresponding "sig-only" rdata will
+    // be created.
+    zone_data_ = loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+                              TEST_DATA_DIR
+                              "/example.org-rrsig-follows-nothing.zone");
+    ZoneNode* node = NULL;
+    zone_data_->insertName(mem_sgmt_, Name("ns1.example.org"), &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    EXPECT_EQ(RRType::A(), rdset->type); // there should be only 1 data here
+    EXPECT_EQ(0, rdset->getRdataCount()); // no RDATA
+    EXPECT_EQ(1, rdset->getSigRdataCount()); // but 1 SIG
+
+    // Teardown checks for memory segment leaks
+}
+
+}

+ 208 - 0
src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc

@@ -0,0 +1,208 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <testutils/dnsmessage_test.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_updater.h>
+
+#include "memory_segment_test.h"
+
+#include <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cassert>
+
+using isc::testutils::textToRRset;
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class ZoneDataUpdaterTest : public ::testing::Test {
+protected:
+    ZoneDataUpdaterTest() :
+        zname_("example.org"), zclass_(RRClass::IN()),
+        zone_data_(ZoneData::create(mem_sgmt_, zname_)),
+        updater_(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, *zone_data_))
+    {}
+
+    ~ZoneDataUpdaterTest() {
+        if (zone_data_ != NULL) {
+            ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        }
+        if (!mem_sgmt_.allMemoryDeallocated()) {
+            ADD_FAILURE() << "Memory leak detected";
+        }
+    }
+
+    void clearZoneData() {
+        assert(zone_data_ != NULL);
+        ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        zone_data_ = ZoneData::create(mem_sgmt_, zname_);
+        updater_.reset(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_,
+                                           *zone_data_));
+    }
+
+    const Name zname_;
+    const RRClass zclass_;
+    test::MemorySegmentTest mem_sgmt_;
+    ZoneData* zone_data_;
+    boost::scoped_ptr<ZoneDataUpdater> updater_;
+};
+
+TEST_F(ZoneDataUpdaterTest, bothNull) {
+    // At least either covered RRset or RRSIG must be non NULL.
+    EXPECT_THROW(updater_->add(ConstRRsetPtr(), ConstRRsetPtr()),
+                 ZoneDataUpdater::NullRRset);
+}
+
+ZoneNode*
+getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
+        ZoneData* zone_data)
+{
+    ZoneNode* node = NULL;
+    zone_data->insertName(mem_sgmt, name, &node);
+    EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
+    return (node);
+}
+
+TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
+    // RRSIG that doesn't have covered RRset can be added.  The resulting
+    // rdataset won't have "normal" RDATA but sig RDATA.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "www.example.org. 3600 IN RRSIG A 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    ZoneNode* node = getNode(mem_sgmt_, Name("www.example.org"), zone_data_);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    rdset = RdataSet::find(rdset, RRType::A(), true);
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    EXPECT_EQ(0, rdset->getRdataCount());
+    EXPECT_EQ(1, rdset->getSigRdataCount());
+
+    // The RRSIG covering A prohibits an actual A RRset from being added.
+    // This should be loosened in future version, but we check the current
+    // behavior.
+    EXPECT_THROW(updater_->add(
+                     textToRRset("www.example.org. 3600 IN A 192.0.2.1"),
+                     ConstRRsetPtr()), ZoneDataUpdater::AddError);
+
+    // The special "wildcarding" node mark should be added for the RRSIG-only
+    // case, too.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "*.wild.example.org. 3600 IN RRSIG A 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("wild.example.org"), zone_data_);
+    EXPECT_TRUE(node->getFlag(ZoneData::WILDCARD_NODE));
+
+    // Simply adding RRSIG covering (delegating NS) shouldn't enable callback
+    // in search.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "child.example.org. 3600 IN RRSIG NS 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("child.example.org"), zone_data_);
+    EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
+
+    // Same for DNAME
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "dname.example.org. 3600 IN RRSIG DNAME 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("dname.example.org"), zone_data_);
+    EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
+
+    // Likewise, RRSIG for NSEC3PARAM alone shouldn't make the zone
+    // "NSEC3-signed".
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_FALSE(zone_data_->isNSEC3Signed());
+
+    // And same for (RRSIG for) NSEC and "is signed".
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_FALSE(zone_data_->isSigned());
+}
+
+// Commonly used checks for rrsigForNSEC3Only
+void
+checkNSEC3Rdata(isc::util::MemorySegment& mem_sgmt, const Name& name,
+                ZoneData* zone_data)
+{
+    ZoneNode* node = NULL;
+    zone_data->getNSEC3Data()->insertName(mem_sgmt, name, &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    ASSERT_EQ(RRType::NSEC3(), rdset->type);
+    EXPECT_EQ(0, rdset->getRdataCount());
+    EXPECT_EQ(1, rdset->getSigRdataCount());
+}
+
+TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
+    // Adding only RRSIG covering NSEC3 is tricky.  It should go to the
+    // separate NSEC3 tree, but the separate space is only created when
+    // NSEC3 or NSEC3PARAM is added.  So, in many cases RRSIG-only is allowed,
+    // but if no NSEC3 or NSEC3PARAM has been added it will be rejected.
+
+    // Below we use abnormal owner names and RDATA for NSEC3s for brevity,
+    // but that doesn't matter for this test.
+
+    // Add NSEC3PARAM, then RRSIG-only, which is okay.
+    updater_->add(textToRRset(
+                      "example.org. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD"),
+                  textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_TRUE(zone_data_->isNSEC3Signed());
+    updater_->add(ConstRRsetPtr(),
+                  textToRRset(
+                      "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+
+    // Clear the current content of zone, then add NSEC3
+    clearZoneData();
+    updater_->add(textToRRset(
+                      "AABB.example.org. 3600 IN NSEC3 1 0 10 AA 00000000 A"),
+                  textToRRset(
+                      "AABB.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    updater_->add(ConstRRsetPtr(),
+                  textToRRset(
+                      "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+
+    // If we add only RRSIG without any NSEC3 related data beforehand,
+    // it will be rejected; it's a limitation of the current implementation.
+    clearZoneData();
+    EXPECT_THROW(updater_->add(
+                     ConstRRsetPtr(),
+                     textToRRset(
+                         "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                         "20150420235959 20051021000000 1 example.org. FAKE")),
+                 isc::NotImplemented);
+}
+
+}

+ 81 - 0
src/lib/datasrc/tests/memory/zone_finder_unittest.cc

@@ -1358,6 +1358,74 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {
                  DataSourceError);
 }
 
+TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
+    // Make the zone "NSEC signed"
+    addToZoneData(rr_nsec_);
+    const ZoneFinder::FindResultFlags expected_flags =
+        ZoneFinder::RESULT_NSEC_SIGNED;
+
+    // Add A for ns.example.org, and RRSIG-only covering TXT for the same name.
+    // query for the TXT should result in NXRRSET.
+    addToZoneData(rr_ns_a_);
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
+                     "20120715220826 1234 example.com. FAKE"));
+    findTest(Name("ns.example.org"), RRType::TXT(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+
+    // Add RRSIG-only covering NSEC.  This shouldn't be returned when NSEC is
+    // requested, whether it's for NXRRSET or NXDOMAIN
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    // The added RRSIG for NSEC could be used for NXRRSET but shouldn't
+    findTest(Name("ns.example.org"), RRType::TXT(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+             expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
+    // The added RRSIG for NSEC could be used for NXDOMAIN but shouldn't
+    findTest(Name("nz.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, rr_nsec_,
+             expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
+
+    // RRSIG-only CNAME shouldn't be accidentally confused with real CNAME.
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("nocname.example.org"), RRType::A(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+
+    // RRSIG-only for NS wouldn't invoke delegation anyway, but we check this
+    // case explicitly.
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("nodelegation.example.org"), RRType::A(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+    findTest(Name("www.nodelegation.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+
+    // Same for RRSIG-only for DNAME
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("www.nodname.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+    // If we have a delegation NS at this node, it will be a bit trickier,
+    // because the zonecut processing actually takes place at the node.
+    // But the RRSIG-only for DNAME shouldn't confuse the process and the NS
+    // should win.
+    ConstRRsetPtr ns_rrset =
+        textToRRset("nodname.example.org. 300 IN NS ns.nodname.example.org.");
+    addToZoneData(ns_rrset);
+    findTest(Name("www.nodname.example.org"), RRType::A(),
+             ZoneFinder::DELEGATION, true, ns_rrset);
+}
+
 /// \brief NSEC3 specific tests fixture for the InMemoryZoneFinder class
 class InMemoryZoneFinderNSEC3Test : public InMemoryZoneFinderTest {
 public:
@@ -1481,4 +1549,17 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3Walk) {
         }
     }
 }
+
+TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
+    // add an RRSIG-only NSEC3 to the NSEC3 space, and try to find it; it
+    // should result in an exception.
+    const string n8_hash = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("n8.example.org"), false),
+                 DataSourceError);
+}
+
 }

+ 1 - 1
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -1219,7 +1219,7 @@ TEST_F(InMemoryZoneFinderTest, loadFromIterator) {
     // purpose of this test, so it should just succeed.
     db_client = unittest::createSQLite3Client(
         class_, origin_, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied",
-        TEST_DATA_DIR "/contexttest.zone");
+        TEST_DATA_DIR "/contexttest-almost-obsolete.zone");
     zone_finder_.load(*db_client->getIterator(origin_));
 
     // just checking a couple of RRs in the new version of zone.

+ 85 - 0
src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone

@@ -0,0 +1,85 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+example.org.			      3600 IN NS	ns1.example.org.
+example.org.			      3600 IN NS	ns2.example.org.
+example.org.			      3600 IN RRSIG	NS 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+example.org.			      3600 IN MX	1 mx1.example.org.
+example.org.			      3600 IN MX	2 mx2.example.org.
+example.org.			      3600 IN MX	3 mx.a.example.org.
+
+ns1.example.org.		      3600 IN A		192.0.2.1
+ns1.example.org.		      3600 IN RRSIG	A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+ns1.example.org.		      3600 IN AAAA	2001:db8::1
+ns1.example.org.		      3600 IN RRSIG	AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+ns2.example.org.		      3600 IN A		192.0.2.2
+ns2.example.org.		      3600 IN TXT	"text data"
+
+mx1.example.org.		      3600 IN A		192.0.2.10
+mx2.example.org.		      3600 IN AAAA	2001:db8::10
+
+;; delegation
+a.example.org.			      3600 IN NS	ns1.a.example.org.
+a.example.org.			      3600 IN NS	ns2.a.example.org.
+a.example.org.			      3600 IN NS	ns.example.com.
+
+ns1.a.example.org.		      3600 IN A		192.0.2.5
+ns2.a.example.org.		      3600 IN A		192.0.2.6
+ns2.a.example.org.		      3600 IN AAAA	2001:db8::6
+mx.a.example.org.		      3600 IN A		192.0.2.7
+
+;; delegation, one of its NS names is at zone cut.
+b.example.org.			      3600 IN NS	ns.b.example.org.
+b.example.org.			      3600 IN NS	b.example.org.
+b.example.org.			      3600 IN AAAA	2001:db8::8
+
+ns.b.example.org.		      3600 IN A		192.0.2.9
+
+;; The MX name is at a zone cut.  shouldn't be included in the
+;; additional section.
+mxatcut.example.org.		      3600 IN MX	1 b.example.org.
+
+;; delegation, one of its NS names is under a DNAME delegation point;
+;; another is at that point; and yet another is under DNAME below a
+;; zone cut.
+c.example.org. 	      	      3600 IN NS	ns.dname.example.org.
+c.example.org. 	      	      3600 IN NS	dname.example.org.
+c.example.org.      	      3600 IN NS	ns.deepdname.example.org.
+ns.dname.example.org.		      3600 IN A		192.0.2.11
+dname.example.org.		      3600 IN A		192.0.2.12
+ns.deepdname.example.org.	      3600 IN AAAA	2001:db8::9
+
+;; delegation, one of its NS name is at an empty non terminal.
+d.example.org. 	      	      3600 IN NS	ns.empty.example.org.
+d.example.org. 	      	      3600 IN NS	ns1.example.org.
+;; by adding these two we can create an empty RB node for
+;; ns.empty.example.org in the in-memory zone
+foo.ns.empty.example.org.     3600 IN A		192.0.2.13
+bar.ns.empty.example.org.     3600 IN A		192.0.2.14
+
+;; delegation; the NS name matches a wildcard (and there's no exact
+;; match).  One of the NS names matches an empty wildcard node, for
+;; which no additional record should be provided (or any other
+;; disruption should happen).
+e.example.org. 	      	      3600 IN NS	ns.wild.example.org.
+e.example.org. 	      	      3600 IN NS	ns.emptywild.example.org.
+e.example.org. 	      	      3600 IN NS	ns2.example.org.
+*.wild.example.org.	      3600 IN A		192.0.2.15
+a.*.emptywild.example.org.    3600 IN AAAA	2001:db8::2
+
+;; additional for an answer RRset (MX) as a result of wildcard
+;; expansion
+*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
+
+;; the owner name of additional for an answer RRset (MX) has DNAME
+dnamemx.example.org. 3600 IN MX 1 dname.example.org.
+
+;; CNAME
+alias.example.org. 3600 IN CNAME cname.example.org.
+
+;; DNAME
+dname.example.org. 3600 IN DNAME dname.example.com.
+
+;; DNAME under a NS (strange one)
+deepdname.c.example.org. 3600 IN DNAME deepdname.example.com.

+ 7 - 0
src/lib/datasrc/tests/testdata/contexttest.zone

@@ -68,6 +68,13 @@ e.example.org. 	      	      3600 IN NS	ns2.example.org.
 *.wild.example.org.	      3600 IN A		192.0.2.15
 a.*.emptywild.example.org.    3600 IN AAAA	2001:db8::2
 
+;; One of the additional records actually only has RRSIG, which should
+;; be ignored.
+f.example.org. 	      	      3600 IN MX 5 mx1.f.example.org.
+f.example.org. 	      	      3600 IN MX 10 mx2.f.example.org.
+mx1.f.example.org.     	      3600 IN RRSIG A 5 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+mx2.f.example.org.     	      3600 IN A 192.0.2.16
+
 ;; additional for an answer RRset (MX) as a result of wildcard
 ;; expansion
 *.wildmx.example.org. 3600 IN MX 1 mx1.example.org.

+ 12 - 0
src/lib/datasrc/tests/zone_finder_context_unittest.cc

@@ -418,4 +418,16 @@ TEST_P(ZoneFinderContextTest, getAdditionalForAny) {
                 result_sets_.begin(), result_sets_.end());
 }
 
+TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
+    // This has two MX records, but type-A for one of them only has RRSIG.
+    // It shouldn't be contained in the result.
+    ZoneFinderContextPtr ctx = finder_->find(Name("f.example.org"),
+                                             RRType::MX(),
+                                             ZoneFinder::FIND_DNSSEC);
+    EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+    ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+    rrsetsCheck("mx2.f.example.org. 3600 IN A 192.0.2.16\n",
+                result_sets_.begin(), result_sets_.end());
+}
+
 }

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

@@ -21,8 +21,9 @@ libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
 libb10_dhcp___la_SOURCES += option.cc option.h
-libb10_dhcp___la_SOURCES += option_data_types.h
+libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
 libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
+libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
 libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
 libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
 libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h

+ 2 - 2
src/lib/dhcp/iface_mgr.cc

@@ -1098,9 +1098,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
 
     pkt->updateTimestamp();
 
-    pkt->setLocalAddr(IOAddress::from_bytes(AF_INET6,
+    pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6,
                       reinterpret_cast<const uint8_t*>(&to_addr)));
-    pkt->setRemoteAddr(IOAddress::from_bytes(AF_INET6,
+    pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6,
                        reinterpret_cast<const uint8_t*>(&from.sin6_addr)));
     pkt->setRemotePort(ntohs(from.sin6_port));
     pkt->setIndex(ifindex);

+ 1 - 1
src/lib/dhcp/iface_mgr_linux.cc

@@ -302,7 +302,7 @@ void Netlink::ipaddrs_get(IfaceMgr::Iface& iface, NetlinkMessages& addr_info) {
 
             memcpy(addr, RTA_DATA(rta_tb[IFLA_ADDRESS]),
                    ifa->ifa_family==AF_INET?V4ADDRESS_LEN:V6ADDRESS_LEN);
-            IOAddress a = IOAddress::from_bytes(ifa->ifa_family, addr);
+            IOAddress a = IOAddress::fromBytes(ifa->ifa_family, addr);
             iface.addAddress(a);
 
             /// TODO: Read lifetimes of configured IPv6 addresses

+ 62 - 54
src/lib/dhcp/libdhcp++.cc

@@ -48,8 +48,12 @@ const OptionDefContainer&
 LibDHCP::getOptionDefs(Option::Universe u) {
     switch (u) {
     case Option::V4:
+        initStdOptionDefs4();
         return (v4option_defs_);
     case Option::V6:
+        if (v6option_defs_.size() == 0) {
+            initStdOptionDefs6();
+        }
         return (v6option_defs_);
     default:
         isc_throw(isc::BadValue, "invalid universe " << u << " specified");
@@ -96,30 +100,43 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
             // @todo: consider throwing exception here.
             return (offset);
         }
+
+        // Get the list of stdandard option definitions.
+        OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
+        // Get the search index #1. It allows to search for option definitions
+        // using option code.
+        const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+        // Get all options with the particular option code. Note that option code
+        // is non-unique within this container however at this point we expect
+        // to get one option definition with the particular code. If more are
+        // returned we report an error.
+        const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
+        // Get the number of returned option definitions for the option code.
+        size_t num_defs = distance(range.first, range.second);
         OptionPtr opt;
-        switch (opt_type) {
-        case D6O_IA_NA:
-        case D6O_IA_PD:
-            opt = OptionPtr(new Option6IA(opt_type,
-                                          buf.begin() + offset,
-                                          buf.begin() + offset + opt_len));
-            break;
-        case D6O_IAADDR:
-            opt = OptionPtr(new Option6IAAddr(opt_type,
-                                              buf.begin() + offset,
-                                              buf.begin() + offset + opt_len));
-            break;
-        case D6O_ORO:
-            opt = OptionPtr(new Option6IntArray<uint16_t>(opt_type,
-                                                          buf.begin() + offset,
-                                                          buf.begin() + offset + opt_len));
-            break;
-        default:
-            opt = OptionPtr(new Option(Option::V6,
-                                       opt_type,
+        if (num_defs > 1) {
+            // Multiple options of the same code are not supported right now!
+            isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
+                      " for option type " << opt_type << " returned. Currently it is not"
+                      " supported to initialize multiple option definitions"
+                      " for the same option code. This will be supported once"
+                      " support for option spaces is implemented");
+        } else if (num_defs == 0) {
+            // @todo Don't crash if definition does not exist because only a few
+            // option definitions are initialized right now. In the future
+            // we will initialize definitions for all options and we will
+            // remove this elseif. For now, return generic option.
+            opt = OptionPtr(new Option(Option::V6, opt_type,
                                        buf.begin() + offset,
                                        buf.begin() + offset + opt_len));
-            break;
+        } else {
+            // The option definition has been found. Use it to create
+            // the option instance from the provided buffer chunk.
+            const OptionDefinitionPtr& def = *(range.first);
+            assert(def);
+            opt = def->optionFactory(Option::V6, opt_type,
+                                     buf.begin() + offset,
+                                     buf.begin() + offset + opt_len);
         }
         // add option to options
         options.insert(std::make_pair(opt_type, opt));
@@ -229,20 +246,6 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u,
 }
 
 void
-LibDHCP::initStdOptionDefs(Option::Universe u) {
-    switch (u) {
-    case Option::V4:
-        initStdOptionDefs4();
-        break;
-    case Option::V6:
-        initStdOptionDefs6();
-        break;
-    default:
-        isc_throw(isc::BadValue, "invalid universe " << u << " specified");
-    }
-}
-
-void
 LibDHCP::initStdOptionDefs4() {
     isc_throw(isc::NotImplemented, "initStdOptionDefs4 is not implemented");
 }
@@ -254,19 +257,20 @@ LibDHCP::initStdOptionDefs6() {
     struct OptionParams {
         std::string name;
         uint16_t code;
-        OptionDefinition::DataType type;
+        OptionDataType type;
         bool array;
     };
     OptionParams params[] = {
-        { "CLIENTID", D6O_CLIENTID, OptionDefinition::BINARY_TYPE, false },
-        { "SERVERID", D6O_SERVERID, OptionDefinition::BINARY_TYPE, false },
-        { "IA_NA", D6O_IA_NA, OptionDefinition::RECORD_TYPE, false },
-        { "IAADDR", D6O_IAADDR, OptionDefinition::RECORD_TYPE, false },
-        { "ORO", D6O_ORO, OptionDefinition::UINT16_TYPE, true },
-        { "ELAPSED_TIME", D6O_ELAPSED_TIME, OptionDefinition::UINT16_TYPE, false },
-        { "STATUS_CODE", D6O_STATUS_CODE, OptionDefinition::RECORD_TYPE, false },
-        { "RAPID_COMMIT", D6O_RAPID_COMMIT, OptionDefinition::EMPTY_TYPE, false },
-        { "DNS_SERVERS", D6O_NAME_SERVERS, OptionDefinition::IPV6_ADDRESS_TYPE, true }
+        { "CLIENTID", D6O_CLIENTID, OPT_BINARY_TYPE, false },
+        { "SERVERID", D6O_SERVERID, OPT_BINARY_TYPE, false },
+        { "IA_NA", D6O_IA_NA, OPT_RECORD_TYPE, false },
+        { "IAADDR", D6O_IAADDR, OPT_RECORD_TYPE, false },
+        { "ORO", D6O_ORO, OPT_UINT16_TYPE, true },
+        { "ELAPSED_TIME", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false },
+        { "STATUS_CODE", D6O_STATUS_CODE, OPT_RECORD_TYPE, false },
+        { "RAPID_COMMIT", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false },
+        { "DNS_SERVERS", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
+        { "IA_PD", D6O_IA_PD, OPT_RECORD_TYPE, false }
     };
     const int params_size = sizeof(params) / sizeof(params[0]);
 
@@ -277,18 +281,19 @@ LibDHCP::initStdOptionDefs6() {
                                                             params[i].array));
         switch(params[i].code) {
         case D6O_IA_NA:
+        case D6O_IA_PD:
             for (int j = 0; j < 3; ++j) {
-                definition->addRecordField(OptionDefinition::UINT32_TYPE);
+                definition->addRecordField(OPT_UINT32_TYPE);
             }
             break;
         case D6O_IAADDR:
-            definition->addRecordField(OptionDefinition::IPV6_ADDRESS_TYPE);
-            definition->addRecordField(OptionDefinition::UINT32_TYPE);
-            definition->addRecordField(OptionDefinition::UINT32_TYPE);
+            definition->addRecordField(OPT_IPV6_ADDRESS_TYPE);
+            definition->addRecordField(OPT_UINT32_TYPE);
+            definition->addRecordField(OPT_UINT32_TYPE);
             break;
         case D6O_STATUS_CODE:
-            definition->addRecordField(OptionDefinition::UINT16_TYPE);
-            definition->addRecordField(OptionDefinition::STRING_TYPE);
+            definition->addRecordField(OPT_UINT16_TYPE);
+            definition->addRecordField(OPT_STRING_TYPE);
             break;
         default:
             // The default case is intentionally left empty
@@ -298,9 +303,12 @@ LibDHCP::initStdOptionDefs6() {
         try {
             definition->validate();
         } catch (const Exception& ex) {
-            isc_throw(isc::Unexpected, "internal server error: invalid definition of standard"
-                      << " DHCPv6 option (with code " << params[i].code << "): "
-                      << ex.what());
+            // This is unlikely event that validation fails and may
+            // be only caused by programming error. To guarantee the
+            // data consistency we clear all option definitions that
+            // have been added so far and pass the exception forward.
+            v6option_defs_.clear();
+            throw;
         }
         v6option_defs_.push_back(definition);
     }

+ 9 - 17
src/lib/dhcp/libdhcp++.h

@@ -33,7 +33,14 @@ public:
 
     /// @brief Return collection of option definitions.
     ///
+    /// Method returns the collection of DHCP standard DHCP
+    /// option definitions.
+    /// @todo DHCPv4 option definitions are not implemented. For now
+    /// this function will throw isc::NotImplemented in case of attempt
+    /// to get option definitions for V4 universe.
+    ///
     /// @param u universe of the options (V4 or V6).
+    ///
     /// @return collection of option definitions.
     static const OptionDefContainer& getOptionDefs(Option::Universe u);
 
@@ -113,21 +120,6 @@ public:
                                       uint16_t type,
                                       Option::Factory * factory);
 
-    /// Initialize standard DHCP options (V4 or V6).
-    ///
-    /// The method creates option definitions for all options
-    /// (DHCPv4 or DHCPv6 depending on universe specified).
-    /// Currently DHCPv4 option definitions initialization is not
-    /// implemented thus this function will throw isc::NotImplemented
-    /// if V4 universe is specified.
-    ///
-    /// @param u universe
-    /// @throw isc::Unexpected if internal error occured during option
-    /// definitions creation.
-    /// @throw std::bad_alloc if system went out of memory.
-    /// @throw isc::NotImplemented when V4 universe specified.
-    static void initStdOptionDefs(Option::Universe u);
-
 private:
 
     /// Initialize standard DHCPv4 option definitions.
@@ -144,9 +136,9 @@ private:
     ///
     /// The method creates option definitions for all DHCPv6 options.
     ///
-    /// @throw isc::Unexpected if internal error occured during option
-    /// definitions creation.
     /// @throw std::bad_alloc if system went out of memory.
+    /// @throw MalformedOptionDefinition if any of the definitions
+    /// is incorect. This is a programming error.
     static void initStdOptionDefs6();
 
     /// pointers to factories that produce DHCPv6 options

+ 52 - 24
src/lib/dhcp/option.cc

@@ -62,14 +62,14 @@ Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
 void
 Option::check() {
     if ( (universe_ != V4) && (universe_ != V6) ) {
-        isc_throw(BadValue, "Invalid universe type specified."
+        isc_throw(BadValue, "Invalid universe type specified. "
                   << "Only V4 and V6 are allowed.");
     }
 
     if (universe_ == V4) {
 
         if (type_ > 255) {
-            isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big."
+            isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. "
                       << "For DHCPv4 allowed type range is 0..255");
         } else if (data_.size() > 255) {
             isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big.");
@@ -87,20 +87,21 @@ void Option::pack(isc::util::OutputBuffer& buf) {
     switch (universe_) {
     case V6:
         return (pack6(buf));
+
     case V4:
         return (pack4(buf));
+
     default:
-        isc_throw(BadValue, "Failed to pack " << type_ << " option. Do not "
-                  << "use this method for options other than DHCPv6.");
+        isc_throw(BadValue, "Failed to pack " << type_ << " option as the "
+                  << "universe type is unknown.");
     }
 }
 
 void
 Option::pack4(isc::util::OutputBuffer& buf) {
-    switch (universe_) {
-    case V4: {
+    if (universe_ == V4) {
         if (len() > 255) {
-            isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big."
+            isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. "
                       << "At most 255 bytes are supported.");
             /// TODO Larger options can be stored as separate instances
             /// of DHCPv4 options. Clients MUST concatenate them.
@@ -109,33 +110,46 @@ Option::pack4(isc::util::OutputBuffer& buf) {
 
         buf.writeUint8(type_);
         buf.writeUint8(len() - getHeaderLen());
+        if (!data_.empty()) {
+            buf.writeData(&data_[0], data_.size());
+        }
 
-        buf.writeData(&data_[0], data_.size());
+        packOptions(buf);
 
-        LibDHCP::packOptions(buf, options_);
-        return;
+    } else {
+        isc_throw(BadValue, "Invalid universe type " << universe_);
     }
-    case V6:
-        /// TODO: Do we need a sanity check for option size here?
-        buf.writeUint16(type_);
-        buf.writeUint16(len() - getHeaderLen());
 
-        LibDHCP::packOptions(buf, options_);
-        return;
-    default:
-        isc_throw(OutOfRange, "Invalid universe type" << universe_);
-    }
+    return;
 }
 
 void Option::pack6(isc::util::OutputBuffer& buf) {
-    buf.writeUint16(type_);
-    buf.writeUint16(len() - getHeaderLen());
+    if (universe_ == V6) {
+        buf.writeUint16(type_);
+        buf.writeUint16(len() - getHeaderLen());
+        if (!data_.empty()) {
+            buf.writeData(&data_[0], data_.size());
+        }
 
-    if (! data_.empty()) {
-        buf.writeData(&data_[0], data_.size());
+        packOptions(buf);
+    } else {
+        isc_throw(BadValue, "Invalid universe type " << universe_);
     }
+    return;
+}
 
-    return LibDHCP::packOptions6(buf, options_);
+void
+Option::packOptions(isc::util::OutputBuffer& buf) {
+    switch (universe_) {
+    case V4:
+        LibDHCP::packOptions(buf, options_);
+        return;
+    case V6:
+        LibDHCP::packOptions6(buf, options_);
+        return;
+    default:
+        isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+    }
 }
 
 void Option::unpack(OptionBufferConstIter begin,
@@ -143,6 +157,20 @@ void Option::unpack(OptionBufferConstIter begin,
     data_ = OptionBuffer(begin, end);
 }
 
+void
+Option::unpackOptions(const OptionBuffer& buf) {
+    switch (universe_) {
+    case V4:
+        LibDHCP::unpackOptions4(buf, options_);
+        return;
+    case V6:
+        LibDHCP::unpackOptions6(buf, options_);
+        return;
+    default:
+        isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+    }
+}
+
 uint16_t Option::len() {
     // Returns length of the complete option (data length + DHCPv4/DHCPv6
     // option header)

+ 41 - 5
src/lib/dhcp/option.h

@@ -119,7 +119,7 @@ public:
     /// This constructor takes vector<uint8_t>& which is used in cases
     /// when content of the option will be copied and stored within
     /// option object. V4 Options follow that approach already.
-    /// TODO Migrate V6 options to that approach.
+    /// @todo Migrate V6 options to that approach.
     ///
     /// @param u specifies universe (V4 or V6)
     /// @param type option type (0-255 for V4 and 0-65535 for V6)
@@ -131,7 +131,7 @@ public:
     /// This contructor is similar to the previous one, but it does not take
     /// the whole vector<uint8_t>, but rather subset of it.
     ///
-    /// TODO: This can be templated to use different containers, not just
+    /// @todo This can be templated to use different containers, not just
     /// vector. Prototype should look like this:
     /// template<typename InputIterator> Option(Universe u, uint16_t type,
     /// InputIterator first, InputIterator last);
@@ -160,19 +160,24 @@ public:
     /// byte after stored option (that is useful for writing options one after
     /// another). Used in DHCPv6 options.
     ///
-    /// TODO: Migrate DHCPv6 code to pack(OutputBuffer& buf) version
+    /// @todo Migrate DHCPv6 code to pack(OutputBuffer& buf) version
     ///
     /// @param buf pointer to a buffer
+    ///
+    /// @throw BadValue Universe of the option is neither V4 nor V6.
     virtual void pack(isc::util::OutputBuffer& buf);
 
     /// @brief Writes option in a wire-format to a buffer.
     ///
     /// Method will throw if option storing fails for some reason.
     ///
-    /// TODO Once old (DHCPv6) implementation is rewritten,
+    /// @todo Once old (DHCPv6) implementation is rewritten,
     /// unify pack4() and pack6() and rename them to just pack().
     ///
     /// @param buf output buffer (option will be stored there)
+    ///
+    /// @throw OutOfRange Option type is greater than 255.
+    /// @throw BadValue Universe is not V4.
     virtual void pack4(isc::util::OutputBuffer& buf);
 
     /// @brief Parses received buffer.
@@ -303,8 +308,39 @@ protected:
     /// defined suboptions. Version for building DHCPv4 options.
     ///
     /// @param buf output buffer (built options will be stored here)
+    ///
+    /// @throw BadValue Universe is not V6.
     virtual void pack6(isc::util::OutputBuffer& buf);
 
+    /// @brief Store sub options in a buffer.
+    ///
+    /// This method stores all sub-options defined for a particular
+    /// option in a on-wire format in output buffer provided.
+    /// This function is called by pack function in this class or
+    /// derived classes that override pack.
+    ///
+    /// @param [out] buf output buffer.
+    ///
+    /// @todo The set of exceptions thrown by this function depend on
+    /// exceptions thrown by pack methods invoked on objects
+    /// representing sub options. We should consider whether to aggregate
+    /// those into one exception which can be documented here.
+    void packOptions(isc::util::OutputBuffer& buf);
+
+    /// @brief Builds a collection of sub options from the buffer.
+    ///
+    /// This method parses the provided buffer and builds a collection
+    /// of objects representing sub options. This function may throw
+    /// different exceptions when option assembly fails.
+    ///
+    /// @param buf buffer to be parsed.
+    ///
+    /// @todo The set of exceptions thrown by this function depend on
+    /// exceptions thrown by unpack methods invoked on objects
+    /// representing sub options. We should consider whether to aggregate
+    /// those into one exception which can be documented here.
+    void unpackOptions(const OptionBuffer& buf);
+
     /// @brief A private method used for option correctness.
     ///
     /// It is used in constructors. In there are any problems detected
@@ -324,7 +360,7 @@ protected:
     /// collection for storing suboptions
     OptionCollection options_;
 
-    /// TODO: probably 2 different containers have to be used for v4 (unique
+    /// @todo probably 2 different containers have to be used for v4 (unique
     /// options) and v6 (options with the same type can repeat)
 };
 

+ 1 - 1
src/lib/dhcp/option6_addrlst.cc

@@ -84,7 +84,7 @@ void Option6AddrLst::unpack(OptionBufferConstIter begin,
                   << " is not divisible by 16.");
     }
     while (begin != end) {
-        addrs_.push_back(IOAddress::from_bytes(AF_INET6, &(*begin)));
+        addrs_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin)));
         begin += V6ADDRESS_LEN;
     }
 }

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

@@ -44,7 +44,7 @@ void Option6IA::pack(isc::util::OutputBuffer& buf) {
     buf.writeUint32(t1_);
     buf.writeUint32(t2_);
 
-    LibDHCP::packOptions6(buf, options_);
+    packOptions(buf);
 }
 
 void Option6IA::unpack(OptionBufferConstIter begin,
@@ -62,7 +62,7 @@ void Option6IA::unpack(OptionBufferConstIter begin,
     t2_ = readUint32( &(*begin) );
     begin += sizeof(uint32_t);
 
-    LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
+    unpackOptions(OptionBuffer(begin, end));
 }
 
 std::string Option6IA::toText(int indent /* = 0*/) {

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

@@ -59,7 +59,7 @@ void Option6IAAddr::pack(isc::util::OutputBuffer& buf) {
     buf.writeUint32(valid_);
 
     // parse suboption (there shouldn't be any for IAADDR)
-    LibDHCP::packOptions6(buf, options_);
+    packOptions(buf);
 }
 
 void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
@@ -69,7 +69,7 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
     }
 
     // 16 bytes: IPv6 address
-    addr_ = IOAddress::from_bytes(AF_INET6, &(*begin));
+    addr_ = IOAddress::fromBytes(AF_INET6, &(*begin));
     begin += V6ADDRESS_LEN;
 
     preferred_ = readUint32( &(*begin) );
@@ -77,7 +77,8 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
 
     valid_ = readUint32( &(*begin) );
     begin += sizeof(uint32_t);
-    LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
+
+    unpackOptions(OptionBuffer(begin, end));
 }
 
 std::string Option6IAAddr::toText(int indent /* =0 */) {

+ 10 - 10
src/lib/dhcp/option6_int.h

@@ -48,7 +48,7 @@ public:
     /// as template parameter is not a supported integer type.
     Option6Int(uint16_t type, T value)
         : Option(Option::V6, type), value_(value) {
-        if (!OptionDataTypes<T>::valid) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
     }
@@ -69,7 +69,7 @@ public:
     Option6Int(uint16_t type, OptionBufferConstIter begin,
                OptionBufferConstIter end)
         : Option(Option::V6, type) {
-        if (!OptionDataTypes<T>::valid) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
         unpack(begin, end);
@@ -89,9 +89,9 @@ public:
         // Depending on the data type length we use different utility functions
         // writeUint16 or writeUint32 which write the data in the network byte
         // order to the provided buffer. The same functions can be safely used
-        // for either unsiged or signed integers so there is not need to create
+        // for either unsigned or signed integers so there is not need to create
         // special cases for intX_t types.
-        switch (OptionDataTypes<T>::len) {
+        switch (OptionDataTypeTraits<T>::len) {
         case 1:
             buf.writeUint8(value_);
             break;
@@ -104,7 +104,7 @@ public:
         default:
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
-        LibDHCP::packOptions6(buf, options_);
+        packOptions(buf);
     }
 
     /// @brief Parses received buffer
@@ -128,9 +128,9 @@ public:
         // Depending on the data type length we use different utility functions
         // readUint16 or readUint32 which read the data laid in the network byte
         // order from the provided buffer. The same functions can be safely used
-        // for either unsiged or signed integers so there is not need to create
+        // for either unsigned or signed integers so there is not need to create
         // special cases for intX_t types.
-        int data_size_len = OptionDataTypes<T>::len;
+        int data_size_len = OptionDataTypeTraits<T>::len;
         switch (data_size_len) {
         case 1:
             value_ = *begin;
@@ -145,11 +145,11 @@ public:
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
         // Use local variable to set a new value for this iterator.
-        // When using OptionDataTypes<T>::len directly some versions
+        // When using OptionDataTypeTraits<T>::len directly some versions
         // of clang complain about unresolved reference to
-        // OptionDataTypes structure during linking.
+        // OptionDataTypeTraits structure during linking.
         begin += data_size_len;
-        LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
+        unpackOptions(OptionBuffer(begin, end));
     }
 
     /// @brief Set option value.

+ 9 - 9
src/lib/dhcp/option6_int_array.h

@@ -58,7 +58,7 @@ public:
     Option6IntArray(uint16_t type)
         : Option(Option::V6, type),
           values_(0) {
-        if (!OptionDataTypes<T>::valid) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
     }
@@ -74,7 +74,7 @@ public:
     /// as template parameter is not a supported integer type.
     Option6IntArray(uint16_t type, const OptionBuffer& buf)
         : Option(Option::V6, type) {
-        if (!OptionDataTypes<T>::valid) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
         unpack(buf.begin(), buf.end());
@@ -97,7 +97,7 @@ public:
     Option6IntArray(uint16_t type, OptionBufferConstIter begin,
                     OptionBufferConstIter end)
         : Option(Option::V6, type) {
-        if (!OptionDataTypes<T>::valid) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
             isc_throw(dhcp::InvalidDataType, "non-integer type");
         }
         unpack(begin, end);
@@ -118,9 +118,9 @@ public:
             // Depending on the data type length we use different utility functions
             // writeUint16 or writeUint32 which write the data in the network byte
             // order to the provided buffer. The same functions can be safely used
-            // for either unsiged or signed integers so there is not need to create
+            // for either unsigned or signed integers so there is not need to create
             // special cases for intX_t types.
-            switch (OptionDataTypes<T>::len) {
+            switch (OptionDataTypeTraits<T>::len) {
             case 1:
                 buf.writeUint8(values_[i]);
                 break;
@@ -164,9 +164,9 @@ public:
             // Depending on the data type length we use different utility functions
             // readUint16 or readUint32 which read the data laid in the network byte
             // order from the provided buffer. The same functions can be safely used
-            // for either unsiged or signed integers so there is not need to create
+            // for either unsigned or signed integers so there is not need to create
             // special cases for intX_t types.
-            int data_size_len = OptionDataTypes<T>::len;
+            int data_size_len = OptionDataTypeTraits<T>::len;
             switch (data_size_len) {
             case 1:
                 values_.push_back(*begin);
@@ -181,9 +181,9 @@ public:
                 isc_throw(dhcp::InvalidDataType, "non-integer type");
             }
             // Use local variable to set a new value for this iterator.
-            // When using OptionDataTypes<T>::len directly some versions
+            // When using OptionDataTypeTraits<T>::len directly some versions
             // of clang complain about unresolved reference to
-            // OptionDataTypes structure during linking.
+            // OptionDataTypeTraits structure during linking.
             begin += data_size_len;
         }
         // We do not unpack sub-options here because we have array-type option.

+ 370 - 0
src/lib/dhcp/option_custom.cc

@@ -0,0 +1,370 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_custom.h>
+#include <util/encode/hex.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+                             Universe u,
+                             const OptionBuffer& data)
+    : Option(u, def.getCode(), data.begin(), data.end()),
+      definition_(def) {
+    createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+                             Universe u,
+                             OptionBufferConstIter first,
+                             OptionBufferConstIter last)
+    : Option(u, def.getCode(), first, last),
+      definition_(def) {
+    createBuffers();
+}
+
+void
+OptionCustom::checkIndex(const uint32_t index) const {
+    if (index >= buffers_.size()) {
+        isc_throw(isc::OutOfRange, "specified data field index " << index
+                  << " is out of rangex.");
+    }
+}
+
+void
+OptionCustom::createBuffers() {
+    // Check that the option definition is correct as we are going
+    // to use it to split the data_ buffer into set of sub buffers.
+    definition_.validate();
+
+    std::vector<OptionBuffer> buffers;
+    OptionBuffer::iterator data = data_.begin();
+
+    OptionDataType data_type = definition_.getType();
+    if (data_type == OPT_RECORD_TYPE) {
+        // An option comprises a record of data fields. We need to
+        // get types of these data fields to allocate enough space
+        // for each buffer.
+        const OptionDefinition::RecordFieldsCollection& fields =
+            definition_.getRecordFields();
+
+        // Go over all data fields within a record.
+        for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+             field != fields.end(); ++field) {
+            // For fixed-size data type such as boolean, integer, even
+            // IP address we can use the utility function to get the required
+            // buffer size.
+            int data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+            // For variable size types (such as string) the function above
+            // will return 0 so we need to do a runtime check. Since variable
+            // length data fields may be laid only at the end of an option we
+            // consume the rest of this option. Note that validate() function
+            // in OptionDefinition object should have checked whether the
+            // data fields layout is correct (that the variable string fields
+            // are laid at the end).
+            if (data_size == 0) {
+                data_size = std::distance(data, data_.end());
+                if (data_size == 0) {
+                    // If we reached the end of buffer we assume that this option is
+                    // truncated because there is no remaining data to initialize
+                    // an option field.
+                    if (data_size == 0) {
+                        isc_throw(OutOfRange, "option buffer truncated");
+                    }
+                }
+            } else {
+                // Our data field requires that there is a certain chunk of
+                // data left in the buffer. If not, option is truncated.
+                if (std::distance(data, data_.end()) < data_size) {
+                    isc_throw(OutOfRange, "option buffer truncated");
+                }
+            }
+            // Store the created buffer.
+            buffers.push_back(OptionBuffer(data, data + data_size));
+            // Proceed to the next data field.
+            data += data_size;
+        }
+    } else if (data_type != OPT_EMPTY_TYPE) {
+        // If data_type value is other than OPT_RECORD_TYPE, our option is
+        // empty (have no data at all) or it comprises one or more
+        // data fields of the same type. The type of those fields
+        // is held in the data_type variable so let's use it to determine
+        // a size of buffers.
+        int data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+        // The check below will fail if the input buffer is too short
+        // for the data size being held by this option.
+        // Note that data_size returned by getDataTypeLen may be zero
+        // if variable length data is being held by the option but
+        // this will not cause this check to throw exception.
+        if (std::distance(data, data_.end()) < data_size) {
+            isc_throw(OutOfRange, "option buffer truncated");
+        }
+        // For an array of values we are taking different path because
+        // we have to handle multiple buffers.
+        if (definition_.getArrayType()) {
+            // We don't perform other checks for data types that can't be
+            // used together with array indicator such as strings, empty field
+            // etc. This is because OptionDefinition::validate function should
+            // have checked this already. Thus data_size must be greater than
+            // zero.
+            assert(data_size > 0);
+            // Get equal chunks of data and store as collection of buffers.
+            // Truncate any remaining part which length is not divisible by
+            // data_size. Note that it is ok to truncate the data if and only
+            // if the data buffer is long enough to keep at least one value.
+            // This has been checked above already.
+            do {
+                buffers.push_back(OptionBuffer(data, data + data_size));
+                data += data_size;
+            } while (std::distance(data, data_.end()) >= data_size);
+        } else {
+            // For non-arrays the data_size can be zero because
+            // getDataTypeLen returns zero for variable size data types
+            // such as strings. Simply take whole buffer.
+            if (data_size == 0) {
+                data_size = std::distance(data, data_.end());
+            }
+            if (data_size > 0) {
+                buffers.push_back(OptionBuffer(data, data + data_size));
+            } else {
+                isc_throw(OutOfRange, "option buffer truncated");
+            }
+        }
+    }
+    // If everything went ok we can replace old buffer set with new ones.
+    std::swap(buffers_, buffers);
+}
+
+std::string
+OptionCustom::dataFieldToText(const OptionDataType data_type,
+                              const uint32_t index) const {
+    std::ostringstream text;
+
+    // Get the value of the data field.
+    switch (data_type) {
+    case OPT_BINARY_TYPE:
+        text << util::encode::encodeHex(readBinary(index));
+        break;
+    case OPT_BOOLEAN_TYPE:
+        text << (readBoolean(index) ? "true" : "false");
+        break;
+    case OPT_INT8_TYPE:
+        text << readInteger<int8_t>(index);
+        break;
+    case OPT_INT16_TYPE:
+        text << readInteger<int16_t>(index);
+        break;
+    case OPT_INT32_TYPE:
+        text << readInteger<int32_t>(index);
+        break;
+    case OPT_UINT8_TYPE:
+        text << readInteger<uint8_t>(index);
+        break;
+    case OPT_UINT16_TYPE:
+        text << readInteger<uint16_t>(index);
+        break;
+    case OPT_UINT32_TYPE:
+        text << readInteger<uint32_t>(index);
+        break;
+    case OPT_IPV4_ADDRESS_TYPE:
+    case OPT_IPV6_ADDRESS_TYPE:
+        text << readAddress(index).toText();
+        break;
+    case OPT_STRING_TYPE:
+        text << readString(index);
+        break;
+    default:
+        ;
+    }
+
+    // Append data field type in brackets.
+    text << " ( " << OptionDataTypeUtil::getDataTypeName(data_type) << " ) ";
+
+    return (text.str());
+}
+
+void
+OptionCustom::pack4(isc::util::OutputBuffer& buf) {
+    if (len() > 255) {
+        isc_throw(OutOfRange, "DHCPv4 Option " << type_
+                  << " value is too high. At most 255 is supported.");
+    }
+
+    buf.writeUint8(type_);
+    buf.writeUint8(len() - getHeaderLen());
+
+    // Write data from buffers.
+    for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
+         it != buffers_.end(); ++it) {
+        // In theory the createBuffers function should have taken
+        // care that there are no empty buffers added to the
+        // collection but it is almost always good to make sure.
+        if (!it->empty()) {
+            buf.writeData(&(*it)[0], it->size());
+        }
+    }
+
+    // Write suboptions.
+    packOptions(buf);
+}
+
+void
+OptionCustom::pack6(isc::util::OutputBuffer& buf) {
+    buf.writeUint16(type_);
+    buf.writeUint16(len() - getHeaderLen());
+
+    // Write data from buffers.
+    for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
+         it != buffers_.end(); ++it) {
+        if (!it->empty()) {
+            buf.writeData(&(*it)[0], it->size());
+        }
+    }
+
+    packOptions(buf);
+}
+
+asiolink::IOAddress
+OptionCustom::readAddress(const uint32_t index) const {
+    checkIndex(index);
+
+    // The address being read can be either IPv4 or IPv6. The decision
+    // is made based on the buffer length. If it holds 4 bytes it is IPv4
+    // address, if it holds 16 bytes it is IPv6.
+    if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) {
+        return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET));
+    } else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) {
+        return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
+    } else {
+        isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+                  << " IP address. Invalid buffer length " << buffers_[index].size());
+    }
+}
+
+const OptionBuffer&
+OptionCustom::readBinary(const uint32_t index) const {
+    checkIndex(index);
+    return (buffers_[index]);
+}
+
+bool
+OptionCustom::readBoolean(const uint32_t index) const {
+    checkIndex(index);
+    return (OptionDataTypeUtil::readBool(buffers_[index]));
+}
+
+std::string
+OptionCustom::readString(const uint32_t index) const {
+    checkIndex(index);
+    return (OptionDataTypeUtil::readString(buffers_[index]));
+}
+
+void
+OptionCustom::unpack(OptionBufferConstIter begin,
+                     OptionBufferConstIter end) {
+    data_ = OptionBuffer(begin, end);
+    // Chop the buffer stored in data_ into set of sub buffers.
+    createBuffers();
+}
+
+uint16_t
+OptionCustom::len() {
+    // The length of the option is a sum of option header ...
+    int length = getHeaderLen();
+
+    // ... lengths of all buffers that hold option data ...
+    for (std::vector<OptionBuffer>::const_iterator buf = buffers_.begin();
+         buf != buffers_.end(); ++buf) {
+        length += buf->size();
+    }
+
+    // ... and lengths of all suboptions
+    for (OptionCustom::OptionCollection::iterator it = options_.begin();
+         it != options_.end();
+         ++it) {
+        length += (*it).second->len();
+    }
+
+    return (length);
+}
+
+void OptionCustom::setData(const OptionBufferConstIter first,
+                     const OptionBufferConstIter last) {
+    // We will copy entire option buffer, so we have to resize data_.
+    data_.resize(std::distance(first, last));
+    std::copy(first, last, data_.begin());
+
+    // Chop the data_ buffer into set of buffers that represent
+    // option fields data.
+    createBuffers();
+}
+
+std::string OptionCustom::toText(int indent) {
+    std::stringstream tmp;
+
+    for (int i = 0; i < indent; ++i)
+        tmp << " ";
+
+    tmp << "type=" << type_ << ", len=" << len()-getHeaderLen()
+        << ", data fields:" << std::endl;
+
+    OptionDataType data_type = definition_.getType();
+    if (data_type == OPT_RECORD_TYPE) {
+        const OptionDefinition::RecordFieldsCollection& fields =
+            definition_.getRecordFields();
+
+        // For record types we iterate over fields defined in
+        // option definition and match the appropriate buffer
+        // with them.
+        for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+             field != fields.end(); ++field) {
+            for (int j = 0; j < indent + 2; ++j) {
+                tmp << " ";
+            }
+            tmp << "#" << std::distance(fields.begin(), field) << " "
+                << dataFieldToText(*field, std::distance(fields.begin(),
+                                                         field))
+                << std::endl;
+        }
+    } else {
+        // For non-record types we iterate over all buffers
+        // and print the data type set globally for an option
+        // definition. We take the same code path for arrays
+        // and non-arrays as they only differ in such a way that
+        // non-arrays have just single data field.
+        for (unsigned int i = 0; i < getDataFieldsNum(); ++i) {
+            for (int j = 0; j < indent + 2; ++j) {
+                tmp << " ";
+            }
+            tmp << "#" << i << " "
+                << dataFieldToText(definition_.getType(), i)
+                << std::endl;
+        }
+    }
+
+    // print suboptions
+    for (OptionCollection::const_iterator opt = options_.begin();
+         opt != options_.end();
+         ++opt) {
+        tmp << (*opt).second->toText(indent+2);
+    }
+    return tmp.str();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 235 - 0
src/lib/dhcp/option_custom.h

@@ -0,0 +1,235 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_CUSTOM_H
+#define OPTION_CUSTOM_H
+
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <util/io_utilities.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Option with defined data fields represented as buffers that can
+/// be accessed using data field index.
+///
+/// This class represents an option which has defined structure: data fields
+/// of specific types and order. Those fields can be accessed using indexes,
+/// where index 0 represents first data field within an option. The last
+/// field can be accessed using index equal to 'number of fields' - 1.
+/// Internally, the option data is stored as a collection of OptionBuffer
+/// objects, each representing data for a particular data field. This data
+/// can be converted to the actual data type using methods implemented
+/// within this class. This class is used to represent those options that
+/// can't be represented by any other specialized class (this excludes the
+/// Option class which is generic and can be used to represent any option).
+class OptionCustom : public Option {
+public:
+
+    /// @brief Constructor, used for options to be sent.
+    ///
+    /// This constructor creates an instance of an option from the whole
+    /// supplied buffer. This constructor is mainly used to create an
+    /// instances of options to be stored in outgoing DHCP packets.
+    /// The buffer used to create the instance of an option can be
+    /// created from the option data specified in server's configuration.
+    ///
+    /// @param def option definition.
+    /// @param u specifies universe (V4 or V6).
+    /// @param data content of the option.
+    ///
+    /// @throw OutOfRange if option buffer is truncated.
+    ///
+    /// @todo list all exceptions thrown by ctor.
+    OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data);
+
+    /// @brief Constructor, used for received options.
+    ///
+    /// This constructor creates an instance an option from the portion
+    /// of the buffer specified by iterators. This is mainly useful when
+    /// parsing received packets. Such packets are represented by a single
+    /// buffer holding option data and all sub options. Methods that are
+    /// parsing a packet, supply relevant portions of the packet buffer
+    /// to this constructor to create option instances out of it.
+    ///
+    /// @param def option definition.
+    /// @param u specifies universe (V4 or V6).
+    /// @param first iterator to the first element that should be copied.
+    /// @param last iterator to the next element after the last one
+    /// to be copied.
+    ///
+    /// @throw OutOfRange if option buffer is truncated.
+    ///
+    /// @todo list all exceptions thrown by ctor.
+    OptionCustom(const OptionDefinition& def, Universe u,
+                 OptionBufferConstIter first, OptionBufferConstIter last);
+
+    /// @brief Return a number of the data fields.
+    ///
+    /// @return number of data fields held by the option.
+    uint32_t getDataFieldsNum() const { return (buffers_.size()); }
+
+    /// @brief Read a buffer as IP address.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @return IP address read from a buffer.
+    /// @throw isc::OutOfRange if index is out of range.
+    asiolink::IOAddress readAddress(const uint32_t index) const;
+
+    /// @brief Read a buffer as binary data.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @return read buffer holding binary data.
+    const OptionBuffer& readBinary(const uint32_t index) const;
+
+    /// @brief Read a buffer as boolean value.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @return read boolean value.
+    bool readBoolean(const uint32_t index) const;
+
+    /// @brief Read a buffer as integer value.
+    ///
+    /// @param index buffer index.
+    /// @tparam integer type of a value being returned.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @return read integer value.
+    template<typename T>
+    T readInteger(const uint32_t index) const {
+        checkIndex(index);
+
+        // Check that the requested return type is a supported integer.
+        if (!OptionDataTypeTraits<T>::integer_type) {
+            isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
+                      " by readInteger is not supported integer type");
+        }
+
+        // Get the option definition type.
+        OptionDataType data_type = definition_.getType();
+        if (data_type == OPT_RECORD_TYPE) {
+            const OptionDefinition::RecordFieldsCollection& record_fields =
+                definition_.getRecordFields();
+            // When we initialized buffers we have already checked that
+            // the number of these buffers is equal to number of option
+            // fields in the record so the condition below should be met.
+            assert(index < record_fields.size());
+            // Get the data type to be returned.
+            data_type = record_fields[index];
+        }
+
+        // Requested data type must match the data type in a record.
+        if (OptionDataTypeTraits<T>::type != data_type) {
+            isc_throw(isc::dhcp::InvalidDataType,
+                      "unable to read option field with index " << index
+                      << " as integer value. The field's data type"
+                      << data_type << " does not match the integer type"
+                      << "returned by the readInteger function.");
+        }
+        // When we created the buffer we have checked that it has a
+        // valid size so this condition here should be always fulfiled.
+        assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
+        // Read an integer value.
+        return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
+    }
+
+    /// @brief Read a buffer as string value.
+    ///
+    /// @param index buffer index.
+    ///
+    /// @return string value read from buffer.
+    /// @throw isc::OutOfRange if index is out of range.
+    std::string readString(const uint32_t index) const;
+
+    /// @brief Parses received buffer.
+    ///
+    /// @param begin iterator to first byte of option data
+    /// @param end iterator to end of option data (first byte after option end)
+    virtual void unpack(OptionBufferConstIter begin,
+                        OptionBufferConstIter end);
+
+    /// @brief Returns string representation of the option.
+    ///
+    /// @param indent number of spaces before printed text.
+    ///
+    /// @return string with text representation.
+    virtual std::string toText(int indent = 0);
+
+    /// @brief Returns length of the complete option (data length +
+    ///        DHCPv4/DHCPv6 option header)
+    ///
+    /// @return length of the option
+    virtual uint16_t len();
+
+    /// @brief Sets content of this option from buffer.
+    ///
+    /// Option will be resized to length of buffer.
+    ///
+    /// @param first iterator pointing begining of buffer to copy.
+    /// @param last iterator pointing to end of buffer to copy.
+    void setData(const OptionBufferConstIter first,
+                 const OptionBufferConstIter last);
+
+protected:
+
+    /// @brief Writes DHCPv4 option in a wire format to a buffer.
+    ///
+    /// @param buf output buffer (option will be stored there).
+    virtual void pack4(isc::util::OutputBuffer& buf);
+
+    /// @brief Writes DHCPv6 option in a wire format to a buffer.
+    ///
+    /// @param buf output buffer (built options will be stored here)
+    virtual void pack6(isc::util::OutputBuffer& buf);
+
+private:
+
+    /// @brief Check if data field index is valid.
+    ///
+    /// @param index Data field index to check.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    void checkIndex(const uint32_t index) const;
+
+    /// @brief Create collection of buffers representing data field values.
+    void createBuffers();
+
+    /// @brief Return a text representation of a data field.
+    ///
+    /// @param data_type data type of a field.
+    /// @param index data field buffer index within a custom option.
+    ///
+    /// @return text representation of a data field.
+    std::string dataFieldToText(const OptionDataType data_type,
+                                const uint32_t index) const;
+
+    /// Option definition used to create an option.
+    OptionDefinition definition_;
+
+    /// The collection of buffers holding data for option fields.
+    /// The order of buffers corresponds to the order of option
+    /// fields.
+    std::vector<OptionBuffer> buffers_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_CUSTOM_H

+ 227 - 0
src/lib/dhcp/option_data_types.cc

@@ -0,0 +1,227 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/option_data_types.h>
+#include <util/encode/hex.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionDataTypeUtil::OptionDataTypeUtil() {
+    data_types_["empty"] = OPT_EMPTY_TYPE;
+    data_types_["binary"] = OPT_BINARY_TYPE;
+    data_types_["boolean"] = OPT_BOOLEAN_TYPE;
+    data_types_["int8"] = OPT_INT8_TYPE;
+    data_types_["int16"] = OPT_INT16_TYPE;
+    data_types_["int32"] = OPT_INT32_TYPE;
+    data_types_["uint8"] = OPT_UINT8_TYPE;
+    data_types_["uint16"] = OPT_UINT16_TYPE;
+    data_types_["uint32"] = OPT_UINT32_TYPE;
+    data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
+    data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
+    data_types_["string"] = OPT_STRING_TYPE;
+    data_types_["fqdn"] = OPT_FQDN_TYPE;
+    data_types_["record"] = OPT_RECORD_TYPE;
+
+    data_type_names_[OPT_EMPTY_TYPE] = "empty";
+    data_type_names_[OPT_BINARY_TYPE] = "binary";
+    data_type_names_[OPT_BOOLEAN_TYPE] = "boolean";
+    data_type_names_[OPT_INT8_TYPE] = "int8";
+    data_type_names_[OPT_INT16_TYPE] = "int16";
+    data_type_names_[OPT_INT32_TYPE] = "int32";
+    data_type_names_[OPT_UINT8_TYPE] = "uint8";
+    data_type_names_[OPT_UINT16_TYPE] = "uint16";
+    data_type_names_[OPT_UINT32_TYPE] = "uint32";
+    data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
+    data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address";
+    data_type_names_[OPT_STRING_TYPE] = "string";
+    data_type_names_[OPT_FQDN_TYPE] = "fqdn";
+    data_type_names_[OPT_RECORD_TYPE] = "record";
+    // The "unknown" data type is declared here so as
+    // it can be returned by reference by a getDataTypeName
+    // function it no other type is suitable. Other than that
+    // this is unused.
+    data_type_names_[OPT_UNKNOWN_TYPE] = "unknown";
+}
+
+OptionDataType
+OptionDataTypeUtil::getDataType(const std::string& data_type) {
+    return (OptionDataTypeUtil::instance().getDataTypeImpl(data_type));
+}
+
+OptionDataType
+OptionDataTypeUtil::getDataTypeImpl(const std::string& data_type) const {
+    std::map<std::string, OptionDataType>::const_iterator data_type_it =
+        data_types_.find(data_type);
+    if (data_type_it != data_types_.end()) {
+        return (data_type_it->second);
+    }
+    return (OPT_UNKNOWN_TYPE);
+}
+
+int
+OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
+    switch (data_type) {
+    case OPT_BOOLEAN_TYPE:
+    case OPT_INT8_TYPE:
+    case OPT_UINT8_TYPE:
+        return (1);
+
+    case OPT_INT16_TYPE:
+    case OPT_UINT16_TYPE:
+        return (2);
+
+    case OPT_INT32_TYPE:
+    case OPT_UINT32_TYPE:
+        return (4);
+
+    case OPT_IPV4_ADDRESS_TYPE:
+        return (asiolink::V4ADDRESS_LEN);
+
+    case OPT_IPV6_ADDRESS_TYPE:
+        return (asiolink::V6ADDRESS_LEN);
+
+    default:
+        ;
+    }
+    return (0);
+}
+
+const std::string&
+OptionDataTypeUtil::getDataTypeName(const OptionDataType data_type) {
+    return (OptionDataTypeUtil::instance().getDataTypeNameImpl(data_type));
+}
+
+const std::string&
+OptionDataTypeUtil::getDataTypeNameImpl(const OptionDataType data_type) const {
+    std::map<OptionDataType, std::string>::const_iterator data_type_it =
+        data_type_names_.find(data_type);
+    if (data_type_it != data_type_names_.end()) {
+        return (data_type_it->second);
+    }
+    return (data_type_names_.find(OPT_UNKNOWN_TYPE)->second);
+}
+
+OptionDataTypeUtil&
+OptionDataTypeUtil::instance() {
+    static OptionDataTypeUtil instance;
+    return (instance);
+}
+
+asiolink::IOAddress
+OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf,
+                                const short family) {
+    using namespace isc::asiolink;
+    if (family == AF_INET) {
+        if (buf.size() < V4ADDRESS_LEN) {
+            isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+                      << " IPv4 address. Invalid buffer size: " << buf.size());
+        }
+        return (IOAddress::fromBytes(AF_INET, &buf[0]));
+    } else if (family == AF_INET6) {
+        if (buf.size() < V6ADDRESS_LEN) {
+            isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+                      << " IPv6 address. Invalid buffer size: " << buf.size());
+        }
+        return (IOAddress::fromBytes(AF_INET6, &buf[0]));
+    } else {
+        isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+                  "IP address. Invalid family: " << family);
+    }
+}
+
+void
+OptionDataTypeUtil::writeAddress(const asiolink::IOAddress& address,
+                                 std::vector<uint8_t>& buf) {
+    // @todo There is a ticket 2396 submitted, which adds the
+    // functionality to return a buffer representation of
+    // IOAddress. If so, this function can be simplified.
+    if (address.getAddress().is_v4()) {
+        asio::ip::address_v4::bytes_type addr_bytes =
+            address.getAddress().to_v4().to_bytes();
+        // Increase the buffer size by the size of IPv4 address.
+        buf.resize(buf.size() + addr_bytes.size());
+        std::copy_backward(addr_bytes.begin(), addr_bytes.end(),
+                           buf.end());
+    } else if (address.getAddress().is_v6()) {
+        asio::ip::address_v6::bytes_type addr_bytes =
+            address.getAddress().to_v6().to_bytes();
+        // Incresase the buffer size by the size of IPv6 address.
+        buf.resize(buf.size() + addr_bytes.size());
+        std::copy_backward(addr_bytes.begin(), addr_bytes.end(),
+                           buf.end());
+    } else {
+        isc_throw(BadDataTypeCast, "the address " << address.toText()
+                  << " is neither valid IPv4 not IPv6 address.");
+    }
+}
+
+void
+OptionDataTypeUtil::writeBinary(const std::string& hex_str,
+                                std::vector<uint8_t>& buf) {
+    // Binary value means that the value is encoded as a string
+    // of hexadecimal digits. We need to decode this string
+    // to the binary format here.
+    OptionBuffer binary;
+    try {
+        util::encode::decodeHex(hex_str, binary);
+    } catch (const Exception& ex) {
+        isc_throw(BadDataTypeCast, "unable to cast " << hex_str
+                  << " to binary data type: " << ex.what());
+    }
+    // Decode was successful so append decoded binary value
+    // to the buffer.
+    buf.insert(buf.end(), binary.begin(), binary.end());
+}
+
+bool
+OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
+    if (buf.size() < 1) {
+        isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+                  << " value. Invalid buffer size " << buf.size());
+    }
+    if (buf[0] == 1) {
+        return (true);
+    } else if (buf[0] == 0) {
+        return (false);
+    }
+    isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+              << " value. Invalid value " << static_cast<int>(buf[0]));
+}
+
+void
+OptionDataTypeUtil::writeBool(const bool value,
+                              std::vector<uint8_t>& buf) {
+    buf.push_back(static_cast<uint8_t>(value ? 1 : 0));
+}
+
+std::string
+OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
+    std::string value;
+    if (buf.size() > 0) {
+        value.insert(value.end(), buf.begin(), buf.end());
+    }
+    return (value);
+}
+
+void
+OptionDataTypeUtil::writeString(const std::string& value,
+                                std::vector<uint8_t>& buf) {
+    if (value.size() > 0) {
+        buf.insert(buf.end(), value.begin(), value.end());
+    }
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 310 - 8
src/lib/dhcp/option_data_types.h

@@ -15,7 +15,10 @@
 #ifndef OPTION_DATA_TYPES_H
 #define OPTION_DATA_TYPES_H
 
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
 #include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
 
 #include <stdint.h>
 
@@ -29,57 +32,356 @@ public:
         isc::Exception(file, line, what) { };
 };
 
-/// @brief Trait class for integer data types supported in DHCP option definitions.
+/// @brief Exception to be thrown when cast to the data type was unsuccessful.
+class BadDataTypeCast : public Exception {
+public:
+    BadDataTypeCast(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Data types of DHCP option fields.
+///
+/// @warning The order of data types matters: OPT_UNKNOWN_TYPE
+/// must always be the last position. Also, OPT_RECORD_TYPE
+/// must be at last but one position. This is because some
+/// functions perform sanity checks on data type values using
+/// '>' operators, assuming that all values beyond the
+/// OPT_RECORD_TYPE are invalid.
+enum OptionDataType {
+    OPT_EMPTY_TYPE,
+    OPT_BINARY_TYPE,
+    OPT_BOOLEAN_TYPE,
+    OPT_INT8_TYPE,
+    OPT_INT16_TYPE,
+    OPT_INT32_TYPE,
+    OPT_UINT8_TYPE,
+    OPT_UINT16_TYPE,
+    OPT_UINT32_TYPE,
+    OPT_ANY_ADDRESS_TYPE,
+    OPT_IPV4_ADDRESS_TYPE,
+    OPT_IPV6_ADDRESS_TYPE,
+    OPT_STRING_TYPE,
+    OPT_FQDN_TYPE,
+    OPT_RECORD_TYPE,
+    OPT_UNKNOWN_TYPE
+};
+
+/// @brief Trait class for data types supported in DHCP option definitions.
 ///
 /// This is useful to check whether the type specified as template parameter
 /// is supported by classes like Option6Int, Option6IntArray and some template
 /// factory functions in OptionDefinition class.
 template<typename T>
-struct OptionDataTypes {
+struct OptionDataTypeTraits {
     static const bool valid = false;
     static const int len = 0;
+    static const bool integer_type = false;
+    static const OptionDataType type = OPT_UNKNOWN_TYPE;
+};
+
+/// binary type is supported
+template<>
+struct OptionDataTypeTraits<OptionBuffer> {
+    static const bool valid = true;
+    static const int len = 0;
+    static const bool integer_type = false;
+    static const OptionDataType type = OPT_BINARY_TYPE;
+};
+
+/// bool type is supported
+template<>
+struct OptionDataTypeTraits<bool> {
+    static const bool valid = true;
+    static const int len = sizeof(bool);
+    static const bool integer_type = false;
+    static const OptionDataType type = OPT_BOOLEAN_TYPE;
 };
 
 /// int8_t type is supported.
 template<>
-struct OptionDataTypes<int8_t> {
+struct OptionDataTypeTraits<int8_t> {
     static const bool valid = true;
     static const int len = 1;
+    static const bool integer_type = true;
+    static const OptionDataType type = OPT_INT8_TYPE;
 };
 
 /// int16_t type is supported.
 template<>
-struct OptionDataTypes<int16_t> {
+struct OptionDataTypeTraits<int16_t> {
     static const bool valid = true;
     static const int len = 2;
+    static const bool integer_type = true;
+    static const OptionDataType type = OPT_INT16_TYPE;
 };
 
 /// int32_t type is supported.
 template<>
-struct OptionDataTypes<int32_t> {
+struct OptionDataTypeTraits<int32_t> {
     static const bool valid = true;
     static const int len = 4;
+    static const bool integer_type = true;
+    static const OptionDataType type = OPT_INT32_TYPE;
 };
 
 /// uint8_t type is supported.
 template<>
-struct OptionDataTypes<uint8_t> {
+struct OptionDataTypeTraits<uint8_t> {
     static const bool valid = true;
     static const int len = 1;
+    static const bool integer_type = true;
+    static const OptionDataType type = OPT_UINT8_TYPE;
 };
 
 /// uint16_t type is supported.
 template<>
-struct OptionDataTypes<uint16_t> {
+struct OptionDataTypeTraits<uint16_t> {
     static const bool valid = true;
     static const int len = 2;
+    static const bool integer_type = true;
+    static const OptionDataType type = OPT_UINT16_TYPE;
 };
 
 /// uint32_t type is supported.
 template<>
-struct OptionDataTypes<uint32_t> {
+struct OptionDataTypeTraits<uint32_t> {
     static const bool valid = true;
     static const int len = 4;
+    static const bool integer_type = true;
+    static const OptionDataType type = OPT_UINT32_TYPE;
+};
+
+/// IPv4 and IPv6 address type is supported
+template<>
+struct OptionDataTypeTraits<asiolink::IOAddress> {
+    static const bool valid = true;
+    // The len value is used to determine the size of the data
+    // to be written to an option buffer. IOAddress object may
+    // either represent an IPv4 or IPv6 addresses which have
+    // different lengths. Thus we can't put fixed value here.
+    // The length of a data to be written into an option buffer
+    // have to be determined in the runtime for a particular
+    // IOAddress object. Thus setting len to zero.
+    static const int len = 0;
+    static const bool integer_type = false;
+    static const OptionDataType type = OPT_ANY_ADDRESS_TYPE;
+};
+
+/// string type is supported
+template<>
+struct OptionDataTypeTraits<std::string> {
+    static const bool valid = true;
+    // The len value is used to determine the size of the data
+    // to be written to an option buffer. For strings this
+    // size is unknown until we actually deal with the particular
+    // string to be written. Thus setting it to zero.
+    static const int len = 0;
+    static const bool integer_type = false;
+    static const OptionDataType type = OPT_STRING_TYPE;
+};
+
+/// @brief Utility class for option data types.
+///
+/// This class provides a set of utility functions to operate on
+/// supported DHCP option data types. It includes conversion
+/// between enumerator values representing data types and data
+/// type names. It also includes a set of functions that write
+/// data into option buffers and read data from option buffers.
+/// The data being written and read are converted from/to actual
+/// data types.
+/// @note This is a singleton class but it can be accessed via
+/// static methods only.
+class OptionDataTypeUtil {
+public:
+
+    /// @brief Return option data type from its name.
+    ///
+    /// @param data_type data type name.
+    /// @return option data type.
+    static OptionDataType getDataType(const std::string& data_type);
+
+    /// @brief Return option data type name from the data type enumerator.
+    ///
+    /// @param data_type option data type.
+    /// @return option data type name.
+    static const std::string& getDataTypeName(const OptionDataType data_type);
+
+    /// @brief Get data type buffer length.
+    ///
+    /// This function returns the size of a particular data type.
+    /// Values retured by this function correspond to the data type
+    /// sizes defined in OptionDataTypeTraits (IPV4_ADDRESS_TYPE and
+    /// IPV6_ADDRESS_TYPE are exceptions here) so they rather indicate
+    /// the fixed length of the data being written into the buffer,
+    /// not the size of the particular data type. Thus for data types
+    /// such as string, binary etc. for which the buffer length can't
+    /// be determined this function returns 0.
+    /// In addition, this function returns the data sizes for
+    /// IPV4_ADDRESS_TYPE and IPV6_ADDRESS_TYPE as their buffer
+    /// representations have fixed data lengths: 4 and 16 respectively.
+    ///
+    /// @param data_type data type which size is to be returned.
+    /// @return data type size or zero for variable length types.
+    static int getDataTypeLen(const OptionDataType data_type);
+
+    /// @brief Read IPv4 or IPv6 addres from a buffer.
+    ///
+    /// @param buf input buffer.
+    /// @param family address family: AF_INET or AF_INET6.
+    /// 
+    /// @return address being read.
+    static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
+                                           const short family);
+
+    /// @brief Append IPv4 or IPv6 address to a buffer.
+    ///
+    /// @param address IPv4 or IPv6 address.
+    /// @param [out] buf output buffer.
+    static void writeAddress(const asiolink::IOAddress& address,
+                             std::vector<uint8_t>& buf);
+
+    /// @brief Append hex-encoded binary values to a buffer.
+    ///
+    /// @param hex_str string representing a binary value encoded
+    /// with hexadecimal digits (without 0x prefix).
+    /// @param [out] buf output buffer.
+    static void writeBinary(const std::string& hex_str,
+                            std::vector<uint8_t>& buf);
+
+    /// @brief Read boolean value from a buffer.
+    ///
+    /// @param buf input buffer.
+    /// @return boolean value read from a buffer.
+    static bool readBool(const std::vector<uint8_t>& buf);
+
+    /// @brief Append boolean value into a buffer.
+    ///
+    /// The bool value is encoded in a buffer in such a way that
+    /// "1" means "true" and "0" means "false".
+    ///
+    /// @param value boolean value to be written.
+    /// @param [out] buf output buffer.
+    static void writeBool(const bool value, std::vector<uint8_t>& buf);
+
+    /// @brief Read integer value from a buffer.
+    ///
+    /// @param buf input buffer.
+    /// @tparam integer type of the returned value.
+    /// @return integer value being read.
+    template<typename T>
+    static T readInt(const std::vector<uint8_t>& buf) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
+            isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
+                      " by readInteger is unsupported integer type");
+        }
+
+        assert(buf.size() == OptionDataTypeTraits<T>::len);
+        T value;
+        switch (OptionDataTypeTraits<T>::len) {
+        case 1:
+            value = *(buf.begin());
+            break;
+        case 2:
+            // Calling readUint16 works either for unsigned
+            // or signed types.
+            value = isc::util::readUint16(&(*buf.begin()));
+            break;
+        case 4:
+            // Calling readUint32 works either for unsigned
+            // or signed types.
+            value = isc::util::readUint32(&(*buf.begin()));
+            break;
+        default:
+            // This should not happen because we made checks on data types
+            // but it does not hurt to keep throw statement here.
+            isc_throw(isc::dhcp::InvalidDataType,
+                      "invalid size of the data type to be read as integer.");
+        }
+        return (value);
+    }
+
+    /// @brief Append integer or unsigned integer value to a buffer.
+    ///
+    /// @param value an integer value to be written into a buffer.
+    /// @param [out] buf output buffer.
+    /// @tparam data type of the value.
+    template<typename T>
+    static void writeInt(const T value,
+                         std::vector<uint8_t>& buf) {
+        if (!OptionDataTypeTraits<T>::integer_type) {
+            isc_throw(InvalidDataType, "provided data type is not the supported.");
+        }
+        switch (OptionDataTypeTraits<T>::len) {
+        case 1:
+            buf.push_back(static_cast<uint8_t>(value));
+            break;
+        case 2:
+            buf.resize(buf.size() + 2);
+            isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2]);
+            break;
+        case 4:
+            buf.resize(buf.size() + 4);
+            isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4]);
+            break;
+        default:
+            // The cases above cover whole range of possible data lengths because
+            // we check at the beginning of this function that given data type is
+            // a supported integer type which can be only 1,2 or 4 bytes long.
+            ;
+        }
+    }
+
+    /// @brief Read string value from a buffer.
+    ///
+    /// @param buf input buffer.
+    ///
+    /// @return string value being read.
+    static std::string readString(const std::vector<uint8_t>& buf);
+
+    /// @brief Write UTF8-encoded string into a buffer.
+    ///
+    /// @param value string value to be written into a buffer.
+    /// @param [out] buf output buffer.
+    static void writeString(const std::string& value,
+                            std::vector<uint8_t>& buf);
+private:
+
+    /// The container holding mapping of data type names to
+    /// data types enumerator.
+    std::map<std::string, OptionDataType> data_types_;
+
+    /// The container holding mapping of data types to data
+    /// type names.
+    std::map<OptionDataType, std::string> data_type_names_;
+
+    /// @brief Private constructor.
+    ///
+    /// This constructor is private because this class should
+    /// be used as singleton (through static public functions).
+    OptionDataTypeUtil();
+
+    /// @brief Return instance of OptionDataTypeUtil
+    ///
+    /// This function is used by some of the public static functions
+    /// to create an instance of OptionDataTypeUtil class.
+    /// When instance is called it calls the class'es constructor
+    /// and initializes some of the private data members.
+    ///
+    /// @return instance of OptionDataTypeUtil singleton.
+    static OptionDataTypeUtil& instance();
+
+    /// @brief Return option data type from its name.
+    ///
+    /// @param data_type data type name.
+    /// @return option data type.
+    OptionDataType getDataTypeImpl(const std::string& data_type) const;
+
+    /// @brief Return option data type name from the data type enumerator.
+    ///
+    /// @param data_type option data type.
+    /// @return option data type name.
+    const std::string& getDataTypeNameImpl(const OptionDataType data_type) const;
 };
 
 

+ 307 - 120
src/lib/dhcp/option_definition.cc

@@ -20,6 +20,7 @@
 #include <dhcp/option6_int.h>
 #include <dhcp/option6_int_array.h>
 #include <dhcp/option_definition.h>
+#include <util/encode/hex.h>
 
 using namespace std;
 using namespace isc::util;
@@ -27,32 +28,6 @@ using namespace isc::util;
 namespace isc {
 namespace dhcp {
 
-OptionDefinition::DataTypeUtil::DataTypeUtil() {
-    data_types_["empty"] = EMPTY_TYPE;
-    data_types_["binary"] = BINARY_TYPE;
-    data_types_["boolean"] = BOOLEAN_TYPE;
-    data_types_["int8"] = INT8_TYPE;
-    data_types_["int16"] = INT16_TYPE;
-    data_types_["int32"] = INT32_TYPE;
-    data_types_["uint8"] = UINT8_TYPE;
-    data_types_["uint16"] = UINT16_TYPE;
-    data_types_["uint32"] = UINT32_TYPE;
-    data_types_["ipv4-address"] = IPV4_ADDRESS_TYPE;
-    data_types_["ipv6-address"] = IPV6_ADDRESS_TYPE;
-    data_types_["string"] = STRING_TYPE;
-    data_types_["fqdn"] = FQDN_TYPE;
-    data_types_["record"] = RECORD_TYPE;
-}
-
-OptionDefinition::DataType
-OptionDefinition::DataTypeUtil::getDataType(const std::string& data_type) {
-    std::map<std::string, DataType>::const_iterator data_type_it =
-        data_types_.find(data_type);
-    if (data_type_it != data_types_.end()) {
-        return (data_type_it->second);
-    }
-    return UNKNOWN_TYPE;
-}
 
 OptionDefinition::OptionDefinition(const std::string& name,
                                  const uint16_t code,
@@ -60,17 +35,17 @@ OptionDefinition::OptionDefinition(const std::string& name,
                                  const bool array_type /* = false */)
     : name_(name),
       code_(code),
-      type_(UNKNOWN_TYPE),
+      type_(OPT_UNKNOWN_TYPE),
       array_type_(array_type) {
     // Data type is held as enum value by this class.
     // Use the provided option type string to get the
     // corresponding enum value.
-    type_ = DataTypeUtil::instance().getDataType(type);
+    type_ = OptionDataTypeUtil::getDataType(type);
 }
 
 OptionDefinition::OptionDefinition(const std::string& name,
                                    const uint16_t code,
-                                   const DataType type,
+                                   const OptionDataType type,
                                    const bool array_type /* = false */)
     : name_(name),
       code_(code),
@@ -80,67 +55,115 @@ OptionDefinition::OptionDefinition(const std::string& name,
 
 void
 OptionDefinition::addRecordField(const std::string& data_type_name) {
-    DataType data_type = DataTypeUtil::instance().getDataType(data_type_name);
+    OptionDataType data_type = OptionDataTypeUtil::getDataType(data_type_name);
     addRecordField(data_type);
 }
 
 void
-OptionDefinition::addRecordField(const DataType data_type) {
-    if (type_ != RECORD_TYPE) {
+OptionDefinition::addRecordField(const OptionDataType data_type) {
+    if (type_ != OPT_RECORD_TYPE) {
         isc_throw(isc::InvalidOperation, "'record' option type must be used"
                   " to add data fields to the record");
     }
-    if (data_type >= UNKNOWN_TYPE) {
-        isc_throw(isc::BadValue, "attempted to add invalid data type to the record");
+    if (data_type >= OPT_RECORD_TYPE ||
+        data_type == OPT_ANY_ADDRESS_TYPE ||
+        data_type == OPT_EMPTY_TYPE) {
+        isc_throw(isc::BadValue, "attempted to add invalid data type to the record.");
     }
     record_fields_.push_back(data_type);
 }
 
-Option::Factory*
-OptionDefinition::getFactory() const {
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+                                OptionBufferConstIter begin,
+                                OptionBufferConstIter end) const {
     validate();
+    
+    try {
+        if (type_ == OPT_BINARY_TYPE) {
+            return (factoryGeneric(u, type, begin, end));
+
+        } else if (type_ == OPT_IPV6_ADDRESS_TYPE && array_type_) {
+            return (factoryAddrList6(type, begin, end));
+
+        } else if (type_ == OPT_IPV4_ADDRESS_TYPE && array_type_) {
+            return (factoryAddrList4(type, begin, end));
+
+        } else if (type_ == OPT_EMPTY_TYPE) {
+            return (factoryEmpty(u, type));
+
+        } else if (u == Option::V6 &&
+                   code_ == D6O_IA_NA &&
+                   haveIA6Format()) {
+            return (factoryIA6(type, begin, end));
+
+        } else if (u == Option::V6 &&
+                   code_ == D6O_IAADDR &&
+                   haveIAAddr6Format()) {
+            return (factoryIAAddr6(type, begin, end));
+
+        } else if (type_ == OPT_UINT8_TYPE) {
+            if (array_type_) {
+                return (factoryGeneric(u, type, begin, end));
+            } else {
+                return (factoryInteger<uint8_t>(u, type, begin, end));
+            }
+
+        } else if (type_ == OPT_UINT16_TYPE) {
+            if (array_type_) {
+                return (factoryIntegerArray<uint16_t>(type, begin, end));
+            } else {
+                return (factoryInteger<uint16_t>(u, type, begin, end));
+            }
+
+        } else if (type_ == OPT_UINT32_TYPE) {
+            if (array_type_) {
+                return (factoryIntegerArray<uint32_t>(type, begin, end));
+            } else {
+                return (factoryInteger<uint32_t>(u, type, begin, end));
+            }
 
-    // @todo This function must be extended to return more factory
-    // functions that create instances of more specialized options.
-    // This requires us to first implement those more specialized
-    // options that will be derived from Option class.
-    if (type_ == BINARY_TYPE) {
-        return (factoryGeneric);
-    } else if (type_ == IPV6_ADDRESS_TYPE && array_type_) {
-        return (factoryAddrList6);
-    } else if (type_ == IPV4_ADDRESS_TYPE && array_type_) {
-        return (factoryAddrList4);
-    } else if (type_ == EMPTY_TYPE) {
-        return (factoryEmpty);
-    } else if (code_ == D6O_IA_NA && haveIA6Format()) {
-        return (factoryIA6);
-    } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
-        return (factoryIAAddr6);
-    } else if (type_ == UINT8_TYPE) {
-        if (array_type_) {
-            return (factoryGeneric);
-        } else {
-            return (factoryInteger<uint8_t>);
         }
-    } else if (type_ == UINT16_TYPE) {
-        if (array_type_) {
-            return (factoryIntegerArray<uint16_t>);
-        } else {
-            return (factoryInteger<uint16_t>);
+        return (factoryGeneric(u, type, begin, end));
+
+    } catch (const Exception& ex) {
+        isc_throw(InvalidOptionValue, ex.what());
+    }
+}
+
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+                                const OptionBuffer& buf) const {
+    return (optionFactory(u, type, buf.begin(), buf.end()));
+}
+
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+                                const std::vector<std::string>& values) const {
+    validate();
+
+    OptionBuffer buf;
+    if (!array_type_ && type_ != OPT_RECORD_TYPE) {
+        if (values.size() == 0) {
+            isc_throw(InvalidOptionValue, "no option value specified");
         }
-    } else if (type_ == UINT32_TYPE) {
-        if (array_type_) {
-            return (factoryIntegerArray<uint32_t>);
-        } else {
-            return (factoryInteger<uint32_t>);
+        writeToBuffer(values[0], type_, buf);
+    } else if (array_type_ && type_ != OPT_RECORD_TYPE) {
+        for (size_t i = 0; i < values.size(); ++i) {
+            writeToBuffer(values[i], type_, buf);
+        }
+    } else if (type_ == OPT_RECORD_TYPE) {
+        const RecordFieldsCollection& records = getRecordFields();
+        if (records.size() > values.size()) {
+            isc_throw(InvalidOptionValue, "number of data fields for the option"
+                      << " type " << type_ << " is greater than number of values"
+                      << " provided.");
+        }
+        for (size_t i = 0; i < records.size(); ++i) {
+            writeToBuffer(values[i], records[i], buf);
         }
     }
-    // Factory generic returns instance of Option class. However, once we
-    // implement CustomOption class we may want to return factory function
-    // that will create instance of CustomOption rather than Option.
-    // CustomOption will allow to access particular data fields within the
-    // option rather than raw data buffer.
-    return (factoryGeneric);
+    return (optionFactory(u, type, buf.begin(), buf.end()));
 }
 
 void
@@ -153,28 +176,78 @@ OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
 
 void
 OptionDefinition::validate() const {
-    // Option name must not be empty.
+    std::ostringstream err_str;
     if (name_.empty()) {
-        isc_throw(isc::BadValue, "option name must not be empty");
-    }
-    // Option name must not contain spaces.
-    if (name_.find(" ") != string::npos) {
-        isc_throw(isc::BadValue, "option name must not contain spaces");
+        // Option name must not be empty.
+        err_str << "option name must not be empty.";
+    } else if (name_.find(" ") != string::npos) {
+        // Option name must not contain spaces.
+        err_str << "option name must not contain spaces.";
+    } else if (type_ >= OPT_UNKNOWN_TYPE) {
+        // Option definition must be of a known type.
+        err_str << "option type value " << type_ << " is out of range.";
+    } else if (array_type_) {
+        if (type_ == OPT_STRING_TYPE) {
+            // Array of strings is not allowed because there is no way
+            // to determine the size of a particular string and thus there
+            // it no way to tell when other data fields begin.
+            err_str << "array of strings is not a valid option definition.";
+        } else if (type_ == OPT_BINARY_TYPE) {
+            err_str << "array of binary values is not a valid option definition.";
+        } else if (type_ == OPT_EMPTY_TYPE) {
+            err_str << "array of empty value is not a valid option definition.";
+        }
+    } else if (type_ == OPT_RECORD_TYPE) {
+        // At least two data fields should be added to the record. Otherwise
+        // non-record option definition could be used.
+        if (getRecordFields().size() < 2) {
+            err_str << "invalid number of data fields: " << getRecordFields().size()
+                    << " specified for the option of type 'record'. Expected at"
+                    << " least 2 fields.";
+        } else {
+            // If the number of fields is valid we have to check if their order
+            // is valid too. We check that string or binary data fields are not
+            // laid before other fields. But we allow that they are laid at the end of
+            // an option.
+            const RecordFieldsCollection& fields = getRecordFields();
+            for (RecordFieldsConstIter it = fields.begin();
+                 it != fields.end(); ++it) {
+                if (*it == OPT_STRING_TYPE &&
+                    it < fields.end() - 1) {
+                    err_str << "string data field can't be laid before data fields"
+                            << " of other types.";
+                    break;
+                }
+                if (*it == OPT_BINARY_TYPE &&
+                    it < fields.end() - 1) {
+                    err_str << "binary data field can't be laid before data fields"
+                            << " of other types.";
+                }
+                /// Empty type is not allowed within a record.
+                if (*it == OPT_EMPTY_TYPE) {
+                    err_str << "empty data type can't be stored as a field in an"
+                            << " option record.";
+                    break;
+                }
+            }
+        }
+
     }
-    // Unsupported option types are not allowed.
-    if (type_ >= UNKNOWN_TYPE) {
-        isc_throw(isc::OutOfRange, "option type value " << type_
-                  << " is out of range");
+
+    // Non-empty error string means that we have hit the error. We throw
+    // exception and include error string.
+    if (!err_str.str().empty()) {
+        isc_throw(MalformedOptionDefinition, err_str.str());
     }
 }
 
 bool
-OptionDefinition::haveIAx6Format(OptionDefinition::DataType first_type) const {
-   return (haveType(RECORD_TYPE) &&
+OptionDefinition::haveIAx6Format(OptionDataType first_type) const {
+   return (haveType(OPT_RECORD_TYPE) &&
            record_fields_.size() == 3 &&
            record_fields_[0] == first_type &&
-           record_fields_[1] == UINT32_TYPE &&
-           record_fields_[2] == UINT32_TYPE);
+           record_fields_[1] == OPT_UINT32_TYPE &&
+           record_fields_[2] == OPT_UINT32_TYPE);
 }
 
 bool
@@ -185,70 +258,184 @@ OptionDefinition::haveIA6Format() const {
     // arrays do not impose limitations on number of elements in
     // the array while this limitation is needed for IA_NA - need
     // exactly 3 elements.
-    return (haveIAx6Format(UINT32_TYPE));
+    return (haveIAx6Format(OPT_UINT32_TYPE));
 }
 
 bool
 OptionDefinition::haveIAAddr6Format() const {
-    return (haveIAx6Format(IPV6_ADDRESS_TYPE));
+    return (haveIAx6Format(OPT_IPV6_ADDRESS_TYPE));
+}
+
+template<typename T>
+T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const {
+    // Lexical cast in case of our data types make sense only
+    // for uintX_t, intX_t and bool type.
+    if (!OptionDataTypeTraits<T>::integer_type &&
+        OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) {
+        isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and"
+                  << " non-boolean data type");
+    }
+    // We use the 64-bit value here because it has wider range than
+    // any other type we use here and it allows to detect out of
+    // bounds conditions e.g. negative value specified for uintX_t
+    // data type. Obviously if the value exceeds the limits of int64
+    // this function will not handle that properly.
+    int64_t result = 0;
+    try {
+        result = boost::lexical_cast<int64_t>(value_str);
+    } catch (const boost::bad_lexical_cast& ex) {
+        // Prepare error message here.
+        std::string data_type_str = "boolean";
+        if (OptionDataTypeTraits<T>::integer_type) {
+            data_type_str = "integer";
+        }
+        isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str
+                  << " data type for value " << value_str << ": " << ex.what());
+    }
+    // Perform range checks for integer values only (exclude bool values).
+    if (OptionDataTypeTraits<T>::integer_type) {
+        if (result > numeric_limits<T>::max() ||
+            result < numeric_limits<T>::min()) {
+            isc_throw(BadDataTypeCast, "unable to do lexical cast for value "
+                      << value_str << ". This value is expected to be in the range of "
+                      << numeric_limits<T>::min() << ".." << numeric_limits<T>::max());
+        }
+    }
+    return (static_cast<T>(result));
+}
+
+void
+OptionDefinition::writeToBuffer(const std::string& value,
+                                const OptionDataType type,
+                                OptionBuffer& buf) const {
+    // We are going to write value given by value argument to the buffer.
+    // The actual type of the value is given by second argument. Check
+    // this argument to determine how to write this value to the buffer.
+    switch (type) {
+    case OPT_BINARY_TYPE:
+        OptionDataTypeUtil::writeBinary(value, buf);
+        return;
+    case OPT_BOOLEAN_TYPE:
+        // We encode the true value as 1 and false as 0 on 8 bits.
+        // That way we actually waste 7 bits but it seems to be the
+        // simpler way to encode boolean.
+        // @todo Consider if any other encode methods can be used.
+        OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), buf);
+        return;
+    case OPT_INT8_TYPE:
+        OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value),
+                                              buf);
+        return;
+    case OPT_INT16_TYPE:
+        OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<int16_t>(value),
+                                               buf);
+        return;
+    case OPT_INT32_TYPE:
+        OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<int32_t>(value),
+                                               buf);
+        return;
+    case OPT_UINT8_TYPE:
+        OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<uint8_t>(value),
+                                              buf);
+        return;
+    case OPT_UINT16_TYPE:
+        OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<uint16_t>(value),
+                                               buf);
+        return;
+    case OPT_UINT32_TYPE:
+        OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<uint32_t>(value),
+                                               buf);
+        return;
+    case OPT_IPV4_ADDRESS_TYPE:
+    case OPT_IPV6_ADDRESS_TYPE:
+        {
+            asiolink::IOAddress address(value);
+            if (address.getFamily() != AF_INET &&
+                address.getFamily() != AF_INET6) {
+                isc_throw(BadDataTypeCast, "provided address " << address.toText()
+                          << " is not a valid "
+                          << (address.getAddress().is_v4() ? "IPv4" : "IPv6")
+                          << " address");
+            }
+            OptionDataTypeUtil::writeAddress(address, buf);
+            return;
+        }
+    case OPT_STRING_TYPE:
+        OptionDataTypeUtil::writeString(value, buf);
+        return;
+    case OPT_FQDN_TYPE:
+        {
+            // FQDN implementation is not terribly complicated but will require
+            // creation of some additional logic (maybe object) that will parse
+            // the fqdn into labels.
+            isc_throw(isc::NotImplemented, "write of FQDN record into option buffer"
+                      " is not supported yet");
+            return;
+        }
+    default:
+        // We hit this point because invalid option data type has been specified
+        // This may be the case because 'empty' or 'record' data type has been
+        // specified. We don't throw exception here because it will be thrown
+        // at the exit point from this function.
+        ;
+    }
+    isc_throw(isc::BadValue, "attempt to write invalid option data field type"
+              " into the option buffer: " << type);
+
 }
 
 OptionPtr
-OptionDefinition::factoryAddrList4(Option::Universe u, uint16_t type,
-                                   const OptionBuffer& buf) {
-    sanityCheckUniverse(u, Option::V4);
-    boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, buf.begin(),
-                                                                buf.begin() + buf.size()));
+OptionDefinition::factoryAddrList4(uint16_t type,
+                                  OptionBufferConstIter begin,
+                                  OptionBufferConstIter end) {
+    boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin, end));
     return (option);
 }
 
 OptionPtr
-OptionDefinition::factoryAddrList6(Option::Universe u, uint16_t type,
-                                   const OptionBuffer& buf) {
-    sanityCheckUniverse(u, Option::V6);
-    boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, buf.begin(),
-                                                                buf.begin() + buf.size()));
+OptionDefinition::factoryAddrList6(uint16_t type,
+                                   OptionBufferConstIter begin,
+                                   OptionBufferConstIter end) {
+    boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin, end));
     return (option);
 }
 
 
 OptionPtr
-OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
-    if (buf.size() > 0) {
-        isc_throw(isc::BadValue, "input option buffer must be empty"
-                  " when creating empty option instance");
-    }
+OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type) {
     OptionPtr option(new Option(u, type));
     return (option);
 }
 
 OptionPtr
-OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
-    OptionPtr option(new Option(u, type, buf));
+OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type,
+                                 OptionBufferConstIter begin,
+                                 OptionBufferConstIter end) {
+    OptionPtr option(new Option(u, type, begin, end));
     return (option);
 }
 
 OptionPtr
-OptionDefinition::factoryIA6(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
-    sanityCheckUniverse(u, Option::V6);
-    if (buf.size() != Option6IA::OPTION6_IA_LEN) {
-        isc_throw(isc::OutOfRange, "input option buffer has invalid size, expeted "
-                  << Option6IA::OPTION6_IA_LEN << " bytes");
+OptionDefinition::factoryIA6(uint16_t type,
+                             OptionBufferConstIter begin,
+                             OptionBufferConstIter end) {
+    if (std::distance(begin, end) < Option6IA::OPTION6_IA_LEN) {
+        isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
+                  "at least " << Option6IA::OPTION6_IA_LEN << " bytes");
     }
-    boost::shared_ptr<Option6IA> option(new Option6IA(type, buf.begin(),
-                                                      buf.begin() + buf.size()));
+    boost::shared_ptr<Option6IA> option(new Option6IA(type, begin, end));
     return (option);
 }
 
 OptionPtr
-OptionDefinition::factoryIAAddr6(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
-    sanityCheckUniverse(u, Option::V6);
-    if (buf.size() != Option6IAAddr::OPTION6_IAADDR_LEN) {
-        isc_throw(isc::OutOfRange, "input option buffer has invalid size, expeted "
-                  << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
+OptionDefinition::factoryIAAddr6(uint16_t type,
+                                 OptionBufferConstIter begin,
+                                 OptionBufferConstIter end) {
+    if (std::distance(begin, end) < Option6IAAddr::OPTION6_IAADDR_LEN) {
+        isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
+                  " at least " << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
     }
-    boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, buf.begin(),
-                                                      buf.begin() + buf.size()));
+    boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin, end));
     return (option);
 }
 

+ 160 - 120
src/lib/dhcp/option_definition.h

@@ -27,6 +27,21 @@
 namespace isc {
 namespace dhcp {
 
+/// @brief Exception to be thrown when invalid option value has been
+/// specified for a particular option definition.
+class InvalidOptionValue : public Exception {
+public:
+    InvalidOptionValue(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception to be thrown when option definition is invalid.
+class MalformedOptionDefinition : public Exception {
+public:
+    MalformedOptionDefinition(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
 /// @brief Forward declaration to OptionDefinition.
 class OptionDefinition;
 
@@ -82,7 +97,7 @@ class Option6IntArray;
 ///
 /// Should the option comprise data fields of different types, the "record"
 /// option type is used. In such cases the data field types within the record
-/// are specified using \ref OptioDefinition::addRecordField.
+/// are specified using \ref OptionDefinition::addRecordField.
 ///
 /// When the OptionDefinition object has been sucessfully created, it can be
 /// queried to return the appropriate option factory function for the specified
@@ -111,73 +126,11 @@ class Option6IntArray;
 class OptionDefinition {
 public:
 
-    /// Data types of DHCP option fields.
-    enum DataType {
-        EMPTY_TYPE,
-        BINARY_TYPE,
-        BOOLEAN_TYPE,
-        INT8_TYPE,
-        INT16_TYPE,
-        INT32_TYPE,
-        UINT8_TYPE,
-        UINT16_TYPE,
-        UINT32_TYPE,
-        IPV4_ADDRESS_TYPE,
-        IPV6_ADDRESS_TYPE,
-        STRING_TYPE,
-        FQDN_TYPE,
-        RECORD_TYPE,
-        UNKNOWN_TYPE
-    };
-
     /// List of fields within the record.
-    typedef std::vector<DataType> RecordFieldsCollection;
+    typedef std::vector<OptionDataType> RecordFieldsCollection;
     /// Const iterator for record data fields.
-    typedef std::vector<DataType>::const_iterator RecordFieldsConstIter;
-
-private:
+    typedef std::vector<OptionDataType>::const_iterator RecordFieldsConstIter;
 
-    /// @brief Utility class for operations on DataTypes.
-    ///
-    /// This class is implemented as the singleton because the list of
-    /// supported data types need only be loaded only once into memory as it
-    /// can persist for all option definitions.
-    ///
-    /// @todo This class can be extended to return the string value
-    /// representing the data type from the enum value.
-    class DataTypeUtil {
-    public:
-
-        /// @brief Return the sole instance of this class.
-        ///
-        /// @return instance of this class.
-        static DataTypeUtil& instance() {
-            static DataTypeUtil instance;
-            return (instance);
-        }
-
-        /// @brief Convert type given as string value to option data type.
-        ///
-        /// @param data_type_name data type string.
-        ///
-        /// @return option data type.
-        DataType getDataType(const std::string& data_type_name);
-
-    private:
-        /// @brief Private constructor.
-        ///
-        /// Constructor initializes the internal data structures, e.g.
-        /// mapping between data type name and the corresponding enum.
-        /// This constructor is private to ensure that exactly one
-        /// instance of this class can be created using \ref instance
-        /// function.
-        DataTypeUtil();
-
-        /// Map of data types, maps name of the type to enum value.
-        std::map<std::string, DataType> data_types_;
-    };
-
-public:
     /// @brief Constructor.
     ///
     /// @param name option name.
@@ -199,7 +152,7 @@ public:
     /// option fields are the array.
     OptionDefinition(const std::string& name,
                      const uint16_t code,
-                     const DataType type,
+                     const OptionDataType type,
                      const bool array_type = false);
 
     /// @brief Adds data field to the record.
@@ -216,7 +169,7 @@ public:
     ///
     /// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE.
     /// @throw isc::BadValue if specified invalid data type.
-    void addRecordField(const DataType data_type);
+    void addRecordField(const OptionDataType data_type);
 
     /// @brief Return array type indicator.
     ///
@@ -231,13 +184,6 @@ public:
     /// @return option code.
     uint16_t getCode() const { return (code_); }
 
-    /// @brief Return factory function for the given definition.
-    ///
-    /// @throw isc::OutOfRange if \ref validate returns it.
-    /// @throw isc::BadValue if \ref validate returns it.
-    /// @return pointer to a factory function.
-    Option::Factory* getFactory() const;
-
     /// @brief Return option name.
     ///
     /// @return option name.
@@ -251,13 +197,11 @@ public:
     /// @brief Return option data type.
     ///
     /// @return option data type.
-    DataType getType() const { return (type_); };
+    OptionDataType getType() const { return (type_); };
 
     /// @brief Check if the option definition is valid.
     ///
-    /// @throw isc::OutOfRange if invalid option type was specified.
-    /// @throw isc::BadValue if invalid option name was specified,
-    /// e.g. empty or containing spaces.
+    /// @throw MalformedOptionDefinition option definition is invalid.
     void validate() const;
 
     /// @brief Check if specified format is IA_NA option format.
@@ -270,101 +214,163 @@ public:
     /// @return true if specified format is IAADDR option format.
     bool haveIAAddr6Format() const;
 
+    /// @brief Option factory.
+    ///
+    /// This function creates an instance of DHCP option using
+    /// provided chunk of buffer. This function may be used to
+    /// create option which is to be sent in the outgoing packet.
+    ///
+    /// @param u option universe (V4 or V6).
+    /// @param type option type.
+    /// @param begin beginning of the option buffer.
+    /// @param end end of the option buffer.
+    ///
+    /// @return instance of the DHCP option.
+    /// @throw MalformedOptionDefinition if option definition is invalid.
+    /// @throw InvalidOptionValue if data for the option is invalid.
+    OptionPtr optionFactory(Option::Universe u, uint16_t type,
+                            OptionBufferConstIter begin,
+                            OptionBufferConstIter end) const;
+
+    /// @brief Option factory.
+    ///
+    /// This function creates an instance of DHCP option using
+    /// whole provided buffer. This function may be used to
+    /// create option which is to be sent in the outgoing packet.
+    ///
+    /// @param u option universe (V4 or V6).
+    /// @param type option type.
+    /// @param buf option buffer.
+    ///
+    /// @return instance of the DHCP option.
+    /// @throw MalformedOptionDefinition if option definition is invalid.
+    /// @throw InvalidOptionValue if data for the option is invalid.
+    OptionPtr optionFactory(Option::Universe u, uint16_t type,
+                            const OptionBuffer& buf) const;
+
+    /// @brief Option factory.
+    ///
+    /// This function creates an instance of DHCP option using the vector
+    /// of strings which carry data values for option data fields.
+    /// The order of values in the vector corresponds to the order of data
+    /// fields in the option. The supplied string values are cast to
+    /// their actual data types which are determined based on the
+    /// option definition. If cast fails due to type mismatch, an exception
+    /// is thrown. This factory function can be used to create option
+    /// instance when user specified option value in the <b>comma separated
+    /// values</b> format in the configuration database. Provided string
+    /// must be tokenized into the vector of string values and this vector
+    /// can be supplied to this function.
+    ///
+    /// @param u option universe (V4 or V6).
+    /// @param type option type.
+    /// @param values a vector of values to be used to set data for an option.
+    ///
+    /// @return instance of the DHCP option.
+    /// @throw MalformedOptionDefinition if option definition is invalid.
+    /// @throw InvalidOptionValue if data for the option is invalid.
+    OptionPtr optionFactory(Option::Universe u, uint16_t type,
+                            const std::vector<std::string>& values) const;
+
     /// @brief Factory to create option with address list.
     ///
-    /// @param u universe (must be V4).
     /// @param type option type.
-    /// @param buf option buffer with a list of IPv4 addresses.
+    /// @param begin iterator pointing to the beginning of the buffer
+    /// with a list of IPv4 addresses.
+    /// @param end iterator pointing to the end of the buffer with
+    /// a list of IPv4 addresses.
     ///
     /// @throw isc::OutOfRange if length of the provided option buffer
     /// is not multiple of IPV4 address length.
-    static OptionPtr factoryAddrList4(Option::Universe u, uint16_t type,
-                                      const OptionBuffer& buf);
+    static OptionPtr factoryAddrList4(uint16_t type,
+                                      OptionBufferConstIter begin,
+                                      OptionBufferConstIter end);
 
     /// @brief Factory to create option with address list.
     ///
-    /// @param u universe (must be V6).
     /// @param type option type.
-    /// @param buf option buffer with a list of IPv6 addresses.
+    /// @param begin iterator pointing to the beginning of the buffer
+    /// with a list of IPv6 addresses.
+    /// @param end iterator pointing to the end of the buffer with
+    /// a list of IPv6 addresses.
     ///
     /// @throw isc::OutOfaRange if length of provided option buffer
     /// is not multiple of IPV6 address length.
-    static OptionPtr factoryAddrList6(Option::Universe u, uint16_t type,
-                                      const OptionBuffer& buf);
+    static OptionPtr factoryAddrList6(uint16_t type,
+                                      OptionBufferConstIter begin,
+                                      OptionBufferConstIter end);
 
     /// @brief Empty option factory.
     ///
     /// @param u universe (V6 or V4).
     /// @param type option type.
-    /// @param buf option buffer (must be empty).
-    static OptionPtr factoryEmpty(Option::Universe u, uint16_t type,
-                                  const OptionBuffer& buf);
+    static OptionPtr factoryEmpty(Option::Universe u, uint16_t type);
 
     /// @brief Factory to create generic option.
     ///
     /// @param u universe (V6 or V4).
     /// @param type option type.
-    /// @param buf option buffer.
+    /// @param begin iterator pointing to the beginning of the buffer.
+    /// @param end iterator pointing to the end of the buffer.
     static OptionPtr factoryGeneric(Option::Universe u, uint16_t type,
-                                    const OptionBuffer& buf);
+                                    OptionBufferConstIter begin,
+                                    OptionBufferConstIter end);
 
     /// @brief Factory for IA-type of option.
     ///
-    /// @param u universe (must be V6).
     /// @param type option type.
-    /// @param buf option buffer.
+    /// @param begin iterator pointing to the beginning of the buffer.
+    /// @param end iterator pointing to the end of the buffer.
     ///
     /// @throw isc::OutOfRange if provided option buffer is too short or
     /// too long. Expected size is 12 bytes.
     /// @throw isc::BadValue if specified universe value is not V6.
-    static OptionPtr factoryIA6(Option::Universe u, uint16_t type,
-                                const OptionBuffer& buf);
+    static OptionPtr factoryIA6(uint16_t type,
+                                OptionBufferConstIter begin,
+                                OptionBufferConstIter end);
 
     /// @brief Factory for IAADDR-type of option.
     ///
-    /// @param u universe (must be V6).
     /// @param type option type.
-    /// @param buf option buffer.
+    /// @param begin iterator pointing to the beginning of the buffer.
+    /// @param end iterator pointing to the end of the buffer.
     ///
     /// @throw isc::OutOfRange if provided option buffer is too short or
     /// too long. Expected size is 24 bytes.
     /// @throw isc::BadValue if specified universe value is not V6.
-    static OptionPtr factoryIAAddr6(Option::Universe u, uint16_t type,
-                                const OptionBuffer& buf);
+    static OptionPtr factoryIAAddr6(uint16_t type,
+                                    OptionBufferConstIter begin,
+                                    OptionBufferConstIter end);
 
     /// @brief Factory function to create option with integer value.
     ///
     /// @param type option type.
-    /// @param buf option buffer.
+    /// @param begin iterator pointing to the beginning of the buffer.
+    /// @param end iterator pointing to the end of the buffer.
     /// @tparam T type of the data field (must be one of the uintX_t or intX_t).
     ///
     /// @throw isc::OutOfRange if provided option buffer length is invalid.
     template<typename T>
-    static OptionPtr factoryInteger(Option::Universe, uint16_t type, const OptionBuffer& buf) {
-        if (buf.size() > sizeof(T)) {
-            isc_throw(isc::OutOfRange, "provided option buffer is too large, expected: "
-                      << sizeof(T) << " bytes");
-        }
-        OptionPtr option(new Option6Int<T>(type, buf.begin(), buf.end()));
+    static OptionPtr factoryInteger(Option::Universe, uint16_t type,
+                                    OptionBufferConstIter begin,
+                                    OptionBufferConstIter end) {
+        OptionPtr option(new Option6Int<T>(type, begin, end));
         return (option);
     }
 
     /// @brief Factory function to create option with array of integer values.
     ///
     /// @param type option type.
-    /// @param buf option buffer.
+    /// @param begin iterator pointing to the beginning of the buffer.
+    /// @param end iterator pointing to the end of the buffer.
     /// @tparam T type of the data field (must be one of the uintX_t or intX_t).
     ///
     /// @throw isc::OutOfRange if provided option buffer length is invalid.
     template<typename T>
-    static OptionPtr factoryIntegerArray(Option::Universe, uint16_t type, const OptionBuffer& buf) {
-        if (buf.size() == 0) {
-            isc_throw(isc::OutOfRange, "option buffer length must be greater than zero");
-        } else if (buf.size() % OptionDataTypes<T>::len != 0) {
-            isc_throw(isc::OutOfRange, "option buffer length must be multiple of "
-                      << OptionDataTypes<T>::len << " bytes");
-        }
-        OptionPtr option(new Option6IntArray<T>(type, buf.begin(), buf.end()));
+    static OptionPtr factoryIntegerArray(uint16_t type,
+                                         OptionBufferConstIter begin,
+                                         OptionBufferConstIter end) {
+        OptionPtr option(new Option6IntArray<T>(type, begin, end));
         return (option);
     }
 
@@ -379,15 +385,49 @@ private:
     /// @param first_type type of the first data field.
     ///
     /// @return true if actual option format matches expected format.
-    bool haveIAx6Format(const OptionDefinition::DataType first_type) const;
+    bool haveIAx6Format(const OptionDataType first_type) const;
 
     /// @brief Check if specified type matches option definition type.
     ///
     /// @return true if specified type matches option definition type.
-    inline bool haveType(const DataType type) const {
+    inline bool haveType(const OptionDataType type) const {
         return (type == type_);
     }
 
+    /// @brief Perform lexical cast of the value and validate its range.
+    ///
+    /// This function performs lexical cast of a string value to integer
+    /// or boolean value and checks if the resulting value is within a
+    /// range of a target type. Note that range checks are not performed
+    /// on boolean values. The target type should be one of the supported
+    /// integer types or bool.
+    ///
+    /// @param value_str input value given as string.
+    /// @tparam T target type for lexical cast.
+    ///
+    /// @return cast value.
+    /// @throw BadDataTypeCast if cast was not successful.
+    template<typename T>
+    T lexicalCastWithRangeCheck(const std::string& value_str) const;
+
+    /// @brief Write the string value into the provided buffer.
+    ///
+    /// This method writes the given value to the specified buffer.
+    /// The provided string value may represent data of different types.
+    /// The actual data type is specified with the second argument.
+    /// Based on a value of this argument, this function will first
+    /// try to cast the string value to the particular data type and
+    /// if it is successful it will store the data in the buffer
+    /// in a binary format.
+    ///
+    /// @param value string representation of the value to be written.
+    /// @param type the actual data type to be stored.
+    /// @param [in, out] buf buffer where the value is to be stored.
+    ///
+    /// @throw BadDataTypeCast if data write was unsuccessful.
+    void writeToBuffer(const std::string& value, const OptionDataType type,
+                       OptionBuffer& buf) const;
+
     /// @brief Sanity check universe value.
     ///
     /// @param expected_universe expected universe value.
@@ -395,14 +435,14 @@ private:
     ///
     /// @throw isc::BadValue if expected universe and actual universe don't match.
    static inline void sanityCheckUniverse(const Option::Universe expected_universe,
-                                          const Option::Universe actual_universe); 
+                                          const Option::Universe actual_universe);
 
     /// Option name.
     std::string name_;
     /// Option code.
     uint16_t code_;
     /// Option data type.
-    DataType type_;
+    OptionDataType type_;
     /// Indicates wheter option is a single value or array.
     bool array_type_;
     /// Collection of data fields within the record.
@@ -421,7 +461,7 @@ private:
 /// Note that this container can hold multiple options with the
 /// same code. For this reason, the latter index can be used to
 /// obtain a range of options for a particular option code.
-/// 
+///
 /// @todo: need an index to search options using option space name
 /// once option spaces are implemented.
 typedef boost::multi_index_container<

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

@@ -36,6 +36,7 @@ libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_int_array_unittest.cc
 libdhcp___unittests_SOURCES += option6_int_unittest.cc
 libdhcp___unittests_SOURCES += option_definition_unittest.cc
+libdhcp___unittests_SOURCES += option_custom_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc

+ 89 - 66
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -39,8 +39,7 @@ using namespace isc::util;
 namespace {
 class LibDhcpTest : public ::testing::Test {
 public:
-    LibDhcpTest() {
-    }
+    LibDhcpTest() { }
 
     /// @brief Generic factory function to create any option.
     ///
@@ -64,14 +63,13 @@ public:
     /// @param bug buffer to be used to create option instance.
     /// @param expected_type type of the option created by the
     /// factory function returned by the option definition.
-    static void testInitOptionDefs6(const uint16_t code,
+    static void testStdOptionDefs6(const uint16_t code,
                              const OptionBuffer& buf,
                              const std::type_info& expected_type) {
-        // Initialize stdandard options definitions. They are held
-        // in the static container throughout the program.
-        LibDHCP::initStdOptionDefs(Option::V6);
         // Get all option definitions, we will use them to extract
         // the definition for a particular option code.
+        // We don't have to initialize option deinitions here because they
+        // are initialized in the class'es constructor.
         OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
         // Get the container index #1. This one allows for searching
         // option definitions using option code.
@@ -90,14 +88,9 @@ public:
         ASSERT_TRUE(def);
         // Check that option definition is valid.
         ASSERT_NO_THROW(def->validate());
-        // Get the factory function for the particular option
-        // definition. We will use this factory function to
-        // create option instance.
-        Option::Factory* factory = NULL;
-        ASSERT_NO_THROW(factory = def->getFactory());
         OptionPtr option;
         // Create the option.
-        ASSERT_NO_THROW(option = factory(Option::V6, code, buf));
+        ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf));
         // Make sure it is not NULL.
         ASSERT_TRUE(option);
         // And the actual object type is the one that we expect.
@@ -108,14 +101,14 @@ public:
 };
 
 static const uint8_t packed[] = {
-    0, 12, 0, 5, 100, 101, 102, 103, 104, // opt1 (9 bytes)
-    0, 13, 0, 3, 105, 106, 107, // opt2 (7 bytes)
-    0, 14, 0, 2, 108, 109, // opt3 (6 bytes)
-    1,  0, 0, 4, 110, 111, 112, 113, // opt4 (8 bytes)
-    1,  1, 0, 1, 114 // opt5 (5 bytes)
+    0, 1, 0, 5, 100, 101, 102, 103, 104, // CLIENT_ID (9 bytes)
+    0, 2, 0, 3, 105, 106, 107, // SERVER_ID (7 bytes)
+    0, 14, 0, 0, // RAPID_COMMIT (0 bytes)
+    0,  6, 0, 4, 108, 109, 110, 111, // ORO (8 bytes)
+    0,  8, 0, 2, 112, 113 // ELAPSED_TIME (6 bytes)
 };
 
-TEST(LibDhcpTest, optionFactory) {
+TEST_F(LibDhcpTest, optionFactory) {
     OptionBuffer buf;
     // Factory functions for specific options must be registered before
     // they can be used to create options instances. Otherwise exception
@@ -187,7 +180,7 @@ TEST(LibDhcpTest, optionFactory) {
                            opt_clientid->getData().begin()));
 }
 
-TEST(LibDhcpTest, packOptions6) {
+TEST_F(LibDhcpTest, packOptions6) {
     OptionBuffer buf(512);
     isc::dhcp::Option::OptionCollection opts; // list of options
 
@@ -196,11 +189,11 @@ TEST(LibDhcpTest, packOptions6) {
         buf[i]=i+100;
     }
 
-    OptionPtr opt1(new Option(Option::V6, 12, buf.begin() + 0, buf.begin() + 5));
-    OptionPtr opt2(new Option(Option::V6, 13, buf.begin() + 5, buf.begin() + 8));
-    OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 10));
-    OptionPtr opt4(new Option(Option::V6,256, buf.begin() + 10,buf.begin() + 14));
-    OptionPtr opt5(new Option(Option::V6,257, buf.begin() + 14,buf.begin() + 15));
+    OptionPtr opt1(new Option(Option::V6, 1, buf.begin() + 0, buf.begin() + 5));
+    OptionPtr opt2(new Option(Option::V6, 2, buf.begin() + 5, buf.begin() + 8));
+    OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 8));
+    OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
+    OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));
 
     opts.insert(pair<int, OptionPtr >(opt1->getType(), opt1));
     opts.insert(pair<int, OptionPtr >(opt1->getType(), opt2));
@@ -211,11 +204,11 @@ TEST(LibDhcpTest, packOptions6) {
     OutputBuffer assembled(512);
 
     EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts));
-    EXPECT_EQ(35, assembled.getLength()); // options should take 35 bytes
-    EXPECT_EQ(0, memcmp(assembled.getData(), packed, 35) );
+    EXPECT_EQ(sizeof(packed), assembled.getLength());
+    EXPECT_EQ(0, memcmp(assembled.getData(), packed, sizeof(packed)));
 }
 
-TEST(LibDhcpTest, unpackOptions6) {
+TEST_F(LibDhcpTest, unpackOptions6) {
 
     // just couple of random options
     // Option is used as a simple option implementation
@@ -224,55 +217,85 @@ TEST(LibDhcpTest, unpackOptions6) {
     isc::dhcp::Option::OptionCollection options; // list of options
 
     OptionBuffer buf(512);
-    memcpy(&buf[0], packed, 35);
+    memcpy(&buf[0], packed, sizeof(packed));
 
     EXPECT_NO_THROW ({
-        LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin()+35), options);
+            LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin() + sizeof(packed)),
+                                    options);
     });
 
     EXPECT_EQ(options.size(), 5); // there should be 5 options
 
-    isc::dhcp::Option::OptionCollection::const_iterator x = options.find(12);
+    isc::dhcp::Option::OptionCollection::const_iterator x = options.find(1);
     ASSERT_FALSE(x == options.end()); // option 1 should exist
-    EXPECT_EQ(12, x->second->getType());  // this should be option 12
+    EXPECT_EQ(1, x->second->getType());  // this should be option 1
     ASSERT_EQ(9, x->second->len()); // it should be of length 9
-    EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+4, 5)); // data len=5
+    ASSERT_EQ(5, x->second->getData().size());
+    EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed + 4, 5)); // data len=5
 
-    x = options.find(13);
-    ASSERT_FALSE(x == options.end()); // option 13 should exist
-    EXPECT_EQ(13, x->second->getType());  // this should be option 13
+        x = options.find(2);
+    ASSERT_FALSE(x == options.end()); // option 2 should exist
+    EXPECT_EQ(2, x->second->getType());  // this should be option 2
     ASSERT_EQ(7, x->second->len()); // it should be of length 7
-    EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+13, 3)); // data len=3
+    ASSERT_EQ(3, x->second->getData().size());
+    EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed + 13, 3)); // data len=3
 
     x = options.find(14);
-    ASSERT_FALSE(x == options.end()); // option 3 should exist
+    ASSERT_FALSE(x == options.end()); // option 14 should exist
     EXPECT_EQ(14, x->second->getType());  // this should be option 14
-    ASSERT_EQ(6, x->second->len()); // it should be of length 6
-    EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+20, 2)); // data len=2
-
-    x = options.find(256);
-    ASSERT_FALSE(x == options.end()); // option 256 should exist
-    EXPECT_EQ(256, x->second->getType());  // this should be option 256
-    ASSERT_EQ(8, x->second->len()); // it should be of length 7
-    EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+26, 4)); // data len=4
-
-    x = options.find(257);
-    ASSERT_FALSE(x == options.end()); // option 257 should exist
-    EXPECT_EQ(257, x->second->getType());  // this should be option 257
-    ASSERT_EQ(5, x->second->len()); // it should be of length 5
-    EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+34, 1)); // data len=1
+    ASSERT_EQ(4, x->second->len()); // it should be of length 4
+    EXPECT_EQ(0, x->second->getData().size()); // data len = 0
+
+    x = options.find(6);
+    ASSERT_FALSE(x == options.end()); // option 6 should exist
+    EXPECT_EQ(6, x->second->getType());  // this should be option 6
+    ASSERT_EQ(8, x->second->len()); // it should be of length 8
+    // Option with code 6 is the OPTION_ORO. This option is
+    // represented by the Option6IntArray<uint16_t> class which
+    // comprises the set of uint16_t values. We need to cast the
+    // returned pointer to this type to get values stored in it.
+    boost::shared_ptr<Option6IntArray<uint16_t> > opt_oro =
+        boost::dynamic_pointer_cast<Option6IntArray<uint16_t> >(x->second);
+    // This value will be NULL if cast was unsuccessful. This is the case
+    // when returned option has different type than expected.
+    ASSERT_TRUE(opt_oro);
+    // Get set of uint16_t values.
+    std::vector<uint16_t> opts = opt_oro->getValues();
+    // Prepare the refrence data.
+    std::vector<uint16_t> expected_opts;
+    expected_opts.push_back(0x6C6D); // equivalent to: 108, 109
+    expected_opts.push_back(0x6E6F); // equivalent to 110, 111
+    ASSERT_EQ(expected_opts.size(), opts.size());
+    // Validated if option has been unpacked correctly.
+    EXPECT_TRUE(std::equal(expected_opts.begin(), expected_opts.end(),
+                           opts.begin()));
+
+    x = options.find(8);
+    ASSERT_FALSE(x == options.end()); // option 8 should exist
+    EXPECT_EQ(8, x->second->getType());  // this should be option 8
+    ASSERT_EQ(6, x->second->len()); // it should be of length 9
+    // Option with code 8 is OPTION_ELAPSED_TIME. This option is
+    // represented by Option6Int<uint16_t> value that holds single
+    // uint16_t value.
+    boost::shared_ptr<Option6Int<uint16_t> > opt_elapsed_time =
+        boost::dynamic_pointer_cast<Option6Int<uint16_t> >(x->second);
+    // This value will be NULL if cast was unsuccessful. This is the case
+    // when returned option has different type than expected.
+    ASSERT_TRUE(opt_elapsed_time);
+    // Returned value should be equivalent to two byte values: 112, 113
+    EXPECT_EQ(0x7071, opt_elapsed_time->getValue());
 
     x = options.find(0);
     EXPECT_TRUE(x == options.end()); // option 0 not found
 
-    x = options.find(1); // 1 is htons(256) on little endians. Worth checking
+    x = options.find(256); // 256 is htons(1) on little endians. Worth checking
     EXPECT_TRUE(x == options.end()); // option 1 not found
 
-    x = options.find(2);
+    x = options.find(7);
     EXPECT_TRUE(x == options.end()); // option 2 not found
 
     x = options.find(32000);
-    EXPECT_TRUE(x == options.end()); // option 32000 not found
+    EXPECT_TRUE(x == options.end()); // option 32000 not found */
 }
 
 
@@ -284,7 +307,7 @@ static uint8_t v4Opts[] = {
     128, 3, 40, 41, 42
 };
 
-TEST(LibDhcpTest, packOptions4) {
+TEST_F(LibDhcpTest, packOptions4) {
 
     vector<uint8_t> payload[5];
     for (int i = 0; i < 5; i++) {
@@ -316,7 +339,7 @@ TEST(LibDhcpTest, packOptions4) {
 
 }
 
-TEST(LibDhcpTest, unpackOptions4) {
+TEST_F(LibDhcpTest, unpackOptions4) {
 
     vector<uint8_t> packed(v4Opts, v4Opts + sizeof(v4Opts));
     isc::dhcp::Option::OptionCollection options; // list of options
@@ -375,24 +398,24 @@ TEST(LibDhcpTest, unpackOptions4) {
 // @todo Only limited number of option definitions are now created
 // This test have to be extended once all option definitions are
 // created.
-TEST(LibDhcpTest, initStdOptionDefs) {
-    LibDhcpTest::testInitOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
+TEST_F(LibDhcpTest, stdOptionDefs6) {
+    LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
                                      typeid(Option));
-    LibDhcpTest::testInitOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
+    LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
                                      typeid(Option));
-    LibDhcpTest::testInitOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
+    LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
                                      typeid(Option6IA));
-    LibDhcpTest::testInitOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
+    LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
                                      typeid(Option6IAAddr));
-    LibDhcpTest::testInitOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
+    LibDhcpTest::testStdOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
                                      typeid(Option6IntArray<uint16_t>));
-    LibDhcpTest::testInitOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
+    LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
                                      typeid(Option6Int<uint16_t>));
-    LibDhcpTest::testInitOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
+    LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
                                      typeid(Option));
-    LibDhcpTest::testInitOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
+    LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
                                      typeid(Option));
-    LibDhcpTest::testInitOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
+    LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
                                      typeid(Option6AddrLst));
 }
 

+ 1 - 1
src/lib/dhcp/tests/option6_ia_unittest.cc

@@ -206,7 +206,7 @@ TEST_F(Option6IATest, suboptions_unpack) {
 
     Option6IA* ia = 0;
     EXPECT_NO_THROW({
-            ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
+        ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
     });
     ASSERT_TRUE(ia);
 

+ 906 - 0
src/lib/dhcp/tests/option_custom_unittest.cc

@@ -0,0 +1,906 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/option_custom.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+    /// @brief Constructor.
+    OptionCustomTest() { }
+
+    /// @brief Write IP address into a buffer.
+    ///
+    /// @param address address to be written.
+    /// @param [out] buf output buffer.
+    void writeAddress(const asiolink::IOAddress& address,
+                      std::vector<uint8_t>& buf) {
+        short family = address.getFamily();
+        if (family == AF_INET) {
+            asio::ip::address_v4::bytes_type buf_addr =
+                address.getAddress().to_v4().to_bytes();
+            buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+        } else if (family == AF_INET6) {
+            asio::ip::address_v6::bytes_type buf_addr =
+                address.getAddress().to_v6().to_bytes();
+            buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+        }
+    }
+
+    /// @brief Write integer (signed or unsigned) into a buffer.
+    ///
+    /// @param value integer value.
+    /// @param [out] buf output buffer.
+    /// @tparam integer type.
+    template<typename T>
+    void writeInt(T value, std::vector<uint8_t>& buf) {
+        for (int i = 0; i < sizeof(T); ++i) {
+            buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+        }
+    }
+
+    /// @brief Write a string into a buffer.
+    ///
+    /// @param value string to be written into a buffer.
+    /// @param buf output buffer.
+    void writeString(const std::string& value,
+                     std::vector<uint8_t>& buf) {
+        buf.resize(buf.size() + value.size());
+        std::copy_backward(value.c_str(), value.c_str() + value.size(),
+                           buf.end());
+    }
+};
+
+// The purpose of this test is to check that parameters passed to
+// a custom option's constructor are used to initialize class
+// members.
+TEST_F(OptionCustomTest, constructor) {
+    // Create option definition for a DHCPv6 option.
+    OptionDefinition opt_def1("OPTION_FOO", 1000, "boolean", true);
+
+    // Initialize some dummy buffer that holds single boolean value.
+    OptionBuffer buf;
+    buf.push_back(1);
+
+    // Create DHCPv6 option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def1, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // Check if constructor initialized the universe and type correctly.
+    EXPECT_EQ(Option::V6, option->getUniverse());
+    EXPECT_EQ(1000, option->getType());
+
+    // Do another round of testing for DHCPv4 option.
+    OptionDefinition opt_def2("OPTION_FOO", 232, "boolean");
+
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def2, Option::V4, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(Option::V4, option->getUniverse());
+    EXPECT_EQ(232, option->getType());
+}
+
+// The purpose of this test is to verify that 'empty' option definition can
+// be used to create an instance of custom option.
+TEST_F(OptionCustomTest, emptyData) {
+    OptionDefinition opt_def("OPTION_FOO", 232, "empty");
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer()));
+    );
+    ASSERT_TRUE(option);
+
+    // Option is 'empty' so no data fields are expected.
+    EXPECT_EQ(0, option->getDataFieldsNum());
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a binary value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, binaryData) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "binary");
+
+    // Create a buffer holding some binary data. This data will be
+    // used as reference when we read back the data from a created
+    // option.
+    OptionBuffer buf_in(14);
+    for (int i = 0; i < 14; ++i) {
+        buf_in[i] = i;
+    }
+    // Use scoped pointer because it allows to declare the option
+    // in the function scope and initialize it under ASSERT.
+    boost::scoped_ptr<OptionCustom> option;
+    // Custom option may throw exception if the provided buffer is
+    // malformed.
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf_in));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // The custom option should hold just one buffer that can be
+    // accessed using index 0.
+    OptionBuffer buf_out;
+    ASSERT_NO_THROW(buf_out = option->readBinary(0));
+
+    // Read buffer must match exactly with the buffer used to
+    // create option instance.
+    ASSERT_EQ(buf_in.size(), buf_out.size());
+    EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+    // Check that option with "no data" is rejected.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer())),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that an option definition comprising
+// a single boolean value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, booleanData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+    OptionBuffer buf;
+    // Push back the value that represents 'false'.
+    buf.push_back(0);
+    // Push back the 'true' value. Note that this value should
+    // be ignored by custom option because it holds single boolean
+    // value (according to option definition).
+    buf.push_back(1);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Initialize the value to true because we want to make sure
+    // that it is modified to 'false' by readBoolean below.
+    bool value = true;
+
+    // Read the boolean value from only one available buffer indexed
+    // with 0. It is expected to be 'false'.
+    ASSERT_NO_THROW(value = option->readBoolean(0));
+    EXPECT_FALSE(value);
+
+    // Check that the option with "no data" is rejected.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// 16-bit signed integer value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, int16Data) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "int16");
+
+    OptionBuffer buf;
+    // Store signed integer value in the input buffer.
+    writeInt<int16_t>(-234, buf);
+
+    // Create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Initialize value to 0 explicitely to make sure that is
+    // modified by readInteger function to expected -234.
+    int16_t value = 0;
+    ASSERT_NO_THROW(value = option->readInteger<int16_t>(0));
+    EXPECT_EQ(-234, value);
+
+    // Check that the option is not created when a buffer is
+    // too short (1 byte instead of 2 bytes).
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 1)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// 32-bit signed integer value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, int32Data) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "int32");
+
+    OptionBuffer buf;
+    writeInt<int32_t>(-234, buf);
+    writeInt<int32_t>(100, buf);
+
+    // Create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Initialize value to 0 explicitely to make sure that is
+    // modified by readInteger function to expected -234.
+    int32_t value = 0;
+    ASSERT_NO_THROW(value = option->readInteger<int32_t>(0));
+    EXPECT_EQ(-234, value);
+
+    // Check that the option is not created when a buffer is
+    // too short (3 bytes instead of 4 bytes).
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 3)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single IPv4 addres can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv4AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address");
+
+    // Create input buffer.
+    OptionBuffer buf;
+    writeAddress(IOAddress("192.168.100.50"), buf);
+
+    // Create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    IOAddress address("127.0.0.1");
+    // Read IPv4 address from using index 0.
+    ASSERT_NO_THROW(address = option->readAddress(0));
+
+    EXPECT_EQ("192.168.100.50", address.toText());
+
+    // Check that option is not created if the provided buffer is
+    // too short (use 3 bytes instead of 4).
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.begin() + 3)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single IPv6 addres can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv6AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+    // Initialize input buffer.
+    OptionBuffer buf;
+    writeAddress(IOAddress("2001:db8:1::100"), buf);
+
+    // Create custom option using input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Custom option should comprise exactly one buffer that represents
+    // IPv6 address.
+    IOAddress address("::1");
+    // Read an address from buffer #0.
+    ASSERT_NO_THROW(address = option->readAddress(0));
+
+    EXPECT_EQ("2001:db8:1::100", address.toText());
+
+    // Check that option is not created if the provided buffer is
+    // too short (use 15 bytes instead of 16).
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
+                                      buf.begin() + 15)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// string value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, stringData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+    // Create an input buffer holding some string value.
+    OptionBuffer buf;
+    writeString("hello world!", buf);
+
+    // Create custom option using input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have just one data field.
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    // Custom option should now comprise single string value that
+    // can be accessed using index 0.
+    std::string value;
+    ASSERT_NO_THROW(value = option->readString(0));
+
+    EXPECT_EQ("hello world!", value);
+
+    // Check that option will not be created if empty buffer is provided.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of boolean values can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, booleanDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", true);
+
+    // Create a buffer with 5 values that represent array of
+    // booleans.
+    OptionBuffer buf(5);
+    buf[0] = 1; // true
+    buf[1] = 0; // false
+    buf[2] = 0; // false
+    buf[3] = 1; // true
+    buf[4] = 1; // true
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 5 data fields.
+    ASSERT_EQ(5, option->getDataFieldsNum());
+
+    // Read values from custom option using indexes 0..4 and
+    // check that they are valid.
+    bool value0 = false;
+    ASSERT_NO_THROW(value0 = option->readBoolean(0));
+    EXPECT_TRUE(value0);
+
+    bool value1 = true;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_FALSE(value1);
+
+    bool value2 = true;
+    ASSERT_NO_THROW(value2 = option->readBoolean(2));
+    EXPECT_FALSE(value2);
+
+    bool value3 = false;
+    ASSERT_NO_THROW(value3 = option->readBoolean(3));
+    EXPECT_TRUE(value3);
+
+    bool value4 = false;
+    ASSERT_NO_THROW(value4 = option->readBoolean(4));
+    EXPECT_TRUE(value4);
+
+    // Check that empty buffer can't be used to create option holding
+    // array of boolean values.
+    EXPECT_THROW(
+         option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+         isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of 32-bit signed integer values can be used to create an instance
+// of custom option.
+TEST_F(OptionCustomTest, uint32DataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "uint32", true);
+
+    // Create an input buffer that holds 4 uint32 values that
+    // represent an array.
+    std::vector<uint32_t> values;
+    values.push_back(71234);
+    values.push_back(12234);
+    values.push_back(54362);
+    values.push_back(1234);
+
+    // Store these values in a buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < values.size(); ++i) {
+        writeInt<uint32_t>(values[i], buf);
+    }
+    // Create custom option using the input buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        // Note that we just use a part of the whole buffer here: 13 bytes. We want to
+        // check that buffer length which is non-divisible by 4 (size of uint32_t) is
+        // accepted and only 3 (instead of 4) elements will be stored in a custom option.
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 13));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Expect only 3 values.
+    for (int i = 0; i < 3; ++i) {
+        uint32_t value = 0;
+        ASSERT_NO_THROW(value = option->readInteger<uint32_t>(i));
+        EXPECT_EQ(values[i], value);
+    }
+
+    // Check that too short buffer can't be used to create the option.
+    // Using buffer having length of 3 bytes. The length of 4 bytes is
+    // a minimal length to create the option with single uint32_t value.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+                                      buf.begin() + 3)),
+        isc::OutOfRange
+    );
+
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv4 addresses can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv4AddressDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address", true);
+
+    // Initialize reference data.
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("192.168.0.1"));
+    addresses.push_back(IOAddress("127.0.0.1"));
+    addresses.push_back(IOAddress("10.10.1.2"));
+
+    // Store the collection of IPv4 addresses into the buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // We expect 3 IPv4 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+
+    // Check that it is ok if buffer length is not a multiple of IPv4
+    // address length. Resize it by two bytes.
+    buf.resize(buf.size() + 2);
+    EXPECT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf));
+    );
+
+    // Check that option is not created when the provided buffer
+    // is too short. At least a buffer length of 4 bytes is needed.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
+                                      buf.begin() + 2)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv6 addresses can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv6AddressDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+    // Initialize reference data.
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("2001:db8:1::3"));
+    addresses.push_back(IOAddress("::1"));
+    addresses.push_back(IOAddress("fe80::3"));
+
+    // Store the collection of IPv6 addresses into the buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // We expect 3 IPv6 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("fe80::4");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+
+    // Check that it is ok if buffer length is not a multiple of IPv6
+    // address length. Resize it by two bytes.
+    buf.resize(buf.size() + 2);
+    EXPECT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+
+    // Check that option is not created when the provided buffer
+    // is too short. At least a buffer length of 16 bytes is needed.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+                                      buf.begin() + 15)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a record of various data fields can be used to create an instance of
+// custom option.
+TEST_F(OptionCustomTest, recordData) {
+    // Create the definition of an option which comprises
+    // a record of fields of different types.
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+    OptionBuffer buf;
+    // Initialize field 0.
+    writeInt<uint16_t>(8712, buf);
+    // Initialize field 1 to 'true'
+    buf.push_back(static_cast<unsigned short>(1));
+    // Initialize field 2 to IPv4 address.
+    writeAddress(IOAddress("192.168.0.1"), buf);
+    // Initialize field 3 to IPv6 address.
+    writeAddress(IOAddress("2001:db8:1::1"), buf);
+    // Initialize field 4 to string value.
+    writeString("ABCD", buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+         option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 5 data fields.
+    ASSERT_EQ(5, option->getDataFieldsNum());
+
+    // Verify value in the field 0.
+    uint16_t value0 = 0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(8712, value0);
+
+    // Verify value in the field 1.
+    bool value1 = false;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_TRUE(value1);
+
+    // Verify value in the field 2.
+    IOAddress value2("127.0.0.1");
+    ASSERT_NO_THROW(value2 = option->readAddress(2));
+    EXPECT_EQ("192.168.0.1", value2.toText());
+
+    // Verify value in the field 3.
+    IOAddress value3("::1");
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("2001:db8:1::1", value3.toText());
+
+    // Verify value in the field 4.
+    std::string value4;
+    ASSERT_NO_THROW(value4 = option->readString(4));
+    EXPECT_EQ("ABCD", value4);
+}
+
+// The purpose of this test is to verify that truncated buffer
+// can't be used to create an option being a record of value of
+// different types.
+TEST_F(OptionCustomTest, recordDataTruncated) {
+    // Create the definition of an option which comprises
+    // a record of fields of different types.
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+    OptionBuffer buf;
+    // Initialize field 0.
+    writeInt<uint16_t>(8712, buf);
+    // Initialize field 1 to IPv6 address.
+    writeAddress(IOAddress("2001:db8:1::1"), buf);
+    // Initialize field 2 to string value.
+    writeString("ABCD", buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+
+    // Constructor should not throw exception here because the length of the
+    // buffer meets the minimum length. The first 19 bytes hold data for
+    // all option fields: uint16, IPv4 address and first letter of string.
+    // Note that string will be truncated but this is acceptable because
+    // constructor have no way to determine the length of the original string.
+    EXPECT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 19));
+    );
+
+    // Reduce the buffer length by one byte should cause the constructor
+    // to fail. This is because 18 bytes can only hold first two data fields:
+    // 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
+    // 3 data fields for this option but the length of the data is insufficient
+    // to initialize 3 data field.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)),
+        isc::OutOfRange
+    );
+
+    // Try to further reduce the length of the buffer to make it insufficient
+    // to even initialize the second data field.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 17)),
+        isc::OutOfRange
+    );
+}
+
+// The purpose of this test is to verify that pack function for
+// DHCPv4 custom option works correctly.
+TEST_F(OptionCustomTest, pack4) {
+    OptionDefinition opt_def("OPTION_FOO", 234, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("uint8"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+    OptionBuffer buf;
+    writeInt<uint8_t>(1, buf);
+    writeInt<uint16_t>(1000, buf);
+    writeInt<uint32_t>(100000, buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf));
+    );
+    ASSERT_TRUE(option);
+
+    util::OutputBuffer buf_out(7);
+    ASSERT_NO_THROW(option->pack(buf_out));
+    ASSERT_EQ(9, buf_out.getLength());
+
+    // The original buffer holds the option data but it lacks a header.
+    // We append data length and option code so as it can be directly
+    // compared with the output buffer that holds whole option.
+    buf.insert(buf.begin(), 7);
+    buf.insert(buf.begin(), 234);
+
+    // Validate the buffer.
+    EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
+}
+
+// The purpose of this test is to verify that pack function for
+// DHCPv6 custom option works correctly.
+TEST_F(OptionCustomTest, pack6) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+    ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+    OptionBuffer buf;
+    buf.push_back(1);
+    writeInt<uint16_t>(1000, buf);
+    writeString("hello world", buf);
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    util::OutputBuffer buf_out(buf.size() + option->getHeaderLen());
+    ASSERT_NO_THROW(option->pack(buf_out));
+    ASSERT_EQ(buf.size() + option->getHeaderLen(), buf_out.getLength());
+
+    // The original buffer holds the option data but it lacks a header.
+    // We append data length and option code so as it can be directly
+    // compared with the output buffer that holds whole option.
+    OptionBuffer tmp;
+    writeInt<uint16_t>(1000, tmp);
+    writeInt<uint16_t>(buf.size(), tmp);
+    buf.insert(buf.begin(), tmp.begin(), tmp.end());
+
+    // Validate the buffer.
+    EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
+}
+
+// The purpose of this test is to verify that unpack function works
+// correctly for a custom option.
+TEST_F(OptionCustomTest, unpack) {
+    OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address", true);
+
+    // Initialize reference data.
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("192.168.0.1"));
+    addresses.push_back(IOAddress("127.0.0.1"));
+    addresses.push_back(IOAddress("10.10.1.2"));
+
+    // Store the collection of IPv4 addresses into the buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // We expect 3 IPv4 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+
+    // Remove all addresses we had added. We are going to replace
+    // them with a new set of addresses.
+    addresses.clear();
+
+    // Add new addresses.
+    addresses.push_back(IOAddress("10.1.2.3"));
+    addresses.push_back(IOAddress("85.26.43.234"));
+
+    // Clear the buffer as we need to store new addresses in it.
+    buf.clear();
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Perform 'unpack'.
+    ASSERT_NO_THROW(option->unpack(buf.begin(), buf.end()));
+
+    // Now we should have only 2 data fields.
+    ASSERT_EQ(2, option->getDataFieldsNum());
+
+    // Verify that the addresses have been overwritten.
+    for (int i = 0; i < 2; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+}
+
+// The purpose of this test is to verify that new data can be set for
+// a custom option.
+TEST_F(OptionCustomTest, setData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+    // Initialize reference data.
+    std::vector<IOAddress> addresses;
+    addresses.push_back(IOAddress("2001:db8:1::3"));
+    addresses.push_back(IOAddress("::1"));
+    addresses.push_back(IOAddress("fe80::3"));
+
+    // Store the collection of IPv6 addresses into the buffer.
+    OptionBuffer buf;
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    // We should have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // We expect 3 IPv6 addresses being stored in the option.
+    for (int i = 0; i < 3; ++i) {
+        IOAddress address("fe80::4");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+
+    // Clear addresses we had previously added.
+    addresses.clear();
+
+    // Store new addresses.
+    addresses.push_back(IOAddress("::1"));
+    addresses.push_back(IOAddress("fe80::10"));
+
+    // Clear the buffer as we need to store new addresses in it.
+    buf.clear();
+    for (int i = 0; i < addresses.size(); ++i) {
+        writeAddress(addresses[i], buf);
+    }
+
+    // Replace the option data.
+    ASSERT_NO_THROW(option->setData(buf.begin(), buf.end()));
+
+    // Now we should have only 2 data fields.
+    ASSERT_EQ(2, option->getDataFieldsNum());
+
+    // Check that it has been replaced.
+    for (int i = 0; i < 2; ++i) {
+        IOAddress address("10.10.10.10");
+        ASSERT_NO_THROW(address = option->readAddress(i));
+        EXPECT_EQ(addresses[i].toText(), address.toText());
+    }
+}
+
+// The purpose of this test is to verify that an invalid index
+// value can't be used to access option data fields.
+TEST_F(OptionCustomTest, invalidIndex) {
+    OptionDefinition opt_def("OPTION_FOO", 999, "uint32", true);
+
+    OptionBuffer buf;
+    for (int i = 0; i < 10; ++i) {
+        writeInt<uint32_t>(i, buf);
+    }
+
+    // Use the input buffer to create custom option.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We expect that there are 10 uint32_t values stored in
+    // the option. The 10th element is accessed by index eq 9.
+    // Check that 9 is accepted.
+    EXPECT_NO_THROW(option->readInteger<uint32_t>(9));
+
+    // Check that index value beyond 9 is not accepted.
+    EXPECT_THROW(option->readInteger<uint32_t>(10), isc::OutOfRange);
+    EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
+}
+
+
+
+} // anonymous namespace

+ 431 - 157
src/lib/dhcp/tests/option_definition_unittest.cc

@@ -47,32 +47,35 @@ public:
     OptionDefinitionTest() { }
 };
 
+// The purpose of this test is to verify that OptionDefinition
+// constructor initializes its members correctly.
 TEST_F(OptionDefinitionTest, constructor) {
     // Specify the option data type as string. This should get converted
     // to enum value returned by getType().
     OptionDefinition opt_def1("OPTION_CLIENTID", 1, "string");
     EXPECT_EQ("OPTION_CLIENTID", opt_def1.getName());
+
     EXPECT_EQ(1, opt_def1.getCode());
-    EXPECT_EQ(OptionDefinition::STRING_TYPE,  opt_def1.getType());
+    EXPECT_EQ(OPT_STRING_TYPE,  opt_def1.getType());
     EXPECT_FALSE(opt_def1.getArrayType());
     EXPECT_NO_THROW(opt_def1.validate());
 
     // Specify the option data type as an enum value.
     OptionDefinition opt_def2("OPTION_RAPID_COMMIT", 14,
-                              OptionDefinition::EMPTY_TYPE);
+                              OPT_EMPTY_TYPE);
     EXPECT_EQ("OPTION_RAPID_COMMIT", opt_def2.getName());
     EXPECT_EQ(14, opt_def2.getCode());
-    EXPECT_EQ(OptionDefinition::EMPTY_TYPE, opt_def2.getType());
+    EXPECT_EQ(OPT_EMPTY_TYPE, opt_def2.getType());
     EXPECT_FALSE(opt_def2.getArrayType());
     EXPECT_NO_THROW(opt_def1.validate());
 
     // Check if it is possible to set that option is an array.
     OptionDefinition opt_def3("OPTION_NIS_SERVERS", 27,
-                              OptionDefinition::IPV6_ADDRESS_TYPE,
+                              OPT_IPV6_ADDRESS_TYPE,
                               true);
     EXPECT_EQ("OPTION_NIS_SERVERS", opt_def3.getName());
     EXPECT_EQ(27, opt_def3.getCode());
-    EXPECT_EQ(OptionDefinition::IPV6_ADDRESS_TYPE, opt_def3.getType());
+    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def3.getType());
     EXPECT_TRUE(opt_def3.getArrayType());
     EXPECT_NO_THROW(opt_def3.validate());
 
@@ -81,23 +84,27 @@ TEST_F(OptionDefinitionTest, constructor) {
     // it has been created.
     EXPECT_NO_THROW(
         OptionDefinition opt_def4("OPTION_SERVERID",
-                                  OptionDefinition::UNKNOWN_TYPE + 10,
-                                  OptionDefinition::STRING_TYPE);
+                                  OPT_UNKNOWN_TYPE + 10,
+                                  OPT_STRING_TYPE);
     );
 }
 
+// The purpose of this test is to verify that various data fields
+// can be specified for an option definition when this definition
+// is marked as 'record' and that fields can't be added if option
+// definition is not marked as 'record'.
 TEST_F(OptionDefinitionTest, addRecordField) {
     // We can only add fields to record if the option type has been
     // specified as 'record'. We try all other types but 'record'
     // here and expect exception to be thrown.
-    for (int i = 0; i < OptionDefinition::UNKNOWN_TYPE; ++i) {
+    for (int i = 0; i < OPT_UNKNOWN_TYPE; ++i) {
         // Do not try for 'record' type because this is the only
         // type for which adding record will succeed.
-        if (i == OptionDefinition::RECORD_TYPE) {
+        if (i == OPT_RECORD_TYPE) {
             continue;
         }
         OptionDefinition opt_def("OPTION_IAADDR", 5,
-                                 static_cast<OptionDefinition::DataType>(i));
+                                 static_cast<OptionDataType>(i));
         EXPECT_THROW(opt_def.addRecordField("uint8"), isc::InvalidOperation);
     }
 
@@ -106,54 +113,88 @@ TEST_F(OptionDefinitionTest, addRecordField) {
     EXPECT_NO_THROW(opt_def.addRecordField("ipv6-address"));
     EXPECT_NO_THROW(opt_def.addRecordField("uint32"));
     // It should not matter if we specify field type by its name or using enum.
-    EXPECT_NO_THROW(opt_def.addRecordField(OptionDefinition::UINT32_TYPE));
+    EXPECT_NO_THROW(opt_def.addRecordField(OPT_UINT32_TYPE));
 
     // Check what we have actually added.
     OptionDefinition::RecordFieldsCollection fields = opt_def.getRecordFields();
     ASSERT_EQ(3, fields.size());
-    EXPECT_EQ(OptionDefinition::IPV6_ADDRESS_TYPE, fields[0]);
-    EXPECT_EQ(OptionDefinition::UINT32_TYPE, fields[1]);
-    EXPECT_EQ(OptionDefinition::UINT32_TYPE, fields[2]);
+    EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, fields[0]);
+    EXPECT_EQ(OPT_UINT32_TYPE, fields[1]);
+    EXPECT_EQ(OPT_UINT32_TYPE, fields[2]);
 
     // Let's try some more negative scenarios: use invalid data types.
     EXPECT_THROW(opt_def.addRecordField("unknown_type_xyz"), isc::BadValue);
-    OptionDefinition::DataType invalid_type =
-        static_cast<OptionDefinition::DataType>(OptionDefinition::UNKNOWN_TYPE + 10);
+    OptionDataType invalid_type =
+        static_cast<OptionDataType>(OPT_UNKNOWN_TYPE + 10);
     EXPECT_THROW(opt_def.addRecordField(invalid_type), isc::BadValue);
+
+    // It is bad if we use 'record' option type but don't specify
+    // at least two fields.
+    OptionDefinition opt_def2("OPTION_EMPTY_RECORD", 100, "record");
+    EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
+    opt_def2.addRecordField("uint8");
+    EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
+    opt_def2.addRecordField("uint32");
+    EXPECT_NO_THROW(opt_def2.validate());
 }
 
+// The purpose of this test is to check that validate() function
+// reports errors for invalid option definitions.
 TEST_F(OptionDefinitionTest, validate) {
     // Not supported option type string is not allowed.
     OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, "non-existent-type");
-    EXPECT_THROW(opt_def1.validate(), isc::OutOfRange);
+    EXPECT_THROW(opt_def1.validate(), MalformedOptionDefinition);
 
     // Not supported option type enum value is not allowed.
-    OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID, OptionDefinition::UNKNOWN_TYPE);
-    EXPECT_THROW(opt_def2.validate(), isc::OutOfRange);
+    OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID, OPT_UNKNOWN_TYPE);
+    EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
 
     OptionDefinition opt_def3("OPTION_CLIENTID", D6O_CLIENTID,
-                              static_cast<OptionDefinition::DataType>(OptionDefinition::UNKNOWN_TYPE
+                              static_cast<OptionDataType>(OPT_UNKNOWN_TYPE
                                                                       + 2));
-    EXPECT_THROW(opt_def3.validate(), isc::OutOfRange);
-    
+    EXPECT_THROW(opt_def3.validate(), MalformedOptionDefinition);
+
     // Empty option name is not allowed.
     OptionDefinition opt_def4("", D6O_CLIENTID, "string");
-    EXPECT_THROW(opt_def4.validate(), isc::BadValue);
+    EXPECT_THROW(opt_def4.validate(), MalformedOptionDefinition);
 
     // Option name must not contain spaces.
     OptionDefinition opt_def5(" OPTION_CLIENTID", D6O_CLIENTID, "string");
-    EXPECT_THROW(opt_def5.validate(), isc::BadValue);
+    EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition);
 
-    OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string");
-    EXPECT_THROW(opt_def6.validate(), isc::BadValue);
+    // Option name must not contain spaces.
+    OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string", true);
+    EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition);
+
+    // Having array of strings does not make sense because there is no way
+    // to determine string's length.
+    OptionDefinition opt_def7("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
+    EXPECT_THROW(opt_def7.validate(), MalformedOptionDefinition);
+
+    // It does not make sense to have string field within the record before
+    // other fields because there is no way to determine the length of this
+    // string and thus there is no way to determine where the other field
+    // begins.
+    OptionDefinition opt_def8("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+                              "record");
+    opt_def8.addRecordField("string");
+    opt_def8.addRecordField("uint16");
+    EXPECT_THROW(opt_def8.validate(), MalformedOptionDefinition);
+
+    // ... but it is ok if the string value is the last one.
+    OptionDefinition opt_def9("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+                              "record");
+    opt_def9.addRecordField("uint8");
+    opt_def9.addRecordField("string");
 }
 
-TEST_F(OptionDefinitionTest, factoryAddrList6) {
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv6 addresses will return an instance
+// of option with a list of IPv6 addresses.
+TEST_F(OptionDefinitionTest, ipv6AddressArray) {
     OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS,
                              "ipv6-address", true);
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     // Create a list of some V6 addresses.
     std::vector<asiolink::IOAddress> addrs;
@@ -176,7 +217,7 @@ TEST_F(OptionDefinitionTest, factoryAddrList6) {
     // the provided buffer.
     OptionPtr option_v6;
     ASSERT_NO_THROW(
-        option_v6 = factory(Option::V6, D6O_NIS_SERVERS, buf);
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf);
     );
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option6AddrLst));
     boost::shared_ptr<Option6AddrLst> option_cast_v6 =
@@ -195,17 +236,64 @@ TEST_F(OptionDefinitionTest, factoryAddrList6) {
     buf.insert(buf.end(), 1, 1);
     // It should throw exception then.
     EXPECT_THROW(
-        factory(Option::V6, D6O_NIS_SERVERS, buf),
-        isc::OutOfRange
+        opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf),
+        InvalidOptionValue
     );
 }
 
-TEST_F(OptionDefinitionTest, factoryAddrList4) {
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv6 addresses will return an instance
+// of option with a list of IPv6 addresses. Array of IPv6 addresses
+// is specified as a vector of strings (each string represents single
+// IPv6 address).
+TEST_F(OptionDefinitionTest, ipv6AddressArrayTokenized) {
+    OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS,
+                             "ipv6-address", true);
+
+    // Create a vector of some V6 addresses.
+    std::vector<asiolink::IOAddress> addrs;
+    addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329"));
+    addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319"));
+    addrs.push_back(asiolink::IOAddress("::1"));
+    addrs.push_back(asiolink::IOAddress("::2"));
+
+    // Create a vector of strings representing addresses given above.
+    std::vector<std::string> addrs_str;
+    for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin();
+         it != addrs.end(); ++it) {
+        addrs_str.push_back(it->toText());
+    }
+
+    // Create DHCPv6 option using the list of IPv6 addresses given in the
+    // string form.
+    OptionPtr option_v6;
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS,
+                                          addrs_str);
+    );
+    // Non-null pointer option is supposed to be returned and it
+    // should have Option6AddrLst type.
+    ASSERT_TRUE(option_v6);
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option6AddrLst));
+    // Cast to the actual option type to get IPv6 addresses from it.
+    boost::shared_ptr<Option6AddrLst> option_cast_v6 =
+        boost::static_pointer_cast<Option6AddrLst>(option_v6);
+    // Check that cast was successful.
+    ASSERT_TRUE(option_cast_v6);
+    // Get the list of parsed addresses from the option object.
+    std::vector<asiolink::IOAddress> addrs_returned =
+        option_cast_v6->getAddresses();
+    // Returned addresses must match the addresses that have been used to create
+    // the option instance.
+    EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
+}
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv4 addresses will return an instance
+// of option with a list of IPv4 addresses.
+TEST_F(OptionDefinitionTest, ipv4AddressArray) {
     OptionDefinition opt_def("OPTION_NAME_SERVERS", D6O_NIS_SERVERS,
                              "ipv4-address", true);
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     // Create a list of some V6 addresses.
     std::vector<asiolink::IOAddress> addrs;
@@ -228,7 +316,7 @@ TEST_F(OptionDefinitionTest, factoryAddrList4) {
     // the provided buffer.
     OptionPtr option_v4;
     ASSERT_NO_THROW(
-        option_v4 = factory(Option::V4, DHO_NAME_SERVERS, buf)
+        option_v4 = opt_def.optionFactory(Option::V4, DHO_NAME_SERVERS, buf)
     );
     ASSERT_TRUE(typeid(*option_v4) == typeid(Option4AddrLst));
     // Get the list of parsed addresses from the option object.
@@ -245,19 +333,66 @@ TEST_F(OptionDefinitionTest, factoryAddrList4) {
     // fulfilled anymore.
     buf.insert(buf.end(), 1, 1);
     // It should throw exception then.
-    EXPECT_THROW(factory(Option::V4, DHO_NIS_SERVERS, buf), isc::OutOfRange);
+    EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, buf),
+                 InvalidOptionValue);
+}
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv4 addresses will return an instance
+// of option with a list of IPv4 addresses. The array of IPv4 addresses
+// is specified as a vector of strings (each string represents single
+// IPv4 address).
+TEST_F(OptionDefinitionTest, ipv4AddressArrayTokenized) {
+    OptionDefinition opt_def("OPTION_NIS_SERVERS", DHO_NIS_SERVERS,
+                             "ipv4-address", true);
+
+    // Create a vector of some V6 addresses.
+    std::vector<asiolink::IOAddress> addrs;
+    addrs.push_back(asiolink::IOAddress("192.168.0.1"));
+    addrs.push_back(asiolink::IOAddress("172.16.1.1"));
+    addrs.push_back(asiolink::IOAddress("127.0.0.1"));
+    addrs.push_back(asiolink::IOAddress("213.41.23.12"));
+
+    // Create a vector of strings representing addresses given above.
+    std::vector<std::string> addrs_str;
+    for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin();
+         it != addrs.end(); ++it) {
+        addrs_str.push_back(it->toText());
+    }
+
+    // Create DHCPv4 option using the list of IPv4 addresses given in the
+    // string form.
+    OptionPtr option_v4;
+    ASSERT_NO_THROW(
+        option_v4 = opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS,
+                                          addrs_str);
+    );
+    // Non-null pointer option is supposed to be returned and it
+    // should have Option6AddrLst type.
+    ASSERT_TRUE(option_v4);
+    ASSERT_TRUE(typeid(*option_v4) == typeid(Option4AddrLst));
+    // Cast to the actual option type to get IPv4 addresses from it.
+    boost::shared_ptr<Option4AddrLst> option_cast_v4 =
+        boost::static_pointer_cast<Option4AddrLst>(option_v4);
+    // Check that cast was successful.
+    ASSERT_TRUE(option_cast_v4);
+    // Get the list of parsed addresses from the option object.
+    std::vector<asiolink::IOAddress> addrs_returned =
+        option_cast_v4->getAddresses();
+    // Returned addresses must match the addresses that have been used to create
+    // the option instance.
+    EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
 }
 
-TEST_F(OptionDefinitionTest, factoryEmpty) {
+// The purpose of thie test is to verify that option definition for
+// 'empty' option can be created and that it returns 'empty' option.
+TEST_F(OptionDefinitionTest, empty) {
     OptionDefinition opt_def("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT, "empty");
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     // Create option instance and provide empty buffer as expected.
     OptionPtr option_v6;
     ASSERT_NO_THROW(
-        option_v6 = factory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer())
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer())
     );
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
     // Expect 'empty' DHCPv6 option.
@@ -266,31 +401,23 @@ TEST_F(OptionDefinitionTest, factoryEmpty) {
     EXPECT_EQ(0, option_v6->getData().size());
 
     // Repeat the same test scenario for DHCPv4 option.
-    EXPECT_THROW(factory(Option::V4, 214, OptionBuffer(2)),isc::BadValue);
-
     OptionPtr option_v4;
-    ASSERT_NO_THROW(option_v4 = factory(Option::V4, 214, OptionBuffer()));
+    ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, OptionBuffer()));
     // Expect 'empty' DHCPv4 option.
     EXPECT_EQ(Option::V4, option_v4->getUniverse());
     EXPECT_EQ(2, option_v4->getHeaderLen());
     EXPECT_EQ(0, option_v4->getData().size());
-
-    // This factory produces empty option (consisting of option type
-    // and length). Attempt to provide some data in the buffer should
-    // result in exception.
-    EXPECT_THROW(factory(Option::V6, D6O_RAPID_COMMIT,OptionBuffer(2)),isc::BadValue);
 }
 
-TEST_F(OptionDefinitionTest, factoryBinary) {
+// The purpose of this test is to verify that definition can be
+// creates for the option that holds binary data.
+TEST_F(OptionDefinitionTest, binary) {
     // Binary option is the one that is represented by the generic
     // Option class. In fact all options can be represented by this
     // class but for some of them it is just natural. The SERVERID
     // option consists of the option code, length and binary data so
     // this one was picked for this test.
     OptionDefinition opt_def("OPTION_SERVERID", D6O_SERVERID, "binary");
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     // Prepare some dummy data (serverid): 0, 1, 2 etc.
     OptionBuffer buf(14);
@@ -302,7 +429,7 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
     // object of the type Option should be returned.
     OptionPtr option_v6;
     ASSERT_NO_THROW(
-        option_v6 = factory(Option::V6, D6O_SERVERID, buf);
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_SERVERID, buf);
     );
     // Expect base option type returned.
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
@@ -320,7 +447,7 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
 
     // Repeat the same test scenario for DHCPv4 option.
     OptionPtr option_v4;
-    ASSERT_NO_THROW(option_v4 = factory(Option::V4, 214, buf));
+    ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, buf));
     // Expect 'empty' DHCPv4 option.
     EXPECT_EQ(Option::V4, option_v4->getUniverse());
     EXPECT_EQ(2, option_v4->getHeaderLen());
@@ -331,19 +458,19 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
                            buf.begin()));
 }
 
-TEST_F(OptionDefinitionTest, factoryIA6) {
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IA_NA option is used. This option comprises three uint32 fields.
+TEST_F(OptionDefinitionTest, recordIA6) {
     // This option consists of IAID, T1 and T2 fields (each 4 bytes long).
     const int option6_ia_len = 12;
 
     // Get the factory function pointer.
-    OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, "record", true);
+    OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, "record", false);
     // Each data field is uint32.
     for (int i = 0; i < 3; ++i) {
         EXPECT_NO_THROW(opt_def.addRecordField("uint32"));
     }
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     // Check the positive scenario.
     OptionBuffer buf(12);
@@ -351,7 +478,7 @@ TEST_F(OptionDefinitionTest, factoryIA6) {
         buf[i] = i;
     }
     OptionPtr option_v6;
-    ASSERT_NO_THROW(option_v6 = factory(Option::V6, D6O_IA_NA, buf));
+    ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IA_NA, buf));
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IA));
     boost::shared_ptr<Option6IA> option_cast_v6 =
         boost::static_pointer_cast<Option6IA>(option_v6);
@@ -359,25 +486,18 @@ TEST_F(OptionDefinitionTest, factoryIA6) {
     EXPECT_EQ(0x04050607, option_cast_v6->getT1());
     EXPECT_EQ(0x08090A0B, option_cast_v6->getT2());
 
-    // This should work for DHCPv6 only, try passing invalid universe value.
-    EXPECT_THROW(
-        factory(Option::V4, D6O_IA_NA, OptionBuffer(option6_ia_len)),
-        isc::BadValue
-    );
-    // The length of the buffer must be 12 bytes.
+    // The length of the buffer must be at least 12 bytes.
     // Check too short buffer.
     EXPECT_THROW(
-        factory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)),
-        isc::OutOfRange
+        opt_def.optionFactory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)),
+        InvalidOptionValue
      );
-    // Check too long buffer.
-    EXPECT_THROW(
-        factory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len + 1)),
-        isc::OutOfRange
-    );
 }
 
-TEST_F(OptionDefinitionTest, factoryIAAddr6) {
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IAADDR option is used.
+TEST_F(OptionDefinitionTest, recordIAAddr6) {
     // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and
     // valid-lifetime fields (each 4 bytes long).
     const int option6_iaaddr_len = 24;
@@ -386,9 +506,6 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) {
     ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
     ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
     ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     // Check the positive scenario.
     OptionPtr option_v6;
@@ -403,7 +520,7 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) {
     for (int i = 0; i < option6_iaaddr_len - asiolink::V6ADDRESS_LEN; ++i) {
         buf.push_back(i);
     }
-    ASSERT_NO_THROW(option_v6 = factory(Option::V6, D6O_IAADDR, buf));
+    ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, buf));
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IAAddr));
     boost::shared_ptr<Option6IAAddr> option_cast_v6 =
         boost::static_pointer_cast<Option6IAAddr>(option_v6);
@@ -411,44 +528,54 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) {
     EXPECT_EQ(0x00010203, option_cast_v6->getPreferred());
     EXPECT_EQ(0x04050607, option_cast_v6->getValid());
 
-    // This should work for DHCPv6 only, try passing invalid universe value.
-    EXPECT_THROW(
-        factory(Option::V4, D6O_IAADDR, OptionBuffer(option6_iaaddr_len)),
-        isc::BadValue
-    );
-    // The length of the buffer must be 12 bytes.
+    // The length of the buffer must be at least 12 bytes.
     // Check too short buffer.
     EXPECT_THROW(
-        factory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)),
-        isc::OutOfRange
+        opt_def.optionFactory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)),
+        InvalidOptionValue
      );
-    // Check too long buffer.
-    EXPECT_THROW(
-        factory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len + 1)),
-        isc::OutOfRange
-    );
 }
 
-TEST_F(OptionDefinitionTest, factoryIntegerInvalidType) {
-    // The template function factoryInteger<> accepts integer values only
-    // as template typename. Here we try passing different type and
-    // see if it rejects it.
-    EXPECT_THROW(
-        OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, OptionBuffer(1)),
-        isc::dhcp::InvalidDataType
-    );
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IAADDR option is used. The data for the option is speicifed as
+// a vector of strings. Each string carries the data for the corresponding
+// data field.
+TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) {
+    // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and
+    // valid-lifetime fields (each 4 bytes long).
+    OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, "record");
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+    ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+    // Check the positive scenario.
+    std::vector<std::string> data_field_values;
+    data_field_values.push_back("2001:0db8::ff00:0042:8329");
+    data_field_values.push_back("1234");
+    data_field_values.push_back("5678");
+
+    OptionPtr option_v6;
+    ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR,
+                                                      data_field_values));
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IAAddr));
+    boost::shared_ptr<Option6IAAddr> option_cast_v6 =
+        boost::static_pointer_cast<Option6IAAddr>(option_v6);
+    EXPECT_EQ("2001:db8::ff00:42:8329", option_cast_v6->getAddress().toText());
+    EXPECT_EQ(1234, option_cast_v6->getPreferred());
+    EXPECT_EQ(5678, option_cast_v6->getValid());
 }
 
-TEST_F(OptionDefinitionTest, factoryUint8) {
+// The purpose of this test is to verify that definition for option that
+// comprises single uint8 value can be created and that this definition
+// can be used to create an option with single uint8 value.
+TEST_F(OptionDefinitionTest, uint8) {
     OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, "uint8");
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     OptionPtr option_v6;
     // Try to use correct buffer length = 1 byte.
     ASSERT_NO_THROW(
-        option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer(1, 1));
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer(1, 1));
     );
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint8_t>));
     // Validate the value.
@@ -456,26 +583,42 @@ TEST_F(OptionDefinitionTest, factoryUint8) {
         boost::static_pointer_cast<Option6Int<uint8_t> >(option_v6);
     EXPECT_EQ(1, option_cast_v6->getValue());
 
-    // Try to provide too large buffer. Expect exception.
+    // Try to provide zero-length buffer. Expect exception.
     EXPECT_THROW(
-        option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer(3)),
-        isc::OutOfRange
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer()),
+        InvalidOptionValue
     );
 
-    // Try to provide zero-length buffer. Expect exception.
-    EXPECT_THROW(
-        option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer()),
-        isc::OutOfRange
+    // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint8 value can be created and that this definition
+// can be used to create an option with single uint8 value.
+TEST_F(OptionDefinitionTest, uint8Tokenized) {
+    OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, "uint8");
+
+    OptionPtr option_v6;
+    std::vector<std::string> values;
+    values.push_back("123");
+    values.push_back("456");
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values);
     );
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint8_t>));
+    // Validate the value.
+    boost::shared_ptr<Option6Int<uint8_t> > option_cast_v6 =
+        boost::static_pointer_cast<Option6Int<uint8_t> >(option_v6);
+    EXPECT_EQ(123, option_cast_v6->getValue());
 
     // @todo Add more cases for DHCPv4
 }
 
-TEST_F(OptionDefinitionTest, factoryUint16) {
+// The purpose of this test is to verify that definition for option that
+// comprises single uint16 value can be created and that this definition
+// can be used to create an option with single uint16 value.
+TEST_F(OptionDefinitionTest, uint16) {
     OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, "uint16");
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     OptionPtr option_v6;
     // Try to use correct buffer length = 2 bytes.
@@ -483,7 +626,7 @@ TEST_F(OptionDefinitionTest, factoryUint16) {
     buf.push_back(1);
     buf.push_back(2);
     ASSERT_NO_THROW(
-        option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, buf);
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, buf);
     );
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint16_t>));
     // Validate the value.
@@ -491,25 +634,44 @@ TEST_F(OptionDefinitionTest, factoryUint16) {
         boost::static_pointer_cast<Option6Int<uint16_t> >(option_v6);
     EXPECT_EQ(0x0102, option_cast_v6->getValue());
 
-    // Try to provide too large buffer. Expect exception.
-    EXPECT_THROW(
-        option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(3)),
-        isc::OutOfRange
-    );
     // Try to provide zero-length buffer. Expect exception.
     EXPECT_THROW(
-        option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)),
-        isc::OutOfRange
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)),
+        InvalidOptionValue
     );
 
     // @todo Add more cases for DHCPv4
 }
 
-TEST_F(OptionDefinitionTest, factoryUint32) {
+// The purpose of this test is to verify that definition for option that
+// comprises single uint16 value can be created and that this definition
+// can be used to create an option with single uint16 value.
+TEST_F(OptionDefinitionTest, uint16Tokenized) {
+    OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, "uint16");
+
+    OptionPtr option_v6;
+
+    std::vector<std::string> values;
+    values.push_back("1234");
+    values.push_back("5678");
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, values);
+    );
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint16_t>));
+    // Validate the value.
+    boost::shared_ptr<Option6Int<uint16_t> > option_cast_v6 =
+        boost::static_pointer_cast<Option6Int<uint16_t> >(option_v6);
+    EXPECT_EQ(1234, option_cast_v6->getValue());
+
+    // @todo Add more cases for DHCPv4
+
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint32 value can be created and that this definition
+// can be used to create an option with single uint32 value.
+TEST_F(OptionDefinitionTest, uint32) {
     OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, "uint32");
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     OptionPtr option_v6;
     OptionBuffer buf;
@@ -518,7 +680,7 @@ TEST_F(OptionDefinitionTest, factoryUint32) {
     buf.push_back(3);
     buf.push_back(4);
     ASSERT_NO_THROW(
-        option_v6 = factory(Option::V6, D6O_CLT_TIME, buf);
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, buf);
     );
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint32_t>));
     // Validate the value.
@@ -526,27 +688,44 @@ TEST_F(OptionDefinitionTest, factoryUint32) {
         boost::static_pointer_cast<Option6Int<uint32_t> >(option_v6);
     EXPECT_EQ(0x01020304, option_cast_v6->getValue());
 
-    // Try to provide too large buffer. Expect exception.
+    // Try to provide too short buffer. Expect exception.
     EXPECT_THROW(
-        option_v6 = factory(Option::V6, D6O_CLT_TIME, OptionBuffer(5)),
-        isc::OutOfRange
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)),
+        InvalidOptionValue
     );
-    // Try to provide zero-length buffer. Expect exception.
-    EXPECT_THROW(
-        option_v6 = factory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)),
-        isc::OutOfRange
+
+    // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint32 value can be created and that this definition
+// can be used to create an option with single uint32 value.
+TEST_F(OptionDefinitionTest, uint32Tokenized) {
+    OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, "uint32");
+
+    OptionPtr option_v6;
+    std::vector<std::string> values;
+    values.push_back("123456");
+    values.push_back("789");
+    ASSERT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, values);
     );
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint32_t>));
+    // Validate the value.
+    boost::shared_ptr<Option6Int<uint32_t> > option_cast_v6 =
+        boost::static_pointer_cast<Option6Int<uint32_t> >(option_v6);
+    EXPECT_EQ(123456, option_cast_v6->getValue());
 
     // @todo Add more cases for DHCPv4
 }
 
-TEST_F(OptionDefinitionTest, factoryUint16Array) {
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint16 values can be created and that this definition
+// can be used to create option with an array of uint16 values.
+TEST_F(OptionDefinitionTest, uint16Array) {
     // Let's define some dummy option.
     const uint16_t opt_code = 79;
     OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "uint16", true);
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     OptionPtr option_v6;
     // Positive scenario, initiate the buffer with length being
@@ -558,7 +737,7 @@ TEST_F(OptionDefinitionTest, factoryUint16Array) {
     }
     // Constructor should succeed because buffer has correct size.
     EXPECT_NO_THROW(
-        option_v6 = factory(Option::V6, opt_code, buf);
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf);
     );
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint16_t>));
     boost::shared_ptr<Option6IntArray<uint16_t> > option_cast_v6 =
@@ -576,24 +755,50 @@ TEST_F(OptionDefinitionTest, factoryUint16Array) {
     // Provided buffer size must be greater than zero. Check if we
     // get exception if we provide zero-length buffer.
     EXPECT_THROW(
-        option_v6 = factory(Option::V6, opt_code, OptionBuffer()),
-        isc::OutOfRange
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()),
+        InvalidOptionValue
     );
     // Buffer length must be multiple of data type size.
     EXPECT_THROW(
-        option_v6 = factory(Option::V6, opt_code, OptionBuffer(5)),
-        isc::OutOfRange
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)),
+        InvalidOptionValue
+    );
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint16 values can be created and that this definition
+// can be used to create option with an array of uint16 values.
+TEST_F(OptionDefinitionTest, uint16ArrayTokenized) {
+    // Let's define some dummy option.
+    const uint16_t opt_code = 79;
+    OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "uint16", true);
+
+    OptionPtr option_v6;
+    std::vector<std::string> str_values;
+    str_values.push_back("12345");
+    str_values.push_back("5679");
+    str_values.push_back("12");
+    EXPECT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values);
     );
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint16_t>));
+    boost::shared_ptr<Option6IntArray<uint16_t> > option_cast_v6 =
+        boost::static_pointer_cast<Option6IntArray<uint16_t> >(option_v6);
+    // Get the values from the initiated options and validate.
+    std::vector<uint16_t> values = option_cast_v6->getValues();
+    EXPECT_EQ(12345, values[0]);
+    EXPECT_EQ(5679, values[1]);
+    EXPECT_EQ(12, values[2]);
 }
 
-TEST_F(OptionDefinitionTest, factoryUint32Array) {
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint32 values can be created and that this definition
+// can be used to create option with an array of uint32 values.
+TEST_F(OptionDefinitionTest, uint32Array) {
     // Let's define some dummy option.
     const uint16_t opt_code = 80;
 
     OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "uint32", true);
-    Option::Factory* factory(NULL);
-    EXPECT_NO_THROW(factory = opt_def.getFactory());
-    ASSERT_TRUE(factory != NULL);
 
     OptionPtr option_v6;
     // Positive scenario, initiate the buffer with length being
@@ -605,7 +810,7 @@ TEST_F(OptionDefinitionTest, factoryUint32Array) {
     }
     // Constructor should succeed because buffer has correct size.
     EXPECT_NO_THROW(
-        option_v6 = factory(Option::V6, opt_code, buf);
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf);
     );
     ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint32_t>));
     boost::shared_ptr<Option6IntArray<uint32_t> > option_cast_v6 =
@@ -623,16 +828,85 @@ TEST_F(OptionDefinitionTest, factoryUint32Array) {
     // Provided buffer size must be greater than zero. Check if we
     // get exception if we provide zero-length buffer.
     EXPECT_THROW(
-        option_v6 = factory(Option::V6, opt_code, OptionBuffer()),
-        isc::OutOfRange
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()),
+        InvalidOptionValue
     );
     // Buffer length must be multiple of data type size.
     EXPECT_THROW(
-        option_v6 = factory(Option::V6, opt_code, OptionBuffer(5)),
-        isc::OutOfRange
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)),
+        InvalidOptionValue
+    );
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint32 values can be created and that this definition
+// can be used to create option with an array of uint32 values.
+TEST_F(OptionDefinitionTest, uint32ArrayTokenized) {
+    // Let's define some dummy option.
+    const uint16_t opt_code = 80;
+
+    OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "uint32", true);
+
+    OptionPtr option_v6;
+    std::vector<std::string> str_values;
+    str_values.push_back("123456");
+    str_values.push_back("7");
+    str_values.push_back("256");
+    str_values.push_back("1111");
+
+    EXPECT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values);
+    );
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint32_t>));
+    boost::shared_ptr<Option6IntArray<uint32_t> > option_cast_v6 =
+        boost::static_pointer_cast<Option6IntArray<uint32_t> >(option_v6);
+    // Get the values from the initiated options and validate.
+    std::vector<uint32_t> values = option_cast_v6->getValues();
+    EXPECT_EQ(123456, values[0]);
+    EXPECT_EQ(7, values[1]);
+    EXPECT_EQ(256, values[2]);
+    EXPECT_EQ(1111, values[3]);
+}
+
+// The purpose of this test is to verify that the definition can be created
+// for the option that comprises string value in the UTF8 format.
+TEST_F(OptionDefinitionTest, utf8StringTokenized) {
+    // Let's create some dummy option.
+    const uint16_t opt_code = 80;
+    OptionDefinition opt_def("OPTION_WITH_STRING", opt_code, "string");
+    
+    std::vector<std::string> values;
+    values.push_back("Hello World");
+    values.push_back("this string should not be included in the option");
+    OptionPtr option_v6;
+    EXPECT_NO_THROW(
+        option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
+    );
+    ASSERT_TRUE(option_v6);
+    ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
+    std::vector<uint8_t> data = option_v6->getData();
+    std::vector<uint8_t> ref_data(values[0].c_str(), values[0].c_str()
+                                  + values[0].length());
+    EXPECT_TRUE(std::equal(ref_data.begin(), ref_data.end(), data.begin()));
+}
+
+// The purpose of this test is to check that non-integer data type can't
+// be used for factoryInteger function.
+TEST_F(OptionDefinitionTest, integerInvalidType) {
+    // The template function factoryInteger<> accepts integer values only
+    // as template typename. Here we try passing different type and
+    // see if it rejects it.
+    OptionBuffer buf(1);
+    EXPECT_THROW(
+        OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE,
+                                               buf.begin(), buf.end()),
+        isc::dhcp::InvalidDataType
     );
 }
 
+// The purpose of this test is to verify that helper methods
+// haveIA6Format and haveIAAddr6Format can be used to determine
+// IA_NA  and IAADDR option formats.
 TEST_F(OptionDefinitionTest, recognizeFormat) {
     // IA_NA option format.
     OptionDefinition opt_def1("OPTION_IA_NA", D6O_IA_NA, "record");

+ 2 - 2
src/lib/dhcpsrv/addr_utilities.cc

@@ -79,7 +79,7 @@ isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& pref
     }
 
     // Finally, let's wrap this into nice and easy IOAddress object.
-    return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
+    return (isc::asiolink::IOAddress::fromBytes(AF_INET6, packed));
 }
 
 /// @brief calculates the first IPv4 address in a IPv4 prefix
@@ -159,7 +159,7 @@ isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefi
     }
 
     // Finally, let's wrap this into nice and easy IOAddress object.
-    return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
+    return (isc::asiolink::IOAddress::fromBytes(AF_INET6, packed));
 }
 
 }; // end of anonymous namespace

+ 1 - 1
src/lib/dhcpsrv/alloc_engine.cc

@@ -51,7 +51,7 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
         }
     }
 
-    return (IOAddress::from_bytes(addr.getFamily(), packed));
+    return (IOAddress::fromBytes(addr.getFamily(), packed));
 }
 
 

+ 1 - 0
src/lib/dhcpsrv/tests/.gitignore

@@ -0,0 +1 @@
+/libdhcpsrv_unittests

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

@@ -119,6 +119,7 @@ libb10_dns___la_SOURCES += tsigerror.h tsigerror.cc
 libb10_dns___la_SOURCES += tsigkey.h tsigkey.cc
 libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
 libb10_dns___la_SOURCES += character_string.h character_string.cc
+libb10_dns___la_SOURCES += master_loader_callbacks.h
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
 libb10_dns___la_SOURCES += rdata/generic/detail/nsec3param_common.cc

+ 224 - 15
src/lib/dns/master_lexer.cc

@@ -19,7 +19,9 @@
 #include <dns/master_lexer_state.h>
 
 #include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
 
+#include <bitset>
 #include <cassert>
 #include <string>
 #include <vector>
@@ -29,20 +31,33 @@ namespace dns {
 
 namespace {
 typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr;
-}
+} // end unnamed namespace
 using namespace master_lexer_internal;
 
+
 struct MasterLexer::MasterLexerImpl {
     MasterLexerImpl() : source_(NULL), token_(Token::NOT_STARTED),
-                        paren_count_(0), last_was_eol_(false)
-    {}
+                        paren_count_(0), last_was_eol_(false),
+                        has_previous_(false),
+                        previous_paren_count_(0),
+                        previous_was_eol_(false)
+    {
+        separators_.set('\r');
+        separators_.set('\n');
+        separators_.set(' ');
+        separators_.set('\t');
+        separators_.set('(');
+        separators_.set(')');
+        esc_separators_.set('\r');
+        esc_separators_.set('\n');
+    }
 
     // A helper method to skip possible comments toward the end of EOL or EOF.
     // commonly used by state classes.  It returns the corresponding "end-of"
     // character in case it's a comment; otherwise it simply returns the
     // current character.
-    int skipComment(int c) {
-        if (c == ';') {
+    int skipComment(int c, bool escaped = false) {
+        if (c == ';' && !escaped) {
             while (true) {
                 c = source_->getChar();
                 if (c == '\n' || c == InputSource::END_OF_STREAM) {
@@ -53,14 +68,39 @@ struct MasterLexer::MasterLexerImpl {
         return (c);
     }
 
+    bool isTokenEnd(int c, bool escaped) {
+        // Special case of EOF (end of stream); this is not in the bitmaps
+        if (c == InputSource::END_OF_STREAM) {
+            return (true);
+        }
+        // In this implementation we only ensure the behavior for unsigned
+        // range of characters, so we restrict the range of the values up to
+        // 0x7f = 127
+        return (escaped ? esc_separators_.test(c & 0x7f) :
+                separators_.test(c & 0x7f));
+    }
+
     std::vector<InputSourcePtr> sources_;
     InputSource* source_;       // current source (NULL if sources_ is empty)
     Token token_;               // currently recognized token (set by a state)
+    std::vector<char> data_;    // placeholder for string data
 
     // These are used in states, and defined here only as a placeholder.
     // The main lexer class does not need these members.
     size_t paren_count_;        // nest count of the parentheses
     bool last_was_eol_; // whether the lexer just passed an end-of-line
+
+    // Bitmaps that gives whether a given (positive) character should be
+    // considered a separator of a string/number token.  The esc_ version
+    // is a subset of the other, excluding characters that can be ignored
+    // if escaped by a backslash.  See isTokenEnd() for the bitmap size.
+    std::bitset<128> separators_;
+    std::bitset<128> esc_separators_;
+
+    // These are to allow restoring state before previous token.
+    bool has_previous_;
+    size_t previous_paren_count_;
+    bool previous_was_eol_;
 };
 
 MasterLexer::MasterLexer() : impl_(new MasterLexerImpl) {
@@ -86,6 +126,7 @@ MasterLexer::pushSource(const char* filename, std::string* error) {
     }
 
     impl_->source_ = impl_->sources_.back().get();
+    impl_->has_previous_ = false;
     return (true);
 }
 
@@ -93,6 +134,7 @@ void
 MasterLexer::pushSource(std::istream& input) {
     impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
     impl_->source_ = impl_->sources_.back().get();
+    impl_->has_previous_ = false;
 }
 
 void
@@ -104,6 +146,7 @@ MasterLexer::popSource() {
     impl_->sources_.pop_back();
     impl_->source_ = impl_->sources_.empty() ? NULL :
         impl_->sources_.back().get();
+    impl_->has_previous_ = false;
 }
 
 std::string
@@ -122,15 +165,57 @@ MasterLexer::getSourceLine() const {
     return (impl_->sources_.back()->getCurrentLine());
 }
 
+const MasterLexer::Token&
+MasterLexer::getNextToken(Options options) {
+    // If the source is not available
+    if (impl_->source_ == NULL) {
+        isc_throw(isc::InvalidOperation, "No source to read tokens from");
+    }
+    // Store the current state so we can restore it in ungetToken
+    impl_->previous_paren_count_ = impl_->paren_count_;
+    impl_->previous_was_eol_ = impl_->last_was_eol_;
+    impl_->source_->mark();
+    impl_->has_previous_ = true;
+    // Reset the token now. This is to check a token was actually produced.
+    // This is debugging aid.
+    impl_->token_ = Token(Token::NO_TOKEN_PRODUCED);
+    // And get the token
+
+    // This actually handles EOF internally too.
+    const State* state = State::start(*this, options);
+    if (state != NULL) {
+        state->handle(*this);
+    }
+    // Make sure a token was produced. Since this Can Not Happen, we assert
+    // here instead of throwing.
+    assert(impl_->token_.getType() != Token::ERROR ||
+           impl_->token_.getErrorCode() != Token::NO_TOKEN_PRODUCED);
+    return (impl_->token_);
+}
+
+void
+MasterLexer::ungetToken() {
+    if (impl_->has_previous_) {
+        impl_->has_previous_ = false;
+        impl_->source_->ungetAll();
+        impl_->last_was_eol_ = impl_->previous_was_eol_;
+        impl_->paren_count_ = impl_->previous_paren_count_;
+    } else {
+        isc_throw(isc::InvalidOperation, "No token to unget ready");
+    }
+}
+
 namespace {
 const char* const error_text[] = {
     "lexer not started",        // NOT_STARTED
     "unbalanced parentheses",   // UNBALANCED_PAREN
     "unexpected end of input",  // UNEXPECTED_END
-    "unbalanced quotes"         // UNBALANCED_QUOTES
+    "unbalanced quotes",        // UNBALANCED_QUOTES
+    "no token produced",        // NO_TOKEN_PRODUCED
+    "number out of range"       // NUMBER_OUT_OF_RANGE
 };
 const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]);
-}
+} // end unnamed namespace
 
 std::string
 MasterLexer::Token::getErrorText() const {
@@ -171,7 +256,7 @@ class CRLF : public State {
 public:
     CRLF() {}
     virtual ~CRLF() {}          // see the base class for the destructor
-    virtual const State* handle(MasterLexer& lexer) const {
+    virtual void handle(MasterLexer& lexer) const {
         // We've just seen '\r'.  If this is part of a sequence of '\r\n',
         // we combine them as a single END-OF-LINE.  Otherwise we treat the
         // single '\r' as an EOL and continue tokeniziation from the character
@@ -188,18 +273,28 @@ public:
         }
         getLexerImpl(lexer)->token_ = Token(Token::END_OF_LINE);
         getLexerImpl(lexer)->last_was_eol_ = true;
-        return (NULL);
     }
 };
 
-// Currently this is provided mostly as a place holder
 class String : public State {
 public:
     String() {}
     virtual ~String() {}      // see the base class for the destructor
-    virtual const State* handle(MasterLexer& /*lexer*/) const {
-        return (NULL);
-    }
+    virtual void handle(MasterLexer& lexer) const;
+};
+
+class QString : public State {
+public:
+    QString() {}
+    virtual ~QString() {}      // see the base class for the destructor
+    virtual void handle(MasterLexer& lexer) const;
+};
+
+class Number : public State {
+public:
+    Number() {}
+    virtual ~Number() {}
+    virtual void handle(MasterLexer& lexer) const;
 };
 
 // We use a common instance of a each state in a singleton-like way to save
@@ -209,7 +304,9 @@ public:
 // this file.
 const CRLF CRLF_STATE;
 const String STRING_STATE;
-}
+const QString QSTRING_STATE;
+const Number NUMBER_STATE;
+} // end unnamed namespace
 
 const State&
 State::getInstance(ID state_id) {
@@ -218,6 +315,10 @@ State::getInstance(ID state_id) {
         return (CRLF_STATE);
     case String:
         return (STRING_STATE);
+    case QString:
+        return (QSTRING_STATE);
+    case Number:
+        return (NUMBER_STATE);
     }
 
     // This is a bug of the caller, and this method is only expected to be
@@ -233,6 +334,9 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
     MasterLexer::MasterLexerImpl& lexerimpl = *lexer.impl_;
     size_t& paren_count = lexerimpl.paren_count_;
 
+    // Note: the if-else in the loop is getting complicated.  When we complete
+    // #2374, revisit the organization to see if we need a fundamental
+    // refactoring.
     while (true) {
         const int c = lexerimpl.skipComment(lexerimpl.source_->getChar());
         if (c == InputSource::END_OF_STREAM) {
@@ -262,6 +366,9 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
             if (paren_count == 0) { // check if we are in () (see above)
                 return (&CRLF_STATE);
             }
+        } else if (c == '"' && (options & MasterLexer::QSTRING) != 0) {
+            lexerimpl.last_was_eol_ = false;
+            return (&QSTRING_STATE);
         } else if (c == '(') {
             lexerimpl.last_was_eol_ = false;
             ++paren_count;
@@ -272,8 +379,14 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
                 return (NULL);
             }
             --paren_count;
+        } else if ((options & MasterLexer::NUMBER) != 0 &&isdigit(c)) {
+            lexerimpl.last_was_eol_ = false;
+            // this character will be handled in the number state
+            lexerimpl.source_->ungetChar();
+            return (&NUMBER_STATE);
         } else {
-            // Note: in #2373 we should probably ungetChar().
+            // this character will be handled in the string state
+            lexerimpl.source_->ungetChar();
             lexerimpl.last_was_eol_ = false;
             return (&STRING_STATE);
         }
@@ -281,6 +394,102 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
     }
 }
 
+void
+String::handle(MasterLexer& lexer) const {
+    std::vector<char>& data = getLexerImpl(lexer)->data_;
+    data.clear();
+
+    bool escaped = false;
+    while (true) {
+        const int c = getLexerImpl(lexer)->skipComment(
+            getLexerImpl(lexer)->source_->getChar(), escaped);
+
+        if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
+            getLexerImpl(lexer)->source_->ungetChar();
+            getLexerImpl(lexer)->token_ =
+                MasterLexer::Token(&data.at(0), data.size());
+            return;
+        }
+        escaped = (c == '\\' && !escaped);
+        data.push_back(c);
+    }
+}
+
+void
+QString::handle(MasterLexer& lexer) const {
+    MasterLexer::Token& token = getLexerImpl(lexer)->token_;
+    std::vector<char>& data = getLexerImpl(lexer)->data_;
+    data.clear();
+
+    bool escaped = false;
+    while (true) {
+        const int c = getLexerImpl(lexer)->source_->getChar();
+        if (c == InputSource::END_OF_STREAM) {
+            token = Token(Token::UNEXPECTED_END);
+            return;
+        } else if (c == '"') {
+            if (escaped) {
+                // found escaped '"'. overwrite the preceding backslash.
+                assert(!data.empty());
+                escaped = false;
+                data.back() = '"';
+            } else {
+                token = MasterLexer::Token(&data.at(0), data.size(), true);
+                return;
+            }
+        } else if (c == '\n' && !escaped) {
+            getLexerImpl(lexer)->source_->ungetChar();
+            token = Token(Token::UNBALANCED_QUOTES);
+            return;
+        } else {
+            escaped = (c == '\\' && !escaped);
+            data.push_back(c);
+        }
+    }
+}
+
+void
+Number::handle(MasterLexer& lexer) const {
+    MasterLexer::Token& token = getLexerImpl(lexer)->token_;
+
+    // It may yet turn out to be a string, so we first
+    // collect all the data
+    bool digits_only = true;
+    std::vector<char>& data = getLexerImpl(lexer)->data_;
+    data.clear();
+    bool escaped = false;
+
+    while (true) {
+        const int c = getLexerImpl(lexer)->skipComment(
+            getLexerImpl(lexer)->source_->getChar(), escaped);
+        if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
+            getLexerImpl(lexer)->source_->ungetChar();
+            if (digits_only) {
+                // Close the string for lexical_cast
+                data.push_back('\0');
+                try {
+                    const uint32_t number32 =
+                        boost::lexical_cast<uint32_t, const char*>(&data[0]);
+                    token = MasterLexer::Token(number32);
+                } catch (const boost::bad_lexical_cast&) {
+                    // Since we already know we have only digits,
+                    // range should be the only possible problem.
+                    token = Token(Token::NUMBER_OUT_OF_RANGE);
+                }
+            } else {
+                token = MasterLexer::Token(&data.at(0),
+                                           data.size());
+            }
+            return;
+        }
+        if (!isdigit(c)) {
+            digits_only = false;
+        }
+        escaped = (c == '\\' && !escaped);
+        data.push_back(c);
+    }
+}
+
 } // namespace master_lexer_internal
 
 } // end of namespace dns

+ 85 - 6
src/lib/dns/master_lexer.h

@@ -69,6 +69,14 @@ class State;
 class MasterLexer {
     friend class master_lexer_internal::State;
 public:
+    /// \brief Exception thrown when we fail to read from the input
+    /// stream or file.
+    struct ReadError : public Unexpected {
+        ReadError(const char* file, size_t line, const char* what) :
+            Unexpected(file, line, what)
+        {}
+    };
+
     class Token;       // we define it separately for better readability
 
     /// \brief Options for getNextToken.
@@ -178,6 +186,52 @@ public:
     /// \return The current line number of the source (see the description)
     size_t getSourceLine() const;
 
+    /// \brief Parse and return another token from the input.
+    ///
+    /// It reads a bit of the last opened source and produces another token
+    /// found in it.
+    ///
+    /// This method does not provide the strong exception guarantee. Generally,
+    /// if it throws, the object should not be used any more and should be
+    /// discarded. It was decided all the exceptions thrown from here are
+    /// serious enough that aborting the loading process is the only reasonable
+    /// recovery anyway, so the strong exception guarantee is not needed.
+    ///
+    /// \param options The options can be used to modify the tokenization.
+    ///     The method can be made reporting things which are usually ignored
+    ///     by this parameter. Multiple options can be passed at once by
+    ///     bitwise or (eg. option1 | option 2). See description of available
+    ///     options.
+    /// \return Next token found in the input. Note that the token refers to
+    ///     some internal data in the lexer. It is valid only until
+    ///     getNextToken or ungetToken is called. Also, the token becomes
+    ///     invalid when the lexer is destroyed.
+    /// \throw isc::InvalidOperation in case the source is not available. This
+    ///     may mean the pushSource() has not been called yet, or that the
+    ///     current source has been read past the end.
+    /// \throw ReadError in case there's problem reading from the underlying
+    ///     source (eg. I/O error in the file on the disk).
+    /// \throw std::bad_alloc in case allocation of some internal resources
+    ///     or the token fail.
+    const Token& getNextToken(Options options = NONE);
+
+    /// \brief Return the last token back to the lexer.
+    ///
+    /// The method undoes the lasts call to getNextToken(). If you call the
+    /// getNextToken() again with the same options, it'll return the same
+    /// token. If the options are different, it may return a different token,
+    /// but it acts as if the previous getNextToken() was never called.
+    ///
+    /// It is possible to return only one token back in time (you can't call
+    /// ungetToken() twice in a row without calling getNextToken() in between
+    /// successfully).
+    ///
+    /// It does not work after change of source (by pushSource or popSource).
+    ///
+    /// \throw isc::InvalidOperation If called second time in a row or if
+    ///     getNextToken() was not called since the last change of the source.
+    void ungetToken();
+
 private:
     struct MasterLexerImpl;
     MasterLexerImpl* impl_;
@@ -216,10 +270,10 @@ public:
     /// as an unsigned 32-bit integer.  If we see the need for larger integers
     /// or negative numbers, we can then extend the token types.
     enum Type {
-        END_OF_LINE, ///< End of line detected (if asked for detecting it)
-        END_OF_FILE, ///< End of file detected (if asked for detecting it)
+        END_OF_LINE, ///< End of line detected
+        END_OF_FILE, ///< End of file detected
         INITIAL_WS,  ///< White spaces at the beginning of a line after an
-                     ///< end of line
+                     ///< end of line (if asked for detecting it)
         NOVALUE_TYPE_MAX = INITIAL_WS, ///< Max integer corresponding to
                                        /// no-value (type only) types.
                                        /// Mainly for internal use.
@@ -234,8 +288,11 @@ public:
         NOT_STARTED, ///< The lexer is just initialized and has no token
         UNBALANCED_PAREN,       ///< Unbalanced parentheses detected
         UNEXPECTED_END, ///< The lexer reaches the end of line or file
-                       /// unexpectedly
+                        /// unexpectedly
         UNBALANCED_QUOTES,      ///< Unbalanced quotations detected
+        NO_TOKEN_PRODUCED, ///< No token was produced. This means programmer
+                           /// error and should never get out of the lexer.
+        NUMBER_OUT_OF_RANGE, ///< Number was out of range
         MAX_ERROR_CODE ///< Max integer corresponding to valid error codes.
                        /// (excluding this one). Mainly for internal use.
     };
@@ -340,12 +397,34 @@ public:
     ///                       string object.
     /// \return A std::string object corresponding to the string token value.
     std::string getString() const {
+        std::string ret;
+        getString(ret);
+        return (ret);
+    }
+
+    /// \brief Fill in a string with the value of a string-variant token.
+    ///
+    /// This is similar to the other version of \c getString(), but
+    /// the caller is supposed to pass a placeholder string object.
+    /// This will be more efficient if the caller uses the same
+    /// \c MasterLexer repeatedly and needs to get string token in the
+    /// form of a string object many times as this version could reuse
+    /// the existing internal storage of the passed string.
+    ///
+    /// Any existing content of the passed string will be removed.
+    ///
+    /// \throw InvalidOperation Called on a non string-variant types of token.
+    /// \throw std::bad_alloc Resource allocation failure in constructing the
+    ///                       string object.
+    ///
+    /// \param ret A string object to be filled with the token string.
+    void getString(std::string& ret) const {
         if (type_ != STRING && type_ != QSTRING) {
             isc_throw(InvalidOperation,
                       "Token::getString() for non string-variant type");
         }
-        return (std::string(val_.str_region_.beg,
-                            val_.str_region_.beg + val_.str_region_.len));
+        ret.assign(val_.str_region_.beg,
+                   val_.str_region_.beg + val_.str_region_.len);
     }
 
     /// \brief Return the value of a string-variant token as a string object.

+ 2 - 1
src/lib/dns/master_lexer_inputsource.cc

@@ -13,6 +13,7 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
 
 #include <cerrno>
 #include <cstring>
@@ -94,7 +95,7 @@ InputSource::getChar() {
         // This has to come after the .eof() check as some
         // implementations seem to check the eofbit also in .fail().
         if (input_.fail()) {
-            isc_throw(ReadError,
+            isc_throw(MasterLexer::ReadError,
                       "Error reading from the input stream: " << getName());
         }
         buffer_.push_back(c);

+ 2 - 10
src/lib/dns/master_lexer_inputsource.h

@@ -56,14 +56,6 @@ public:
         {}
     };
 
-    /// \brief Exception thrown when we fail to read from the input
-    /// stream or file.
-    struct ReadError : public Unexpected {
-        ReadError(const char* file, size_t line, const char* what) :
-            Unexpected(file, line, what)
-        {}
-    };
-
     /// \brief Exception thrown when we fail to open the input file.
     struct OpenError : public Unexpected {
         OpenError(const char* file, size_t line, const char* what) :
@@ -124,8 +116,8 @@ public:
     /// \brief Returns a single character from the input source. If end
     /// of file is reached, \c END_OF_STREAM is returned.
     ///
-    /// \throws ReadError when reading from the input stream or file
-    /// fails.
+    /// \throws MasterLexer::ReadError when reading from the input stream or
+    /// file fails.
     int getChar();
 
     /// \brief Skips backward a single character in the input

+ 12 - 8
src/lib/dns/master_lexer_state.h

@@ -17,6 +17,8 @@
 
 #include <dns/master_lexer.h>
 
+#include <boost/function.hpp>
+
 namespace isc {
 namespace dns {
 
@@ -67,7 +69,7 @@ public:
     /// tokenization session.  The lexer passes a reference to itself
     /// and options given in \c getNextToken().
     ///
-    /// \throw InputSource::ReadError Unexpected I/O error
+    /// \throw MasterLexer::ReadError Unexpected I/O error
     /// \throw std::bad_alloc Internal resource allocation failure
     ///
     /// \param lexer The lexer object that holds the main context.
@@ -80,16 +82,16 @@ public:
     /// \brief Handle the process of one specific state.
     ///
     /// This method is expected to be called on the object returned by
-    /// start(), and keep called on the returned object until NULL is
-    /// returned.  The call chain will form the complete state transition.
+    /// start(). In the usual state transition design pattern, it would
+    /// return the next state. But as we noticed, we never have another
+    /// state, so we simplify it by not returning anything instead of
+    /// returning NULL every time.
     ///
-    /// \throw InputSource::ReadError Unexpected I/O error
+    /// \throw MasterLexer::ReadError Unexpected I/O error
     /// \throw std::bad_alloc Internal resource allocation failure
     ///
     /// \param lexer The lexer object that holds the main context.
-    /// \return A pointer to the next state object or NULL if the transition
-    /// is completed.
-    virtual const State* handle(MasterLexer& lexer) const = 0;
+    virtual void handle(MasterLexer& lexer) const = 0;
 
     /// \brief Types of states.
     ///
@@ -98,7 +100,9 @@ public:
     /// a way to get an instance of a specific state.
     enum ID {
         CRLF,                  ///< Just seen a carriage-return character
-        String                 ///< Handling a string token
+        String,                ///< Handling a string token
+        QString,               ///< Handling a quoted string token
+        Number                 ///< Handling a number
     };
 
     /// \brief Returns a \c State instance of the given state.

+ 122 - 0
src/lib/dns/master_loader_callbacks.h

@@ -0,0 +1,122 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MASTER_LOADER_CALLBACKS_H
+#define MASTER_LOADER_CALLBACKS_H
+
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dns {
+
+class AbstractRRset;
+typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
+
+/// \brief Type of callback to add a RRset.
+///
+/// This type of callback is used by the loader to report another loaded
+/// RRset. The RRset is no longer preserved by the loader and is fully
+/// owned by the callback.
+///
+/// \param RRset The rrset to add. It does not contain the accompanying
+///     RRSIG (if the zone is signed), they are reported with separate
+///     calls to the callback.
+typedef boost::function<void(const RRsetPtr& rrset)> AddRRsetCallback;
+
+/// \brief Set of issue callbacks for a loader.
+///
+/// This holds a set of callbacks by which a loader (such as MasterLoader)
+/// can report loaded RRsets, errors and other unusual conditions.
+///
+/// All the callbacks must be set.
+class MasterLoaderCallbacks {
+public:
+    /// \brief Type of one callback to report problems.
+    ///
+    /// This is the type of one callback used to report an unusual
+    /// condition or error.
+    ///
+    /// \param source_name The name of the source where the problem happened.
+    ///     This is usually a file name.
+    /// \param source_line Position of the problem, counted in lines from the
+    ///     beginning of the source.
+    /// \param reason Human readable description of what happened.
+    typedef boost::function<void(const std::string& source_name,
+                                 size_t source_line,
+                                 const std::string& reason)> IssueCallback;
+
+    /// \brief Constructor
+    ///
+    /// Initializes the callbacks.
+    ///
+    /// \param error The error callback to use.
+    /// \param warning The warning callback to use.
+    /// \throw isc::InvalidParameter if any of the callbacks is empty.
+    MasterLoaderCallbacks(const IssueCallback& error,
+                          const IssueCallback& warning) :
+        error_(error),
+        warning_(warning)
+    {
+        if (error_.empty() || warning_.empty()) {
+            isc_throw(isc::InvalidParameter,
+                      "Empty function passed as callback");
+        }
+    }
+
+    /// \brief Call callback for serious errors
+    ///
+    /// This is called whenever there's a serious problem which makes the data
+    /// being loaded unusable. Further processing may or may not happen after
+    /// this (for example to detect further errors), but the data should not
+    /// be used.
+    ///
+    /// It calls whatever was passed to the error parameter to the constructor.
+    ///
+    /// If the caller of the loader wants to abort, it is possible to throw
+    /// from the callback, which aborts the load.
+    void error(const std::string& source_name, size_t source_line,
+               const std::string& reason)
+    {
+        error_(source_name, source_line, reason);
+    }
+
+    /// \brief Call callback for potential problems
+    ///
+    /// This is called whenever a minor problem is discovered. This might mean
+    /// the data is completely OK, it just looks suspicious.
+    ///
+    /// It calls whatever was passed to the warn parameter to the constructor.
+    ///
+    /// The loading will continue after the callback. If the caller wants to
+    /// abort (which is probably not a very good idea, since warnings
+    /// may be false positives), it is possible to throw from inside the
+    /// callback.
+    void warning(const std::string& source_name, size_t source_line,
+                 const std::string& reason)
+    {
+        warning_(source_name, source_line, reason);
+    }
+
+private:
+    IssueCallback error_, warning_;
+};
+
+}
+}
+
+#endif // LOADER_CALLBACKS_H

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

@@ -71,6 +71,7 @@ run_unittests_SOURCES += tsigerror_unittest.cc
 run_unittests_SOURCES += tsigkey_unittest.cc
 run_unittests_SOURCES += tsigrecord_unittest.cc
 run_unittests_SOURCES += character_string_unittest.cc
+run_unittests_SOURCES += master_loader_callbacks_test.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 # We shouldn't need to include BOTAN_LIBS here, but there

+ 347 - 6
src/lib/dns/tests/master_lexer_state_unittest.cc

@@ -32,6 +32,8 @@ protected:
                              s_null(NULL),
                              s_crlf(State::getInstance(State::CRLF)),
                              s_string(State::getInstance(State::String)),
+                             s_qstring(State::getInstance(State::QString)),
+                             s_number(State::getInstance(State::Number)),
                              options(MasterLexer::NONE),
                              orig_options(options)
     {}
@@ -42,6 +44,8 @@ protected:
     const State* const s_null;
     const State& s_crlf;
     const State& s_string;
+    const State& s_qstring;
+    const State& s_number;
     std::stringstream ss;
     MasterLexer::Options options, orig_options;
 };
@@ -111,8 +115,7 @@ TEST_F(MasterLexerStateTest, parentheses) {
     EXPECT_EQ(1, s_crlf.getParenCount(lexer)); // check post condition
     EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
 
-    // skip 'a' (note: until #2373 it's actually skipped as part of the '('
-    // handling)
+    // skip 'a'
     s_string.handle(lexer);
 
     // Then handle ')'.  '\n' before ')' isn't recognized because
@@ -226,7 +229,7 @@ TEST_F(MasterLexerStateTest, crlf) {
 
     // 1. A sequence of \r, \n is recognized as a single 'end-of-line'
     EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
-    EXPECT_EQ(s_null, s_crlf.handle(lexer));   // recognize '\n'
+    s_crlf.handle(lexer);   // recognize '\n'
     EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
     EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
 
@@ -234,23 +237,361 @@ TEST_F(MasterLexerStateTest, crlf) {
     // 'end-of-line'.  then there will be "initial WS"
     EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
     // see ' ', "unget" it
-    EXPECT_EQ(s_null, s_crlf.handle(lexer));
+    s_crlf.handle(lexer);
     EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize ' '
     EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
 
     // 3. comment between \r and \n
     EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
     // skip comments, recognize '\n'
-    EXPECT_EQ(s_null, s_crlf.handle(lexer));
+    s_crlf.handle(lexer);
     EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
     EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // skip 'a'
 
     // 4. \r then EOF
     EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
     // see EOF, then "unget" it
-    EXPECT_EQ(s_null, s_crlf.handle(lexer));
+    s_crlf.handle(lexer);
     EXPECT_EQ(s_null, State::start(lexer, common_options));  // recognize EOF
     EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
 }
 
+// Commonly used check for string related test cases, checking if the given
+// token has expected values.
+void
+stringTokenCheck(const std::string& expected, const MasterLexer::Token& token,
+                 bool quoted = false)
+{
+    EXPECT_EQ(quoted ? Token::QSTRING : Token::STRING, token.getType());
+    EXPECT_EQ(expected, token.getString());
+    const std::string actual(token.getStringRegion().beg,
+                             token.getStringRegion().beg +
+                             token.getStringRegion().len);
+    EXPECT_EQ(expected, actual);
+}
+
+TEST_F(MasterLexerStateTest, string) {
+    // Check with simple strings followed by separate characters
+    ss << "followed-by-EOL\n";
+    ss << "followed-by-CR\r";
+    ss << "followed-by-space ";
+    ss << "followed-by-tab\t";
+    ss << "followed-by-comment;this is comment and ignored\n";
+    ss << "followed-by-paren(closing)";
+    ss << "followed-by-EOF";
+    lexer.pushSource(ss);
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see \n
+    EXPECT_FALSE(s_string.wasLastEOL(lexer));
+    stringTokenCheck("followed-by-EOL", s_string.getToken(lexer));
+    EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see \r
+    stringTokenCheck("followed-by-CR", s_string.getToken(lexer));
+    EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // handle \r...
+    s_crlf.handle(lexer); // ...and skip it
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see ' '
+    stringTokenCheck("followed-by-space", s_string.getToken(lexer));
+
+    // skip ' ', then recognize the next string
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see \t
+    stringTokenCheck("followed-by-tab", s_string.getToken(lexer));
+
+    // skip \t, then recognize the next string
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see comment
+    stringTokenCheck("followed-by-comment", s_string.getToken(lexer));
+    EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see '('
+    stringTokenCheck("followed-by-paren", s_string.getToken(lexer));
+    EXPECT_EQ(&s_string, State::start(lexer, common_options)); // str in ()
+    s_string.handle(lexer); // recognize the str, see ')'
+    stringTokenCheck("closing", s_string.getToken(lexer));
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see EOF
+    stringTokenCheck("followed-by-EOF", s_string.getToken(lexer));
+}
+
+TEST_F(MasterLexerStateTest, stringEscape) {
+    // some of the separate characters should be considered part of the
+    // string if escaped.
+    ss << "escaped\\ space ";
+    ss << "escaped\\\ttab ";
+    ss << "escaped\\(paren ";
+    ss << "escaped\\)close ";
+    ss << "escaped\\;comment ";
+    ss << "escaped\\\\ backslash "; // second '\' shouldn't escape ' '
+    lexer.pushSource(ss);
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("escaped\\ space", s_string.getToken(lexer));
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("escaped\\\ttab", s_string.getToken(lexer));
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("escaped\\(paren", s_string.getToken(lexer));
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("escaped\\)close", s_string.getToken(lexer));
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("escaped\\;comment", s_string.getToken(lexer));
+
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see ' ' in mid
+    stringTokenCheck("escaped\\\\", s_string.getToken(lexer));
+
+    // Confirm the word that follows the escaped '\' is correctly recognized.
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("backslash", s_string.getToken(lexer));
+}
+
+TEST_F(MasterLexerStateTest, quotedString) {
+    ss << "\"ignore-quotes\"\n";
+    ss << "\"quoted string\" "; // space is part of the qstring
+    // also check other separator characters. note that \r doesn't cause
+    // UNBALANCED_QUOTES.  Not sure if it's intentional, but that's how the
+    // BIND 9 version works, so we follow it (it should be too minor to matter
+    // in practice anyway)
+    ss << "\"quoted()\t\rstring\" ";
+    ss << "\"escape\\ in quote\" ";
+    ss << "\"escaped\\\"\" ";
+    ss << "\"escaped backslash\\\\\" ";
+    ss << "\"no;comment\"";
+    lexer.pushSource(ss);
+
+    // by default, '"' doesn't have any special meaning and part of string
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer); // recognize str, see \n
+    stringTokenCheck("\"ignore-quotes\"", s_string.getToken(lexer));
+    EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
+    EXPECT_TRUE(s_string.wasLastEOL(lexer));
+
+    // If QSTRING is specified in option, '"' is regarded as a beginning of
+    // a quoted string.
+    const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    EXPECT_FALSE(s_string.wasLastEOL(lexer)); // EOL is canceled due to '"'
+    s_qstring.handle(lexer);
+    stringTokenCheck("quoted string", s_string.getToken(lexer), true);
+
+    // Also checks other separator characters within a qstring
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    stringTokenCheck("quoted()\t\rstring", s_string.getToken(lexer), true);
+
+    // escape character mostly doesn't have any effect in the qstring
+    // processing
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    stringTokenCheck("escape\\ in quote", s_string.getToken(lexer), true);
+
+    // The only exception is the quotation mark itself.  Note that the escape
+    // only works on the quotation mark immediately after it.
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    stringTokenCheck("escaped\"", s_string.getToken(lexer), true);
+
+    // quoted '\' then '"'.  Unlike the previous case '"' shouldn't be
+    // escaped.
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    stringTokenCheck("escaped backslash\\\\", s_string.getToken(lexer), true);
+
+    // ';' has no meaning in a quoted string (not indicating a comment)
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    stringTokenCheck("no;comment", s_string.getToken(lexer), true);
+}
+
+TEST_F(MasterLexerStateTest, brokenQuotedString) {
+    ss << "\"unbalanced-quote\n";
+    ss << "\"quoted\\\n\" ";
+    ss << "\"unclosed quote and EOF";
+    lexer.pushSource(ss);
+
+    // EOL is encountered without closing the quote
+    const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
+    EXPECT_EQ(Token::UNBALANCED_QUOTES,
+              s_qstring.getToken(lexer).getErrorCode());
+    // We can resume after the error from the '\n'
+    EXPECT_EQ(s_null, State::start(lexer, options));
+    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+
+    // \n is okay in a quoted string if escaped
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    stringTokenCheck("quoted\\\n", s_string.getToken(lexer), true);
+
+    // EOF is encountered without closing the quote
+    EXPECT_EQ(&s_qstring, State::start(lexer, options));
+    s_qstring.handle(lexer);
+    ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
+    EXPECT_EQ(Token::UNEXPECTED_END, s_qstring.getToken(lexer).getErrorCode());
+    // If we continue we'll simply see the EOF
+    EXPECT_EQ(s_null, State::start(lexer, options));
+    EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+TEST_F(MasterLexerStateTest, basicNumbers) {
+    ss << "0 ";
+    ss << "1 ";
+    ss << "12345 ";
+    ss << "4294967295 "; // 2^32-1
+    ss << "4294967296 "; // Out of range
+    ss << "340282366920938463463374607431768211456 ";
+                         // Very much out of range (2^128)
+    ss << "005 ";        // Leading zeroes are ignored
+    ss << "42;asdf\n";   // Number with comment
+    ss << "37";          // Simple number again, here to make
+                         // sure none of the above messed up
+                         // the tokenizer
+    lexer.pushSource(ss);
+
+    // Ask the lexer to recognize numbers as well
+    const MasterLexer::Options options = common_options | MasterLexer::NUMBER;
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(0, s_number.getToken(lexer).getNumber());
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(1, s_number.getToken(lexer).getNumber());
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(12345, s_number.getToken(lexer).getNumber());
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(4294967295u, s_number.getToken(lexer).getNumber());
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
+              s_number.getToken(lexer).getErrorCode());
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
+              s_number.getToken(lexer).getErrorCode());
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(5, s_number.getToken(lexer).getNumber());
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(42, s_number.getToken(lexer).getNumber());
+
+    EXPECT_EQ(s_null, State::start(lexer, options));
+    EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
+    EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    EXPECT_EQ(37, s_number.getToken(lexer).getNumber());
+
+    // If we continue we'll simply see the EOF
+    EXPECT_EQ(s_null, State::start(lexer, options));
+    EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+// Test tokens that look like (or start out as) numbers,
+// but turn out to be strings. Tests include escaped characters.
+TEST_F(MasterLexerStateTest, stringNumbers) {
+    ss << "123 ";        // Should be read as a string if the
+                         // NUMBER option is not given
+    ss << "-1 ";         // Negative numbers are interpreted
+                         // as strings (unsigned integers only)
+    ss << "123abc456 ";  // 'Numbers' containing non-digits should
+                         // be interpreted as strings
+    ss << "123\\456 ";   // Numbers containing escaped digits are
+                         // interpreted as strings
+    ss << "3scaped\\ space ";
+    ss << "3scaped\\\ttab ";
+    ss << "3scaped\\(paren ";
+    ss << "3scaped\\)close ";
+    ss << "3scaped\\;comment ";
+    ss << "3scaped\\\\ 8ackslash "; // second '\' shouldn't escape ' '
+
+    lexer.pushSource(ss);
+
+    // Note that common_options does not include MasterLexer::NUMBER,
+    // so the token should be recognized as a string
+    EXPECT_EQ(&s_string, State::start(lexer, common_options));
+    s_string.handle(lexer);
+    stringTokenCheck("123", s_string.getToken(lexer), false);
+
+    // Ask the lexer to recognize numbers as well
+    const MasterLexer::Options options = common_options | MasterLexer::NUMBER;
+
+    EXPECT_EQ(&s_string, State::start(lexer, options));
+    s_string.handle(lexer);
+    stringTokenCheck("-1", s_string.getToken(lexer), false);
+
+    // Starts out as a number, but ends up being a string
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    stringTokenCheck("123abc456", s_number.getToken(lexer), false);
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer);
+    stringTokenCheck("123\\456", s_number.getToken(lexer), false);
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("3scaped\\ space", s_number.getToken(lexer));
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("3scaped\\\ttab", s_number.getToken(lexer));
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("3scaped\\(paren", s_number.getToken(lexer));
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("3scaped\\)close", s_number.getToken(lexer));
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("3scaped\\;comment", s_number.getToken(lexer));
+
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer); // recognize str, see ' ' in mid
+    stringTokenCheck("3scaped\\\\", s_number.getToken(lexer));
+
+    // Confirm the word that follows the escaped '\' is correctly recognized.
+    EXPECT_EQ(&s_number, State::start(lexer, options));
+    s_number.handle(lexer); // recognize str, see ' ' at end
+    stringTokenCheck("8ackslash", s_number.getToken(lexer));
+
+    // If we continue we'll simply see the EOF
+    EXPECT_EQ(s_null, State::start(lexer, options));
+    EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
 }
+
+} // end anonymous namespace
+

+ 15 - 2
src/lib/dns/tests/master_lexer_token_unittest.cc

@@ -48,6 +48,9 @@ TEST_F(MasterLexerTokenTest, strings) {
     // basic construction and getter checks
     EXPECT_EQ(MasterLexer::Token::STRING, token_str.getType());
     EXPECT_EQ(std::string("string token"), token_str.getString());
+    std::string strval = "dummy"; // this should be replaced
+    token_str.getString(strval);
+    EXPECT_EQ(std::string("string token"), strval);
     const MasterLexer::Token::StringRegion str_region =
         token_str.getStringRegion();
     EXPECT_EQ(TEST_STRING, str_region.beg);
@@ -60,6 +63,8 @@ TEST_F(MasterLexerTokenTest, strings) {
     expected_str.push_back('\0');
     EXPECT_EQ(expected_str,
               MasterLexer::Token(TEST_STRING, TEST_STRING_LEN + 1).getString());
+    MasterLexer::Token(TEST_STRING, TEST_STRING_LEN + 1).getString(strval);
+    EXPECT_EQ(expected_str, strval);
 
     // Construct type of qstring
     EXPECT_EQ(MasterLexer::Token::QSTRING,
@@ -72,7 +77,9 @@ TEST_F(MasterLexerTokenTest, strings) {
 
     // getString/StringRegion() aren't allowed for non string(-variant) types
     EXPECT_THROW(token_eof.getString(), isc::InvalidOperation);
+    EXPECT_THROW(token_eof.getString(strval), isc::InvalidOperation);
     EXPECT_THROW(token_num.getString(), isc::InvalidOperation);
+    EXPECT_THROW(token_num.getString(strval), isc::InvalidOperation);
     EXPECT_THROW(token_eof.getStringRegion(), isc::InvalidOperation);
     EXPECT_THROW(token_num.getStringRegion(), isc::InvalidOperation);
 }
@@ -135,15 +142,21 @@ TEST_F(MasterLexerTokenTest, errors) {
     EXPECT_EQ("unbalanced quotes",
               MasterLexer::Token(MasterLexer::Token::UNBALANCED_QUOTES).
               getErrorText());
+    EXPECT_EQ("no token produced",
+              MasterLexer::Token(MasterLexer::Token::NO_TOKEN_PRODUCED).
+              getErrorText());
+    EXPECT_EQ("number out of range",
+              MasterLexer::Token(MasterLexer::Token::NUMBER_OUT_OF_RANGE).
+              getErrorText());
 
     // getErrorCode/Text() isn't allowed for non number types
     EXPECT_THROW(token_num.getErrorCode(), isc::InvalidOperation);
     EXPECT_THROW(token_num.getErrorText(), isc::InvalidOperation);
 
-    // Only the pre-defined error code is accepted.  Hardcoding '4' (max code
+    // Only the pre-defined error code is accepted.  Hardcoding '6' (max code
     // + 1) is intentional; it'd be actually better if we notice it when we
     // update the enum list (which shouldn't happen too often).
-    EXPECT_THROW(MasterLexer::Token(MasterLexer::Token::ErrorCode(4)),
+    EXPECT_THROW(MasterLexer::Token(MasterLexer::Token::ErrorCode(6)),
                  isc::InvalidParameter);
 
     // Check the coexistence of "from number" and "from error-code"

+ 160 - 0
src/lib/dns/tests/master_lexer_unittest.cc

@@ -15,10 +15,14 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/master_lexer.h>
+#include <dns/master_lexer_state.h>
 
 #include <gtest/gtest.h>
 
 #include <boost/lexical_cast.hpp>
+#include <boost/function.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
 
 #include <string>
 #include <sstream>
@@ -27,6 +31,8 @@ using namespace isc::dns;
 using std::string;
 using std::stringstream;
 using boost::lexical_cast;
+using boost::scoped_ptr;
+using master_lexer_internal::State;
 
 namespace {
 
@@ -124,4 +130,158 @@ TEST_F(MasterLexerTest, invalidPop) {
     EXPECT_THROW(lexer.popSource(), isc::InvalidOperation);
 }
 
+// Test it is not possible to get token when no source is available.
+TEST_F(MasterLexerTest, noSource) {
+    EXPECT_THROW(lexer.getNextToken(), isc::InvalidOperation);
+}
+
+// Test getting some tokens
+TEST_F(MasterLexerTest, getNextToken) {
+    ss << "\n   \n\"STRING\"\n";
+    lexer.pushSource(ss);
+
+    // First, the newline should get out.
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    // Then the whitespace, if we specify the option.
+    EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
+              lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+    // The newline
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    // The (quoted) string
+    EXPECT_EQ(MasterLexer::Token::QSTRING,
+              lexer.getNextToken(MasterLexer::QSTRING).getType());
+
+    // And the end of line and file
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Test we correctly find end of file.
+TEST_F(MasterLexerTest, eof) {
+    // Let the ss empty.
+    lexer.pushSource(ss);
+
+    // The first one is found to be EOF
+    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+    // And it stays on EOF for any following attempts
+    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+    // And we can step back one token, but that is the EOF too.
+    lexer.ungetToken();
+    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Check we properly return error when there's an opened parentheses and no
+// closing one
+TEST_F(MasterLexerTest, getUnbalancedParen) {
+    ss << "(\"string\"";
+    lexer.pushSource(ss);
+
+    // The string gets out first
+    EXPECT_EQ(MasterLexer::Token::STRING, lexer.getNextToken().getType());
+    // Then an unbalanced parenthesis
+    EXPECT_EQ(MasterLexer::Token::UNBALANCED_PAREN,
+              lexer.getNextToken().getErrorCode());
+    // And then EOF
+    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Check we properly return error when there's an opened quoted string and no
+// closing one
+TEST_F(MasterLexerTest, getUnbalancedString) {
+    ss << "\"string";
+    lexer.pushSource(ss);
+
+    // Then an unbalanced qstring (reported as an unexpected end)
+    EXPECT_EQ(MasterLexer::Token::UNEXPECTED_END,
+              lexer.getNextToken(MasterLexer::QSTRING).getErrorCode());
+    // And then EOF
+    EXPECT_EQ(MasterLexer::Token::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Test ungetting tokens works
+TEST_F(MasterLexerTest, ungetToken) {
+    ss << "\n (\"string\"\n) more";
+    lexer.pushSource(ss);
+
+    // Try getting the newline
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    // Return it and get again
+    lexer.ungetToken();
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    // Get the string and return it back
+    EXPECT_EQ(MasterLexer::Token::QSTRING,
+              lexer.getNextToken(MasterLexer::QSTRING).getType());
+    lexer.ungetToken();
+    // But if we change the options, it honors them
+    EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
+              lexer.getNextToken(MasterLexer::QSTRING |
+                                 MasterLexer::INITIAL_WS).getType());
+    // Get to the "more" string
+    EXPECT_EQ(MasterLexer::Token::QSTRING,
+              lexer.getNextToken(MasterLexer::QSTRING).getType());
+    EXPECT_EQ(MasterLexer::Token::STRING,
+              lexer.getNextToken(MasterLexer::QSTRING).getType());
+    // Return it back. It should get inside the parentheses.
+    // Upon next attempt to get it again, the newline inside the parentheses
+    // should be still ignored.
+    lexer.ungetToken();
+    EXPECT_EQ(MasterLexer::Token::STRING,
+              lexer.getNextToken(MasterLexer::QSTRING).getType());
+}
+
+// Check ungetting token without overriding the start method. We also
+// check it works well with changing options between the calls.
+TEST_F(MasterLexerTest, ungetRealOptions) {
+    ss << "\n    \n";
+    lexer.pushSource(ss);
+    // Skip the first newline
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+
+    // If we call it the usual way, it skips up to the newline and returns
+    // it
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+
+    // Now we return it. If we call it again, but with different options,
+    // we get the initial whitespace.
+    lexer.ungetToken();
+    EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
+              lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+}
+
+// Test only one token can be ungotten
+TEST_F(MasterLexerTest, ungetTwice) {
+    ss << "\n";
+    lexer.pushSource(ss);
+
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    // Unget the token. It can be done once
+    lexer.ungetToken();
+    // But not twice
+    EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+}
+
+// Test we can't unget a token before we get one
+TEST_F(MasterLexerTest, ungetBeforeGet) {
+    lexer.pushSource(ss); // Just to eliminate the missing source problem
+    EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+}
+
+// Test we can't unget a token after a source switch, even when we got
+// something before.
+TEST_F(MasterLexerTest, ungetAfterSwitch) {
+    ss << "\n\n";
+    lexer.pushSource(ss);
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    // Switch the source
+    std::stringstream ss2;
+    ss2 << "\n\n";
+    lexer.pushSource(ss2);
+    EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+    // We can get from the new source
+    EXPECT_EQ(MasterLexer::Token::END_OF_LINE, lexer.getNextToken().getType());
+    // And when we drop the current source, we can't unget again
+    lexer.popSource();
+    EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+}
+
 }

+ 84 - 0
src/lib/dns/tests/master_loader_callbacks_test.cc

@@ -0,0 +1,84 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/master_loader_callbacks.h>
+#include <dns/rrset.h>
+#include <dns/name.h>
+#include <dns/rrttl.h>
+#include <dns/rrclass.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+
+namespace {
+
+using std::string;
+using namespace isc::dns;
+
+class MasterLoaderCallbacksTest : public ::testing::Test {
+protected:
+    MasterLoaderCallbacksTest() :
+        last_was_error_(false), // Not needed, but then cppcheck complains
+        issue_called_(false),
+        rrset_(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
+                         RRTTL(3600))),
+        error_(boost::bind(&MasterLoaderCallbacksTest::checkCallback, this,
+                           true, _1, _2, _3)),
+        warning_(boost::bind(&MasterLoaderCallbacksTest::checkCallback, this,
+                             false, _1, _2, _3)),
+        callbacks_(error_, warning_)
+    {}
+
+    void checkCallback(bool error, const string& source, size_t line,
+                       const string& reason)
+    {
+        issue_called_ = true;
+        last_was_error_ = error;
+        EXPECT_EQ("source", source);
+        EXPECT_EQ(1, line);
+        EXPECT_EQ("reason", reason);
+    }
+    bool last_was_error_;
+    bool issue_called_;
+    const RRsetPtr rrset_;
+    const MasterLoaderCallbacks::IssueCallback error_, warning_;
+    MasterLoaderCallbacks callbacks_;
+};
+
+// Check the constructor rejects empty callbacks, but accepts non-empty ones
+TEST_F(MasterLoaderCallbacksTest, constructor) {
+    EXPECT_THROW(MasterLoaderCallbacks(MasterLoaderCallbacks::IssueCallback(),
+                                       warning_), isc::InvalidParameter);
+    EXPECT_THROW(MasterLoaderCallbacks(error_,
+                                       MasterLoaderCallbacks::IssueCallback()),
+                 isc::InvalidParameter);
+    EXPECT_NO_THROW(MasterLoaderCallbacks(error_, warning_));
+}
+
+// Call the issue callbacks
+TEST_F(MasterLoaderCallbacksTest, issueCall) {
+    callbacks_.error("source", 1, "reason");
+    EXPECT_TRUE(last_was_error_);
+    EXPECT_TRUE(issue_called_);
+
+    issue_called_ = false;
+
+    callbacks_.warning("source", 1, "reason");
+    EXPECT_FALSE(last_was_error_);
+    EXPECT_TRUE(issue_called_);
+}
+
+}

+ 1 - 1
src/lib/log/logger_manager_impl.cc

@@ -203,7 +203,7 @@ void LoggerManagerImpl::setConsoleAppenderLayout(
         log4cplus::SharedAppenderPtr& appender)
 {
     // Create the pattern we want for the output - local time.
-    string pattern = "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c] %m\n";
+    string pattern = "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n";
 
     // Finally the text of the message
     auto_ptr<log4cplus::Layout> layout(new log4cplus::PatternLayout(pattern));

+ 7 - 0
src/lib/log/tests/Makefile.am

@@ -127,3 +127,10 @@ check-local:
 	$(SHELL) $(abs_builddir)/local_file_test.sh
 	$(SHELL) $(abs_builddir)/logger_lock_test.sh
 	$(SHELL) $(abs_builddir)/severity_test.sh
+
+noinst_SCRIPTS  = console_test.sh
+noinst_SCRIPTS += destination_test.sh
+noinst_SCRIPTS += init_logger_test.sh
+noinst_SCRIPTS += local_file_test.sh
+noinst_SCRIPTS += logger_lock_test.sh
+noinst_SCRIPTS += severity_test.sh

+ 16 - 3
src/lib/log/tests/destination_test.sh.in

@@ -20,6 +20,8 @@ echo $testname
 
 failcount=0
 tempfile=@abs_builddir@/destination_test_tempfile_$$
+destfile1_tmp=@abs_builddir@/destination_test_destfile_1_tmp_$$
+destfile2_tmp=@abs_builddir@/destination_test_destfile_2_tmp_$$
 destfile1=@abs_builddir@/destination_test_destfile_1_$$
 destfile2=@abs_builddir@/destination_test_destfile_2_$$
 
@@ -40,7 +42,11 @@ FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
 ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
 .
 rm -f $destfile1 $destfile2
-./logger_example -s error -f $destfile1 -f $destfile2
+./logger_example -s error -f $destfile1_tmp -f $destfile2_tmp
+
+# strip the pids
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile1_tmp > $destfile1
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile2_tmp > $destfile2
 
 echo -n  "   - destination 1:"
 cut -d' ' -f3- $destfile1 | diff $tempfile -
@@ -50,9 +56,16 @@ echo -n  "   - destination 2:"
 cut -d' ' -f3- $destfile2 | diff $tempfile -
 passfail $?
 
+# Tidy up.
+rm -f $tempfile $destfile1_tmp $destfile2_tmp $destfile1 $destfile2
+
 echo     "2. Two loggers, different destinations and severities"
 rm -f $destfile1 $destfile2
-./logger_example -l example -s info -f $destfile1 -l alpha -s warn -f $destfile2
+./logger_example -l example -s info -f $destfile1_tmp -l alpha -s warn -f $destfile2_tmp
+
+# strip the pids
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile1_tmp > $destfile1
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile2_tmp > $destfile2
 
 # All output for example and example.beta should have gone to destfile1.
 # Output for example.alpha should have done to destfile2.
@@ -86,6 +99,6 @@ else
 fi
 
 # Tidy up.
-rm -f $tempfile $destfile1 $destfile2
+rm -f $tempfile $destfile1_tmp $destfile2_tmp $destfile1 $destfile2
 
 exit $failcount

+ 14 - 7
src/lib/log/tests/init_logger_test.sh.in

@@ -21,6 +21,7 @@ echo $testname
 
 failcount=0
 tempfile=@abs_builddir@/init_logger_test_tempfile_$$
+destfile_tmp=@abs_builddir@/init_logger_test_destfile_tmp_$$
 destfile=@abs_builddir@/init_logger_test_destfile_$$
 
 passfail() {
@@ -45,6 +46,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
 FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
 .
 B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=DEBUG B10_LOGGER_DBGLEVEL=99 ./init_logger_test | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
     cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
@@ -58,6 +60,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
 FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
 .
 B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=DEBUG B10_LOGGER_DBGLEVEL=50 ./init_logger_test | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
     cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
@@ -68,6 +71,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
 FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
 .
 B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=WARN ./init_logger_test | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
     cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
@@ -77,20 +81,23 @@ echo -n  "   - stdout: "
 cat > $tempfile << .
 FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
 .
-rm -f $destfile
-B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stdout ./init_logger_test 1> $destfile
+rm -f $destfile_tmp $destfile
+B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stdout ./init_logger_test 1> $destfile_tmp
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
 cut -d' ' -f3- $destfile | diff $tempfile -
 passfail $?
 
 echo -n  "   - stderr: "
-rm -f $destfile
-B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stderr ./init_logger_test 2> $destfile
+rm -f $destfile_tmp $destfile
+B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stderr ./init_logger_test 2> $destfile_tmp
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
 cut -d' ' -f3- $destfile | diff $tempfile -
 passfail $?
 
 echo -n  "   - file: "
-rm -f $destfile
-B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=$destfile ./init_logger_test
+rm -f $destfile_tmp $destfile
+B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=$destfile_tmp ./init_logger_test
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
 cut -d' ' -f3- $destfile | diff $tempfile -
 passfail $?
 
@@ -105,6 +112,6 @@ else
 fi
 
 # Tidy up.
-rm -f $tempfile $destfile
+rm -f $tempfile $destfile_tmp $destfile
 
 exit $failcount

+ 6 - 2
src/lib/log/tests/local_file_test.sh.in

@@ -51,7 +51,9 @@ FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
 ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
 WARN  [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
 .
-./logger_example -c stdout -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout -s warn $localmes | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+    cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
 echo -n "2. Report error if unable to read local message file:"
@@ -66,7 +68,9 @@ ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_erro
 WARN  [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
 .
 rm -f $localmes
-./logger_example -c stdout -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout -s warn $localmes | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+    cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
 if [ $failcount -eq 0 ]; then

+ 2 - 1
src/lib/log/tests/logger_lock_test.sh.in

@@ -36,7 +36,8 @@ INFO  [bind10.log] LOG_LOCK_TEST_MESSAGE this is a test message.
 LOGGER_LOCK_TEST: UNLOCK
 .
 rm -f $destfile
-B10_LOGGER_SEVERITY=INFO B10_LOGGER_DESTINATION=stdout ./logger_lock_test > $destfile
+B10_LOGGER_SEVERITY=INFO B10_LOGGER_DESTINATION=stdout ./logger_lock_test | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' > $destfile
 cut -d' ' -f3- $destfile | diff $tempfile -
 passfail $?
 

+ 9 - 3
src/lib/log/tests/severity_test.sh.in

@@ -43,7 +43,9 @@ ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_erro
 WARN  [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
 INFO  [example.beta] LOG_READ_ERROR error reading from message file beta: info
 .
-./logger_example -c stdout | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+    cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
 echo -n "2. Severity filter:"
@@ -53,7 +55,9 @@ ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
 FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
 ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
 .
-./logger_example -c stdout -s error | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout -s error | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+    cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
 echo -n "3. Debug level:"
@@ -72,7 +76,9 @@ WARN  [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
 INFO  [example.beta] LOG_READ_ERROR error reading from message file beta: info
 DEBUG [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta/25
 .
-./logger_example -c stdout -s debug -d 25 | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout -s debug -d 25 | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+    cut -d' ' -f3- | diff $tempfile -
 passfail $?
 
 if [ $failcount -eq 0 ]; then

+ 1 - 1
src/lib/python/isc/log/tests/check_output.sh

@@ -1,3 +1,3 @@
 #!/bin/sh
 
-"$1" 2>&1 | cut -d\  -f3- | diff - "$2" 1>&2
+"$1" 2>&1 | sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | cut -d\  -f3- | diff - "$2" 1>&2

+ 0 - 0
tests/tools/perfdhcp/tests/test_control_unittest.cc


Some files were not shown because too many files changed in this diff