Browse Source

Merge branch 'master' into trac2503

Conflicts:
	src/lib/datasrc/tests/memory/testdata/Makefile.am
Mukund Sivaraman 12 years ago
parent
commit
6fe86386be
100 changed files with 7153 additions and 1210 deletions
  1. 52 0
      ChangeLog
  2. 3 1
      configure.ac
  3. 2 2
      doc/Doxyfile
  4. 14 7
      doc/guide/bind10-guide.xml
  5. 4 2
      src/bin/auth/main.cc
  6. 1 1
      src/bin/auth/tests/auth_srv_unittest.cc
  7. 53 28
      src/bin/bind10/bind10_src.py.in
  8. 763 2
      src/bin/bind10/tests/bind10_test.py.in
  9. 1 1
      src/bin/cfgmgr/b10-cfgmgr.py.in
  10. 1 1
      src/bin/cmdctl/cmdctl.py.in
  11. 1 1
      src/bin/ddns/ddns.py.in
  12. 7 1
      src/bin/dhcp4/main.cc
  13. 30 7
      src/bin/dhcp6/dhcp6_srv.cc
  14. 8 2
      src/bin/dhcp6/main.cc
  15. 16 5
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  16. 6 6
      src/bin/resolver/main.cc
  17. 2 2
      src/bin/stats/stats.py.in
  18. 2 2
      src/bin/stats/stats_httpd.py.in
  19. 4 6
      src/bin/xfrin/b10-xfrin.xml
  20. 46 4
      src/bin/xfrin/tests/xfrin_test.py
  21. 32 15
      src/bin/xfrin/xfrin.py.in
  22. 8 1
      src/bin/xfrin/xfrin_messages.mes
  23. 1 1
      src/bin/xfrout/xfrout.py.in
  24. 3 2
      src/bin/zonemgr/zonemgr.py.in
  25. 1 1
      src/bin/zonemgr/zonemgr_messages.mes
  26. 15 0
      src/lib/asiolink/io_address.cc
  27. 21 2
      src/lib/asiolink/io_address.h
  28. 41 0
      src/lib/asiolink/tests/io_address_unittest.cc
  29. 11 2
      src/lib/datasrc/memory/zone_finder.cc
  30. 10 0
      src/lib/datasrc/tests/memory/testdata/2504-test.zone
  31. 1 0
      src/lib/datasrc/tests/memory/testdata/Makefile.am
  32. 21 0
      src/lib/datasrc/tests/memory/zone_finder_unittest.cc
  33. 3 1
      src/lib/dhcp/Makefile.am
  34. 12 12
      src/lib/dhcp/duid.cc
  35. 30 19
      src/lib/dhcp/duid.h
  36. 22 46
      src/lib/dhcp/libdhcp++.cc
  37. 12 1
      src/lib/dhcp/libdhcp++.h
  38. 300 34
      src/lib/dhcp/option_custom.cc
  39. 158 34
      src/lib/dhcp/option_custom.h
  40. 40 1
      src/lib/dhcp/option_data_types.cc
  41. 42 2
      src/lib/dhcp/option_data_types.h
  42. 62 33
      src/lib/dhcp/option_definition.cc
  43. 1 1
      src/lib/dhcp/option_definition.h
  44. 184 0
      src/lib/dhcp/std_option_defs.h
  45. 1 0
      src/lib/dhcp/tests/Makefile.am
  46. 150 22
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  47. 531 22
      src/lib/dhcp/tests/option_custom_unittest.cc
  48. 491 0
      src/lib/dhcp/tests/option_data_types_unittest.cc
  49. 2 1
      src/lib/dhcp/tests/option_definition_unittest.cc
  50. 1 1
      src/lib/dhcpsrv/Makefile.am
  51. 13 0
      src/lib/dhcpsrv/database_backends.dox
  52. 8 3
      src/lib/dhcpsrv/dhcpdb_create.mysql
  53. 42 11
      src/lib/dhcpsrv/lease_mgr.cc
  54. 151 76
      src/lib/dhcpsrv/lease_mgr.h
  55. 0 0
      src/lib/dhcpsrv/libdhcpsrv.dox
  56. 2 3
      src/lib/dhcpsrv/memfile_lease_mgr.h
  57. 896 316
      src/lib/dhcpsrv/mysql_lease_mgr.cc
  58. 232 25
      src/lib/dhcpsrv/mysql_lease_mgr.h
  59. 1 1
      src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
  60. 356 26
      src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
  61. 1 1
      src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
  62. 851 253
      src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
  63. 6 4
      src/lib/dhcpsrv/tests/schema_copy.h
  64. 4 0
      src/lib/dns/Makefile.am
  65. 2 1
      src/lib/dns/gen-rdatacode.py.in
  66. 1 1
      src/lib/dns/python/tests/rdata_python_test.py
  67. 6 0
      src/lib/dns/python/tests/tsigkey_python_test.py
  68. 11 6
      src/lib/dns/python/tsigkey_python.cc
  69. 98 0
      src/lib/dns/rdata/generic/detail/char_string.cc
  70. 63 0
      src/lib/dns/rdata/generic/detail/char_string.h
  71. 74 49
      src/lib/dns/rdata/generic/detail/txt_like.h
  72. 20 6
      src/lib/dns/rdata/generic/spf_99.cc
  73. 3 1
      src/lib/dns/rdata/generic/spf_99.h
  74. 16 2
      src/lib/dns/rdata/generic/txt_16.cc
  75. 3 1
      src/lib/dns/rdata/generic/txt_16.h
  76. 1 0
      src/lib/dns/tests/Makefile.am
  77. 147 0
      src/lib/dns/tests/rdata_char_string_unittest.cc
  78. 173 39
      src/lib/dns/tests/rdata_txt_like_unittest.cc
  79. 3 3
      src/lib/dns/tests/testdata/rdata_txt_fromWire1
  80. 9 0
      src/lib/dns/tests/tsigkey_unittest.cc
  81. 11 1
      src/lib/dns/tsigkey.cc
  82. 22 0
      src/lib/dns/tsigkey.h
  83. 1 0
      src/lib/log/Makefile.am
  84. 13 1
      src/lib/log/README
  85. 96 0
      src/lib/log/buffer_appender_impl.cc
  86. 118 0
      src/lib/log/buffer_appender_impl.h
  87. 4 2
      src/lib/log/logger_manager.cc
  88. 22 2
      src/lib/log/logger_manager.h
  89. 61 19
      src/lib/log/logger_manager_impl.cc
  90. 41 9
      src/lib/log/logger_manager_impl.h
  91. 2 2
      src/lib/log/logger_support.cc
  92. 5 1
      src/lib/log/logger_support.h
  93. 13 0
      src/lib/log/tests/Makefile.am
  94. 146 0
      src/lib/log/tests/buffer_appender_unittest.cc
  95. 71 0
      src/lib/log/tests/buffer_logger_test.cc
  96. 61 0
      src/lib/log/tests/buffer_logger_test.sh.in
  97. 7 2
      src/lib/python/isc/config/cfgmgr.py
  98. 21 8
      src/lib/python/isc/log/log.cc
  99. 22 0
      src/lib/python/isc/log/tests/log_test.py
  100. 0 0
      src/lib/util/python/gen_wiredata.py.in

+ 52 - 0
ChangeLog

@@ -1,3 +1,55 @@
+522.	[func]*		jelte
+	Configuration of TSIG keys for b10-xfrin has changed; instead of
+	specifying the full TSIG key (<name>:<base64>:<algo>) it now expects
+	just the name, and uses the global TSIG Key Ring like all the other
+	components (configuration list /tsig_keys/keys).
+	Note: this is not automatically updated, so if you use TSIG in
+	xfrin, you need to update your configuration.
+	(Trac #1351, git e65b7b36f60f14b7abe083da411e6934cdfbae7a)
+
+521.	[func]		marcin
+	Implemented definitions for DHCPv6 standard options identified
+	by codes up to 48. These definitions are now used by the DHCPv6
+	server to create instances of options being sent to a client.
+	(Trac #2491, git 0a4faa07777189ed9c25211987a1a9b574015a95)
+
+520.	[func]		jelte
+	The system no longer prints initial log messages to stdout
+	regardless of what logging configuration is present, but it
+	temporarily stores any log messages until the configuration is
+	processed. If there is no specific configuration, or if the
+	configuration cannot be accessed, it will still fall back to stdout.
+	Note that there are still a few instances where output is printed,
+	these shall be addressed separately.
+	Note also that, currently, in case it falls back to stdout (such as
+	when it cannot connect to b10-cfgmgr), all log messages are always
+	printed (including debug messages), regardless of whether -v was
+	used. This shall also be addressed in a future change.
+	(Trac #2445, git 74a0abe5a6d10b28e4a3e360e87b129c232dea68)
+
+519.	[bug]		muks
+	Fixed a problem in inmem NSEC lookup which caused returning an
+	incorrect NSEC record or (in rare cases) assert failures
+	when a non-existent domain was queried, which was a sub-domain of
+	a domain that existed.
+	(Trac #2504, git 835553eb309d100b062051f7ef18422d2e8e3ae4)
+
+518.	[func]		stephen
+	Extend DHCP MySQL backend to handle IPv4 addresses.
+	(Trac #2404, git ce7db48d3ff5d5aad12b1da5e67ae60073cb2607)
+
+517.	[func]		stephen
+	Added IOAddress::toBytes() to get byte representation of address.
+	Also added convenience methods for V4/V6 address determination.
+	(Trac #2396, git c23f87e8ac3ea781b38d688f8f7b58539f85e35a)
+
+516.	[bug]		marcin
+	Fixed 'make distcheck' failure when running perfdhcp unit tests.
+	The unit tests used to read files from the folder specified
+	with the path relative to current folder, thus when the test was
+	run from a different folder the files could not be found.
+	(Trac #2479, git 4e8325e1b309f1d388a3055ec1e1df98c377f383)
+
 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

+ 3 - 1
configure.ac

@@ -903,11 +903,12 @@ AC_SUBST(MULTITHREADING_FLAG)
 #
 GTEST_LDFLAGS=
 GTEST_LDADD=
-# TODO: set DISTCHECK_GTEST_CONFIGURE_FLAG for --with-gtest too
 DISTCHECK_GTEST_CONFIGURE_FLAG=
 
 if test "x$enable_gtest" = "xyes" ; then
 
+    DISTCHECK_GTEST_CONFIGURE_FLAG="--with-gtest=$gtest_path"
+
     if test -n "$with_gtest_source" ; then
 
           if test "x$GTEST_SOURCE" = "xyes" ; then
@@ -1375,6 +1376,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/log/tests/console_test.sh
            src/lib/log/tests/destination_test.sh
            src/lib/log/tests/init_logger_test.sh
+           src/lib/log/tests/buffer_logger_test.sh
            src/lib/log/tests/local_file_test.sh
            src/lib/log/tests/logger_lock_test.sh
            src/lib/log/tests/severity_test.sh

+ 2 - 2
doc/Doxyfile

@@ -580,8 +580,8 @@ INPUT                  = ../src/lib/exceptions ../src/lib/cc \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
     ../src/lib/util/threads/ ../src/lib/resolve ../src/lib/acl \
-    ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/bin/dhcp4 \
-    ../tests/tools/perfdhcp devel
+    ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/lib/dhcpsrv \
+    ../src/bin/dhcp4 ../tests/tools/perfdhcp devel
 
 # This tag can be used to specify the character encoding of the source files
 # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is

+ 14 - 7
doc/guide/bind10-guide.xml

@@ -472,7 +472,7 @@ var/
       <title>Packages</title>
 
       <para>
-        Some operating systems or softare package vendors may
+        Some operating systems or software package vendors may
         provide ready-to-use, pre-built software packages for
         the BIND 10 suite.
         Installing a pre-built package means you do not need to
@@ -758,7 +758,7 @@ as a dependency earlier -->
           If the configure fails, it may be due to missing or old
           dependencies.
         </para>
-        
+
         <note>
           <para>For notes on configuring and building DHCPv6 with MySQL see <xref linkend="dhcp6-install">.</xref></para>
         </note>
@@ -1841,10 +1841,8 @@ config set /Boss/components/b10-zonemgr/kind dispensable
         <para>
           The key ring lives in the configuration in "tsig_keys/keys". Most of
           the system uses the keys from there &mdash; ACLs, authoritative server to
-          sign responses to signed queries, and <command>b10-xfrout</command>
-          to sign transfers. The <command>b10-xfrin</command> uses its own
-          configuration for keys, but that will be fixed in Trac ticket
-          <ulink url="http://bind10.isc.org/ticket/1351">#1351</ulink>.
+          sign responses to signed queries, and <command>b10-xfrin</command>
+          and <command>b10-xfrout</command> to sign transfers.
         </para>
 
         <para>
@@ -2157,7 +2155,7 @@ AND_MATCH := "ALL": [ RULE_RAW, RULE_RAW, ... ]
 	you indicate that the system is not usable without the
 	component and if such component fails, the system shuts
 	down no matter when the failure happened.  This is the
-	behaviour of the core components (the ones you can't turn
+	behavior of the core components (the ones you can't turn
 	off), but you can declare any other components as core as
 	well if you wish (but you can turn these off, they just
 	can't fail).
@@ -2722,6 +2720,15 @@ TODO
     </section>
 
     <section>
+        <title>TSIG</title>
+        If you want to use TSIG for incoming transfers, a system wide TSIG
+        key ring must be configured (see <xref linkend="tsig-key-ring"/>).
+        To specify a key to use, set tsig_key value to the name of the key
+        to use from the key ring.
+&gt; <userinput>config set Xfrin/zones[0]/tsig_key "<option>example.key</option>"</userinput>
+    </section>
+
+    <section>
       <title>Enabling IXFR</title>
       <para>
         As noted above, <command>b10-xfrin</command> uses AXFR for

+ 4 - 2
src/bin/auth/main.cc

@@ -147,7 +147,7 @@ main(int argc, char* argv[]) {
     // Initialize logging.  If verbose, we'll use maximum verbosity.
     isc::log::initLogger(AUTH_NAME,
                          (verbose ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, true);
 
     int ret = 0;
 
@@ -256,7 +256,9 @@ main(int argc, char* argv[]) {
 
     // If we haven't registered callback for data sources, this will be just
     // no-op.
-    config_session->removeRemoteConfig("data_sources");
+    if (config_session != NULL) {
+        config_session->removeRemoteConfig("data_sources");
+    }
 
     delete xfrin_session;
     delete config_session;

+ 1 - 1
src/bin/auth/tests/auth_srv_unittest.cc

@@ -225,7 +225,7 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
     message.setHeaderFlag(Message::HEADERFLAG_AA);
     RRsetPtr rrset_version = RRsetPtr(new RRset(version_name, RRClass::CH(),
                                                 RRType::TXT(), RRTTL(0)));
-    rrset_version->addRdata(generic::TXT(PACKAGE_STRING));
+    rrset_version->addRdata(generic::TXT("\"" PACKAGE_STRING "\""));
     message.addRRset(Message::SECTION_ANSWER, rrset_version);
 
     RRsetPtr rrset_version_ns = RRsetPtr(new RRset(apex_name, RRClass::CH(),

+ 53 - 28
src/bin/bind10/bind10_src.py.in

@@ -48,7 +48,7 @@ else:
     PREFIX = "@prefix@"
     DATAROOTDIR = "@datarootdir@"
     SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-    
+
 import subprocess
 import signal
 import re
@@ -76,7 +76,7 @@ import isc.bind10.socket_cache
 import libutil_io_python
 import tempfile
 
-isc.log.init("b10-boss")
+isc.log.init("b10-boss", buffer=True)
 logger = isc.log.Logger("boss")
 
 # Pending system-wide debug level definitions, the ones we
@@ -166,14 +166,14 @@ class ProcessStartError(Exception): pass
 
 class BoB:
     """Boss of BIND class."""
-    
+
     def __init__(self, msgq_socket_file=None, data_path=None,
                  config_filename=None, clear_config=False,
                  verbose=False, nokill=False, setuid=None, setgid=None,
                  username=None, cmdctl_port=None, wait_time=10):
         """
             Initialize the Boss of BIND. This is a singleton (only one can run).
-        
+
             The msgq_socket_file specifies the UNIX domain socket file that the
             msgq process listens on.  If verbose is True, then the boss reports
             what it is doing.
@@ -216,6 +216,12 @@ class BoB:
         self.clear_config = clear_config
         self.cmdctl_port = cmdctl_port
         self.wait_time = wait_time
+        self.msgq_timeout = 5
+
+        # _run_under_unittests is only meant to be used when testing. It
+        # bypasses execution of some code to help with testing.
+        self._run_under_unittests = False
+
         self._component_configurator = isc.bind10.component.Configurator(self,
             isc.bind10.special_component.get_specials())
         # The priorities here make them start in the correct order. First
@@ -332,6 +338,7 @@ class BoB:
         """
         logger.info(BIND10_KILLING_ALL_PROCESSES)
         self.__kill_children(True)
+        self.components = {}
 
     def _read_bind10_config(self):
         """
@@ -400,7 +407,7 @@ class BoB:
                     logger.error(BIND10_STARTUP_UNEXPECTED_MESSAGE, msg)
             except:
                 logger.error(BIND10_STARTUP_UNRECOGNISED_MESSAGE, msg)
-        
+
         return False
 
     # The next few methods start the individual processes of BIND-10.  They
@@ -408,21 +415,34 @@ class BoB:
     # raised which is caught by the caller of start_all_processes(); this kills
     # processes started up to that point before terminating the program.
 
+    def _make_process_info(self, name, args, env,
+                           dev_null_stdout=False, dev_null_stderr=False):
+        """
+            Wrapper around ProcessInfo(), useful to override
+            ProcessInfo() creation during testing.
+        """
+        return ProcessInfo(name, args, env, dev_null_stdout, dev_null_stderr)
+
     def start_msgq(self):
         """
             Start the message queue and connect to the command channel.
         """
         self.log_starting("b10-msgq")
-        msgq_proc = ProcessInfo("b10-msgq", ["b10-msgq"], self.c_channel_env,
-                                True, not self.verbose)
+        msgq_proc = self._make_process_info("b10-msgq", ["b10-msgq"],
+                                            self.c_channel_env,
+                                            True, not self.verbose)
         msgq_proc.spawn()
         self.log_started(msgq_proc.pid)
 
         # Now connect to the c-channel
         cc_connect_start = time.time()
         while self.cc_session is None:
+            # if we are run under unittests, break
+            if self._run_under_unittests:
+                break
+
             # if we have been trying for "a while" give up
-            if (time.time() - cc_connect_start) > 5:
+            if (time.time() - cc_connect_start) > self.msgq_timeout:
                 logger.error(BIND10_CONNECTING_TO_CC_FAIL)
                 raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
 
@@ -434,7 +454,8 @@ class BoB:
 
         # Subscribe to the message queue.  The only messages we expect to receive
         # on this channel are once relating to process startup.
-        self.cc_session.group_subscribe("Boss")
+        if self.cc_session is not None:
+            self.cc_session.group_subscribe("Boss")
 
         return msgq_proc
 
@@ -450,13 +471,14 @@ class BoB:
             args.append("--config-filename=" + self.config_filename)
         if self.clear_config:
             args.append("--clear-config")
-        bind_cfgd = ProcessInfo("b10-cfgmgr", args,
-                                self.c_channel_env)
+        bind_cfgd = self._make_process_info("b10-cfgmgr", args,
+                                            self.c_channel_env)
         bind_cfgd.spawn()
         self.log_started(bind_cfgd.pid)
 
-        # Wait for the configuration manager to start up as subsequent initialization
-        # cannot proceed without it.  The time to wait can be set on the command line.
+        # Wait for the configuration manager to start up as
+        # subsequent initialization cannot proceed without it.  The
+        # time to wait can be set on the command line.
         time_remaining = self.wait_time
         msg, env = self.cc_session.group_recvmsg()
         while time_remaining > 0 and not self.process_running(msg, "ConfigManager"):
@@ -464,7 +486,7 @@ class BoB:
             time.sleep(1)
             time_remaining = time_remaining - 1
             msg, env = self.cc_session.group_recvmsg()
-        
+
         if not self.process_running(msg, "ConfigManager"):
             raise ProcessStartError("Configuration manager process has not started")
 
@@ -481,7 +503,7 @@ class BoB:
             process, the log_starting/log_started methods are not used.
         """
         logger.info(BIND10_STARTING_CC)
-        self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, 
+        self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
                                       self.config_handler,
                                       self.command_handler,
                                       socket_file = self.msgq_socket_file)
@@ -499,7 +521,7 @@ class BoB:
             The port and address arguments are for log messages only.
         """
         self.log_starting(name, port, address)
-        newproc = ProcessInfo(name, args, c_channel_env)
+        newproc = self._make_process_info(name, args, c_channel_env)
         newproc.spawn()
         self.log_started(newproc.pid)
         return newproc
@@ -611,7 +633,6 @@ class BoB:
         if self.msgq_socket_file is not None:
              c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
         logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
-        # try to connect, and if we can't wait a short while
         try:
             self.cc_session = isc.cc.Session(self.msgq_socket_file)
             logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
@@ -679,7 +700,7 @@ class BoB:
         except:
             pass
         # XXX: some delay probably useful... how much is uncertain
-        # I have changed the delay from 0.5 to 1, but sometime it's 
+        # I have changed the delay from 0.5 to 1, but sometime it's
         # still not enough.
         time.sleep(1)
         self.reap_children()
@@ -728,17 +749,19 @@ class BoB:
         return os.waitpid(-1, os.WNOHANG)
 
     def reap_children(self):
-        """Check to see if any of our child processes have exited, 
-        and note this for later handling. 
+        """Check to see if any of our child processes have exited,
+        and note this for later handling.
         """
         while True:
             try:
                 (pid, exit_status) = self._get_process_exit_status()
             except OSError as o:
-                if o.errno == errno.ECHILD: break
+                if o.errno == errno.ECHILD:
+                    break
                 # XXX: should be impossible to get any other error here
                 raise
-            if pid == 0: break
+            if pid == 0:
+                break
             if pid in self.components:
                 # One of the components we know about.  Get information on it.
                 component = self.components.pop(pid)
@@ -760,11 +783,11 @@ class BoB:
         """
             Restart any dead processes:
 
-            * Returns the time when the next process is ready to be restarted. 
+            * Returns the time when the next process is ready to be restarted.
             * If the server is shutting down, returns 0.
             * If there are no processes, returns None.
 
-            The values returned can be safely passed into select() as the 
+            The values returned can be safely passed into select() as the
             timeout value.
 
         """
@@ -909,8 +932,10 @@ class BoB:
         """
         if self._srv_socket is not None:
             self._srv_socket.close()
-            os.remove(self._socket_path)
-            os.rmdir(self._tmpdir)
+            if os.path.exists(self._socket_path):
+                os.remove(self._socket_path)
+            if os.path.isdir(self._tmpdir):
+                os.rmdir(self._tmpdir)
 
     def _srv_accept(self):
         """
@@ -1006,7 +1031,7 @@ boss_of_bind = None
 
 def reaper(signal_number, stack_frame):
     """A child process has died (SIGCHLD received)."""
-    # don't do anything... 
+    # don't do anything...
     # the Python signal handler has been set up to write
     # down a pipe, waking up our select() bit
     pass
@@ -1173,7 +1198,7 @@ and the created lock file must be writable for that user.
         except KeyError:
             pass
 
-        # Next try getting information about the user, assuming user name 
+        # Next try getting information about the user, assuming user name
         # passed.
         # If the information is both a valid user name and user number, we
         # prefer the name because we try it second. A minor point, hopefully.

+ 763 - 2
src/bin/bind10/tests/bind10_test.py.in

@@ -25,6 +25,7 @@ import bind10_src
 import unittest
 import sys
 import os
+import os.path
 import copy
 import signal
 import socket
@@ -34,6 +35,7 @@ import isc
 import isc.log
 import isc.bind10.socket_cache
 import errno
+import random
 
 from isc.testutils.parse_args import TestOptParser, OptsError
 from isc.testutils.ccsession_mock import MockModuleCCSession
@@ -366,6 +368,53 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(creator, bob._socket_cache._creator)
         self.assertRaises(ValueError, bob.set_creator, creator)
 
+    def test_socket_srv(self):
+        """Tests init_socket_srv() and remove_socket_srv() work as expected."""
+        bob = BoB()
+
+        self.assertIsNone(bob._srv_socket)
+        self.assertIsNone(bob._tmpdir)
+        self.assertIsNone(bob._socket_path)
+
+        bob.init_socket_srv()
+
+        self.assertIsNotNone(bob._srv_socket)
+        self.assertNotEqual(-1, bob._srv_socket.fileno())
+        self.assertEqual(os.path.join(bob._tmpdir, 'sockcreator'),
+                         bob._srv_socket.getsockname())
+
+        self.assertIsNotNone(bob._tmpdir)
+        self.assertTrue(os.path.isdir(bob._tmpdir))
+        self.assertIsNotNone(bob._socket_path)
+        self.assertTrue(os.path.exists(bob._socket_path))
+
+        # Check that it's possible to connect to the socket file (this
+        # only works if the socket file exists and the server listens on
+        # it).
+        s = socket.socket(socket.AF_UNIX)
+        try:
+            s.connect(bob._socket_path)
+            can_connect = True
+            s.close()
+        except socket.error as e:
+            can_connect = False
+
+        self.assertTrue(can_connect)
+
+        bob.remove_socket_srv()
+
+        self.assertEqual(-1, bob._srv_socket.fileno())
+        self.assertFalse(os.path.exists(bob._socket_path))
+        self.assertFalse(os.path.isdir(bob._tmpdir))
+
+        # These should not fail either:
+
+        # second call
+        bob.remove_socket_srv()
+
+        bob._srv_socket = None
+        bob.remove_socket_srv()
+
     def test_init_alternate_socket(self):
         bob = BoB("alt_socket_file")
         self.assertEqual(bob.verbose, False)
@@ -461,6 +510,22 @@ class TestBoB(unittest.TestCase):
         self.assertEqual({'command': ['shutdown', {'pid': 42}]},
                          bob.cc_session.msg)
 
+# Mock class for testing BoB's usage of ProcessInfo
+class MockProcessInfo:
+    def __init__(self, name, args, env={}, dev_null_stdout=False,
+                 dev_null_stderr=False):
+        self.name = name
+        self.args = args
+        self.env = env
+        self.dev_null_stdout = dev_null_stdout
+        self.dev_null_stderr = dev_null_stderr
+        self.process = None
+        self.pid = None
+
+    def spawn(self):
+        # set some pid (only used for testing that it is not None anymore)
+        self.pid = 42147
+
 # Class for testing the BoB without actually starting processes.
 # This is used for testing the start/stop components routines and
 # the BoB commands.
@@ -490,6 +555,7 @@ class MockBob(BoB):
         self.c_channel_env = {}
         self.components = { }
         self.creator = False
+        self.get_process_exit_status_called = False
 
         class MockSockCreator(isc.bind10.component.Component):
             def __init__(self, process, boss, kind, address=None, params=None):
@@ -661,6 +727,52 @@ class MockBob(BoB):
             del self.components[12]
         self.cmdctl = False
 
+    def _get_process_exit_status(self):
+        if self.get_process_exit_status_called:
+            return (0, 0)
+        self.get_process_exit_status_called = True
+        return (53, 0)
+
+    def _get_process_exit_status_unknown_pid(self):
+        if self.get_process_exit_status_called:
+            return (0, 0)
+        self.get_process_exit_status_called = True
+        return (42, 0)
+
+    def _get_process_exit_status_raises_oserror_echild(self):
+        raise OSError(errno.ECHILD, 'Mock error')
+
+    def _get_process_exit_status_raises_oserror_other(self):
+        raise OSError(0, 'Mock error')
+
+    def _get_process_exit_status_raises_other(self):
+        raise Exception('Mock error')
+
+    def _make_mock_process_info(self, name, args, c_channel_env,
+                                dev_null_stdout=False, dev_null_stderr=False):
+        return MockProcessInfo(name, args, c_channel_env,
+                               dev_null_stdout, dev_null_stderr)
+
+class MockBobSimple(BoB):
+    def __init__(self):
+        BoB.__init__(self)
+        # Set which process has been started
+        self.started_process_name = None
+        self.started_process_args = None
+        self.started_process_env = None
+
+    def _make_mock_process_info(self, name, args, c_channel_env,
+                                dev_null_stdout=False, dev_null_stderr=False):
+        return MockProcessInfo(name, args, c_channel_env,
+                               dev_null_stdout, dev_null_stderr)
+
+    def start_process(self, name, args, c_channel_env, port=None,
+                      address=None):
+        self.started_process_name = name
+        self.started_process_args = args
+        self.started_process_env = c_channel_env
+        return None
+
 class TestStartStopProcessesBob(unittest.TestCase):
     """
     Check that the start_all_components method starts the right combination
@@ -930,6 +1042,9 @@ class MockComponent:
         self.pid = lambda: pid
         self.address = lambda: address
         self.restarted = False
+        self.forceful = False
+        self.running = True
+        self.has_failed = False
 
     def get_restart_time(self):
         return 0                # arbitrary dummy value
@@ -938,6 +1053,15 @@ class MockComponent:
         self.restarted = True
         return True
 
+    def is_running(self):
+        return self.running
+
+    def failed(self, status):
+        return self.has_failed
+
+    def kill(self, forceful):
+        self.forceful = forceful
+
 class TestBossCmd(unittest.TestCase):
     def test_ping(self):
         """
@@ -1107,6 +1231,20 @@ class TestBossComponents(unittest.TestCase):
                 'process': 'cat'
             }
         }
+        self._tmp_time = None
+        self._tmp_sleep = None
+        self._tmp_module_cc_session = None
+        self._tmp_cc_session = None
+
+    def tearDown(self):
+        if self._tmp_time is not None:
+            time.time = self._tmp_time
+        if self._tmp_sleep is not None:
+            time.sleep = self._tmp_sleep
+        if self._tmp_module_cc_session is not None:
+            isc.config.ModuleCCSession = self._tmp_module_cc_session
+        if self._tmp_cc_session is not None:
+            isc.cc.Session = self._tmp_cc_session
 
     def __unary_hook(self, param):
         """
@@ -1324,14 +1462,618 @@ class TestBossComponents(unittest.TestCase):
         bob._component_configurator._components['test'] = (None, component)
         self.__setup_restart(bob, component)
         self.assertTrue(component.restarted)
-        self.assertFalse(component in bob.components_to_restart)
+        self.assertNotIn(component, bob.components_to_restart)
 
         # Remove the component from the configuration.  It won't be restarted
         # even if scheduled, nor will remain in the to-be-restarted list.
         del bob._component_configurator._components['test']
         self.__setup_restart(bob, component)
         self.assertFalse(component.restarted)
-        self.assertFalse(component in bob.components_to_restart)
+        self.assertNotIn(component, bob.components_to_restart)
+
+    def test_get_processes(self):
+        '''Test that procsses are returned correctly, sorted by pid.'''
+        bob = MockBob()
+
+        pids = list(range(0, 20))
+        random.shuffle(pids)
+
+        for i in range(0, 20):
+            pid = pids[i]
+            component = MockComponent('test' + str(pid), pid,
+                                      'Test' + str(pid))
+            bob.components[pid] = component
+
+        process_list = bob.get_processes()
+        self.assertEqual(20, len(process_list))
+
+        last_pid = -1
+        for process in process_list:
+            pid = process[0]
+            self.assertLessEqual(last_pid, pid)
+            last_pid = pid
+            self.assertEqual([pid, 'test' + str(pid), 'Test' + str(pid)],
+                             process)
+
+    def _test_reap_children_helper(self, runnable, is_running, failed):
+        '''Construct a BoB instance, set various data in it according to
+        passed args and check if the component was added to the list of
+        components to restart.'''
+        bob = MockBob()
+        bob.runnable = runnable
+
+        component = MockComponent('test', 53)
+        component.running = is_running
+        component.has_failed = failed
+        bob.components[53] = component
+
+        self.assertNotIn(component, bob.components_to_restart)
+
+        bob.reap_children()
+
+        if runnable and is_running and not failed:
+            self.assertIn(component, bob.components_to_restart)
+        else:
+            self.assertEqual([], bob.components_to_restart)
+
+    def test_reap_children(self):
+        '''Test that children are queued to be restarted when they ask for it.'''
+        # test various combinations of 3 booleans
+        # (BoB.runnable, component.is_running(), component.failed())
+        self._test_reap_children_helper(False, False, False)
+        self._test_reap_children_helper(False, False, True)
+        self._test_reap_children_helper(False, True,  False)
+        self._test_reap_children_helper(False, True,  True)
+        self._test_reap_children_helper(True,  False, False)
+        self._test_reap_children_helper(True,  False, True)
+        self._test_reap_children_helper(True,  True,  False)
+        self._test_reap_children_helper(True,  True,  True)
+
+        # setup for more tests below
+        bob = MockBob()
+        bob.runnable = True
+        component = MockComponent('test', 53)
+        bob.components[53] = component
+
+        # case where the returned pid is unknown to us. nothing should
+        # happpen then.
+        bob.get_process_exit_status_called = False
+        bob._get_process_exit_status = bob._get_process_exit_status_unknown_pid
+        bob.components_to_restart = []
+        # this should do nothing as the pid is unknown
+        bob.reap_children()
+        self.assertEqual([], bob.components_to_restart)
+
+        # case where bob._get_process_exit_status() raises OSError with
+        # errno.ECHILD
+        bob._get_process_exit_status = \
+            bob._get_process_exit_status_raises_oserror_echild
+        bob.components_to_restart = []
+        # this should catch and handle the OSError
+        bob.reap_children()
+        self.assertEqual([], bob.components_to_restart)
+
+        # case where bob._get_process_exit_status() raises OSError with
+        # errno other than ECHILD
+        bob._get_process_exit_status = \
+            bob._get_process_exit_status_raises_oserror_other
+        with self.assertRaises(OSError):
+            bob.reap_children()
+
+        # case where bob._get_process_exit_status() raises something
+        # other than OSError
+        bob._get_process_exit_status = \
+            bob._get_process_exit_status_raises_other
+        with self.assertRaises(Exception):
+            bob.reap_children()
+
+    def test_kill_started_components(self):
+        '''Test that started components are killed.'''
+        bob = MockBob()
+
+        component = MockComponent('test', 53, 'Test')
+        bob.components[53] = component
+
+        self.assertEqual([[53, 'test', 'Test']], bob.get_processes())
+        bob.kill_started_components()
+        self.assertEqual([], bob.get_processes())
+        self.assertTrue(component.forceful)
+
+    def _start_msgq_helper(self, bob, verbose):
+        bob.verbose = verbose
+        pi = bob.start_msgq()
+        self.assertEqual('b10-msgq', pi.name)
+        self.assertEqual(['b10-msgq'], pi.args)
+        self.assertTrue(pi.dev_null_stdout)
+        self.assertEqual(pi.dev_null_stderr, not verbose)
+        self.assertEqual({'FOO': 'an env string'}, pi.env)
+
+        # this is set by ProcessInfo.spawn()
+        self.assertEqual(42147, pi.pid)
+
+    def test_start_msgq(self):
+        '''Test that b10-msgq is started.'''
+        bob = MockBobSimple()
+        bob.c_channel_env = {'FOO': 'an env string'}
+        bob._run_under_unittests = True
+
+        # use the MockProcessInfo creator
+        bob._make_process_info = bob._make_mock_process_info
+
+        # non-verbose case
+        self._start_msgq_helper(bob, False)
+
+        # verbose case
+        self._start_msgq_helper(bob, True)
+
+    def test_start_msgq_timeout(self):
+        '''Test that b10-msgq startup attempts connections several times
+        and times out eventually.'''
+        bob = MockBobSimple()
+        bob.c_channel_env = {}
+        # set the timeout to an arbitrary pre-determined value (which
+        # code below depends on)
+        bob.msgq_timeout = 1
+        bob._run_under_unittests = False
+
+        # use the MockProcessInfo creator
+        bob._make_process_info = bob._make_mock_process_info
+
+        global attempts
+        global tsec
+        attempts = 0
+        tsec = 0
+        self._tmp_time = time.time
+        self._tmp_sleep = time.sleep
+        def _my_time():
+            global attempts
+            global tsec
+            attempts += 1
+            return tsec
+        def _my_sleep(nsec):
+            global tsec
+            tsec += nsec
+        time.time = _my_time
+        time.sleep = _my_sleep
+
+        global cc_sub
+        cc_sub = None
+        class DummySessionAlwaysFails():
+            def __init__(self, socket_file):
+                raise isc.cc.session.SessionError('Connection fails')
+            def group_subscribe(self, s):
+                global cc_sub
+                cc_sub = s
+
+        isc.cc.Session = DummySessionAlwaysFails
+
+        with self.assertRaises(bind10_src.CChannelConnectError):
+            # An exception will be thrown here when it eventually times
+            # out.
+            pi = bob.start_msgq()
+
+        # time.time() should be called 12 times within the while loop:
+        # starting from 0, and 11 more times from 0.1 to 1.1. There's
+        # another call to time.time() outside the loop, which makes it
+        # 13.
+        self.assertEqual(attempts, 13)
+
+        # group_subscribe() should not have been called here.
+        self.assertIsNone(cc_sub)
+
+        global cc_socket_file
+        cc_socket_file = None
+        cc_sub = None
+        class DummySession():
+            def __init__(self, socket_file):
+                global cc_socket_file
+                cc_socket_file = socket_file
+            def group_subscribe(self, s):
+                global cc_sub
+                cc_sub = s
+
+        isc.cc.Session = DummySession
+
+        # reset values
+        attempts = 0
+        tsec = 0
+
+        pi = bob.start_msgq()
+
+        # just one attempt, but 2 calls to time.time()
+        self.assertEqual(attempts, 2)
+
+        self.assertEqual(cc_socket_file, bob.msgq_socket_file)
+        self.assertEqual(cc_sub, 'Boss')
+
+        # isc.cc.Session, time.time() and time.sleep() are restored
+        # during tearDown().
+
+    def _start_cfgmgr_helper(self, bob, data_path, filename, clear_config):
+        expect_args = ['b10-cfgmgr']
+        if data_path is not None:
+            bob.data_path = data_path
+            expect_args.append('--data-path=' + data_path)
+        if filename is not None:
+            bob.config_filename = filename
+            expect_args.append('--config-filename=' + filename)
+        if clear_config:
+            bob.clear_config = clear_config
+            expect_args.append('--clear-config')
+
+        pi = bob.start_cfgmgr()
+        self.assertEqual('b10-cfgmgr', pi.name)
+        self.assertEqual(expect_args, pi.args)
+        self.assertEqual({'TESTENV': 'A test string'}, pi.env)
+
+        # this is set by ProcessInfo.spawn()
+        self.assertEqual(42147, pi.pid)
+
+    def test_start_cfgmgr(self):
+        '''Test that b10-cfgmgr is started.'''
+        class DummySession():
+            def __init__(self):
+                self._tries = 0
+            def group_recvmsg(self):
+                self._tries += 1
+                # return running on the 3rd try onwards
+                if self._tries >= 3:
+                    return ({'running': 'ConfigManager'}, None)
+                else:
+                    return ({}, None)
+
+        bob = MockBobSimple()
+        bob.c_channel_env = {'TESTENV': 'A test string'}
+        bob.cc_session = DummySession()
+        bob.wait_time = 5
+
+        # use the MockProcessInfo creator
+        bob._make_process_info = bob._make_mock_process_info
+
+        global attempts
+        attempts = 0
+        self._tmp_sleep = time.sleep
+        def _my_sleep(nsec):
+            global attempts
+            attempts += 1
+        time.sleep = _my_sleep
+
+        # defaults
+        self._start_cfgmgr_helper(bob, None, None, False)
+
+        # check that 2 attempts were made. on the 3rd attempt,
+        # process_running() returns that ConfigManager is running.
+        self.assertEqual(attempts, 2)
+
+        # data_path is specified
+        self._start_cfgmgr_helper(bob, '/var/lib/test', None, False)
+
+        # config_filename is specified. Because `bob` is not
+        # reconstructed, data_path is retained from the last call to
+        # _start_cfgmgr_helper().
+        self._start_cfgmgr_helper(bob, '/var/lib/test', 'foo.cfg', False)
+
+        # clear_config is specified. Because `bob` is not reconstructed,
+        # data_path and config_filename are retained from the last call
+        # to _start_cfgmgr_helper().
+        self._start_cfgmgr_helper(bob, '/var/lib/test', 'foo.cfg', True)
+
+    def test_start_cfgmgr_timeout(self):
+        '''Test that b10-cfgmgr startup attempts connections several times
+        and times out eventually.'''
+        class DummySession():
+            def group_recvmsg(self):
+                return (None, None)
+        bob = MockBobSimple()
+        bob.c_channel_env = {}
+        bob.cc_session = DummySession()
+        # set wait_time to an arbitrary pre-determined value (which code
+        # below depends on)
+        bob.wait_time = 2
+
+        # use the MockProcessInfo creator
+        bob._make_process_info = bob._make_mock_process_info
+
+        global attempts
+        attempts = 0
+        self._tmp_sleep = time.sleep
+        def _my_sleep(nsec):
+            global attempts
+            attempts += 1
+        time.sleep = _my_sleep
+
+        # We just check that an exception was thrown, and that several
+        # attempts were made to connect.
+        with self.assertRaises(bind10_src.ProcessStartError):
+            pi = bob.start_cfgmgr()
+
+        # 2 seconds of attempts every 1 second should result in 2 attempts
+        self.assertEqual(attempts, 2)
+
+        # time.sleep() is restored during tearDown().
+
+    def test_start_ccsession(self):
+        '''Test that CC session is started.'''
+        class DummySession():
+            def __init__(self, specfile, config_handler, command_handler,
+                         socket_file):
+                self.specfile = specfile
+                self.config_handler = config_handler
+                self.command_handler = command_handler
+                self.socket_file = socket_file
+                self.started = False
+            def start(self):
+                self.started = True
+        bob = MockBobSimple()
+        self._tmp_module_cc_session = isc.config.ModuleCCSession
+        isc.config.ModuleCCSession = DummySession
+
+        bob.start_ccsession({})
+        self.assertEqual(bind10_src.SPECFILE_LOCATION, bob.ccs.specfile)
+        self.assertEqual(bob.config_handler, bob.ccs.config_handler)
+        self.assertEqual(bob.command_handler, bob.ccs.command_handler)
+        self.assertEqual(bob.msgq_socket_file, bob.ccs.socket_file)
+        self.assertTrue(bob.ccs.started)
+
+        # isc.config.ModuleCCSession is restored during tearDown().
+
+    def test_start_process(self):
+        '''Test that processes can be started.'''
+        bob = MockBob()
+
+        # use the MockProcessInfo creator
+        bob._make_process_info = bob._make_mock_process_info
+
+        pi = bob.start_process('Test Process', ['/bin/true'], {})
+        self.assertEqual('Test Process', pi.name)
+        self.assertEqual(['/bin/true'], pi.args)
+        self.assertEqual({}, pi.env)
+
+        # this is set by ProcessInfo.spawn()
+        self.assertEqual(42147, pi.pid)
+
+    def test_register_process(self):
+        '''Test that processes can be registered with BoB.'''
+        bob = MockBob()
+        component = MockComponent('test', 53, 'Test')
+
+        self.assertFalse(53 in bob.components)
+        bob.register_process(53, component)
+        self.assertTrue(53 in bob.components)
+        self.assertEqual(bob.components[53].name(), 'test')
+        self.assertEqual(bob.components[53].pid(), 53)
+        self.assertEqual(bob.components[53].address(), 'Test')
+
+    def _start_simple_helper(self, bob, verbose):
+        bob.verbose = verbose
+
+        args = ['/bin/true']
+        if verbose:
+            args.append('-v')
+
+        bob.start_simple('/bin/true')
+        self.assertEqual('/bin/true', bob.started_process_name)
+        self.assertEqual(args, bob.started_process_args)
+        self.assertEqual({'TESTENV': 'A test string'}, bob.started_process_env)
+
+    def test_start_simple(self):
+        '''Test simple process startup.'''
+        bob = MockBobSimple()
+        bob.c_channel_env = {'TESTENV': 'A test string'}
+
+        # non-verbose case
+        self._start_simple_helper(bob, False)
+
+        # verbose case
+        self._start_simple_helper(bob, True)
+
+    def _start_auth_helper(self, bob, verbose):
+        bob.verbose = verbose
+
+        args = ['b10-auth']
+        if verbose:
+            args.append('-v')
+
+        bob.start_auth()
+        self.assertEqual('b10-auth', bob.started_process_name)
+        self.assertEqual(args, bob.started_process_args)
+        self.assertEqual({'FOO': 'an env string'}, bob.started_process_env)
+
+    def test_start_auth(self):
+        '''Test that b10-auth is started.'''
+        bob = MockBobSimple()
+        bob.c_channel_env = {'FOO': 'an env string'}
+
+        # non-verbose case
+        self._start_auth_helper(bob, False)
+
+        # verbose case
+        self._start_auth_helper(bob, True)
+
+    def _start_resolver_helper(self, bob, verbose):
+        bob.verbose = verbose
+
+        args = ['b10-resolver']
+        if verbose:
+            args.append('-v')
+
+        bob.start_resolver()
+        self.assertEqual('b10-resolver', bob.started_process_name)
+        self.assertEqual(args, bob.started_process_args)
+        self.assertEqual({'BAR': 'an env string'}, bob.started_process_env)
+
+    def test_start_resolver(self):
+        '''Test that b10-resolver is started.'''
+        bob = MockBobSimple()
+        bob.c_channel_env = {'BAR': 'an env string'}
+
+        # non-verbose case
+        self._start_resolver_helper(bob, False)
+
+        # verbose case
+        self._start_resolver_helper(bob, True)
+
+    def _start_cmdctl_helper(self, bob, verbose, port = None):
+        bob.verbose = verbose
+
+        args = ['b10-cmdctl']
+
+        if port is not None:
+            bob.cmdctl_port = port
+            args.append('--port=9353')
+
+        if verbose:
+            args.append('-v')
+
+        bob.start_cmdctl()
+        self.assertEqual('b10-cmdctl', bob.started_process_name)
+        self.assertEqual(args, bob.started_process_args)
+        self.assertEqual({'BAZ': 'an env string'}, bob.started_process_env)
+
+    def test_start_cmdctl(self):
+        '''Test that b10-cmdctl is started.'''
+        bob = MockBobSimple()
+        bob.c_channel_env = {'BAZ': 'an env string'}
+
+        # non-verbose case
+        self._start_cmdctl_helper(bob, False)
+
+        # verbose case
+        self._start_cmdctl_helper(bob, True)
+
+        # with port, non-verbose case
+        self._start_cmdctl_helper(bob, False, 9353)
+
+        # with port, verbose case
+        self._start_cmdctl_helper(bob, True, 9353)
+
+    def test_socket_data(self):
+        '''Test that BoB._socket_data works as expected.'''
+        class MockSock:
+            def __init__(self, fd, throw):
+                self.fd = fd
+                self.throw = throw
+                self.buf = b'Hello World.\nYou are so nice today.\nXX'
+                self.i = 0
+
+            def recv(self, bufsize, flags = 0):
+                if bufsize != 1:
+                    raise Exception('bufsize != 1')
+                if flags != socket.MSG_DONTWAIT:
+                    raise Exception('flags != socket.MSG_DONTWAIT')
+                # after 15 recv()s, throw a socket.error with EAGAIN to
+                # get _socket_data() to save back what's been read. The
+                # number 15 is arbitrarily chosen, but the checks then
+                # depend on this being 15, i.e., if you adjust this
+                # number, you may have to adjust the checks below too.
+                if self.throw and self.i > 15:
+                    raise socket.error(errno.EAGAIN, 'Try again')
+                if self.i >= len(self.buf):
+                    return b'';
+                t = self.i
+                self.i += 1
+                return self.buf[t:t+1]
+
+            def close(self):
+                return
+
+        class MockBobSocketData(BoB):
+            def __init__(self, throw):
+                self._unix_sockets = {42: (MockSock(42, throw), b'')}
+                self.requests = []
+                self.dead = []
+
+            def socket_request_handler(self, previous, sock):
+                self.requests.append({sock.fd: previous})
+
+            def socket_consumer_dead(self, sock):
+                self.dead.append(sock.fd)
+
+        # Case where we get data every time we call recv()
+        bob = MockBobSocketData(False)
+        bob._socket_data(42)
+        self.assertEqual(bob.requests,
+                         [{42: b'Hello World.'},
+                          {42: b'You are so nice today.'}])
+        self.assertEqual(bob.dead, [42])
+        self.assertEqual({}, bob._unix_sockets)
+
+        # Case where socket.recv() raises EAGAIN. In this case, the
+        # routine is supposed to save what it has back to
+        # BoB._unix_sockets.
+        bob = MockBobSocketData(True)
+        bob._socket_data(42)
+        self.assertEqual(bob.requests, [{42: b'Hello World.'}])
+        self.assertFalse(bob.dead)
+        self.assertEqual(len(bob._unix_sockets), 1)
+        self.assertEqual(bob._unix_sockets[42][1], b'You')
+
+    def test_startup(self):
+        '''Test that BoB.startup() handles failures properly.'''
+        class MockBobStartup(BoB):
+            def __init__(self, throw):
+                self.throw = throw
+                self.started = False
+                self.killed = False
+                self.msgq_socket_file = None
+                self.curproc = 'myproc'
+                self.runnable = False
+
+            def start_all_components(self):
+                self.started = True
+                if self.throw:
+                    raise Exception('Assume starting components has failed.')
+
+            def kill_started_components(self):
+                self.killed = True
+
+        class DummySession():
+            def __init__(self, socket_file):
+                raise isc.cc.session.SessionError('This is the expected case.')
+
+        class DummySessionSocketExists():
+            def __init__(self, socket_file):
+                # simulate that connect passes
+                return
+
+        isc.cc.Session = DummySession
+
+        # All is well case, where all components are started
+        # successfully. We check that the actual call to
+        # start_all_components() is made, and BoB.runnable is true.
+        bob = MockBobStartup(False)
+        r = bob.startup()
+        self.assertIsNone(r)
+        self.assertTrue(bob.started)
+        self.assertFalse(bob.killed)
+        self.assertTrue(bob.runnable)
+        self.assertEqual({}, bob.c_channel_env)
+
+        # Case where starting components fails. We check that
+        # kill_started_components() is called right after, and
+        # BoB.runnable is not modified.
+        bob = MockBobStartup(True)
+        r = bob.startup()
+        # r contains an error message
+        self.assertEqual(r, 'Unable to start myproc: Assume starting components has failed.')
+        self.assertTrue(bob.started)
+        self.assertTrue(bob.killed)
+        self.assertFalse(bob.runnable)
+        self.assertEqual({}, bob.c_channel_env)
+
+        # Check if msgq_socket_file is carried over
+        bob = MockBobStartup(False)
+        bob.msgq_socket_file = 'foo'
+        r = bob.startup()
+        self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env)
+
+        # Check the case when socket file already exists
+        isc.cc.Session = DummySessionSocketExists
+        bob = MockBobStartup(False)
+        r = bob.startup()
+        self.assertIn('already running', r)
+
+        # isc.cc.Session is restored during tearDown().
 
 class SocketSrvTest(unittest.TestCase):
     """
@@ -1563,6 +2305,25 @@ class TestFunctions(unittest.TestCase):
         # second call should not assert anyway
         bind10_src.remove_lock_files()
 
+    def test_get_signame(self):
+        # just test with some samples
+        signame = bind10_src.get_signame(signal.SIGTERM)
+        self.assertEqual('SIGTERM', signame)
+        signame = bind10_src.get_signame(signal.SIGKILL)
+        self.assertEqual('SIGKILL', signame)
+        # 59426 is hopefully an unused signal on most platforms
+        signame = bind10_src.get_signame(59426)
+        self.assertEqual('Unknown signal 59426', signame)
+
+    def test_fatal_signal(self):
+        self.assertIsNone(bind10_src.boss_of_bind)
+        bind10_src.boss_of_bind = BoB()
+        bind10_src.boss_of_bind.runnable = True
+        bind10_src.fatal_signal(signal.SIGTERM, None)
+        # Now, runnable must be False
+        self.assertFalse(bind10_src.boss_of_bind.runnable)
+        bind10_src.boss_of_bind = None
+
 if __name__ == '__main__':
     # store os.environ for test_unchanged_environment
     original_os_environ = copy.deepcopy(os.environ)

+ 1 - 1
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -27,7 +27,7 @@ import glob
 import os.path
 import imp
 import isc.log
-isc.log.init("b10-cfgmgr")
+isc.log.init("b10-cfgmgr", buffer=True)
 from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError, logger
 from isc.log_messages.cfgmgr_messages import *
 

+ 1 - 1
src/bin/cmdctl/cmdctl.py.in

@@ -49,7 +49,7 @@ from hashlib import sha1
 from isc.util import socketserver_mixin
 from isc.log_messages.cmdctl_messages import *
 
-isc.log.init("b10-cmdctl")
+isc.log.init("b10-cmdctl", buffer=True)
 logger = isc.log.Logger("cmdctl")
 
 # Debug level for communication with BIND10

+ 1 - 1
src/bin/ddns/ddns.py.in

@@ -45,7 +45,7 @@ import os.path
 import signal
 import socket
 
-isc.log.init("b10-ddns")
+isc.log.init("b10-ddns", buffer=True)
 logger = isc.log.Logger("ddns")
 TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
 

+ 7 - 1
src/bin/dhcp4/main.cc

@@ -17,6 +17,7 @@
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <log/logger_support.h>
+#include <log/logger_manager.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -93,9 +94,10 @@ main(int argc, char* argv[]) {
     }
 
     // Initialize logging.  If verbose, we'll use maximum verbosity.
+    // If standalone is enabled, do not buffer initial log messages
     isc::log::initLogger(DHCP4_NAME,
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
     LOG_INFO(dhcp4_logger, DHCP4_STARTING);
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
               .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
@@ -112,6 +114,10 @@ main(int argc, char* argv[]) {
                 LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
                 // Let's continue. It is useful to have the ability to run
                 // DHCP server in stand-alone mode, e.g. for testing
+                // We do need to make sure logging is no longer buffered
+                // since then it would not print until dhcp6 is stopped
+                isc::log::LoggerManager log_manager;
+                log_manager.process();
             }
         } else {
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);

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

@@ -24,6 +24,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
 #include <dhcp/pkt6.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/dhcp6_srv.h>
@@ -348,13 +349,29 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
 }
 
 OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
-
-    // @todo: Implement Option6_StatusCode and rewrite this code here
-    vector<uint8_t> data(text.c_str(), text.c_str() + text.length());
-    data.insert(data.begin(), static_cast<uint8_t>(code % 256));
-    data.insert(data.begin(), static_cast<uint8_t>(code >> 8));
-    OptionPtr status(new Option(Option::V6, D6O_STATUS_CODE, data));
-    return (status);
+    // @todo This function uses OptionCustom class to manage contents
+    // of the data fields. Since this this option is frequently used
+    // it may be good to implement dedicated class to avoid performance
+    // impact.
+
+    // Get the definition of the option holding status code.
+    OptionDefinitionPtr status_code_def =
+        LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
+    // This definition is assumed to be initialized in LibDHCP.
+    assert(status_code_def);
+
+    // As there is no dedicated class to represent Status Code
+    // the OptionCustom class should be returned here.
+    boost::shared_ptr<OptionCustom> option_status =
+        boost::dynamic_pointer_cast<
+            OptionCustom>(status_code_def->optionFactory(Option::V6, D6O_STATUS_CODE));
+    assert(option_status);
+
+    // Set status code to 'code' (0 - means data field #0).
+    option_status->writeInteger(code, 0);
+    // Set a message (1 - means data field #1).
+    option_status->writeString(text, 1);
+    return (option_status);
 }
 
 Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
@@ -430,6 +447,10 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // but different wording below)
     if (!subnet) {
         // Create empty IA_NA option with IAID matching the request.
+        // Note that we don't use OptionDefinition class to create this option.
+        // This is because we prefer using a constructor of Option6IA that
+        // initializes IAID. Otherwise we would have to use setIAID() after
+        // creation of the option which has some performance implications.
         boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
 
         // Insert status code NoAddrsAvail.
@@ -471,6 +492,8 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
                                                       hint, fake_allocation);
 
     // Create IA_NA that we will put in the response.
+    // Do not use OptionDefinition to create option's instance so
+    // as we can initialize IAID using a constructor.
     boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
 
     if (lease) {

+ 8 - 2
src/bin/dhcp6/main.cc

@@ -17,6 +17,7 @@
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <log/logger_support.h>
+#include <log/logger_manager.h>
 
 #include <boost/lexical_cast.hpp>
 
@@ -103,9 +104,10 @@ main(int argc, char* argv[]) {
     }
 
     // Initialize logging.  If verbose, we'll use maximum verbosity.
+    // If standalone is enabled, do not buffer initial log messages
     isc::log::initLogger(DHCP6_NAME,
                          (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
     LOG_INFO(dhcp6_logger, DHCP6_STARTING);
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
               .arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
@@ -119,8 +121,12 @@ main(int argc, char* argv[]) {
                 server.establishSession();
             } catch (const std::exception& ex) {
                 LOG_ERROR(dhcp6_logger, DHCP6_SESSION_FAIL).arg(ex.what());
-                // Let's continue. It is useful to have the ability to run 
+                // Let's continue. It is useful to have the ability to run
                 // DHCP server in stand-alone mode, e.g. for testing
+                // We do need to make sure logging is no longer buffered
+                // since then it would not print until dhcp6 is stopped
+                isc::log::LoggerManager log_manager;
+                log_manager.process();
             }
         } else {
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);

+ 16 - 5
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -801,12 +801,23 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
     ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
 
     // a dummy content for client-id
-    uint8_t expected[] = {0x0, 0x3, 0x41, 0x42, 0x43, 0x44, 0x45};
-    OptionBuffer exp(expected, expected + sizeof(expected));
-
+    uint8_t expected[] = {
+        0x0, 0xD, // option code = 14
+        0x0, 0x7, // option length = 7
+        0x0, 0x3, // status code = 3
+        0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
+    };
+    // Create the option.
     OptionPtr status = srv->createStatusCode(3, "ABCDE");
-
-    EXPECT_TRUE(status->getData() == exp);
+    // Allocate an output buffer. We will store the option
+    // in wire format here.
+    OutputBuffer buf(sizeof(expected));
+    // Prepare the wire format.
+    ASSERT_NO_THROW(status->pack(buf));
+    // Check that the option buffer has valid length (option header + data).
+    ASSERT_EQ(sizeof(expected), buf.getLength());
+    // Verify the contents of the option.
+    EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected)));
 }
 
 // This test verifies if the selectSubnet() method works as expected.

+ 6 - 6
src/bin/resolver/main.cc

@@ -143,7 +143,7 @@ main(int argc, char* argv[]) {
     // temporary initLogger() code.  If verbose, we'll use maximum verbosity.
     isc::log::initLogger(RESOLVER_NAME,
                          (verbose ? isc::log::DEBUG : isc::log::INFO),
-                         isc::log::MAX_DEBUG_LEVEL, NULL);
+                         isc::log::MAX_DEBUG_LEVEL, NULL, true);
 
     // Print the starting message
     string cmdline = argv[0];
@@ -177,7 +177,7 @@ main(int argc, char* argv[]) {
 
         isc::cache::ResolverCache cache;
         resolver->setCache(cache);
-        
+
         // TODO priming query, remove root from direct
         // Fake a priming query result here (TODO2 how to flag non-expiry?)
         // propagation to runningquery. And check for forwarder mode?
@@ -185,21 +185,21 @@ main(int argc, char* argv[]) {
                                             isc::dns::Name("."),
                                             isc::dns::RRClass::IN(),
                                             isc::dns::RRType::NS()));
-        isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."), 
+        isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
                                          isc::dns::RRClass::IN(),
                                          isc::dns::RRType::NS(),
                                          isc::dns::RRTTL(8888)));
         root_ns_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::NS(),
                                                              isc::dns::RRClass::IN(),
                                                              "l.root-servers.net."));
-        isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"), 
+        isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
                                         isc::dns::RRClass::IN(),
                                         isc::dns::RRType::A(),
                                         isc::dns::RRTTL(8888)));
         root_a_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
                                                              isc::dns::RRClass::IN(),
                                                              "199.7.83.42"));
-        isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"), 
+        isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
                                         isc::dns::RRClass::IN(),
                                         isc::dns::RRType::AAAA(),
                                         isc::dns::RRTTL(8888)));
@@ -216,7 +216,7 @@ main(int argc, char* argv[]) {
         cache.update(root_ns_rrset);
         cache.update(root_a_rrset);
         cache.update(root_aaaa_rrset);
-        
+
         DNSService dns_service(io_service, checkin, lookup, answer);
         resolver->setDNSService(dns_service);
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);

+ 2 - 2
src/bin/stats/stats.py.in

@@ -31,7 +31,7 @@ import isc.util.process
 import isc.log
 from isc.log_messages.stats_messages import *
 
-isc.log.init("b10-stats")
+isc.log.init("b10-stats", buffer=True)
 logger = isc.log.Logger("stats")
 
 # Some constants for debug levels.
@@ -682,7 +682,7 @@ if __name__ == "__main__":
             help="enable maximum debug logging")
         (options, args) = parser.parse_args()
         if options.verbose:
-            isc.log.init("b10-stats", "DEBUG", 99)
+            isc.log.init("b10-stats", "DEBUG", 99, buffer=True)
         stats = Stats()
         stats.start()
     except OptionValueError as ove:

+ 2 - 2
src/bin/stats/stats_httpd.py.in

@@ -39,7 +39,7 @@ import isc.util.process
 import isc.log
 from isc.log_messages.stats_httpd_messages import *
 
-isc.log.init("b10-stats-httpd")
+isc.log.init("b10-stats-httpd", buffer=True)
 logger = isc.log.Logger("stats-httpd")
 
 # Some constants for debug levels.
@@ -609,7 +609,7 @@ if __name__ == "__main__":
             help="enable maximum debug logging")
         (options, args) = parser.parse_args()
         if options.verbose:
-            isc.log.init("b10-stats-httpd", "DEBUG", 99)
+            isc.log.init("b10-stats-httpd", "DEBUG", 99, buffer=True)
         stats_httpd = StatsHttpd()
         stats_httpd.start()
     except OptionValueError as ove:

+ 4 - 6
src/bin/xfrin/b10-xfrin.xml

@@ -112,20 +112,18 @@ in separate zonemgr process.
       <varname>master_addr</varname> (the zone master to transfer from),
       <varname>master_port</varname> (defaults to 53),
       <varname>use_ixfr</varname> (defaults to false), and
-      <varname>tsig_key</varname> (optional TSIG key to use).
-      The <varname>tsig_key</varname> is specified using a full string
-      colon-delimited name:key:algorithm representation (e.g.
-      <quote>foo.example.org:EvABsfU2h7uofnmqaRCrhHunGsd=:hmac-sha1</quote>).
+      <varname>tsig_key</varname> (optional TSIG key name to use).
+      The <varname>tsig_key</varname> is specified using a name that
+      corresponds to one of the TSIG keys configured in the global
+      TSIG key ring (<quote>/tsig_keys/keys</quote>).
     </para>
 <!-- TODO: document this better -->
-<!-- TODO: the tsig_key format may change -->
 
     <para>
       (The site-wide <varname>master_addr</varname> and
       <varname>master_port</varname> configurations are deprecated;
       use the <varname>zones</varname> list configuration instead.)
     </para>
-<!-- NOTE: also tsig_key but not mentioning since so short lived. -->
 
 <!-- TODO: formating -->
     <para>

+ 46 - 4
src/bin/xfrin/tests/xfrin_test.py

@@ -26,6 +26,7 @@ from xfrin import *
 import xfrin
 from isc.xfrin.diff import Diff
 import isc.log
+from isc.server_common.tsig_keyring import init_keyring, get_keyring
 # If we use any python library that is basically a wrapper for
 # a library we use as well (like sqlite3 in our datasources),
 # we must make sure we import ours first; If we have special
@@ -139,6 +140,16 @@ class MockCC(MockModuleCCSession):
         if identifier == "zones/use_ixfr":
             return False
 
+    def add_remote_config_by_name(self, name, callback):
+        pass
+
+    def get_remote_config_value(self, module, identifier):
+        if module == 'tsig_keys' and identifier == 'keys':
+            return (['example.com.key.:EvAAsfU2h7uofnmqaTCrhHunGsc='], True)
+        else:
+            raise Exception('MockCC requested for unknown config value ' +
+                            + module + "/" + identifier)
+
     def remove_remote_config(self, module_name):
         pass
 
@@ -229,6 +240,7 @@ class MockXfrin(Xfrin):
     def _cc_setup(self):
         self._tsig_key = None
         self._module_cc = MockCC()
+        init_keyring(self._module_cc)
         pass
 
     def _get_db_file(self):
@@ -2427,9 +2439,10 @@ class TestXfrin(unittest.TestCase):
             self.assertEqual(str(zone_info.master_addr), zone_config['master_addr'])
             self.assertEqual(zone_info.master_port, zone_config['master_port'])
             if 'tsig_key' in zone_config:
-                self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text())
+                self.assertEqual(zone_info.tsig_key_name.to_text(),
+                                 Name(zone_config['tsig_key']).to_text())
             else:
-                self.assertIsNone(zone_info.tsig_key)
+                self.assertIsNone(zone_info.tsig_key_name)
             if 'use_ixfr' in zone_config and\
                zone_config.get('use_ixfr'):
                 self.assertTrue(zone_info.use_ixfr)
@@ -2562,7 +2575,7 @@ class TestXfrin(unittest.TestCase):
                   { 'name': 'test2.example.',
                     'master_addr': '192.0.2.9',
                     'master_port': 53,
-                    'tsig_key': 'badkey'
+                    'tsig_key': 'badkey..'
                   }
                 ]}
         self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
@@ -2581,13 +2594,14 @@ class TestXfrin(unittest.TestCase):
         self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
         self._check_zones_config(config)
 
-    def common_ixfr_setup(self, xfr_mode, use_ixfr):
+    def common_ixfr_setup(self, xfr_mode, use_ixfr, tsig_key_str = None):
         # This helper method explicitly sets up a zone configuration with
         # use_ixfr, and invokes either retransfer or refresh.
         # Shared by some of the following test cases.
         config = {'zones': [
                 {'name': 'example.com.',
                  'master_addr': '192.0.2.1',
+                 'tsig_key': tsig_key_str,
                  'use_ixfr': use_ixfr}]}
         self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
         self.assertEqual(self.xfr.command_handler(xfr_mode,
@@ -2603,6 +2617,34 @@ class TestXfrin(unittest.TestCase):
         self.common_ixfr_setup('refresh', True)
         self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type)
 
+    def test_command_handler_retransfer_with_tsig(self):
+        self.common_ixfr_setup('retransfer', False, 'example.com.key')
+        self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+
+    def test_command_handler_retransfer_with_tsig_bad_key(self):
+        # bad keys should not reach xfrin, but should they somehow,
+        # they are ignored (and result in 'key not found' + error log).
+        self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+                          'retransfer', False, 'bad.key')
+
+    def test_command_handler_retransfer_with_tsig_unknown_key(self):
+        self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+                          'retransfer', False, 'no.such.key')
+
+    def test_command_handler_refresh_with_tsig(self):
+        self.common_ixfr_setup('refresh', False, 'example.com.key')
+        self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+
+    def test_command_handler_refresh_with_tsig_bad_key(self):
+        # bad keys should not reach xfrin, but should they somehow,
+        # they are ignored (and result in 'key not found' + error log).
+        self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+                          'refresh', False, 'bad.key')
+
+    def test_command_handler_refresh_with_tsig_unknown_key(self):
+        self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+                          'refresh', False, 'no.such.key')
+
     def test_command_handler_retransfer_ixfr_disabled(self):
         # Similar to the previous case, but explicitly disabled.  AXFR should
         # be used.

+ 32 - 15
src/bin/xfrin/xfrin.py.in

@@ -34,9 +34,10 @@ from isc.datasrc import DataSourceClient, ZoneFinder
 import isc.net.parse
 from isc.xfrin.diff import Diff
 from isc.server_common.auth_command import auth_loadzone_command
+from isc.server_common.tsig_keyring import init_keyring, get_keyring
 from isc.log_messages.xfrin_messages import *
 
-isc.log.init("b10-xfrin")
+isc.log.init("b10-xfrin", buffer=True)
 logger = isc.log.Logger("xfrin")
 
 # Pending system-wide debug level definitions, the ones we
@@ -69,7 +70,10 @@ AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
 
 AUTH_MODULE_NAME = 'Auth'
 XFROUT_MODULE_NAME = 'Xfrout'
+
+# Remote module and identifiers (according to their spec files)
 ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
+
 REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
 
 # Constants for debug levels.
@@ -1179,7 +1183,7 @@ class ZoneInfo:
 
         self.set_master_port(config_data.get('master_port'))
         self.set_zone_class(config_data.get('class'))
-        self.set_tsig_key(config_data.get('tsig_key'))
+        self.set_tsig_key_name(config_data.get('tsig_key'))
         self.set_use_ixfr(config_data.get('use_ixfr'))
 
     def set_name(self, name_str):
@@ -1240,20 +1244,32 @@ class ZoneInfo:
                 errmsg = "invalid zone class: " + zone_class_str
                 raise XfrinZoneInfoException(errmsg)
 
-    def set_tsig_key(self, tsig_key_str):
-        """Set the tsig_key for this zone, given a TSIG key string
-           representation. If tsig_key_str is None, no TSIG key will
-           be set. Raises XfrinZoneInfoException if tsig_key_str cannot
-           be parsed."""
+    def set_tsig_key_name(self, tsig_key_str):
+        """Set the name of the tsig_key for this zone. If tsig_key_str
+           is None, no TSIG key will be used. This name is used to
+           find the TSIG key to use for transfers in the global TSIG
+           key ring.
+           Raises XfrinZoneInfoException if tsig_key_str is not a valid
+           (dns) name."""
         if tsig_key_str is None:
-            self.tsig_key = None
+            self.tsig_key_name = None
         else:
+            # can throw a number of exceptions but it is just one
+            # call, so Exception should be OK here
             try:
-                self.tsig_key = TSIGKey(tsig_key_str)
-            except InvalidParameter as ipe:
-                logger.error(XFRIN_BAD_TSIG_KEY_STRING, tsig_key_str)
-                errmsg = "bad TSIG key string: " + tsig_key_str
-                raise XfrinZoneInfoException(errmsg)
+                self.tsig_key_name = Name(tsig_key_str)
+            except Exception as exc:
+                raise XfrinZoneInfoException("Bad TSIG key name: " + str(exc))
+
+    def get_tsig_key(self):
+        if self.tsig_key_name is None:
+            return None
+        result, key = get_keyring().find(self.tsig_key_name)
+        if result != isc.dns.TSIGKeyRing.SUCCESS:
+            raise XfrinZoneInfoException("TSIG key not found in keyring: " +
+                                         self.tsig_key_name.to_text())
+        else:
+            return key
 
     def set_use_ixfr(self, use_ixfr):
         """Set use_ixfr. If set to True, it will use
@@ -1308,6 +1324,7 @@ class Xfrin:
         self.config_handler(config_data)
         self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION,
                                           self._auth_config_handler)
+        init_keyring(self._module_cc)
 
     def _cc_check_command(self):
         '''This is a straightforward wrapper for cc.check_command,
@@ -1468,7 +1485,7 @@ class Xfrin:
                                                rrclass,
                                                self._get_db_file(),
                                                master_addr,
-                                               zone_info.tsig_key, request_type,
+                                               zone_info.get_tsig_key(), request_type,
                                                True)
                         answer = create_answer(ret[0], ret[1])
                     else:
@@ -1491,7 +1508,7 @@ class Xfrin:
                 tsig_key = None
                 request_type = RRType.AXFR()
                 if zone_info:
-                    tsig_key = zone_info.tsig_key
+                    tsig_key = zone_info.get_tsig_key()
                     if zone_info.use_ixfr:
                         request_type = RRType.IXFR()
                 db_file = args.get('db_file') or self._get_db_file()

+ 8 - 1
src/bin/xfrin/xfrin_messages.mes

@@ -43,7 +43,7 @@ The master port as read from the configuration is not a valid port number.
 
 % XFRIN_BAD_TSIG_KEY_STRING bad TSIG key string: %1
 The TSIG key string as read from the configuration does not represent
-a valid TSIG key.
+a valid TSIG key. The key is ignored.
 
 % XFRIN_BAD_ZONE_CLASS Invalid zone class: %1
 The zone class as read from the configuration is not a valid DNS class.
@@ -160,6 +160,13 @@ run time: Time (in seconds) the complete axfr took
 
 bytes/second: Transfer speed
 
+% XFRIN_TSIG_KEY_NOT_FOUND TSIG key not found in key ring: %1
+An attempt to start a transfer with TSIG was made, but the configured TSIG
+key name was not found in the TSIG key ring (configuration option
+tsig_keys/keys). The transfer is aborted. The key name that could not be
+found is shown in the log message. Check the configuration and the
+TSIG key ring.
+
 % XFRIN_UNKNOWN_ERROR unknown error: %1
 An uncaught exception was raised while running the xfrin daemon. The
 exception message is printed in the log message.

+ 1 - 1
src/bin/xfrout/xfrout.py.in

@@ -38,7 +38,7 @@ import isc.server_common.tsig_keyring
 
 from isc.log_messages.xfrout_messages import *
 
-isc.log.init("b10-xfrout")
+isc.log.init("b10-xfrout", buffer=True)
 logger = isc.log.Logger("xfrout")
 
 # Pending system-wide debug level definitions, the ones we

+ 3 - 2
src/bin/zonemgr/zonemgr.py.in

@@ -42,7 +42,7 @@ from isc.log_messages.zonemgr_messages import *
 from isc.notify import notify_out
 
 # Initialize logging for called modules.
-isc.log.init("b10-zonemgr")
+isc.log.init("b10-zonemgr", buffer=True)
 logger = isc.log.Logger("zonemgr")
 
 # Pending system-wide debug level definitions, the ones we
@@ -193,7 +193,8 @@ class ZonemgrRefresh:
     def zone_handle_notify(self, zone_name_class, master):
         """Handle zone notify"""
         if (self._zone_not_exist(zone_name_class)):
-            logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0], zone_name_class[1])
+            logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0],
+                         zone_name_class[1], master)
             raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) "
                                    "doesn't belong to zonemgr" % zone_name_class)
         self._set_zone_notifier_master(zone_name_class, master)

+ 1 - 1
src/bin/zonemgr/zonemgr_messages.mes

@@ -138,7 +138,7 @@ zone, or, if this error appears without the administrator giving transfer
 commands, it can indicate an error in the program, as it should not have
 initiated transfers of unknown zones on its own.
 
-% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1 (class %2) is not known to the zone manager
+% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1/%2 from %3 is not known to the zone manager
 A NOTIFY was received but the zone that was the subject of the operation
 is not being managed by the zone manager.  This may indicate an error
 in the program (as the operation should not have been initiated if this

+ 15 - 0
src/lib/asiolink/io_address.cc

@@ -76,6 +76,21 @@ IOAddress::fromBytes(short family, const uint8_t* data) {
     return IOAddress(string(addr_str));
 }
 
+std::vector<uint8_t>
+IOAddress::toBytes() const {
+    if (asio_address_.is_v4()) {
+        const asio::ip::address_v4::bytes_type bytes4 =
+            asio_address_.to_v4().to_bytes();
+        return (std::vector<uint8_t>(bytes4.begin(), bytes4.end()));
+    }
+
+    // Not V4 address, so must be a V6 address (else we could never construct
+    // this object).
+    const asio::ip::address_v6::bytes_type bytes6 =
+        asio_address_.to_v6().to_bytes();
+    return (std::vector<uint8_t>(bytes6.begin(), bytes6.end()));
+}
+
 short
 IOAddress::getFamily() const {
     if (asio_address_.is_v4()) {

+ 21 - 2
src/lib/asiolink/io_address.h

@@ -24,6 +24,7 @@
 
 #include <functional>
 #include <string>
+#include <vector>
 
 #include <exceptions/exceptions.h>
 
@@ -103,6 +104,19 @@ public:
     /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
     short getFamily() const;
 
+    /// \brief Convenience function to check for an IPv4 address
+    ///
+    /// \return true if the address is a V4 address
+    bool isV4() const {
+        return (asio_address_.is_v4());
+    }
+
+    /// \brief Convenience function to check for an IPv6 address
+    ///
+    /// \return true if the address is a V6 address
+    bool isV6() const {
+        return (asio_address_.is_v6());
+    }
 
     /// \brief Creates an address from over wire data.
     ///
@@ -110,8 +124,13 @@ public:
     /// \param data pointer to first char of data
     ///
     /// \return Created IOAddress object
-    static IOAddress
-    fromBytes(short family, const uint8_t* data);
+    static IOAddress fromBytes(short family, const uint8_t* data);
+
+    /// \brief Return address as set of bytes
+    ///
+    /// \return Contents of the address as a set of bytes in network-byte
+    ///         order.
+    std::vector<uint8_t> toBytes() const;
 
     /// \brief Compare addresses for equality
     ///

+ 41 - 0
src/lib/asiolink/tests/io_address_unittest.cc

@@ -18,7 +18,9 @@
 #include <asiolink/io_error.h>
 #include <asiolink/io_address.h>
 
+#include <algorithm>
 #include <cstring>
+#include <vector>
 
 using namespace isc::asiolink;
 
@@ -84,6 +86,45 @@ TEST(IOAddressTest, fromBytes) {
     EXPECT_EQ(addr.toText(), IOAddress("192.0.2.3").toText());
 }
 
+TEST(IOAddressTest, toBytesV4) {
+    // Address and network byte-order representation of the address.
+    const char* V4STRING = "192.0.2.1";
+    uint8_t V4[] = {0xc0, 0x00, 0x02, 0x01};
+
+    std::vector<uint8_t> actual = IOAddress(V4STRING).toBytes();
+    ASSERT_EQ(sizeof(V4), actual.size());
+    EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V4));
+}
+
+TEST(IOAddressTest, toBytesV6) {
+    // Address and network byte-order representation of the address.
+    const char* V6STRING = "2001:db8:1::dead:beef";
+    uint8_t V6[] = {
+        0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00,
+        0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef 
+    };
+
+    std::vector<uint8_t> actual = IOAddress(V6STRING).toBytes();
+    ASSERT_EQ(sizeof(V6), actual.size());
+    EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V6));
+}
+
+TEST(IOAddressTest, isV4) {
+    const IOAddress address4("192.0.2.1");
+    const IOAddress address6("2001:db8:1::dead:beef");
+
+    EXPECT_TRUE(address4.isV4());
+    EXPECT_FALSE(address6.isV4());
+}
+
+TEST(IOAddressTest, isV6) {
+    const IOAddress address4("192.0.2.1");
+    const IOAddress address6("2001:db8:1::dead:beef");
+
+    EXPECT_FALSE(address4.isV6());
+    EXPECT_TRUE(address6.isV6());
+}
+
 TEST(IOAddressTest, uint32) {
     IOAddress addr1("192.0.2.5");
 

+ 11 - 2
src/lib/datasrc/memory/zone_finder.cc

@@ -305,8 +305,16 @@ getClosestNSEC(const ZoneData& zone_data,
     }
 
     const ZoneNode* prev_node;
-    while ((prev_node = zone_data.getZoneTree().previousNode(node_path))
-           != NULL) {
+    if (node_path.getLastComparisonResult().getRelation() ==
+        NameComparisonResult::SUBDOMAIN) {
+         // In case the search ended as a sub-domain, the previous node
+         // is already at the top of node_path.
+         prev_node = node_path.getLastComparedNode();
+    } else {
+         prev_node = zone_data.getZoneTree().previousNode(node_path);
+    }
+
+    while (prev_node != NULL) {
         if (!prev_node->isEmpty()) {
             const RdataSet* found =
                 RdataSet::find(prev_node->getData(), RRType::NSEC());
@@ -314,6 +322,7 @@ getClosestNSEC(const ZoneData& zone_data,
                 return (ConstNodeRRset(prev_node, found));
             }
         }
+        prev_node = zone_data.getZoneTree().previousNode(node_path);
     }
     // This must be impossible and should be an internal bug.
     // See the description at the method declaration.

+ 10 - 0
src/lib/datasrc/tests/memory/testdata/2504-test.zone

@@ -0,0 +1,10 @@
+example.com.	3600	IN	SOA	ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+example.com.	3600	IN	RRSIG	SOA 5 2 3600 20130104152459 20121207152459 5196 example.com. ijAvh4ZzAfMCKKWN64aR5CWaHYTAvhJjgBLV+1/RFPG3150DwDr9EI0bCdVyRIMDDLOeQnn0N70rtc051rA2pJVNpEzG2hPIy3Yd2kDsbFBn0atiz3rvjpRLcmmWWYoZihpkFwKRxD41OzJPLg83/yJr1nAu3qSQXh4LmuNfgwI=
+example.com.	3600	IN	A	192.0.2.1
+example.com.	3600	IN	RRSIG	A 5 2 3600 20130104152459 20121207152459 5196 example.com. XnVKYuSH+BANWiULSJAk6wmwnba8hUS9j2cgrCMFcn43XlAUCCNigoMCWhaGbsssMaOyjdwfL3sQZr/NHaQ0ITSL8IZj7t8rOiCvCrUAktuG5UuHmHET6XKPkf03JaoPXIzO9smBqvjFHz1HsuPGxwGh8ztY0p291iXk0zbRwc8=
+example.com.	3600	IN	NS	example.com.
+example.com.	3600	IN	RRSIG	NS 5 2 3600 20130104152459 20121207152459 5196 example.com. W5v7dr/WV8FGdxWFS0h1crd1MRxkSrkcmcAs7CT0+uhmVvbx06PsBxGRuCHyL8Y5NimsMs2RLjhkUkJw1+aSLVtTlbC8Pg5dFDK3bGkzBEq3wRcIXf5bM2Lf+l/cWxGY0NgR1Wrq0ckXsnFxegGm9G3OtpCZgTv0L+9cCO4MS7c=
+example.com.	3600	IN	DNSKEY	256 3 5 AwEAAZIOhUpUld/OBPeNJ26O1twKj3fRLPt4X8H6N01t4s+VT5v9jaCnCVX4O1LbALdJUv5uPwL4gy4qvf+7Z3Xanp7QCZ5i7ivS1qfiz2tfacXwtVv4aI4EqS7deYN6yD4S/vIpwW+2FoqUWhQtdhC68ex1YfjeEI+CUbAKlF5XgQR5 ;{id = 5196 (zsk), size = 1024b}
+example.com.	3600	IN	RRSIG	DNSKEY 5 2 3600 20130104152459 20121207152459 5196 example.com. OiN+DBuEDnyEmMe0Qa4MN4SIJ71e+INNhOvoGBpWARiWu83QlPkoJhdU1GADBaanYdFL8UI0os6w2dkwp4aghChD+KWO40NuhUY2LrEUS2jbO3hEcCT3/acaVyucwVv1FjfC4d561Lkfnh8DM9nk5i75IeWVLklMqret1t/f0uY=
+example.com.	7200	IN	NSEC	example.com. A NS SOA RRSIG NSEC DNSKEY
+example.com.	7200	IN	RRSIG	NSEC 5 2 7200 20130104152459 20121207152459 5196 example.com. P4eCHTNJImfwQ+wLa3jeySkVeJnzCmb4zFUDQEZIk3GSjLGUHZhHswqSAhpgevx6ZNX/pOqN/Dybf9oadFCZXyoMQDijP02LcDEl4X1ccNiakg6i/9RG9PcEx5ZWJlCFAJ0tOhp1kauZu/HUlkscTAVgBRud0qEclWJacH1k80E=

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

@@ -31,3 +31,4 @@ EXTRA_DIST += example.org-wildcard-ns.zone
 EXTRA_DIST += example.org-wildcard-nsec3.zone
 
 EXTRA_DIST += 2503-test.zone
+EXTRA_DIST += 2504-test.zone

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

@@ -1430,6 +1430,27 @@ TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
              ZoneFinder::DELEGATION, true, ns_rrset);
 }
 
+// \brief testcase for #2504 (Problem in inmem NSEC denial of existence
+// handling)
+TEST_F(InMemoryZoneFinderTest, NSECNonExistentTest) {
+    shared_ptr<ZoneTableSegment> ztable_segment(
+         new ZoneTableSegmentTest(class_, mem_sgmt_));
+    InMemoryClient client(ztable_segment, class_);
+    Name name("example.com.");
+
+    client.load(name, TEST_DATA_DIR "/2504-test.zone");
+    DataSourceClient::FindResult result(client.findZone(name));
+
+    // Check for a non-existing name
+    Name search_name("nonexist.example.com.");
+    ZoneFinderContextPtr find_result(
+        result.zone_finder->find(search_name,
+                                 RRType::A(), ZoneFinder::FIND_DNSSEC));
+    // We don't find the domain, but find() must complete (not throw or
+    // assert).
+    EXPECT_EQ(ZoneFinder::NXDOMAIN, find_result->code);
+}
+
 /// \brief NSEC3 specific tests fixture for the InMemoryZoneFinder class
 class InMemoryZoneFinderNSEC3Test : public InMemoryZoneFinderTest {
 public:

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

@@ -33,14 +33,16 @@ libb10_dhcp___la_SOURCES += option6_int_array.h
 libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libb10_dhcp___la_SOURCES += std_option_defs.h
 
 libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
 libb10_dhcp___la_LIBADD   = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libb10_dhcp___la_LIBADD  += $(top_builddir)/src/lib/dns/libb10-dns++.la
 libb10_dhcp___la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
 libb10_dhcp___la_LDFLAGS  = -no-undefined -version-info 2:0:0
 
-EXTRA_DIST  = README
+EXTRA_DIST  = README libdhcp++.dox
 
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the

+ 12 - 12
src/lib/dhcp/duid.cc

@@ -33,7 +33,7 @@ DUID::DUID(const std::vector<uint8_t>& duid) {
     }
 }
 
-DUID::DUID(const uint8_t * data, size_t len) {
+DUID::DUID(const uint8_t* data, size_t len) {
     if (len > MAX_DUID_LEN) {
         isc_throw(OutOfRange, "DUID too large");
     }
@@ -72,36 +72,36 @@ std::string DUID::toText() const {
     return (tmp.str());
 }
 
-bool DUID::operator == (const DUID& other) const {
+bool DUID::operator==(const DUID& other) const {
     return (this->duid_ == other.duid_);
 }
 
-bool DUID::operator != (const DUID& other) const {
+bool DUID::operator!=(const DUID& other) const {
     return (this->duid_ != other.duid_);
 }
 
-/// constructor based on vector<uint8_t>
+// Constructor based on vector<uint8_t>
 ClientId::ClientId(const std::vector<uint8_t>& clientid)
-    :DUID(clientid) {
+    : DUID(clientid) {
 }
 
-/// constructor based on C-style data
+// Constructor based on C-style data
 ClientId::ClientId(const uint8_t *clientid, size_t len)
-    :DUID(clientid, len) {
+    : DUID(clientid, len) {
 }
 
-/// @brief returns a copy of client-id data
+// Returns a copy of client-id data
 const std::vector<uint8_t> ClientId::getClientId() const {
     return (duid_);
 }
 
-// compares two client-ids
-bool ClientId::operator == (const ClientId& other) const {
+// Compares two client-ids
+bool ClientId::operator==(const ClientId& other) const {
     return (this->duid_ == other.duid_);
 }
 
-// compares two client-ids
-bool ClientId::operator != (const ClientId& other) const {
+// Compares two client-ids
+bool ClientId::operator!=(const ClientId& other) const {
     return (this->duid_ != other.duid_);
 }
 

+ 30 - 19
src/lib/dhcp/duid.h

@@ -45,13 +45,13 @@ class DUID {
         DUID_MAX          ///< not a real type, just maximum defined value + 1
     } DUIDType;
 
-    /// @brief creates a DUID
+    /// @brief Constructor from vector
     DUID(const std::vector<uint8_t>& duid);
 
-    /// @brief creates a DUID
-    DUID(const uint8_t *duid, size_t len);
+    /// @brief Constructor from array and array size
+    DUID(const uint8_t* duid, size_t len);
 
-    /// @brief returns a const reference to the actual DUID value
+    /// @brief Returns a const reference to the actual DUID value
     ///
     /// Note: For safety reasons, this method returns a copy of data as
     /// otherwise the reference would be only valid as long as the object that
@@ -60,47 +60,58 @@ class DUID {
     /// (e.g. storeSelf()) that will avoid data copying.
     const std::vector<uint8_t> getDuid() const;
 
-    /// @brief returns DUID type
+    /// @brief Returns the DUID type
     DUIDType getType() const;
 
-    /// returns textual prepresentation (e.g. 00:01:02:03:ff)
+    /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
     std::string toText() const;
 
-    /// compares two DUIDs
+    /// @brief Compares two DUIDs for equality
     bool operator==(const DUID& other) const;
 
-    /// compares two DUIDs
+    /// @brief Compares two DUIDs for inequality
     bool operator!=(const DUID& other) const;
 
  protected:
-    /// the actual content of the DUID
+    /// The actual content of the DUID
     std::vector<uint8_t> duid_;
 };
 
+/// @brief Shared pointer to a DUID
 typedef boost::shared_ptr<DUID> DuidPtr;
 
+
+
 /// @brief Holds Client identifier or client IPv4 address
 ///
 /// This class is intended to be a generic IPv4 client identifier. It can hold
 /// a client-id
 class ClientId : DUID {
- public:
+public:
+    /// @brief Maximum size of a client ID
+    ///
+    /// This is the same as the maximum size of the underlying DUID.
+    ///
+    /// @note RFC 2131 does not specify an upper length of a client ID, the
+    ///       value chosen here just being that of the underlying DUID.  For
+    ///       some backend database, there may be a possible (minor)
+    ///       performance enhancement if this were smaller.
+    static const size_t MAX_CLIENT_ID_LEN = DUID::MAX_DUID_LEN;
 
-    /// constructor based on vector<uint8_t>
+    /// @brief Constructor based on vector<uint8_t>
     ClientId(const std::vector<uint8_t>& clientid);
 
-    /// constructor based on C-style data
-    ClientId(const uint8_t *clientid, size_t len);
+    /// @brief Constructor based on array and array size
+    ClientId(const uint8_t* clientid, size_t len);
 
-    /// @brief returns reference to the client-id data
-    ///
+    /// @brief Returns reference to the client-id data
     const std::vector<uint8_t> getClientId() const;
 
-    // compares two client-ids
-    bool operator == (const ClientId& other) const;
+    /// @brief Compares two client-ids for equality
+    bool operator==(const ClientId& other) const;
 
-    // compares two client-ids
-    bool operator != (const ClientId& other) const;
+    /// @brief Compares two client-ids for inequality
+    bool operator!=(const ClientId& other) const;
 };
 
 }; // end of isc::dhcp namespace

+ 22 - 46
src/lib/dhcp/libdhcp++.cc

@@ -22,6 +22,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int_array.h>
 #include <dhcp/option_definition.h>
+#include <dhcp/std_option_defs.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
 
@@ -45,7 +46,7 @@ OptionDefContainer LibDHCP::v4option_defs_;
 OptionDefContainer LibDHCP::v6option_defs_;
 
 const OptionDefContainer&
-LibDHCP::getOptionDefs(Option::Universe u) {
+LibDHCP::getOptionDefs(const Option::Universe u) {
     switch (u) {
     case Option::V4:
         initStdOptionDefs4();
@@ -60,6 +61,17 @@ LibDHCP::getOptionDefs(Option::Universe u) {
     }
 }
 
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
+    const OptionDefContainer& defs = getOptionDefs(u);
+    const OptionDefContainerTypeIndex& idx = defs.get<1>();
+    const OptionDefContainerTypeRange& range = idx.equal_range(code);
+    if (range.first != range.second) {
+        return (*range.first);
+    }
+    return (OptionDefinitionPtr());
+}
+
 OptionPtr
 LibDHCP::optionFactory(Option::Universe u,
                        uint16_t type,
@@ -254,52 +266,16 @@ void
 LibDHCP::initStdOptionDefs6() {
     v6option_defs_.clear();
 
-    struct OptionParams {
-        std::string name;
-        uint16_t code;
-        OptionDataType type;
-        bool array;
-    };
-    OptionParams params[] = {
-        { "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]);
-
-    for (int i = 0; i < params_size; ++i) {
-        OptionDefinitionPtr definition(new OptionDefinition(params[i].name,
-                                                            params[i].code,
-                                                            params[i].type,
-                                                            params[i].array));
-        switch(params[i].code) {
-        case D6O_IA_NA:
-        case D6O_IA_PD:
-            for (int j = 0; j < 3; ++j) {
-                definition->addRecordField(OPT_UINT32_TYPE);
-            }
-            break;
-        case D6O_IAADDR:
-            definition->addRecordField(OPT_IPV6_ADDRESS_TYPE);
-            definition->addRecordField(OPT_UINT32_TYPE);
-            definition->addRecordField(OPT_UINT32_TYPE);
-            break;
-        case D6O_STATUS_CODE:
-            definition->addRecordField(OPT_UINT16_TYPE);
-            definition->addRecordField(OPT_STRING_TYPE);
-            break;
-        default:
-            // The default case is intentionally left empty
-            // as it does not need any processing.
-            ;
+    for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
+        OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
+                                                            OPTION_DEF_PARAMS6[i].code,
+                                                            OPTION_DEF_PARAMS6[i].type,
+                                                            OPTION_DEF_PARAMS6[i].array));
+
+        for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
+            definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
         }
+
         try {
             definition->validate();
         } catch (const Exception& ex) {

+ 12 - 1
src/lib/dhcp/libdhcp++.h

@@ -42,7 +42,18 @@ public:
     /// @param u universe of the options (V4 or V6).
     ///
     /// @return collection of option definitions.
-    static const OptionDefContainer& getOptionDefs(Option::Universe u);
+    static const OptionDefContainer& getOptionDefs(const Option::Universe u);
+
+    /// @brief Return the first option definition matching a
+    /// particular option code.
+    ///
+    /// @param u universe (V4 or V6)
+    /// @param code option code.
+    ///
+    /// @return reference to an option definition being requested
+    /// or NULL pointer if option definition has not been found.
+    static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+                                            const uint16_t code);
 
     /// @brief Factory function to create instance of option.
     ///

+ 300 - 34
src/lib/dhcp/option_custom.cc

@@ -21,11 +21,28 @@ namespace isc {
 namespace dhcp {
 
 OptionCustom::OptionCustom(const OptionDefinition& def,
+                           Universe u)
+    : Option(u, def.getCode(), OptionBuffer()),
+      definition_(def) {
+    createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
                              Universe u,
                              const OptionBuffer& data)
     : Option(u, def.getCode(), data.begin(), data.end()),
       definition_(def) {
-    createBuffers();
+    // It is possible that no data is provided if an option
+    // is being created on a server side. In such case a bunch
+    // of buffers with default values is first created and then
+    // the values are replaced using writeXXX functions. Thus
+    // we need to detect that no data has been specified and
+    // take a different code path.
+    if (!data_.empty()) {
+        createBuffers(data_);
+    } else {
+        createBuffers();
+    }
 }
 
 OptionCustom::OptionCustom(const OptionDefinition& def,
@@ -34,25 +51,156 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
                              OptionBufferConstIter last)
     : Option(u, def.getCode(), first, last),
       definition_(def) {
-    createBuffers();
+    createBuffers(data_);
+}
+
+void
+OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+    checkArrayType();
+
+    if ((address.getFamily() == AF_INET &&
+         definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
+        (address.getFamily() == AF_INET6 &&
+         definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
+        isc_throw(BadDataTypeCast, "invalid address specified "
+                  << address.toText() << ". Expected a valid IPv"
+                  << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ? "4" : "6")
+                  << " address.");
+    }
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writeAddress(address, buf);
+    buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const bool value) {
+    checkArrayType();
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writeBool(value, buf);
+    buffers_.push_back(buf);
 }
 
 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.");
+                  << " is out of range.");
+    }
+}
+
+template<typename T>
+void
+OptionCustom::checkDataType(const uint32_t index) const {
+    // Check that the requested return type is a supported integer.
+    if (!OptionDataTypeTraits<T>::integer_type) {
+        isc_throw(isc::dhcp::InvalidDataType, "specified data type"
+                  " is not a 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];
+    }
+
+    if (OptionDataTypeTraits<T>::type != data_type) {
+        isc_throw(isc::dhcp::InvalidDataType,
+                  "specified data type " << data_type << " does not"
+                  " match the data type in an option definition for field"
+                  " index " << index);
     }
 }
 
 void
 OptionCustom::createBuffers() {
+    definition_.validate();
+
+    std::vector<OptionBuffer> buffers;
+
+    OptionDataType data_type = definition_.getType();
+    // This function is called when an empty data buffer has been
+    // passed to the constructor. In such cases values for particular
+    // data fields will be set using modifier functions but for now
+    // we need to initialize a set of buffers that are specified
+    // for an option by its definition. Since there is no data yet,
+    // we are going to fill these buffers with default values.
+    if (data_type == OPT_RECORD_TYPE) {
+        // For record types we need to iterate over all data fields
+        // specified in option definition and create corresponding
+        // buffers for each of them.
+        const OptionDefinition::RecordFieldsCollection fields =
+            definition_.getRecordFields();
+
+        for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+             field != fields.end(); ++field) {
+            OptionBuffer buf;
+
+            // For data types that have a fixed size we can use the
+            // utility function to get the buffer's size.
+            size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+            // For variable data sizes the utility function returns zero.
+            // It is ok for string values because the default string
+            // is 'empty'. However for FQDN the empty value is not valid
+            // so we initialize it to '.'.
+            if (data_size == 0 &&
+                *field == OPT_FQDN_TYPE) {
+                OptionDataTypeUtil::writeFqdn(".", buf);
+            } else {
+                // At this point we can resize the buffer. Note that
+                // for string values we are setting the empty buffer
+                // here.
+                buf.resize(data_size);
+            }
+            // We have the buffer with default value prepared so we
+            // add it to the set of buffers.
+            buffers.push_back(buf);
+        }
+    } else if (!definition_.getArrayType() &&
+               data_type != OPT_EMPTY_TYPE) {
+        // For either 'empty' options we don't have to create any buffers
+        // for obvious reason. For arrays we also don't create any buffers
+        // yet because the set of fields that belong to the array is open
+        // ended so we can't allocate required buffers until we know how
+        // many of them are needed.
+        // For non-arrays we have a single value being held by the option
+        // so we have to allocate exactly one buffer.
+        OptionBuffer buf;
+        size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+        if (data_size == 0 &&
+            data_type == OPT_FQDN_TYPE) {
+            OptionDataTypeUtil::writeFqdn(".", buf);
+        } else {
+            // Note that if our option holds a string value then
+            // we are making empty buffer here.
+            buf.resize(data_size);
+        }
+        // Add a buffer that we have created and leave.
+        buffers.push_back(buf);
+    }
+    // The 'swap' is used here because we want to make sure that we
+    // don't touch buffers_ until we successfully allocate all
+    // buffers to be stored there.
+    std::swap(buffers, buffers_);
+}
+
+void
+OptionCustom::createBuffers(const OptionBuffer& data_buf) {
     // 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();
+    OptionBuffer::const_iterator data = data_buf.begin();
 
     OptionDataType data_type = definition_.getType();
     if (data_type == OPT_RECORD_TYPE) {
@@ -68,17 +216,31 @@ OptionCustom::createBuffers() {
             // 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).
+            size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+            // For variable size types (e.g. string) the function above will
+            // return 0 so we need to do a runtime check of the length.
             if (data_size == 0) {
-                data_size = std::distance(data, data_.end());
+                // FQDN is a special data type as it stores variable length data
+                // but the data length is encoded in the buffer. The easiest way
+                // to obtain the length of the data is to read the FQDN. The
+                // utility function will return the size of the buffer on success.
+                if (*field == OPT_FQDN_TYPE) {
+                    std::string fqdn =
+                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+                    // The size of the buffer holding an FQDN is always
+                    // 1 byte larger than the size of the string
+                    // representation of this FQDN.
+                    data_size = fqdn.size() + 1;
+                } else {
+                    // In other case we are dealing with string or binary value
+                    // which size can't be determined. Thus we consume the
+                    // remaining part of the buffer for it. Note that variable
+                    // size data can be laid at the end of the option only and
+                    // that the validate() function in OptionDefinition object
+                    // should have checked wheter it is a case for this option.
+                    data_size = std::distance(data, data_buf.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
@@ -90,7 +252,7 @@ OptionCustom::createBuffers() {
             } 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) {
+                if (std::distance(data, data_buf.end()) < data_size) {
                     isc_throw(OutOfRange, "option buffer truncated");
                 }
             }
@@ -105,39 +267,64 @@ OptionCustom::createBuffers() {
         // 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);
+        size_t 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) {
+        if (std::distance(data, data_buf.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 {
+            while (data != data_buf.end()) {
+                // FQDN is a special case because it is of a variable length.
+                // The actual length for a particular FQDN is encoded within
+                // a buffer so we have to actually read the FQDN from a buffer
+                // to get it.
+                if (data_type == OPT_FQDN_TYPE) {
+                    std::string fqdn =
+                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+                    // The size of the buffer holding an FQDN is always
+                    // 1 byte larger than the size of the string
+                    // representation of this FQDN.
+                    data_size = fqdn.size() + 1;
+                }
+                // 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 chunks of data and store as a 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.
+                if (std::distance(data, data_buf.end()) < data_size) {
+                    break;
+                }
                 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());
+                // For FQDN we get the size by actually reading the FQDN.
+                if (data_type == OPT_FQDN_TYPE) {
+                    std::string fqdn =
+                        OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+                    // The size of the buffer holding an FQDN is always
+                    // 1 bytes larger than the size of the string
+                    // representation of this FQDN.
+                    data_size = fqdn.size() + 1;
+                } else {
+                    data_size = std::distance(data, data_buf.end());
+                }
             }
             if (data_size > 0) {
                 buffers.push_back(OptionBuffer(data, data + data_size));
@@ -185,6 +372,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_type,
     case OPT_IPV6_ADDRESS_TYPE:
         text << readAddress(index).toText();
         break;
+    case OPT_FQDN_TYPE:
+        text << readFqdn(index);
+        break;
     case OPT_STRING_TYPE:
         text << readString(index);
         break;
@@ -252,8 +442,31 @@ OptionCustom::readAddress(const uint32_t index) const {
         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());
+                  << " IP address. Invalid buffer length "
+                  << buffers_[index].size() << ".");
+    }
+}
+
+void
+OptionCustom::writeAddress(const asiolink::IOAddress& address,
+                           const uint32_t index) {
+    using namespace isc::asiolink;
+
+    checkIndex(index);
+
+    if ((address.getFamily() == AF_INET &&
+         buffers_[index].size() != V4ADDRESS_LEN) ||
+        (address.getFamily() == AF_INET6 &&
+         buffers_[index].size() != V6ADDRESS_LEN)) {
+        isc_throw(BadDataTypeCast, "invalid address specified "
+                  << address.toText() << ". Expected a valid IPv"
+                  << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
+                  << " address.");
     }
+
+    OptionBuffer buf;
+    OptionDataTypeUtil::writeAddress(address, buf);
+    std::swap(buf, buffers_[index]);
 }
 
 const OptionBuffer&
@@ -262,12 +475,50 @@ OptionCustom::readBinary(const uint32_t index) const {
     return (buffers_[index]);
 }
 
+void
+OptionCustom::writeBinary(const OptionBuffer& buf,
+                          const uint32_t index) {
+    checkIndex(index);
+    buffers_[index] = buf;
+}
+
 bool
 OptionCustom::readBoolean(const uint32_t index) const {
     checkIndex(index);
     return (OptionDataTypeUtil::readBool(buffers_[index]));
 }
 
+void
+OptionCustom::writeBoolean(const bool value, const uint32_t index) {
+    checkIndex(index);
+
+    buffers_[index].clear();
+    OptionDataTypeUtil::writeBool(value, buffers_[index]);
+}
+
+std::string
+OptionCustom::readFqdn(const uint32_t index) const {
+    checkIndex(index);
+    return (OptionDataTypeUtil::readFqdn(buffers_[index]));
+}
+
+void
+OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
+    checkIndex(index);
+
+    // Create a temporay buffer where the FQDN will be written.
+    OptionBuffer buf;
+    // Try to write to the temporary buffer rather than to the
+    // buffers_ member directly guarantees that we don't modify
+    // (clear) buffers_ until we are sure that the provided FQDN
+    // is valid.
+    OptionDataTypeUtil::writeFqdn(fqdn, buf);
+    // If we got to this point it means that the FQDN is valid.
+    // We can move the contents of the teporary buffer to the
+    // target buffer.
+    std::swap(buffers_[index], buf);
+}
+
 std::string
 OptionCustom::readString(const uint32_t index) const {
     checkIndex(index);
@@ -275,11 +526,26 @@ OptionCustom::readString(const uint32_t index) const {
 }
 
 void
+OptionCustom::writeString(const std::string& text, const uint32_t index) {
+    checkIndex(index);
+
+    // Let's clear a buffer as we want to replace the value of the
+    // whole buffer. If we fail to clear the buffer the data will
+    // be appended.
+    buffers_[index].clear();
+    // If the text value is empty we can leave because the buffer
+    // is already empty.
+    if (!text.empty()) {
+        OptionDataTypeUtil::writeString(text, 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();
+    createBuffers(data_);
 }
 
 uint16_t
@@ -311,7 +577,7 @@ void OptionCustom::setData(const OptionBufferConstIter first,
 
     // Chop the data_ buffer into set of buffers that represent
     // option fields data.
-    createBuffers();
+    createBuffers(data_);
 }
 
 std::string OptionCustom::toText(int indent) {

+ 158 - 34
src/lib/dhcp/option_custom.h

@@ -40,6 +40,17 @@ public:
 
     /// @brief Constructor, used for options to be sent.
     ///
+    /// This constructor creates an instance of an option with default
+    /// data set for all data fields. The option buffers are allocated
+    /// according to data size being stored in particular data fields.
+    /// For variable size data empty buffers are created.
+    ///
+    /// @param def option definition.
+    /// @param u specifies universe (V4 or V6)
+    OptionCustom(const OptionDefinition& def, Universe u);
+
+    /// @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.
@@ -76,6 +87,37 @@ public:
     OptionCustom(const OptionDefinition& def, Universe u,
                  OptionBufferConstIter first, OptionBufferConstIter last);
 
+    /// @brief Create new buffer and set its value as an IP address.
+    ///
+    /// @param address IPv4 or IPv6 address to be written to
+    /// a buffer being created.
+    void addArrayDataField(const asiolink::IOAddress& address);
+
+    /// @brief Create new buffer and store boolean value in it.
+    ///
+    /// @param value value to be stored in the created buffer.
+    void addArrayDataField(const bool value);
+
+    /// @brief Create new buffer and store integer value in it.
+    ///
+    /// @param value value to be stored in the created buffer.
+    /// @tparam T integer type of the value being stored.
+    template<typename T>
+    void addArrayDataField(const T value) {
+        checkArrayType();
+
+        OptionDataType data_type = definition_.getType();
+        if (OptionDataTypeTraits<T>::type != data_type) {
+            isc_throw(isc::dhcp::InvalidDataType,
+                      "specified data type " << data_type << " does not"
+                      " match the data type in an option definition");
+        }
+
+        OptionBuffer buf;
+        OptionDataTypeUtil::writeInt<T>(value, buf);
+        buffers_.push_back(buf);
+    }
+
     /// @brief Return a number of the data fields.
     ///
     /// @return number of data fields held by the option.
@@ -87,7 +129,17 @@ public:
     ///
     /// @return IP address read from a buffer.
     /// @throw isc::OutOfRange if index is out of range.
-    asiolink::IOAddress readAddress(const uint32_t index) const;
+    asiolink::IOAddress readAddress(const uint32_t index = 0) const;
+
+    /// @brief Write an IP address into a buffer.
+    ///
+    /// @param address IP address being written.
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid.
+    void writeAddress(const asiolink::IOAddress& address,
+                      const uint32_t index = 0);
 
     /// @brief Read a buffer as binary data.
     ///
@@ -95,7 +147,13 @@ public:
     ///
     /// @throw isc::OutOfRange if index is out of range.
     /// @return read buffer holding binary data.
-    const OptionBuffer& readBinary(const uint32_t index) const;
+    const OptionBuffer& readBinary(const uint32_t index = 0) const;
+
+    /// @brief Write binary data into a buffer.
+    ///
+    /// @param buf buffer holding binary data to be written.
+    /// @param index buffer index.
+    void writeBinary(const OptionBuffer& buf, const uint32_t index = 0);
 
     /// @brief Read a buffer as boolean value.
     ///
@@ -103,7 +161,34 @@ public:
     ///
     /// @throw isc::OutOfRange if index is out of range.
     /// @return read boolean value.
-    bool readBoolean(const uint32_t index) const;
+    bool readBoolean(const uint32_t index = 0) const;
+
+    /// @brief Write a boolean value into a buffer.
+    ///
+    /// @param value boolean value to be written.
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    void writeBoolean(const bool value, const uint32_t index = 0);
+
+    /// @brief Read a buffer as FQDN.
+    ///
+    /// @param index buffer index.
+    /// @param [out] len number of bytes read from a buffer.
+    ///
+    /// @throw isc::OutOfRange if buffer index is out of range.
+    /// @throw isc::dhcp::BadDataTypeCast if a buffer being read
+    /// does not hold a valid FQDN.
+    /// @return string representation if FQDN.
+    std::string readFqdn(const uint32_t index = 0) const;
+
+    /// @brief Write an FQDN into a buffer.
+    ///
+    /// @param fqdn text representation of FQDN.
+    /// @param index buffer index.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    void writeFqdn(const std::string& fqdn, const uint32_t index = 0);
 
     /// @brief Read a buffer as integer value.
     ///
@@ -111,38 +196,15 @@ public:
     /// @tparam integer type of a value being returned.
     ///
     /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::InvalidDataType if T is invalid.
     /// @return read integer value.
     template<typename T>
-    T readInteger(const uint32_t index) const {
+    T readInteger(const uint32_t index = 0) const {
+        // Check that the index is not out of range.
         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.");
-        }
+        // Check that T points to a valid integer type and this type
+        // is consistent with an option definition.
+        checkDataType<T>(index);
         // 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);
@@ -150,13 +212,43 @@ public:
         return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
     }
 
+    /// @brief Write an integer value into a buffer.
+    ///
+    /// @param value integer value to be written.
+    /// @param index buffer index.
+    /// @tparam T integer type of a value being written.
+    ///
+    /// @throw isc::OutOfRange if index is out of range.
+    /// @throw isc::dhcp::InvalidDataType if T is invalid.
+    template<typename T>
+    void writeInteger(const T value, const uint32_t index = 0) {
+        // Check that the index is not out of range.
+        checkIndex(index);
+        // Check that T points to a valid integer type and this type
+        // is consistent with an option definition.
+        checkDataType<T>(index);
+        // Get some temporary buffer.
+        OptionBuffer buf;
+        // Try to write to the buffer.
+        OptionDataTypeUtil::writeInt<T>(value, buf);
+        // If successful, replace the old buffer with new one.
+        std::swap(buffers_[index], buf);
+    }
+
     /// @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;
+    std::string readString(const uint32_t index = 0) const;
+
+    /// @brief Write a string value into a buffer.
+    ///
+    /// @param text the string value to be written.
+    /// @param buffer index.
+    void writeString(const std::string& text,
+                     const uint32_t index = 0);
 
     /// @brief Parses received buffer.
     ///
@@ -201,6 +293,33 @@ protected:
 
 private:
 
+    /// @brief Verify that the option comprises an array of values.
+    ///
+    /// This helper function is used by createArrayEntry functions
+    /// and throws an exception if the particular option is not
+    /// an array.
+    ///
+    /// @throw isc::InvalidOperation if option is not an array.
+    inline void checkArrayType() const {
+        if (!definition_.getArrayType()) {
+            isc_throw(InvalidOperation, "failed to add new array entry to an"
+                      << " option. The option is not an array.");
+        }
+    }
+
+    /// @brief Verify that the integer type is consistent with option
+    /// field type.
+    ///
+    /// This convenience function checks that the data type specified as T
+    /// is consistent with a type of a data field identified by index.
+    ///
+    /// @param index data field index.
+    /// @tparam data type to be validated.
+    ///
+    /// @throw isc::dhcp::InvalidDataType if the type is invalid.
+    template<typename T>
+    void checkDataType(const uint32_t index) const;
+
     /// @brief Check if data field index is valid.
     ///
     /// @param index Data field index to check.
@@ -208,9 +327,14 @@ private:
     /// @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.
+    /// @brief Create a collection of non initialized buffers.
     void createBuffers();
 
+    /// @brief Create collection of buffers representing data field values.
+    ///
+    /// @param data_buf a buffer to be parsed.
+    void createBuffers(const OptionBuffer& data_buf);
+
     /// @brief Return a text representation of a data field.
     ///
     /// @param data_type data type of a field.

+ 40 - 1
src/lib/dhcp/option_data_types.cc

@@ -13,6 +13,8 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dhcp/option_data_types.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
 #include <util/encode/hex.h>
 
 namespace isc {
@@ -207,9 +209,46 @@ OptionDataTypeUtil::writeBool(const bool value,
 }
 
 std::string
+OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
+    // If buffer is empty emit an error.
+    if (buf.empty()) {
+        isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer."
+                  << " The buffer is empty.");
+    }
+    // Set up an InputBuffer so as we can use isc::dns::Name object to get the FQDN.
+    isc::util::InputBuffer in_buf(static_cast<const void*>(&buf[0]), buf.size());
+    try {
+        // Try to create an object from the buffer. If exception is thrown
+        // it means that the buffer doesn't hold a valid domain name (invalid
+        // syntax).
+        isc::dns::Name name(in_buf);
+        return (name.toText());
+    } catch (const isc::Exception& ex) {
+        // Unable to convert the data in the buffer into FQDN.
+        isc_throw(BadDataTypeCast, ex.what());
+    }
+}
+
+void
+OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
+                              std::vector<uint8_t>& buf) {
+    try {
+        isc::dns::Name name(fqdn);
+        isc::dns::LabelSequence labels(name);
+        if (labels.getDataLength() > 0) {
+            size_t read_len = 0;
+            const uint8_t* data = labels.getData(&read_len);
+            buf.insert(buf.end(), data, data + read_len);
+        }
+    } catch (const isc::Exception& ex) {
+        isc_throw(BadDataTypeCast, ex.what());
+    }
+}
+
+std::string
 OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
     std::string value;
-    if (buf.size() > 0) {
+    if (!buf.empty()) {
         value.insert(value.end(), buf.begin(), buf.end());
     }
     return (value);

+ 42 - 2
src/lib/dhcp/option_data_types.h

@@ -225,11 +225,13 @@ public:
     /// @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.
+    /// @brief Read IPv4 or IPv6 address from a buffer.
     ///
     /// @param buf input buffer.
     /// @param family address family: AF_INET or AF_INET6.
     /// 
+    /// @throw isc::dhcp::BadDataTypeCast when the data being read
+    /// is truncated.
     /// @return address being read.
     static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
                                            const short family);
@@ -252,6 +254,9 @@ public:
     /// @brief Read boolean value from a buffer.
     ///
     /// @param buf input buffer.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast when the data being read
+    /// is truncated or the value is invalid (neither 1 nor 0).
     /// @return boolean value read from a buffer.
     static bool readBool(const std::vector<uint8_t>& buf);
 
@@ -268,6 +273,9 @@ public:
     ///
     /// @param buf input buffer.
     /// @tparam integer type of the returned value.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast when the data in the buffer
+    /// is truncated.
     /// @return integer value being read.
     template<typename T>
     static T readInt(const std::vector<uint8_t>& buf) {
@@ -276,7 +284,12 @@ public:
                       " by readInteger is unsupported integer type");
         }
 
-        assert(buf.size() == OptionDataTypeTraits<T>::len);
+        if (buf.size() < OptionDataTypeTraits<T>::len) {
+            isc_throw(isc::dhcp::BadDataTypeCast,
+                      "failed to read an integer value from a buffer"
+                      << " - buffer is truncated.");
+        }
+
         T value;
         switch (OptionDataTypeTraits<T>::len) {
         case 1:
@@ -332,6 +345,33 @@ public:
         }
     }
 
+    /// @brief Read FQDN from a buffer as a string value.
+    ///
+    /// The format of an FQDN within a buffer complies with RFC1035,
+    /// section 3.1.
+    ///
+    /// @param buf input buffer holding a FQDN.
+    ///
+    /// @throw BadDataTypeCast if a FQDN stored within a buffer is
+    /// invalid (e.g. empty, contains invalid characters, truncated).
+    /// @return fully qualified domain name in a text form.
+    static std::string readFqdn(const std::vector<uint8_t>& buf);
+
+    /// @brief Append FQDN into a buffer.
+    ///
+    /// This method appends the Fully Qualified Domain Name (FQDN)
+    /// represented as string value into a buffer. The format of
+    /// the FQDN being stored into a buffer complies with RFC1035,
+    /// section 3.1.
+    ///
+    /// @param fqdn fully qualified domain name to be written.
+    /// @param [out] buf output buffer.
+    ///
+    /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
+    /// is invalid.
+    static void writeFqdn(const std::string& fqdn,
+                          std::vector<uint8_t>& buf);
+
     /// @brief Read string value from a buffer.
     ///
     /// @param buf input buffer.

+ 62 - 33
src/lib/dhcp/option_definition.cc

@@ -19,6 +19,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int.h>
 #include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
 #include <dhcp/option_definition.h>
 #include <util/encode/hex.h>
 
@@ -78,53 +79,81 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
                                 OptionBufferConstIter begin,
                                 OptionBufferConstIter end) const {
     validate();
-    
+
     try {
-        if (type_ == OPT_BINARY_TYPE) {
+        switch(type_) {
+        case OPT_EMPTY_TYPE:
+            return (factoryEmpty(u, type));
+
+        case OPT_BINARY_TYPE:
             return (factoryGeneric(u, type, begin, end));
 
-        } else if (type_ == OPT_IPV6_ADDRESS_TYPE && array_type_) {
-            return (factoryAddrList6(type, begin, end));
+        case OPT_UINT8_TYPE:
+            return (array_type_ ? factoryGeneric(u, type, begin, end) :
+                    factoryInteger<uint8_t>(u, type, begin, end));
 
-        } else if (type_ == OPT_IPV4_ADDRESS_TYPE && array_type_) {
-            return (factoryAddrList4(type, begin, end));
+        case OPT_INT8_TYPE:
+            return (array_type_ ? factoryGeneric(u, type, begin, end) :
+                    factoryInteger<int8_t>(u, type, begin, end));
 
-        } else if (type_ == OPT_EMPTY_TYPE) {
-            return (factoryEmpty(u, type));
+        case OPT_UINT16_TYPE:
+            return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+                    factoryInteger<uint16_t>(u, type, begin, end));
 
-        } else if (u == Option::V6 &&
-                   code_ == D6O_IA_NA &&
-                   haveIA6Format()) {
-            return (factoryIA6(type, begin, end));
+        case OPT_INT16_TYPE:
+            return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+                    factoryInteger<int16_t>(u, type, begin, end));
 
-        } else if (u == Option::V6 &&
-                   code_ == D6O_IAADDR &&
-                   haveIAAddr6Format()) {
-            return (factoryIAAddr6(type, begin, end));
+        case OPT_UINT32_TYPE:
+            return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+                    factoryInteger<uint32_t>(u, 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));
-            }
+        case OPT_INT32_TYPE:
+            return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+                    factoryInteger<int32_t>(u, type, begin, end));
 
-        } else if (type_ == OPT_UINT16_TYPE) {
+        case OPT_IPV4_ADDRESS_TYPE:
+            // If definition specifies that an option is an array
+            // of IPv4 addresses we return an instance of specialized
+            // class (OptionAddrLst4). For non-array types there is no
+            // specialized class yet implemented so we drop through
+            // to return an instance of OptionCustom.
             if (array_type_) {
-                return (factoryIntegerArray<uint16_t>(type, begin, end));
-            } else {
-                return (factoryInteger<uint16_t>(u, type, begin, end));
+                return (factoryAddrList4(type, begin, end));
             }
+            break;
 
-        } else if (type_ == OPT_UINT32_TYPE) {
+        case OPT_IPV6_ADDRESS_TYPE:
+            // Handle array type only here (see comments for
+            // OPT_IPV4_ADDRESS_TYPE case).
             if (array_type_) {
-                return (factoryIntegerArray<uint32_t>(type, begin, end));
-            } else {
-                return (factoryInteger<uint32_t>(u, type, begin, end));
+                return (factoryAddrList6(type, begin, end));
+            }
+            break;
+
+        default:
+            if (u == Option::V6) {
+                if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) &&
+                    haveIA6Format()) {
+                    // Return Option6IA instance for IA_PD and IA_NA option
+                    // types only. We don't want to return Option6IA for other
+                    // options that comprise 3 UINT32 data fields because
+                    // Option6IA accessors' and modifiers' names are derived
+                    // from the IA_NA and IA_PD options' field names: IAID,
+                    // T1, T2. Using functions such as getIAID, getT1 etc. for
+                    // options other than IA_NA and IA_PD would be bad practice
+                    // and cause confusion.
+                    return (factoryIA6(type, begin, end));
+
+                } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
+                    // Rerurn Option6IAAddr option instance for the IAADDR
+                    // option only for the same reasons as described in
+                    // for IA_NA and IA_PD above.
+                    return (factoryIAAddr6(type, begin, end));
+                }
             }
-
         }
-        return (factoryGeneric(u, type, begin, end));
+        return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end))));
 
     } catch (const Exception& ex) {
         isc_throw(InvalidOptionValue, ex.what());
@@ -144,7 +173,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
 
     OptionBuffer buf;
     if (!array_type_ && type_ != OPT_RECORD_TYPE) {
-        if (values.size() == 0) {
+        if (values.empty()) {
             isc_throw(InvalidOptionValue, "no option value specified");
         }
         writeToBuffer(values[0], type_, buf);

+ 1 - 1
src/lib/dhcp/option_definition.h

@@ -246,7 +246,7 @@ public:
     /// @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;
+                            const OptionBuffer& buf = OptionBuffer()) const;
 
     /// @brief Option factory.
     ///

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

@@ -0,0 +1,184 @@
+// 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 STD_OPTION_DEFS_H
+#define STD_OPTION_DEFS_H
+
+#include <dhcp/option_data_types.h>
+
+namespace {
+
+/// @brief Declare an array holding parameters used to create instance
+/// of a definition for option comprising a record of data fields.
+///
+/// @param name name of the array being declared.
+/// @param types data types of fields that belong to the record.
+#ifndef RECORD_DECL
+#define RECORD_DECL(name, types...) static OptionDataType name[] = { types }
+#endif
+
+/// @brief A pair of values: one pointing to the array holding types of
+/// data fields belonging to the record, and size of this array.
+///
+/// @param name name of the array holding data fields' types.
+#ifndef RECORD_DEF
+#define RECORD_DEF(name) name, sizeof(name) / sizeof(name[0])
+#endif
+
+#ifndef NO_RECORD_DEF
+#define NO_RECORD_DEF 0, 0
+#endif
+
+using namespace isc::dhcp;
+
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+    const char* name;         // option name
+    uint16_t code;            // option code
+    OptionDataType type;      // data type
+    bool array;               // is array
+    OptionDataType* records;  // record fields
+    size_t records_size;      // number of fields in a record
+};
+
+// client-fqdn
+RECORD_DECL(clientFqdnRecords, OPT_UINT8_TYPE, OPT_FQDN_TYPE);
+// geoconf-civic
+RECORD_DECL(geoconfCivicRecords, OPT_UINT8_TYPE, OPT_UINT16_TYPE,
+            OPT_BINARY_TYPE);
+// iaddr
+RECORD_DECL(iaaddrRecords, OPT_IPV6_ADDRESS_TYPE, OPT_UINT32_TYPE,
+            OPT_UINT32_TYPE);
+// ia-na
+RECORD_DECL(ianaRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-pd
+RECORD_DECL(iapdRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-prefix
+RECORD_DECL(iaPrefixRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE,
+            OPT_UINT8_TYPE, OPT_BINARY_TYPE);
+// lq-query
+RECORD_DECL(lqQueryRecords, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
+// lq-relay-data
+RECORD_DECL(lqRelayData, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
+// remote-id
+RECORD_DECL(remoteIdRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// status-code
+RECORD_DECL(statusCodeRecords, OPT_UINT16_TYPE, OPT_STRING_TYPE);
+// vendor-class
+RECORD_DECL(vendorClassRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// vendor-opts
+RECORD_DECL(vendorOptsRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+
+/// Standard DHCPv6 option definitions.
+///
+/// @warning in this array, the initializers are provided for all
+/// OptionDefParams struct's members despite initializers for
+/// 'records' and 'record_size' could be ommited for entries for
+/// which 'type' does not equal to OPT_RECORD_TYPE. If initializers
+/// are ommitted the corresponding values should default to 0.
+/// This however does not work on Solaris (GCC) which issues a
+/// warning about lack of initializers for some struct members
+/// causing build to fail.
+static const OptionDefParams OPTION_DEF_PARAMS6[] = {
+    { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(ianaRecords) },
+    { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+    { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(iaaddrRecords) },
+    { "oro", D6O_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+    { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+    { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+    { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    // Unfortunatelly the AUTH option contains a 64-bit data field
+    // called 'replay-detection' that can't be added as a record
+    // field to a custom option. Also, there is no dedicated
+    // option class to handle it so we simply return binary
+    // option type for now.
+    // @todo implement a class to handle AUTH option.
+    { "auth", D6O_AUTH, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF },
+    { "status-code", D6O_STATUS_CODE, OPT_RECORD_TYPE, false,
+      RECORD_DEF(statusCodeRecords) },
+    { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
+    { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    { "vendor-class", D6O_VENDOR_CLASS, OPT_RECORD_TYPE, false,
+      RECORD_DEF(vendorClassRecords) },
+    { "vendor-opts", D6O_VENDOR_OPTS, OPT_RECORD_TYPE, false,
+      RECORD_DEF(vendorOptsRecords) },
+    { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+    { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+    { "reconf-accept", D6O_RECONF_ACCEPT, OPT_EMPTY_TYPE, false,
+      NO_RECORD_DEF },
+    { "sip-server-dns", D6O_SIP_SERVERS_DNS, OPT_FQDN_TYPE, true,
+      NO_RECORD_DEF },
+    { "sip-server-addr", D6O_SIP_SERVERS_ADDR, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF },
+    { "dns-servers", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF },
+    { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF },
+    { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(iapdRecords) },
+    { "iaprefix", D6O_IAPREFIX, OPT_RECORD_TYPE, false,
+      RECORD_DEF(iaPrefixRecords) },
+    { "nis-servers", D6O_NIS_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF },
+    { "nisp-servers", D6O_NISP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF },
+    { "nis-domain-name", D6O_NIS_DOMAIN_NAME, OPT_FQDN_TYPE, true,
+      NO_RECORD_DEF },
+    { "nisp-domain-name", D6O_NISP_DOMAIN_NAME, OPT_FQDN_TYPE, true,
+      NO_RECORD_DEF },
+    { "sntp-servers", D6O_SNTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF },
+    { "information-refresh-time", D6O_INFORMATION_REFRESH_TIME,
+      OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+    { "bcmcs-server-dns", D6O_BCMCS_SERVER_D, OPT_FQDN_TYPE, true,
+      NO_RECORD_DEF },
+    { "bcmcs-server-addr", D6O_BCMCS_SERVER_A, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF },
+    { "geoconf-civic", D6O_GEOCONF_CIVIC, OPT_RECORD_TYPE, false,
+      RECORD_DEF(geoconfCivicRecords) },
+    { "remote-id", D6O_REMOTE_ID, OPT_RECORD_TYPE, false,
+      RECORD_DEF(remoteIdRecords) },
+    { "subscriber-id", D6O_SUBSCRIBER_ID, OPT_BINARY_TYPE, false,
+      NO_RECORD_DEF },
+    { "client-fqdn", D6O_CLIENT_FQDN, OPT_RECORD_TYPE, false,
+      RECORD_DEF(clientFqdnRecords) },
+    { "pana-agent", D6O_PANA_AGENT, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF },
+    { "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, OPT_STRING_TYPE, false,
+      NO_RECORD_DEF },
+    { "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, OPT_STRING_TYPE, false,
+      NO_RECORD_DEF },
+    { "ero", D6O_ERO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+    { "lq-query", D6O_LQ_QUERY, OPT_RECORD_TYPE, false,
+      RECORD_DEF(lqQueryRecords) },
+    { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
+    { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+    { "lq-relay-data", D6O_LQ_RELAY_DATA, OPT_RECORD_TYPE, false,
+      RECORD_DEF(lqRelayData) },
+    { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
+      NO_RECORD_DEF }
+
+    // @todo There is still a bunch of options for which we have to provide
+    // definitions but we don't do it because they are not really
+    // critical right now.
+};
+
+/// Number of option definitions defined.
+const int OPTION_DEF_PARAMS_SIZE6  =
+    sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
+
+}; // anonymous namespace
+
+#endif // STD_OPTION_DEFS_H

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

@@ -35,6 +35,7 @@ libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 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_data_types_unittest.cc
 libdhcp___unittests_SOURCES += option_definition_unittest.cc
 libdhcp___unittests_SOURCES += option_custom_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc

+ 150 - 22
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -22,6 +22,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int.h>
 #include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
 #include <util/buffer.h>
 
 #include <gtest/gtest.h>
@@ -68,8 +69,8 @@ public:
                              const std::type_info& expected_type) {
         // 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.
+        // We don't have to initialize option definitions here because they
+        // are initialized in the class's constructor.
         OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
         // Get the container index #1. This one allows for searching
         // option definitions using option code.
@@ -90,13 +91,15 @@ public:
         ASSERT_NO_THROW(def->validate());
         OptionPtr option;
         // Create the option.
-        ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf));
+        ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf))
+            << "Option creation failed to option code " << code;
         // Make sure it is not NULL.
         ASSERT_TRUE(option);
         // And the actual object type is the one that we expect.
         // Note that for many options there are dedicated classes
         // derived from Option class to represent them.
-        EXPECT_TRUE(typeid(*option) == expected_type);
+        EXPECT_TRUE(typeid(*option) == expected_type)
+            << "Invalid class returned for option code " << code;
     }
 };
 
@@ -399,24 +402,149 @@ TEST_F(LibDhcpTest, unpackOptions4) {
 // This test have to be extended once all option definitions are
 // created.
 TEST_F(LibDhcpTest, stdOptionDefs6) {
-    LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
-                                     typeid(Option));
-    LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
-                                     typeid(Option));
-    LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
-                                     typeid(Option6IA));
-    LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
-                                     typeid(Option6IAAddr));
-    LibDhcpTest::testStdOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
-                                     typeid(Option6IntArray<uint16_t>));
-    LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
-                                     typeid(Option6Int<uint16_t>));
-    LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
-                                     typeid(Option));
-    LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
-                                     typeid(Option));
-    LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
-                                     typeid(Option6AddrLst));
+
+    // Create a buffer that holds dummy option data.
+    // It will be used to create most of the options.
+    std::vector<uint8_t> buf(48, 1);
+
+    // Prepare buffer holding an array of FQDNs.
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+    // Initialize a vector with the FQDN data.
+    std::vector<uint8_t> fqdn_buf(data, data + sizeof(data));
+
+    // The CLIENT_FQDN holds a uint8_t value and FQDN. We have
+    // to add the uint8_t value to it and then append the buffer
+    // holding some valid FQDN.
+    std::vector<uint8_t> client_fqdn_buf(1);
+    client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(),
+                           fqdn_buf.end());
+
+    // The actual test starts here for all supported option codes.
+    LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, buf, typeid(Option6IA));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, buf,
+                                    typeid(Option6Int<uint32_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, buf, typeid(Option6IAAddr));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_ORO, buf,
+                                    typeid(Option6IntArray<uint16_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, buf,
+                                    typeid(Option6Int<uint8_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, buf,
+                                    typeid(Option6Int<uint16_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, buf,
+                                    typeid(Option6Int<uint8_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, buf, typeid(Option6IA));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME,
+                                    buf, typeid(Option6Int<uint32_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, buf,typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+                                    typeid(Option6AddrLst));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_ERO, buf,
+                                    typeid(Option6IntArray<uint16_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, buf, typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, buf, typeid(Option));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, buf,
+                                    typeid(Option6Int<uint32_t>));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, buf,
+                                    typeid(OptionCustom));
+
+    LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, buf,
+                                    typeid(Option6AddrLst));
 }
 
 }

+ 531 - 22
src/lib/dhcp/tests/option_custom_unittest.cc

@@ -106,6 +106,17 @@ TEST_F(OptionCustomTest, constructor) {
 
     EXPECT_EQ(Option::V4, option->getUniverse());
     EXPECT_EQ(232, option->getType());
+
+    // Try to create an option using 'empty data' constructor
+    OptionDefinition opt_def3("OPTION_FOO", 1000, "uint32");
+
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def3, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(Option::V6, option->getUniverse());
+    EXPECT_EQ(1000, option->getType());
 }
 
 // The purpose of this test is to verify that 'empty' option definition can
@@ -113,9 +124,10 @@ TEST_F(OptionCustomTest, constructor) {
 TEST_F(OptionCustomTest, emptyData) {
     OptionDefinition opt_def("OPTION_FOO", 232, "empty");
 
+    OptionBuffer buf;
     boost::scoped_ptr<OptionCustom> option;
     ASSERT_NO_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer()));
+        option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
     );
     ASSERT_TRUE(option);
 
@@ -159,8 +171,10 @@ TEST_F(OptionCustomTest, binaryData) {
     EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
 
     // Check that option with "no data" is rejected.
+    buf_in.clear();
     EXPECT_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V4, OptionBuffer())),
+        option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(),
+                                      buf_in.end())),
         isc::OutOfRange
     );
 }
@@ -197,12 +211,46 @@ TEST_F(OptionCustomTest, booleanData) {
     EXPECT_FALSE(value);
 
     // Check that the option with "no data" is rejected.
+    buf.clear();
     EXPECT_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
         isc::OutOfRange
     );
 }
 
+// The purpose of this test is to verify that the data from a buffer
+// can be read as FQDN.
+TEST_F(OptionCustomTest, fqdnData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+    };
+
+    std::vector<uint8_t> buf(data, data + sizeof(data));
+
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+    );
+    ASSERT_TRUE(option);
+
+    ASSERT_EQ(1, option->getDataFieldsNum());
+
+    std::string domain0 = option->readFqdn(0);
+    EXPECT_EQ("mydomain.example.com.", domain0);
+
+    // Check that the option with truncated data can't be created.
+    EXPECT_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6,
+                                      buf.begin(), buf.begin() + 4)),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
 // 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) {
@@ -338,6 +386,7 @@ TEST_F(OptionCustomTest, ipv6AddressData) {
     );
 }
 
+
 // 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) {
@@ -365,8 +414,9 @@ TEST_F(OptionCustomTest, stringData) {
     EXPECT_EQ("hello world!", value);
 
     // Check that option will not be created if empty buffer is provided.
+    buf.clear();
     EXPECT_THROW(
-        option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+        option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
         isc::OutOfRange
     );
 }
@@ -419,8 +469,9 @@ TEST_F(OptionCustomTest, booleanDataArray) {
 
     // Check that empty buffer can't be used to create option holding
     // array of boolean values.
+    buf.clear();
     EXPECT_THROW(
-         option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+         option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
          isc::OutOfRange
     );
 }
@@ -472,7 +523,6 @@ TEST_F(OptionCustomTest, uint32DataArray) {
                                       buf.begin() + 3)),
         isc::OutOfRange
     );
-
 }
 
 // The purpose of this test is to verify that the option definition comprising
@@ -575,6 +625,45 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) {
     );
 }
 
+// The purpose of this test is to verify that the option comprising
+// an array of FQDN values can be created from a buffer which holds
+// multiple FQDN values encoded as described in the RFC1035, section
+// 3.1
+TEST_F(OptionCustomTest, fqdnDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn", true);
+
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+
+    // Create a buffer that holds two FQDNs.
+    std::vector<uint8_t> buf(data, data + sizeof(data));
+
+    // Create an option from using a buffer.
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6, buf));
+    );
+    ASSERT_TRUE(option);
+
+    // We expect that two FQDN values have been extracted
+    // from a buffer.
+    ASSERT_EQ(2, option->getDataFieldsNum());
+
+    // Validate both values.
+    std::string domain0 = option->readFqdn(0);
+    EXPECT_EQ("mydomain.example.com.", domain0);
+
+    std::string domain1 = option->readFqdn(1);
+    EXPECT_EQ("example.com.", domain1);
+}
+
 // 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.
@@ -584,20 +673,30 @@ TEST_F(OptionCustomTest, recordData) {
     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("fqdn"));
     ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
     ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
     ASSERT_NO_THROW(opt_def.addRecordField("string"));
 
+    const char fqdn_data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0,
+    };
+
     OptionBuffer buf;
-    // Initialize field 0.
+    // Initialize field 0 to 8712.
     writeInt<uint16_t>(8712, buf);
     // Initialize field 1 to 'true'
     buf.push_back(static_cast<unsigned short>(1));
-    // Initialize field 2 to IPv4 address.
+    // Initialize field 2 to 'mydomain.example.com'.
+    buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
+    // Initialize field 3 to IPv4 address.
     writeAddress(IOAddress("192.168.0.1"), buf);
-    // Initialize field 3 to IPv6 address.
+    // Initialize field 4 to IPv6 address.
     writeAddress(IOAddress("2001:db8:1::1"), buf);
-    // Initialize field 4 to string value.
+    // Initialize field 5 to string value.
     writeString("ABCD", buf);
 
     boost::scoped_ptr<OptionCustom> option;
@@ -606,8 +705,8 @@ TEST_F(OptionCustomTest, recordData) {
     );
     ASSERT_TRUE(option);
 
-    // We should have 5 data fields.
-    ASSERT_EQ(5, option->getDataFieldsNum());
+    // We should have 6 data fields.
+    ASSERT_EQ(6, option->getDataFieldsNum());
 
     // Verify value in the field 0.
     uint16_t value0 = 0;
@@ -620,19 +719,24 @@ TEST_F(OptionCustomTest, recordData) {
     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());
+    std::string value2 = "";
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ("mydomain.example.com.", value2);
 
     // Verify value in the field 3.
-    IOAddress value3("::1");
+    IOAddress value3("127.0.0.1");
     ASSERT_NO_THROW(value3 = option->readAddress(3));
-    EXPECT_EQ("2001:db8:1::1", value3.toText());
+    EXPECT_EQ("192.168.0.1", value3.toText());
 
     // Verify value in the field 4.
-    std::string value4;
-    ASSERT_NO_THROW(value4 = option->readString(4));
-    EXPECT_EQ("ABCD", value4);
+    IOAddress value4("::1");
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+    // Verify value in the field 5.
+    std::string value5;
+    ASSERT_NO_THROW(value5 = option->readString(5));
+    EXPECT_EQ("ABCD", value5);
 }
 
 // The purpose of this test is to verify that truncated buffer
@@ -683,6 +787,413 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
     );
 }
 
+// The purpose of this test is to verify that an option comprising
+// single data field with binary data can be used and that this
+// binary data is properly initialized to a default value. This
+// test also checks that it is possible to override this default
+// value.
+TEST_F(OptionCustomTest, setBinaryData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "binary");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Get the default binary value.
+    OptionBuffer buf;
+    ASSERT_NO_THROW(option->readBinary());
+    // The buffer is by default empty.
+    EXPECT_TRUE(buf.empty());
+    // Prepare input buffer with some dummy data.
+    OptionBuffer buf_in(10);
+    for (int i = 0; i < buf_in.size(); ++i) {
+        buf_in[i] = i;
+    }
+    // Try to override the default binary buffer.
+    ASSERT_NO_THROW(option->writeBinary(buf_in));
+    // And check that it has been actually overriden.
+    ASSERT_NO_THROW(buf = option->readBinary());
+    ASSERT_EQ(buf_in.size(), buf.size());
+    EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that an option comprising
+// single boolean data field can be created and that its default
+// value can be overriden by a new value.
+TEST_F(OptionCustomTest, setBooleanData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+    // Check that the default boolean value is false.
+    bool value = false;
+    ASSERT_NO_THROW(value = option->readBoolean());
+    EXPECT_FALSE(value);
+    // Check that we can override the default value.
+    ASSERT_NO_THROW(option->writeBoolean(true));
+    // Finally, check that it has been actually overriden.
+    ASSERT_NO_THROW(value = option->readBoolean());
+    EXPECT_TRUE(value);
+}
+
+/// The purpose of this test is to verify that the data field value
+/// can be overriden by a new value.
+TEST_F(OptionCustomTest, setUint32Data) {
+    // Create a definition of an option that holds single
+    // uint32 value.
+    OptionDefinition opt_def("OPTION_FOO", 1000, "uint32");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // The default value for integer data fields is 0.
+    uint32_t value = 0;
+    ASSERT_NO_THROW(option->readInteger<uint32_t>());
+    EXPECT_EQ(0, value);
+
+    // Try to set the data field value to something different
+    // than 0.
+    ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234));
+
+    // Verify that it has been set.
+    ASSERT_NO_THROW(value = option->readInteger<uint32_t>());
+    EXPECT_EQ(1234, value);
+}
+
+// The purpose of this test is to verify that an option comprising
+// single IPv4 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv4AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4));
+    );
+    ASSERT_TRUE(option);
+
+    asiolink::IOAddress address("127.0.0.1");
+    ASSERT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("0.0.0.0", address.toText());
+
+    EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1")));
+
+    EXPECT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("192.168.0.1", address.toText());
+}
+
+// The purpose of this test is to verify that an opton comprising
+// single IPv6 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv6AddressData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    asiolink::IOAddress address("::1");
+    ASSERT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("::", address.toText());
+
+    EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1")));
+
+    EXPECT_NO_THROW(address = option->readAddress());
+    EXPECT_EQ("2001:db8:1::1", address.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// single string value can be created and that this value
+// is initialized to the default value. Also, this test checks that
+// this value can be overwritten by a new value.
+TEST_F(OptionCustomTest, setStringData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Get the default value of the option.
+    std::string value;
+    ASSERT_NO_THROW(value = option->readString());
+    // By default the string data field is empty.
+    EXPECT_TRUE(value.empty());
+    // Write some text to this field.
+    ASSERT_NO_THROW(option->writeString("hello world"));
+    // Check that it has been actually written.
+    EXPECT_NO_THROW(value = option->readString());
+    EXPECT_EQ("hello world", value);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// a default FQDN value can be created and that this value can be
+/// overriden after the option has been created.
+TEST_F(OptionCustomTest, setFqdnData) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+    // Read a default FQDN value from the option.
+    std::string fqdn;
+    ASSERT_NO_THROW(fqdn = option->readFqdn());
+    EXPECT_EQ(".", fqdn);
+    // Try override the default FQDN value.
+    ASSERT_NO_THROW(option->writeFqdn("example.com"));
+    // Check that the value has been actually overriden.
+    ASSERT_NO_THROW(fqdn = option->readFqdn());
+    EXPECT_EQ("example.com.", fqdn);
+}
+
+// The purpose of this test is to verify that an option carrying
+// an array of boolean values can be created with no values
+// initially and that values can be later added to it.
+TEST_F(OptionCustomTest, setBooleanDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", true);
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Initially, the array should contain no values.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add some boolean values to it.
+    ASSERT_NO_THROW(option->addArrayDataField(true));
+    ASSERT_NO_THROW(option->addArrayDataField(false));
+    ASSERT_NO_THROW(option->addArrayDataField(true));
+
+    // Verify that the new data fields can be added.
+    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 = false;
+    ASSERT_NO_THROW(value2 = option->readBoolean(2));
+    EXPECT_TRUE(value2);
+}
+
+// The purpose of this test is to verify that am option carying
+// an array of 16-bit signed integer values can be created with
+// no values initially and that the values can be later added to it.
+TEST_F(OptionCustomTest, setUint16DataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "uint16", true);
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Initially, the array should contain no values.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add 3 new data fields holding integer values.
+    ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(67));
+    ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(876));
+    ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(32222));
+
+    // We should now have 3 data fields.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Check that the values have been correctly set.
+    uint16_t value0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(67, value0);
+    uint16_t value1;
+    ASSERT_NO_THROW(value1 = option->readInteger<uint16_t>(1));
+    EXPECT_EQ(876, value1);
+    uint16_t value2;
+    ASSERT_NO_THROW(value2 = option->readInteger<uint16_t>(2));
+    EXPECT_EQ(32222, value2);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv4 address can be created with no addresses and that
+/// multiple IPv4 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv4AddressDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address", true);
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V4));
+    );
+    ASSERT_TRUE(option);
+
+    // Expect that the array does not contain any data fields yet.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add 3 IPv4 addresses.
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.1")));
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.2")));
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.3")));
+
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Check that all IP addresses have been set correctly.
+    IOAddress address0("127.0.0.1");
+    ASSERT_NO_THROW(address0 = option->readAddress(0));
+    EXPECT_EQ("192.168.0.1", address0.toText());
+    IOAddress address1("127.0.0.1");
+    ASSERT_NO_THROW(address1 = option->readAddress(1));
+    EXPECT_EQ("192.168.0.2", address1.toText());
+    IOAddress address2("127.0.0.1");
+    ASSERT_NO_THROW(address2 = option->readAddress(2));
+    EXPECT_EQ("192.168.0.3", address2.toText());
+
+    // Add invalid address (IPv6 instead of IPv4).
+    EXPECT_THROW(
+        option->addArrayDataField(IOAddress("2001:db8:1::1")),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv6 address can be created with no addresses and that
+/// multiple IPv6 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv6AddressDataArray) {
+    OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // Initially, the array does not contain any data fields.
+    ASSERT_EQ(0, option->getDataFieldsNum());
+
+    // Add 3 new IPv6 addresses into the array.
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::1")));
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::2")));
+    ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::3")));
+
+    // We should have now 3 addresses added.
+    ASSERT_EQ(3, option->getDataFieldsNum());
+
+    // Check that they have correct values set.
+    IOAddress address0("::1");
+    ASSERT_NO_THROW(address0 = option->readAddress(0));
+    EXPECT_EQ("2001:db8:1::1", address0.toText());
+    IOAddress address1("::1");
+    ASSERT_NO_THROW(address1 = option->readAddress(1));
+    EXPECT_EQ("2001:db8:1::2", address1.toText());
+    IOAddress address2("::1");
+    ASSERT_NO_THROW(address2 = option->readAddress(2));
+    EXPECT_EQ("2001:db8:1::3", address2.toText());
+
+    // Add invalid address (IPv4 instead of IPv6).
+    EXPECT_THROW(
+        option->addArrayDataField(IOAddress("192.168.0.1")),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+TEST_F(OptionCustomTest, setRecordData) {
+    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("fqdn"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+    ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+    // Create an option and let the data field be initialized
+    // to default value (do not provide any data buffer).
+    boost::scoped_ptr<OptionCustom> option;
+    ASSERT_NO_THROW(
+        option.reset(new OptionCustom(opt_def, Option::V6));
+    );
+    ASSERT_TRUE(option);
+
+    // The number of elements should be equal to number of elements
+    // in the record.
+    ASSERT_EQ(6, option->getDataFieldsNum());
+
+    // Check that the default values have been correctly set.
+    uint16_t value0;
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(0, value0);
+    bool value1 = true;
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_FALSE(value1);
+    std::string value2;
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ(".", value2);
+    IOAddress value3("127.0.0.1");
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("0.0.0.0", value3.toText());
+    IOAddress value4("2001:db8:1::1");
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("::", value4.toText());
+    std::string value5 = "xyz";
+    ASSERT_NO_THROW(value5 = option->readString(5));
+    EXPECT_TRUE(value5.empty());
+
+    // Override each value with a new value.
+    ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+    ASSERT_NO_THROW(option->writeBoolean(true, 1));
+    ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+    ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+    ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+    ASSERT_NO_THROW(option->writeString("hello world", 5));
+
+    // Check that the new values have been correctly set.
+    ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+    EXPECT_EQ(1234, value0);
+    ASSERT_NO_THROW(value1 = option->readBoolean(1));
+    EXPECT_TRUE(value1);
+    ASSERT_NO_THROW(value2 = option->readFqdn(2));
+    EXPECT_EQ("example.com.", value2);
+    ASSERT_NO_THROW(value3 = option->readAddress(3));
+    EXPECT_EQ("192.168.0.1", value3.toText());
+    ASSERT_NO_THROW(value4 = option->readAddress(4));
+    EXPECT_EQ("2001:db8:1::100", value4.toText());
+    ASSERT_NO_THROW(value5 = option->readString(5));
+    EXPECT_EQ(value5, "hello world");
+}
+
 // The purpose of this test is to verify that pack function for
 // DHCPv4 custom option works correctly.
 TEST_F(OptionCustomTest, pack4) {
@@ -901,6 +1412,4 @@ TEST_F(OptionCustomTest, invalidIndex) {
     EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
 }
 
-
-
 } // anonymous namespace

+ 491 - 0
src/lib/dhcp/tests/option_data_types_unittest.cc

@@ -0,0 +1,491 @@
+// 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 <dhcp/option_data_types.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test class for option data type utilities.
+class OptionDataTypesTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    OptionDataTypesTest() { }
+
+    /// @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 goal of this test is to verify that an IPv4 address being
+// stored in a buffer (wire format) can be read into IOAddress
+// object.
+TEST_F(OptionDataTypesTest, readAddress) {
+    // Create some IPv4 address.
+    asiolink::IOAddress address("192.168.0.1");
+    // And store it in a buffer in a wire format.
+    std::vector<uint8_t> buf;
+    writeAddress(address, buf);
+
+    // Now, try to read the IP address with a utility function
+    // being under test.
+    asiolink::IOAddress address_out("127.0.0.1");
+    EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET));
+
+    // Check that the read address matches address that
+    // we used as input.
+    EXPECT_EQ(address.toText(), address_out.toText());
+
+    // Check that an attempt to read the buffer as IPv6 address
+    // causes an error as the IPv6 address needs at least 16 bytes
+    // long buffer.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readAddress(buf, AF_INET6),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    buf.clear();
+
+    // Do another test like this for IPv6 address.
+    address = asiolink::IOAddress("2001:db8:1:0::1");
+    writeAddress(address, buf);
+    EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6));
+    EXPECT_EQ(address.toText(), address_out.toText());
+
+    // Truncate the buffer and expect an error to be reported when
+    // trying to read it.
+    buf.resize(buf.size() - 1);
+    EXPECT_THROW(
+        OptionDataTypeUtil::readAddress(buf, AF_INET6),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The goal of this test is to verify that an IPv6 address
+// is properly converted to wire format and stored in a
+// buffer.
+TEST_F(OptionDataTypesTest, writeAddress) {
+    // Encode an IPv6 address 2001:db8:1::1 in wire format.
+    // This will be used as reference data to validate if
+    // an IPv6 address is stored in a buffer properly.
+    const uint8_t data[] = {
+        0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0,
+        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1
+    };
+    std::vector<uint8_t> buf_in(data, data + sizeof(data));
+
+    // Create IPv6 address object.
+    asiolink::IOAddress address("2001:db8:1::1");
+    // Define the output buffer to write IP address to.
+    std::vector<uint8_t> buf_out;
+    // Write the address to the buffer.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+    // Make sure that input and output buffers have the same size
+    // so we can compare them.
+    ASSERT_EQ(buf_in.size(), buf_out.size());
+    // And finally compare them.
+    EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+    buf_out.clear();
+
+    // Do similar test for IPv4 address.
+    address = asiolink::IOAddress("192.168.0.1");
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+    ASSERT_EQ(4, buf_out.size());
+    // Verify that the IP address has been written correctly.
+    EXPECT_EQ(192, buf_out[0]);
+    EXPECT_EQ(168, buf_out[1]);
+    EXPECT_EQ(0, buf_out[2]);
+    EXPECT_EQ(1, buf_out[3]);
+}
+
+// The purpose of this test is to verify that binary data represented
+// as a string of hexadecimal digits can be written to a buffer.
+TEST_F(OptionDataTypesTest, writeBinary) {
+    // Prepare the reference data.
+    const char data[] = {
+        0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
+        0x6, 0x7, 0x8, 0x9, 0xA, 0xB
+    };
+    std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+    // Create empty vector where binary data will be written to.
+    std::vector<uint8_t> buf;
+    ASSERT_NO_THROW(
+        OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf)
+    );
+    // Verify that the buffer contains valid data.
+    ASSERT_EQ(buf_ref.size(), buf.size());
+    EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that the boolean value stored
+// in a buffer is correctly read from this buffer.
+TEST_F(OptionDataTypesTest, readBool) {
+    // Create an input buffer.
+    std::vector<uint8_t> buf;
+    // 'true' value is encoded as 1 ('false' is encoded as 0)
+    buf.push_back(1);
+
+    // Read the value from the buffer.
+    bool value = false;
+    ASSERT_NO_THROW(
+        value = OptionDataTypeUtil::readBool(buf);
+    );
+    // Verify the value.
+    EXPECT_TRUE(value);
+    // Check if 'false' is read correctly either.
+    buf[0] = 0;
+    ASSERT_NO_THROW(
+        value = OptionDataTypeUtil::readBool(buf);
+    );
+    EXPECT_FALSE(value);
+
+    // Check that invalid value causes exception.
+    buf[0] = 5;
+    ASSERT_THROW(
+        OptionDataTypeUtil::readBool(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The purpose of this test is to verify that boolean values
+// are correctly encoded in a buffer as '1' for 'true' and
+// '0' for 'false' values.
+TEST_F(OptionDataTypesTest, writeBool) {
+    // Create a buffer we will write to.
+    std::vector<uint8_t> buf;
+    // Write the 'true' value to the buffer.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf));
+    // We should now have 'true' value stored in a buffer.
+    ASSERT_EQ(1, buf.size());
+    EXPECT_EQ(buf[0], 1);
+    // Let's append another value to make sure that it is not always
+    // 'true' value being written.
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf));
+    ASSERT_EQ(2, buf.size());
+    // Check that the first value has not changed.
+    EXPECT_EQ(buf[0], 1);
+    // Check the the second value is correct.
+    EXPECT_EQ(buf[1], 0);
+}
+
+// The purpose of this test is to verify that the integer values
+// of different types are correctly read from a buffer.
+TEST_F(OptionDataTypesTest, readInt) {
+    std::vector<uint8_t> buf;
+
+    // Write an 8-bit unsigned integer value to the buffer.
+    writeInt<uint8_t>(129, buf);
+    uint8_t valueUint8 = 0;
+    // Read the value and check that it is valid.
+    ASSERT_NO_THROW(
+        valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf);
+    );
+    EXPECT_EQ(129, valueUint8);
+
+    // Try to read 16-bit value from a buffer holding 8-bit value.
+    // This should result in an exception.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readInt<uint16_t>(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Clear the buffer for the next check we are going to do.
+    buf.clear();
+
+    // Test uint16_t value.
+    writeInt<uint16_t>(1234, buf);
+    uint16_t valueUint16 = 0;
+    ASSERT_NO_THROW(
+        valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf);
+    );
+    EXPECT_EQ(1234, valueUint16);
+
+    // Try to read 32-bit value from a buffer holding 16-bit value.
+    // This should result in an exception.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readInt<uint32_t>(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    buf.clear();
+
+    // Test uint32_t value.
+    writeInt<uint32_t>(56789, buf);
+    uint32_t valueUint32 = 0;
+    ASSERT_NO_THROW(
+        valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf);
+    );
+    EXPECT_EQ(56789, valueUint32);
+    buf.clear();
+
+    // Test int8_t value.
+    writeInt<int8_t>(-65, buf);
+    int8_t valueInt8 = 0;
+    ASSERT_NO_THROW(
+        valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf);
+    );
+    EXPECT_EQ(-65, valueInt8);
+    buf.clear();
+
+    // Try to read 16-bit value from a buffer holding 8-bit value.
+    // This should result in an exception.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readInt<int16_t>(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Test int16_t value.
+    writeInt<int16_t>(2345, buf);
+    int32_t valueInt16 = 0;
+    ASSERT_NO_THROW(
+        valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf);
+    );
+    EXPECT_EQ(2345, valueInt16);
+    buf.clear();
+
+    // Try to read 32-bit value from a buffer holding 16-bit value.
+    // This should result in an exception.
+    EXPECT_THROW(
+        OptionDataTypeUtil::readInt<int32_t>(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Test int32_t value.
+    writeInt<int32_t>(-16543, buf);
+    int32_t valueInt32 = 0;
+    ASSERT_NO_THROW(
+        valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf);
+    );
+    EXPECT_EQ(-16543, valueInt32);
+
+    buf.clear();
+}
+
+// The purpose of this test is to verify that integer values of different
+// types are correctly written to a buffer.
+TEST_F(OptionDataTypesTest, writeInt) {
+    // Prepare the reference buffer.
+    const uint8_t data[] = {
+        0x7F, // 127
+        0x03, 0xFF, // 1023
+        0x00, 0x00, 0x10, 0x00, // 4096
+        0xFF, 0xFF, 0xFC, 0x00, // -1024
+        0x02, 0x00, // 512
+        0x81 // -127
+    };
+    std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+
+    // Fill in the buffer with data. Each write operation appends an
+    // integer value. Eventually the buffer holds all values and should
+    // match with the reference buffer.
+    std::vector<uint8_t> buf;
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf));
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf));
+
+    // Make sure that the buffer has the same size as the reference
+    // buffer.
+    ASSERT_EQ(buf_ref.size(), buf.size());
+    // Compare buffers.
+    EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that FQDN is read from
+// a buffer and returned as a text. The representation of the FQDN
+// in the buffer complies with RFC1035, section 3.1.
+// This test also checks that if invalid (truncated) FQDN is stored
+// in a buffer the appropriate exception is returned when trying to
+// read it as a string.
+TEST_F(OptionDataTypesTest, readFqdn) {
+    // The binary representation of the "mydomain.example.com".
+    // Values: 8, 7, 3 and 0 specify the lengths of subsequent
+    // labels within the FQDN.
+    const char data[] = {
+        8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+        7, 101, 120, 97, 109, 112, 108, 101,      // "example"
+        3, 99, 111, 109,                          // "com"
+        0
+    };
+
+    // Make a vector out of the data.
+    std::vector<uint8_t> buf(data, data + sizeof(data));
+
+    // Read the buffer as FQDN and verify its correctness.
+    std::string fqdn;
+    EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf));
+    EXPECT_EQ("mydomain.example.com.", fqdn);
+
+    // By resizing the buffer we simulate truncation. The first
+    // length field (8) indicate that the first label's size is
+    // 8 but the actual buffer size is 5. Expect that conversion
+    // fails.
+    buf.resize(5);
+    EXPECT_THROW(
+        OptionDataTypeUtil::readFqdn(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Another special case: provide an empty buffer.
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::readFqdn(buf),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The purpose of this test is to verify that FQDN's syntax is validated
+// and that FQDN is correctly written to a buffer in a format described
+// in RFC1035 section 3.1.
+TEST_F(OptionDataTypesTest, writeFqdn) {
+    // Create empty buffer. The FQDN will be written to it.
+    OptionBuffer buf;
+    // Write a domain name into the buffer in the format described
+    // in RFC1035 section 3.1. This function should not throw
+    // exception because domain name is well formed.
+    EXPECT_NO_THROW(
+        OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf)
+    );
+    // The length of the data is 22 (8 bytes for "mydomain" label,
+    // 7 bytes for "example" label, 3 bytes for "com" label and
+    // finally 4 bytes positions between labels where length
+    // information is stored.
+    ASSERT_EQ(22, buf.size());
+
+    // Verify that length fields between labels hold valid values.
+    EXPECT_EQ(8, buf[0]);  // length of "mydomain"
+    EXPECT_EQ(7, buf[9]);  // length of "example"
+    EXPECT_EQ(3, buf[17]); // length of "com"
+    EXPECT_EQ(0, buf[21]); // zero byte at the end.
+
+    // Verify that labels are valid.
+    std::string label0(buf.begin() + 1, buf.begin() + 9);
+    EXPECT_EQ("mydomain", label0);
+
+    std::string label1(buf.begin() + 10, buf.begin() + 17);
+    EXPECT_EQ("example", label1);
+
+    std::string label2(buf.begin() + 18, buf.begin() + 21);
+    EXPECT_EQ("com", label2);
+
+    // The tested function is supposed to append data to a buffer
+    // so let's check that it is a case by appending another domain.
+    OptionDataTypeUtil::writeFqdn("hello.net", buf);
+
+    // The buffer length should be now longer.
+    ASSERT_EQ(33, buf.size());
+
+    // Check the length fields for new labels being appended.
+    EXPECT_EQ(5, buf[22]);
+    EXPECT_EQ(3, buf[28]);
+
+    // And check that labels are ok.
+    std::string label3(buf.begin() + 23, buf.begin() + 28);
+    EXPECT_EQ("hello", label3);
+
+    std::string label4(buf.begin() + 29, buf.begin() + 32);
+    EXPECT_EQ("net", label4);
+
+    // Check that invalid (empty) FQDN is rejected and expected
+    // exception type is thrown.
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::writeFqdn("", buf),
+        isc::dhcp::BadDataTypeCast
+    );
+
+    // Check another invalid domain name (with repeated dot).
+    buf.clear();
+    EXPECT_THROW(
+        OptionDataTypeUtil::writeFqdn("example..com", buf),
+        isc::dhcp::BadDataTypeCast
+    );
+}
+
+// The purpose of this test is to verify that the string
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readString) {
+    // Prepare a buffer with some string in it.
+    std::vector<uint8_t> buf;
+    writeString("hello world", buf);
+
+    // Read the string from the buffer.
+    std::string value;
+    ASSERT_NO_THROW(
+        value = OptionDataTypeUtil::readString(buf);
+    );
+    // Check that it is valid.
+    EXPECT_EQ("hello world", value);
+}
+
+// The purpose of this test is to verify that a string can be
+// stored in a buffer correctly.
+TEST_F(OptionDataTypesTest, writeString) {
+    // Prepare a buffer with a reference data.
+    std::vector<uint8_t> buf_ref;
+    writeString("hello world!", buf_ref);
+    // Create empty buffer we will write to.
+    std::vector<uint8_t> buf;
+    ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf));
+    // Compare two buffers.
+    ASSERT_EQ(buf_ref.size(), buf.size());
+    EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+} // anonymous namespace

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

@@ -23,6 +23,7 @@
 #include <dhcp/option6_iaaddr.h>
 #include <dhcp/option6_int.h>
 #include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
 #include <dhcp/option_definition.h>
 #include <exceptions/exceptions.h>
 
@@ -883,7 +884,7 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
         option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
     );
     ASSERT_TRUE(option_v6);
-    ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
+    ASSERT_TRUE(typeid(*option_v6) == typeid(OptionCustom));
     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());

+ 1 - 1
src/lib/dhcpsrv/Makefile.am

@@ -48,5 +48,5 @@ libb10_dhcpsrv_la_CXXFLAGS += -Wno-unused-parameter
 endif
 
 # Distribute MySQL schema creation script and backend documentation
-EXTRA_DIST = dhcpdb_create.mysql database_backends.dox
+EXTRA_DIST = dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
 dist_pkgdata_DATA = dhcpdb_create.mysql

+ 13 - 0
src/lib/dhcpsrv/database_backends.dox

@@ -8,6 +8,17 @@
   the abstract isc::dhcp::LeaseMgr class.  This provides methods to
   create, retrieve, modify and delete leases in the database.
 
+  There are currently two available Lease Managers, MySQL and Memfile:
+
+  - The MySQL lease manager uses the freely available MySQL as its backend
+  database.  This is not included in BIND 10 DHCP by default:
+  the --with-dhcp-mysql switch must be supplied to "configure" for support
+  to be compiled into the software.
+  - Memfile is an in-memory lease database, with (currently) nothing being
+  written to persistent storage.  The long-term plans for the backend do
+  include the ability to store this on disk, but it is currently a
+  low-priority item.
+
   @section dhcpdb-instantiation Instantiation of Lease Managers
 
   A lease manager is instantiated through the LeaseMgrFactory class.  This
@@ -32,6 +43,8 @@
 
   - <b>type</b> - specifies the type of database backend.  The following values
   for the type keyword are supported:
+     - <B>memfile</b> - In-memory database.  Nothing is written to any
+       external storage, so this should not be used in production.
      - <b>mysql</b> - Use MySQL as the database
 
   The following sections list the database-specific keywords:

+ 8 - 3
src/lib/dhcpsrv/dhcpdb_create.mysql

@@ -34,7 +34,7 @@ CREATE TABLE lease4 (
     address INT UNSIGNED PRIMARY KEY NOT NULL,  # IPv4 address
     hwaddr VARBINARY(20),                       # Hardware address
     client_id VARBINARY(128),                   # Client ID
-    lease_time INT UNSIGNED,                    # Length of the lease (seconds)
+    valid_lifetime INT UNSIGNED,                # Length of the lease (seconds)
     expire TIMESTAMP,                           # Expiration time of the lease
     subnet_id INT UNSIGNED                      # Subnet identification
     ) ENGINE = INNODB;
@@ -43,7 +43,7 @@ CREATE TABLE lease4 (
 # N.B. The use of a VARCHAR for the address is temporary for development:
 # it will eventually be replaced by BINARY(16).
 CREATE TABLE lease6 (
-    address VARCHAR(40) PRIMARY KEY NOT NULL,   # IPv6 address
+    address VARCHAR(39) PRIMARY KEY NOT NULL,   # IPv6 address
     duid VARBINARY(128),                        # DUID
     valid_lifetime INT UNSIGNED,                # Length of the lease (seconds)
     expire TIMESTAMP,                           # Expiration time of the lease
@@ -72,12 +72,17 @@ COMMIT;
 # This table is only modified during schema upgrades.  For historical reasons
 # (related to the names of the columns in the BIND 10 DNS database file), the
 # first column is called "version" and not "major".
+#
+# NOTE: this MUST be kept in step with src/lib/dhcpsrv/tests/schema_copy.h,
+#       which defines the schema for the unit tests.  If you are updating
+#       the version number, the schema has changed: please ensure that
+#       schema_copy.h has been updated as well.
 CREATE TABLE schema_version (
     version INT PRIMARY KEY NOT NULL,       # Major version number
     minor INT                               # Minor version number
     );
 START TRANSACTION;
-INSERT INTO schema_version VALUES (0, 1);
+INSERT INTO schema_version VALUES (1, 0);
 COMMIT;
 
 # Notes:

+ 42 - 11
src/lib/dhcpsrv/lease_mgr.cc

@@ -29,15 +29,16 @@
 
 using namespace std;
 
-using namespace isc::dhcp;
+namespace isc {
+namespace dhcp {
 
-Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
-               uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
-               uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
-    :type_(type), addr_(addr), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
-     preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
-     subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
-     fqdn_rev_(false) {
+Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
+               DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
+               uint32_t t1, uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
+    : addr_(addr), type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+      preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
+      subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
+      fqdn_rev_(false) {
     if (!duid) {
         isc_throw(InvalidOperation, "DUID must be specified for a lease");
     }
@@ -83,16 +84,46 @@ Lease6::toText() {
 }
 
 bool
+Lease4::operator==(const Lease4& other) const {
+    return (
+        addr_ == other.addr_ &&
+        ext_ == other.ext_ &&
+        hwaddr_ == other.hwaddr_ &&
+        *client_id_ == *other.client_id_ &&
+        t1_ == other.t1_ &&
+        t2_ == other.t2_ &&
+        valid_lft_ == other.valid_lft_ &&
+        cltt_ == other.cltt_ &&
+        subnet_id_ == other.subnet_id_ &&
+        fixed_ == other.fixed_ &&
+        hostname_ == other.hostname_ &&
+        fqdn_fwd_ == other.fqdn_fwd_ &&
+        fqdn_rev_ == other.fqdn_rev_ &&
+        comments_ == other.comments_
+    );
+}
+
+bool
 Lease6::operator==(const Lease6& other) const {
     return (
-        type_ == other.type_ &&
         addr_ == other.addr_ &&
+        type_ == other.type_ &&
         prefixlen_ == other.prefixlen_ &&
         iaid_ == other.iaid_ &&
         *duid_ == *other.duid_ &&
         preferred_lft_ == other.preferred_lft_ &&
         valid_lft_ == other.valid_lft_ &&
+        t1_ == other.t1_ &&
+        t2_ == other.t2_ &&
         cltt_ == other.cltt_ &&
-        subnet_id_ == other.subnet_id_
-        );
+        subnet_id_ == other.subnet_id_ &&
+        fixed_ == other.fixed_ &&
+        hostname_ == other.hostname_ &&
+        fqdn_fwd_ == other.fqdn_fwd_ &&
+        fqdn_rev_ == other.fqdn_rev_ &&
+        comments_ == other.comments_
+    );
 }
+
+} // namespace isc::dhcp
+} // namespace isc

+ 151 - 76
src/lib/dhcpsrv/lease_mgr.h

@@ -87,6 +87,13 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+/// @brief Multiple lease records found where one expected
+class MultipleRecords : public Exception {
+public:
+    MultipleRecords(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 /// @brief Attempt to update lease that was not there
 class NoSuchLease : public Exception {
 public:
@@ -94,6 +101,13 @@ public:
         isc::Exception(file, line, what) {}
 };
 
+/// @brief Data is truncated
+class DataTruncated : public Exception {
+public:
+    DataTruncated(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
 /// @brief Structure that holds a lease for IPv4 address
 ///
 /// For performance reasons it is a simple structure, not a class. If we chose
@@ -101,191 +115,253 @@ public:
 /// would be required. As this is a critical part of the code that will be used
 /// extensively, direct access is warranted.
 struct Lease4 {
+    /// @brief Maximum size of a hardware address
+    static const size_t HWADDR_MAX = 20;
+
+    /// @brief Constructor
+    ///
+    /// @param addr IPv4 address as unsigned 32-bit integer in network byte
+    ///        order.
+    /// @param hwaddr Hardware address buffer
+    /// @param hwaddr_len Length of hardware address buffer
+    /// @param clientid Client identification buffer
+    /// @param clientid_len Length of client identification buffer
+    /// @param valid_lft Lifetime of the lease
+    /// @param cltt Client last transmission time
+    /// @param subnet_id Subnet identification
+    Lease4(uint32_t addr, const uint8_t* hwaddr, size_t hwaddr_len,
+           const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
+           time_t cltt, uint32_t subnet_id)
+        : addr_(addr), ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
+          client_id_(new ClientId(clientid, clientid_len)), t1_(0), t2_(0),
+          valid_lft_(valid_lft), cltt_(cltt), subnet_id_(subnet_id),
+          fixed_(false), hostname_(), fqdn_fwd_(false), fqdn_rev_(false),
+          comments_()
+    {}
+
+    /// @brief Default Constructor
+    ///
+    /// Initialize fields that don't have a default constructor.
+    Lease4() : addr_(0) {}
+
+
     /// IPv4 address
     isc::asiolink::IOAddress addr_;
 
     /// @brief Address extension
     ///
-    /// It is envisaged that in some cases IPv4 address will be accompanied with some
-    /// additional data. One example of such use are Address + Port solutions (or
-    /// Port-restricted Addresses), where several clients may get the same address, but
-    /// different port ranges. This feature is not expected to be widely used.
-    /// Under normal circumstances, the value should be 0.
+    /// It is envisaged that in some cases IPv4 address will be accompanied
+    /// with some additional data. One example of such use are Address + Port
+    /// solutions (or Port-restricted Addresses), where several clients may get
+    /// the same address, but different port ranges. This feature is not
+    /// expected to be widely used.  Under normal circumstances, the value
+    /// should be 0.
     uint32_t ext_;
 
-    /// @brief hardware address
+    /// @brief Hardware address
     std::vector<uint8_t> hwaddr_;
 
-    /// @brief client identifier
+    /// @brief Client identifier
+    ///
+    /// @todo Should this be a pointer to a client ID or the ID itself?
+    ///       Compare with the DUID in the Lease6 structure.
     boost::shared_ptr<ClientId> client_id_;
 
-    /// @brief renewal timer
+    /// @brief Renewal timer
     ///
-    /// Specifies renewal time. Although technically it is a property of IA container,
-    /// not the address itself, since our data model does not define separate IA
-    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
-    /// for the same IA, each must have consistent T1 and T2 values. Specified in
-    /// seconds since cltt.
+    /// Specifies renewal time. Although technically it is a property of the
+    /// IA container and not the address itself, since our data model does not
+    /// define a separate IA entity, we are keeping it in the lease. In the
+    /// case of multiple addresses/prefixes for the same IA, each must have
+    /// consistent T1 and T2 values. This is specified in seconds since cltt.
     uint32_t t1_;
 
-    /// @brief rebinding timer
+    /// @brief Rebinding timer
     ///
-    /// Specifies rebinding time. Although technically it is a property of IA container,
-    /// not the address itself, since our data model does not define separate IA
-    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
-    /// for the same IA, each must have consistent T1 and T2 values. Specified in
-    /// seconds since cltt.
+    /// Specifies rebinding time. Although technically it is a property of the
+    /// IA container and not the address itself, since our data model does not
+    /// define a separate IA entity, we are keeping it in the lease. In the
+    /// case of multiple addresses/prefixes for the same IA, each must have
+    /// consistent T1 and T2 values. This is specified in seconds since cltt.
     uint32_t t2_;
 
-    /// @brief valid lifetime
+    /// @brief Valid lifetime
     ///
-    /// Expressed as number of seconds since cltt
+    /// Expressed as number of seconds since cltt.
     uint32_t valid_lft_;
 
-    /// @brief client last transmission time
+    /// @brief Client last transmission time
     ///
-    /// Specifies a timestamp, when last transmission from a client was received.
+    /// Specifies a timestamp giving the time when the last transmission from a
+    /// client was received.
     time_t cltt_;
 
     /// @brief Subnet identifier
     ///
-    /// Specifies subnet-id of the subnet that the lease belongs to
+    /// Specifies the identification of the subnet to which the lease belongs.
     SubnetID subnet_id_;
 
-    /// @brief Is this a fixed lease?
+    /// @brief Fixed lease?
     ///
     /// Fixed leases are kept after they are released/expired.
     bool fixed_;
 
-    /// @brief client hostname
+    /// @brief Client hostname
     ///
     /// This field may be empty
     std::string hostname_;
 
-    /// @brief did we update AAAA record for this lease?
+    /// @brief Forward zone updated?
+    ///
+    /// Set true if the DNS AAAA record for this lease has been updated.
     bool fqdn_fwd_;
 
-    /// @brief did we update PTR record for this lease?
+    /// @brief Reverse zone updated?
+    ///
+    /// Set true if the DNS PTR record for this lease has been updated.
     bool fqdn_rev_;
 
-    /// @brief Lease comments.
+    /// @brief Lease comments
     ///
     /// Currently not used. It may be used for keeping comments made by the
     /// system administrator.
     std::string comments_;
 
-    /// @todo: Add DHCPv4 failover related fields here
+    /// @brief Compare two leases for equality
+    ///
+    /// @param other lease6 object with which to compare
+    bool operator==(const Lease4& other) const;
 
-    /// @brief Constructor
+    /// @brief Compare two leases for inequality
     ///
-    /// Initialize fields that don't have a default constructor.
-    /// @todo Remove this
-    Lease4() : addr_(0) {}
+    /// @param other lease6 object with which to compare
+    bool operator!=(const Lease4& other) const {
+        return (!operator==(other));
+    }
+
+    /// @todo: Add DHCPv4 failover related fields here
 };
 
 /// @brief Pointer to a Lease4 structure.
 typedef boost::shared_ptr<Lease4> Lease4Ptr;
 
 /// @brief A collection of IPv4 leases.
-typedef std::vector< boost::shared_ptr<Lease4Ptr> > Lease4Collection;
+typedef std::vector<Lease4Ptr> Lease4Collection;
+
+
 
 /// @brief Structure that holds a lease for IPv6 address and/or prefix
 ///
-/// For performance reasons it is a simple structure, not a class. Had we chose to
-/// make it a class, all fields would have to be made private and getters/setters
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
 /// would be required. As this is a critical part of the code that will be used
-/// extensively, direct access rather than through getters/setters is warranted.
+/// extensively, direct access is warranted.
 struct Lease6 {
+
+    /// @brief Type of lease contents
     typedef enum {
         LEASE_IA_NA, /// the lease contains non-temporary IPv6 address
         LEASE_IA_TA, /// the lease contains temporary IPv6 address
         LEASE_IA_PD  /// the lease contains IPv6 prefix (for prefix delegation)
     } LeaseType;
 
+    /// @brief Constructor
     Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
            uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
            uint32_t t2, SubnetID subnet_id, uint8_t prefixlen_ = 0);
 
-    /// @brief specifies lease type (normal addr, temporary addr, prefix)
-    LeaseType type_;
-
-    /// IPv6 address
+    /// @brief IPv6 address
+    ///
+    /// IPv6 address or, in the case of a prefix delegation, the prefix.
     isc::asiolink::IOAddress addr_;
 
-    /// IPv6 prefix length (used only for PD)
+    /// @brief Lease type
+    ///
+    /// One of normal address, temporary address, or prefix.
+    LeaseType type_;
+
+    /// @brief IPv6 prefix length
+    ///
+    /// This is used only for prefix delegations and is ignored otherwise.
     uint8_t prefixlen_;
 
-    /// @brief IAID
+    /// @brief Identity Association Identifier (IAID)
     ///
-    /// Identity Association IDentifier. DHCPv6 stores all addresses and prefixes
-    /// in IA containers (IA_NA, IA_TA, IA_PD). Most containers may appear more
-    /// than once in a message. To differentiate between them, IAID field is present
+    /// DHCPv6 stores all addresses and prefixes in IA containers (IA_NA,
+    /// IA_TA, IA_PD). All containers may appear more than once in a message.
+    /// To differentiate between them, the IAID field is present
     uint32_t iaid_;
 
-    /// @brief client identifier
-    boost::shared_ptr<DUID> duid_;
+    /// @brief Client identifier
+    DuidPtr duid_;
 
     /// @brief preferred lifetime
     ///
-    /// This parameter specifies preferred lifetime since the lease was assigned/renewed
-    /// (cltt), expressed in seconds.
+    /// This parameter specifies the preferred lifetime since the lease was
+    /// assigned or renewed (cltt), expressed in seconds.
     uint32_t preferred_lft_;
 
     /// @brief valid lifetime
     ///
-    /// This parameter specified valid lifetime since the lease was assigned/renewed
-    /// (cltt), expressed in seconds.
+    /// This parameter specifies the valid lifetime since the lease waa
+    /// assigned/renewed (cltt), expressed in seconds.
     uint32_t valid_lft_;
 
     /// @brief T1 timer
     ///
-    /// Specifies renewal time. Although technically it is a property of IA container,
-    /// not the address itself, since our data model does not define separate IA
-    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
-    /// for the same IA, each must have consistent T1 and T2 values. Specified in
-    /// seconds since cltt.
-    /// This value will also be useful for failover to calculate the next expected
-    /// client transmission time.
+    /// Specifies renewal time. Although technically it is a property of the
+    /// IA container and not the address itself, since our data model does not
+    /// define a separate IA entity, we are keeping it in the lease. In the
+    /// case of multiple addresses/prefixes for the same IA, each must have
+    /// consistent T1 and T2 values. This is specified in seconds since cltt.
+    /// The value will also be useful for failover to calculate the next
+    /// expected client transmission time.
     uint32_t t1_;
 
     /// @brief T2 timer
     ///
-    /// Specifies rebinding time. Although technically it is a property of IA container,
-    /// not the address itself, since our data model does not define separate IA
-    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
-    /// for the same IA, each must have consistent T1 and T2 values. Specified in
-    /// seconds since cltt.
+    /// Specifies rebinding time. Although technically it is a property of the
+    /// IA container and not the address itself, since our data model does not
+    /// define a separate IA entity, we are keeping it in the lease. In the
+    /// case of multiple addresses/prefixes for the same IA, each must have
+    /// consistent T1 and T2 values. This is specified in seconds since cltt.
     uint32_t t2_;
 
-    /// @brief client last transmission time
+    /// @brief Client last transmission time
     ///
-    /// Specifies a timestamp, when last transmission from a client was received.
+    /// Specifies a timestamp giving the time when the last transmission from a
+    /// client was received.
     time_t cltt_;
 
     /// @brief Subnet identifier
     ///
-    /// Specifies subnet-id of the subnet that the lease belongs to
+    /// Specifies the identification of the subnet to which the lease belongs.
     SubnetID subnet_id_;
 
-    /// @brief Is this a fixed lease?
+    /// @brief Fixed lease?
     ///
     /// Fixed leases are kept after they are released/expired.
     bool fixed_;
 
-    /// @brief client hostname
+    /// @brief Client hostname
     ///
     /// This field may be empty
     std::string hostname_;
 
-    /// @brief did we update AAAA record for this lease?
+    /// @brief Forward zone updated?
+    ///
+    /// Set true if the DNS AAAA record for this lease has been updated.
     bool fqdn_fwd_;
 
-    /// @brief did we update PTR record for this lease?
+    /// @brief Reverse zone updated?
+    ///
+    /// Set true if the DNS PTR record for this lease has been updated.
     bool fqdn_rev_;
 
     /// @brief Lease comments
     ///
-    /// This field is currently not used.
+    /// Currently not used. It may be used for keeping comments made by the
+    /// system administrator.
     std::string comments_;
 
     /// @todo: Add DHCPv6 failover related fields here
@@ -311,13 +387,12 @@ struct Lease6 {
     bool operator!=(const Lease6& other) const {
         return (!operator==(other));
     }
-
 };
 
 /// @brief Pointer to a Lease6 structure.
 typedef boost::shared_ptr<Lease6> Lease6Ptr;
 
-/// @brief Const pointer to a Lease6 structure.
+/// @brief Pointer to a const Lease6 structure.
 typedef boost::shared_ptr<const Lease6> ConstLease6Ptr;
 
 /// @brief A collection of IPv6 leases.
@@ -335,7 +410,7 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
 /// see the documentation of those classes for details.
 class LeaseMgr {
 public:
-    /// Client Hardware address
+    /// Client hardware address
     typedef std::vector<uint8_t> HWAddr;
 
     /// Database configuration parameter map

src/lib/dhcp/libdhcsrv.dox → src/lib/dhcpsrv/libdhcpsrv.dox


+ 2 - 3
src/lib/dhcpsrv/memfile_lease_mgr.h

@@ -199,12 +199,11 @@ public:
 
     /// @brief Returns backend name.
     ///
-    /// As there is no variation, in this case we return the type of the
-    /// backend.
+    /// For now, memfile can only store data in memory.
     ///
     /// @return Name of the backend.
     virtual std::string getName() const {
-        return ("memfile");
+        return ("memory");
     }
 
     /// @brief Returns description of the backend.

File diff suppressed because it is too large
+ 896 - 316
src/lib/dhcpsrv/mysql_lease_mgr.cc


+ 232 - 25
src/lib/dhcpsrv/mysql_lease_mgr.h

@@ -27,18 +27,22 @@ namespace dhcp {
 
 // Define the current database schema values
 
-const uint32_t CURRENT_VERSION_VERSION = 0;
-const uint32_t CURRENT_VERSION_MINOR = 1;
+const uint32_t CURRENT_VERSION_VERSION = 1;
+const uint32_t CURRENT_VERSION_MINOR = 0;
 
 
-// Forward declaration of the Lease6 exchange object.  This class is defined
+// Forward declaration of the Lease exchange objects.  These classes are defined
 // in the .cc file.
+class MySqlLease4Exchange;
 class MySqlLease6Exchange;
 
 
 /// @brief MySQL Lease Manager
 ///
-/// This is a concrete API for the backend for the MySQL database.
+/// This class provides the \ref isc::dhcp::LeaseMgr interface to the MySQL
+/// database.  Use of this backend presupposes that a MySQL database is
+/// available and that the Kea schema has been created within it.
+
 class MySqlLeaseMgr : public LeaseMgr {
 public:
     /// @brief Constructor
@@ -68,7 +72,7 @@ public:
     /// @brief Destructor (closes database)
     virtual ~MySqlLeaseMgr();
 
-    /// @brief Adds an IPv4 lease.
+    /// @brief Adds an IPv4 lease
     ///
     /// @param lease lease to be added
     ///
@@ -79,7 +83,7 @@ public:
     ///        failed.
     virtual bool addLease(const Lease4Ptr& lease);
 
-    /// @brief Adds an IPv6 lease.
+    /// @brief Adds an IPv6 lease
     ///
     /// @param lease lease to be added
     ///
@@ -106,7 +110,7 @@ public:
     /// @brief Returns an IPv4 lease for specified IPv4 address
     ///
     /// This method return a lease that is associated with a given address.
-    /// For other query types (by hardware addr, by DUID) there can be
+    /// For other query types (by hardware addr, by Client ID) there can be
     /// several leases in different subnets (e.g. for mobile clients that
     /// got address in different subnets). However, for a single address
     /// there can be only one lease, so this method returns a pointer to
@@ -115,6 +119,12 @@ public:
     /// @param addr address of the searched lease
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const;
 
 
@@ -128,6 +138,12 @@ public:
     /// @param hwaddr hardware address of the client
     ///
     /// @return lease collection
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
 
     /// @brief Returns existing IPv4 leases for specified hardware address
@@ -140,6 +156,12 @@ public:
     /// @param subnet_id identifier of the subnet that lease must belong to
     ///
     /// @return a pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
                                 SubnetID subnet_id) const;
 
@@ -153,6 +175,12 @@ public:
     /// @param clientid client identifier
     ///
     /// @return lease collection
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Collection getLease4(const ClientId& clientid) const;
 
     /// @brief Returns existing IPv4 lease for specified client-id
@@ -164,6 +192,12 @@ public:
     /// @param subnet_id identifier of the subnet that lease must belong to
     ///
     /// @return a pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease4Ptr getLease4(const ClientId& clientid,
                                 SubnetID subnet_id) const;
 
@@ -179,6 +213,9 @@ public:
     ///
     /// @throw isc::BadValue record retrieved from database had an invalid
     ///        lease type field.
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
     virtual Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
@@ -194,6 +231,14 @@ public:
     /// @param iaid IA identifier
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::BadValue record retrieved from database had an invalid
+    ///        lease type field.
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease6Collection getLease6(const DUID& duid,
                                        uint32_t iaid) const;
 
@@ -204,18 +249,35 @@ public:
     /// @param subnet_id subnet id of the subnet the lease belongs to
     ///
     /// @return smart pointer to the lease (or NULL if a lease is not found)
+    ///
+    /// @throw isc::BadValue record retrieved from database had an invalid
+    ///        lease type field.
+    /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+    ///        fit into the space allocated for the result.  This indicates a
+    ///        programming error.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual Lease6Ptr getLease6(const DUID& duid, uint32_t iaid,
                                 SubnetID subnet_id) const;
 
     /// @brief Updates IPv4 lease.
     ///
+    /// Updates the record of the lease in the database (as identified by the
+    /// address) with the data in the passed lease object.
+    ///
     /// @param lease4 The lease to be updated.
     ///
-    /// If no such lease is present, an exception will be thrown.
+    /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
+    ///        exist.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual void updateLease4(const Lease4Ptr& lease4);
 
     /// @brief Updates IPv6 lease.
     ///
+    /// Updates the record of the lease in the database (as identified by the
+    /// address) with the data in the passed lease object.
+    ///
     /// @param lease6 The lease to be updated.
     ///
     /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
@@ -226,13 +288,24 @@ public:
 
     /// @brief Deletes an IPv4 lease.
     ///
+    /// @todo Merge with deleteLease6: it is possible to determine whether
+    ///       an address is V4 or V6 from the IOAddress argument, so there
+    ///       is no need for separate V4 or V6 methods.
+    ///
     /// @param addr IPv4 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
     virtual bool deleteLease4(const isc::asiolink::IOAddress& addr);
 
     /// @brief Deletes an IPv6 lease.
     ///
+    /// @todo Merge with deleteLease4: it is possible to determine whether
+    ///       an address is V4 or V6 from the IOAddress argument, so there
+    ///       is no need for separate V4 or V6 methods.
+    ///
     /// @param addr IPv6 address of the lease to be deleted.
     ///
     /// @return true if deletion was successful, false if no such lease exists
@@ -343,12 +416,20 @@ public:
     ///
     /// The contents of the enum are indexes into the list of SQL statements
     enum StatementIndex {
+        DELETE_LEASE4,              // Delete from lease4 by address
         DELETE_LEASE6,              // Delete from lease6 by address
+        GET_LEASE4_ADDR,            // Get lease4 by address
+        GET_LEASE4_CLIENTID,        // Get lease4 by client ID
+        GET_LEASE4_CLIENTID_SUBID,  // Get lease4 by client ID & subnet ID
+        GET_LEASE4_HWADDR,          // Get lease4 by HW address
+        GET_LEASE4_HWADDR_SUBID,    // Get lease4 by HW address & subnet ID
         GET_LEASE6_ADDR,            // Get lease6 by address
         GET_LEASE6_DUID_IAID,       // Get lease6 by DUID and IAID
-        GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and Subnet ID
+        GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and subnet ID
         GET_VERSION,                // Obtain version number
+        INSERT_LEASE4,              // Add entry to lease4 table
         INSERT_LEASE6,              // Add entry to lease6 table
+        UPDATE_LEASE4,              // Update a Lease4 entry
         UPDATE_LEASE6,              // Update a Lease6 entry
         NUM_STATEMENTS              // Number of statements
     };
@@ -389,25 +470,150 @@ private:
     /// @throw DbOpenError Error opening the database
     void openDatabase();
 
-    /// @brief Binds Parameters and Executes
+    /// @brief Add Lease Common Code
+    ///
+    /// This method performs the common actions for both flavours (V4 and V6)
+    /// of the addLease method.  It binds the contents of the lease object to
+    /// the prepared statement and adds it to the database.
     ///
-    /// This method abstracts a lot of common processing from the getXxxx()
-    /// methods.  It binds the parameters passed to it to the appropriate
-    /// prepared statement, and binds the variables in the exchange6 object to
-    /// the output parameters of the statement.  It then executes the prepared
-    /// statement.
+    /// @param stindex Index of statemnent being executed
+    /// @param bind MYSQL_BIND array that has been created for the type
+    ///        of lease in question.
     ///
-    /// The data can be retrieved using mysql_stmt_fetch and the getLeaseData()
-    /// method on the exchange6 object.
+    /// @return true if the lease was added, false if it was not added because
+    ///         a lease with that address already exists in the database.
+    ///
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    bool addLeaseCommon(StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
+
+    /// @brief Get Lease Collection Common Code
+    ///
+    /// This method performs the common actions for obtaining multiple leases
+    /// from the database.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param exchange Exchange object to use
+    /// @param lease LeaseCollection object returned.  Note that any leases in
+    ///        the collection when this method is called are not erased: the
+    ///        new data is appended to the end.
+    /// @param single If true, only a single data item is to be retrieved.
+    ///        If more than one is present, a MultipleRecords exception will
+    ///        be thrown.
+    ///
+    /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+    ///        from the database where only one was expected.
+    template <typename Exchange, typename LeaseCollection>
+    void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+                            Exchange& exchange, LeaseCollection& result,
+                            bool single = false) const;
+
+    /// @brief Get Lease Collection
+    ///
+    /// Gets a collection of Lease4 objects.  This is just an interface to
+    /// the get lease collection common code.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param lease LeaseCollection object returned.  Note that any leases in
+    ///        the collection when this method is called are not erased: the
+    ///        new data is appended to the end.
+    ///
+    /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+    ///        from the database where only one was expected.
+    void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+                            Lease4Collection& result) const {
+        getLeaseCollection(stindex, bind, exchange4_, result);
+    }
+
+    /// @brief Get Lease Collection
+    ///
+    /// Gets a collection of Lease6 objects.  This is just an interface to
+    /// the get lease collection common code.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param lease LeaseCollection object returned.  Note that any existing
+    ///        data in the collection is erased first.
+    ///
+    /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+    ///        from the database where only one was expected.
+    void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+                            Lease6Collection& result) const {
+        getLeaseCollection(stindex, bind, exchange6_, result);
+    }
+
+    /// @brief Get Lease4 Common Code
+    ///
+    /// This method performs the common actions for the various getLease4()
+    /// methods.  It acts as an interface to the getLeaseCollection() method,
+    /// but retrieveing only a single lease.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param lease Lease4 object returned
+    void getLease(StatementIndex stindex, MYSQL_BIND* bind,
+                  Lease4Ptr& result) const;
+
+    /// @brief Get Lease6 Common Code
+    ///
+    /// This method performs the common actions for the various getLease46)
+    /// methods.  It acts as an interface to the getLeaseCollection() method,
+    /// but retrieveing only a single lease.
+    ///
+    /// @param stindex Index of statement being executed
+    /// @param bind MYSQL_BIND array for input parameters
+    /// @param lease Lease6 object returned
+    void getLease(StatementIndex stindex, MYSQL_BIND* bind,
+                   Lease6Ptr& result) const;
+
+    /// @brief Update lease common code
+    ///
+    /// Holds the common code for updating a lease.  It binds the parameters
+    /// to the prepared statement, executes it, then checks how many rows
+    /// were affected.
+    ///
+    /// @param stindex Index of prepared statement to be executed
+    /// @param bind Array of MYSQL_BIND objects representing the parameters.
+    ///        (Note that the number is determined by the number of parameters
+    ///        in the statement.)
+    /// @param lease Pointer to the lease object whose record is being updated.
+    ///
+    /// @throw NoSuchLease Could not update a lease because no lease matches
+    ///        the address given.
+    /// @throw isc::dhcp::DbOperationError An operation on the open database has
+    ///        failed.
+    template <typename LeasePtr>
+    void updateLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind,
+                           const LeasePtr& lease);
+
+    /// @brief Delete lease common code
+    ///
+    /// Holds the common code for deleting a lease.  It binds the parameters
+    /// to the prepared statement, executes the statement and checks to
+    /// see how many rows were deleted.
     ///
     /// @param stindex Index of prepared statement to be executed
-    /// @param inbind Array of MYSQL_BIND objects representing the parameters.
+    /// @param bind Array of MYSQL_BIND objects representing the parameters.
     ///        (Note that the number is determined by the number of parameters
     ///        in the statement.)
     ///
+    /// @return true if one or more rows were deleted, false if none were
+    ///         deleted.
+    ///
     /// @throw isc::dhcp::DbOperationError An operation on the open database has
     ///        failed.
-    void bind6AndExecute(StatementIndex stindex, MYSQL_BIND* inbind) const;
+    bool deleteLease(StatementIndex stindex, MYSQL_BIND* bind);
 
     /// @brief Check Error and Throw Exception
     ///
@@ -433,14 +639,15 @@ private:
 
     // Members
 
-    /// Used for transfer of data to/from the database. This is a pointed-to
-    /// object as its contents may change in "const" calls, while the rest
-    /// of this object does not.  (At alternative would be to declare it as
-    /// "mutable".)
-    boost::scoped_ptr<MySqlLease6Exchange> exchange6_;
+    /// The exchange objects are used for transfer of data to/from the database.
+    /// They are pointed-to objects as the contents may change in "const" calls,
+    /// while the rest of this object does not.  (At alternative would be to
+    /// declare them as "mutable".)
+    boost::scoped_ptr<MySqlLease4Exchange> exchange4_; ///< Exchange object
+    boost::scoped_ptr<MySqlLease6Exchange> exchange6_; ///< Exchange object
     MYSQL*              mysql_;                 ///< MySQL context object
-    std::vector<std::string> text_statements_;  ///< Raw text of statements
     std::vector<MYSQL_STMT*> statements_;       ///< Prepared statements
+    std::vector<std::string> text_statements_;  ///< Raw text of statements
 };
 
 }; // end of isc::dhcp namespace

+ 1 - 1
src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// 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

+ 356 - 26
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -22,6 +22,8 @@
 #include <iostream>
 #include <sstream>
 
+#include <time.h>
+
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
@@ -236,16 +238,12 @@ public:
 };
 
 namespace {
-// empty class for now, but may be extended once Addr6 becomes bigger
-class LeaseMgrTest : public ::testing::Test {
-public:
-    LeaseMgrTest() {
-    }
-};
 
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
-TEST_F(LeaseMgrTest, getParameter) {
+/// @brief getParameter test
+///
+/// This test checks if the LeaseMgr can be instantiated and that it
+/// parses parameters string properly.
+TEST(LeaseMgr, getParameter) {
 
     LeaseMgr::ParameterMap pmap;
     pmap[std::string("param1")] = std::string("value1");
@@ -261,36 +259,368 @@ TEST_F(LeaseMgrTest, getParameter) {
 // are purely virtual, so we would only call ConcreteLeaseMgr methods.
 // Those methods are just stubs that do not return anything.
 
+/// @brief Lease4 Constructor Test
+///
+/// Lease4 is also defined in lease_mgr.h, so is tested in this file as well.
+// This test checks if the Lease4 structure can be instantiated correctly
+TEST(Lease4, Lease4Constructor) {
+
+    // Random values for the tests
+    const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+    std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+
+    const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+    std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+    ClientId clientid(clientid_vec);
+
+    // ...and a time
+    const time_t current_time = time(NULL);
+
+    // Other random constants. 
+    const uint32_t SUBNET_ID = 42;
+    const uint32_t VALID_LIFETIME = 500;
+
+    // We want to check that various addresses work, so let's iterate over
+    // these.
+    const uint32_t ADDRESS[] = {
+        0x00000000, 0x01020304, 0x7fffffff, 0x80000000, 0x80000001, 0xffffffff
+    };
+
+    for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+
+        // Create the lease
+        Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR),
+                     CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                     SUBNET_ID);
+
+        EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
+        EXPECT_EQ(0, lease.ext_);
+        EXPECT_TRUE(hwaddr == lease.hwaddr_);
+        EXPECT_TRUE(clientid == *lease.client_id_);
+        EXPECT_EQ(0, lease.t1_);
+        EXPECT_EQ(0, lease.t2_);
+        EXPECT_EQ(VALID_LIFETIME, lease.valid_lft_);
+        EXPECT_EQ(current_time, lease.cltt_);
+        EXPECT_EQ(SUBNET_ID, lease.subnet_id_);
+        EXPECT_FALSE(lease.fixed_);
+        EXPECT_TRUE(lease.hostname_.empty());
+        EXPECT_FALSE(lease.fqdn_fwd_);
+        EXPECT_FALSE(lease.fqdn_rev_);
+        EXPECT_TRUE(lease.comments_.empty());
+    }
+}
+
+/// @brief Lease4 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease4, OperatorEquals) {
+
+    // Random values for the tests
+    const uint32_t ADDRESS = 0x01020304;
+    const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+    std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+    const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+    std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+    ClientId clientid(clientid_vec);
+    const time_t current_time = time(NULL);
+    const uint32_t SUBNET_ID = 42;
+    const uint32_t VALID_LIFETIME = 500;
+
+    // Check when the leases are equal.
+    Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
+                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                  SUBNET_ID);
+    Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
+                  CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+                  SUBNET_ID);
+    EXPECT_TRUE(lease1 == lease2);
+    EXPECT_FALSE(lease1 != lease2);
+
+    // Now vary individual fields in a lease and check that the leases compare
+    // not equal in every case.
+    lease1.addr_ = IOAddress(ADDRESS + 1);
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.addr_ = lease2.addr_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.ext_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.ext_ = lease2.ext_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.hwaddr_[0];
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.hwaddr_ = lease2.hwaddr_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++clientid_vec[0];
+    lease1.client_id_.reset(new ClientId(clientid_vec));
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    --clientid_vec[0];
+    lease1.client_id_.reset(new ClientId(clientid_vec));
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.t1_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.t1_ = lease2.t1_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.t2_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.t2_ = lease2.t2_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.valid_lft_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.valid_lft_ = lease2.valid_lft_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.cltt_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.cltt_ = lease2.cltt_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.subnet_id_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.subnet_id_ = lease2.subnet_id_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fixed_ = !lease1.fixed_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fixed_ = lease2.fixed_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.hostname_ += string("Something random");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.hostname_ = lease2.hostname_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fqdn_rev_ = lease2.fqdn_rev_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.comments_ += string("Something random");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.comments_ = lease2.comments_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
+
+
+
 // Lease6 is also defined in lease_mgr.h, so is tested in this file as well.
 // This test checks if the Lease6 structure can be instantiated correctly
 TEST(Lease6, Lease6Constructor) {
 
-    IOAddress addr("2001:db8:1::456");
+    // check a variety of addresses with different bits set.
+    const char* ADDRESS[] = {
+        "::", "::1", "2001:db8:1::456",
+        "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+        "8000::", "8000::1",
+        "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+    };
 
+    // Other values
     uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
     DuidPtr duid(new DUID(llt, sizeof(llt)));
-
     uint32_t iaid = 7; // just a number
-
     SubnetID subnet_id = 8; // just another number
 
-    Lease6Ptr x(new Lease6(Lease6::LEASE_IA_NA, addr,
-                           duid, iaid, 100, 200, 50, 80,
-                           subnet_id));
-
-    EXPECT_TRUE(x->addr_ == addr);
-    EXPECT_TRUE(*x->duid_ == *duid);
-    EXPECT_TRUE(x->iaid_ == iaid);
-    EXPECT_TRUE(x->subnet_id_ == subnet_id);
-    EXPECT_TRUE(x->type_ == Lease6::LEASE_IA_NA);
-    EXPECT_TRUE(x->preferred_lft_ == 100);
-    EXPECT_TRUE(x->valid_lft_ == 200);
-    EXPECT_TRUE(x->t1_ == 50);
-    EXPECT_TRUE(x->t2_ == 80);
+    for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+        IOAddress addr(ADDRESS[i]);
+        Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr,
+                               duid, iaid, 100, 200, 50, 80,
+                               subnet_id));
+
+        EXPECT_TRUE(lease->addr_ == addr);
+        EXPECT_TRUE(*lease->duid_ == *duid);
+        EXPECT_TRUE(lease->iaid_ == iaid);
+        EXPECT_TRUE(lease->subnet_id_ == subnet_id);
+        EXPECT_TRUE(lease->type_ == Lease6::LEASE_IA_NA);
+        EXPECT_TRUE(lease->preferred_lft_ == 100);
+        EXPECT_TRUE(lease->valid_lft_ == 200);
+        EXPECT_TRUE(lease->t1_ == 50);
+        EXPECT_TRUE(lease->t2_ == 80);
+    }
 
     // Lease6 must be instantiated with a DUID, not with NULL pointer
+    IOAddress addr(ADDRESS[0]);
     EXPECT_THROW(new Lease6(Lease6::LEASE_IA_NA, addr,
                             DuidPtr(), iaid, 100, 200, 50, 80,
                             subnet_id), InvalidOperation);
 }
+
+/// @brief Lease6 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease6, OperatorEquals) {
+
+    // check a variety of addressemas with different bits set.
+    const IOAddress addr("2001:db8:1::456");
+    uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+    DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+    uint32_t iaid = 7; // just a number
+    SubnetID subnet_id = 8; // just another number
+
+    // Check for equality.
+    Lease6 lease1(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
+                               subnet_id);
+    Lease6 lease2(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
+                               subnet_id);
+    EXPECT_TRUE(lease1 == lease2);
+    EXPECT_FALSE(lease1 != lease2);
+
+    // Go through and alter all the fields one by one
+
+    lease1.addr_ = IOAddress("::1");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.addr_ = lease2.addr_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.type_ = Lease6::LEASE_IA_PD;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.type_ = lease2.type_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.prefixlen_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.prefixlen_ = lease2.prefixlen_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.iaid_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.iaid_ = lease2.iaid_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++duid_array[0];
+    lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    --duid_array[0];
+    lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.preferred_lft_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.preferred_lft_ = lease2.preferred_lft_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.valid_lft_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.valid_lft_ = lease2.valid_lft_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.t1_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.t1_ = lease2.t1_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.t2_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.t2_ = lease2.t2_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.cltt_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.cltt_ = lease2.cltt_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    ++lease1.subnet_id_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.subnet_id_ = lease2.subnet_id_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fixed_ = !lease1.fixed_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fixed_ = lease2.fixed_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.hostname_ += string("Something random");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.hostname_ = lease2.hostname_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.fqdn_rev_ = lease2.fqdn_rev_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+    lease1.comments_ += string("Something random");
+    EXPECT_FALSE(lease1 == lease2);
+    EXPECT_TRUE(lease1 != lease2);
+    lease1.comments_ = lease2.comments_;
+    EXPECT_TRUE(lease1 == lease2);  // Check that the reversion has made the
+    EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
 }; // end of anonymous namespace

+ 1 - 1
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -53,7 +53,7 @@ TEST_F(MemfileLeaseMgrTest, getTypeAndName) {
     boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
 
     EXPECT_EQ(std::string("memfile"), lease_mgr->getType());
-    EXPECT_EQ(std::string("memfile"), lease_mgr->getName());
+    EXPECT_EQ(std::string("memory"), lease_mgr->getName());
 }
 
 // Checks that adding/getting/deleting a Lease6 object works.

File diff suppressed because it is too large
+ 851 - 253
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc


+ 6 - 4
src/lib/dhcpsrv/tests/schema_copy.h

@@ -25,7 +25,9 @@ namespace {
 // by semicolons, and the strings must end with a comma.  The final line
 // statement must be NULL (not in quotes)
 
-// THIS MUST BE KEPT UP TO DATE AND UPDATED IF THE SCHEMA CHANGES
+// NOTE: This file mirrors the schema in src/lib/dhcpsrv/dhcpdb_create.mysql.
+//       If this file is altered, please ensure that any change is compatible
+//       with the schema in dhcpdb_create.mysql.
 
 // Deletion of existing tables.
 
@@ -44,13 +46,13 @@ const char* create_statement[] = {
         "address INT UNSIGNED PRIMARY KEY NOT NULL,"
         "hwaddr VARBINARY(20),"
         "client_id VARBINARY(128),"
-        "lease_time INT UNSIGNED,"
+        "valid_lifetime INT UNSIGNED,"
         "expire TIMESTAMP,"
         "subnet_id INT UNSIGNED"
         ") ENGINE = INNODB",
 
     "CREATE TABLE lease6 ("
-        "address VARCHAR(40) PRIMARY KEY NOT NULL,"
+        "address VARCHAR(39) PRIMARY KEY NOT NULL,"
         "duid VARBINARY(128),"
         "valid_lifetime INT UNSIGNED,"
         "expire TIMESTAMP,"
@@ -75,7 +77,7 @@ const char* create_statement[] = {
         "minor INT"
         ")",
 
-    "INSERT INTO schema_version VALUES (0, 1)",
+    "INSERT INTO schema_version VALUES (1, 0)",
 
     NULL
 };

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

@@ -21,6 +21,8 @@ EXTRA_DIST += rdata/ch_3/a_1.cc
 EXTRA_DIST += rdata/ch_3/a_1.h
 EXTRA_DIST += rdata/generic/cname_5.cc
 EXTRA_DIST += rdata/generic/cname_5.h
+EXTRA_DIST += rdata/generic/detail/char_string.cc
+EXTRA_DIST += rdata/generic/detail/char_string.h
 EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
 EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
 EXTRA_DIST += rdata/generic/detail/nsec3param_common.cc
@@ -123,6 +125,8 @@ 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 += master_loader.h
+libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
+libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
 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

+ 2 - 1
src/lib/dns/gen-rdatacode.py.in

@@ -32,7 +32,8 @@ import sys
 #
 # Example:
 #     new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('aaaa', 'in')]
+new_rdata_factory_users = [('aaaa', 'in'), ('txt', 'generic'),
+                           ('spf', 'generic')]
 
 re_typecode = re.compile('([\da-z]+)_(\d+)')
 classcode2txt = {}

+ 1 - 1
src/lib/dns/python/tests/rdata_python_test.py

@@ -38,7 +38,7 @@ class RdataTest(unittest.TestCase):
         self.assertRaises(InvalidRdataText, Rdata, RRType("A"), RRClass("IN"),
                           "Invalid Rdata Text")
         self.assertRaises(CharStringTooLong, Rdata, RRType("TXT"),
-                          RRClass("IN"), ' ' * 256)
+                          RRClass("IN"), 'x' * 256)
         self.assertRaises(InvalidRdataLength, Rdata, RRType("TXT"),
                           RRClass("IN"), bytes(65536))
         self.assertRaises(DNSMessageFORMERR, Rdata, RRType("TXT"),

+ 6 - 0
src/lib/dns/python/tests/tsigkey_python_test.py

@@ -170,6 +170,12 @@ class TSIGKeyRingTest(unittest.TestCase):
         self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
         self.assertEqual(self.secret, key.get_secret())
 
+        (code, key) = self.keyring.find(self.key_name)
+        self.assertEqual(TSIGKeyRing.SUCCESS, code)
+        self.assertEqual(self.key_name, key.get_key_name())
+        self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
+        self.assertEqual(self.secret, key.get_secret())
+
         (code, key) = self.keyring.find(Name('different-key.example'),
                                         self.sha256_name)
         self.assertEqual(TSIGKeyRing.NOTFOUND, code)

+ 11 - 6
src/lib/dns/python/tsigkey_python.cc

@@ -287,7 +287,9 @@ PyMethodDef TSIGKeyRing_methods[] = {
       METH_VARARGS,
       "Remove a TSIGKey for the given name from the TSIGKeyRing." },
     { "find", reinterpret_cast<PyCFunction>(TSIGKeyRing_find), METH_VARARGS,
-      "Find a TSIGKey for the given name in the TSIGKeyRing. "
+      "Find a TSIGKey for the given name in the TSIGKeyRing. Optional "
+      "second argument is an algorithm, in which case it only returns "
+      "a key if both match.\n"
       "It returns a tuple of (result_code, key)." },
     { NULL, NULL, 0, NULL }
 };
@@ -362,13 +364,16 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
 PyObject*
 TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
     PyObject* key_name;
-    PyObject* algorithm_name;
+    PyObject* algorithm_name = NULL;
 
-    if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name,
+    if (PyArg_ParseTuple(args, "O!|O!", &name_type, &key_name,
                          &name_type, &algorithm_name)) {
-        const TSIGKeyRing::FindResult result =
-            self->cppobj->find(PyName_ToName(key_name),
-                               PyName_ToName(algorithm_name));
+        // Can't init TSIGKeyRing::FindResult without actual result,
+        // so use ternary operator
+        TSIGKeyRing::FindResult result = (algorithm_name == NULL) ?
+                    self->cppobj->find(PyName_ToName(key_name)) :
+                    self->cppobj->find(PyName_ToName(key_name),
+                                       PyName_ToName(algorithm_name));
         if (result.key != NULL) {
             s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
             if (key == NULL) {

+ 98 - 0
src/lib/dns/rdata/generic/detail/char_string.cc

@@ -0,0 +1,98 @@
+// 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 <exceptions/exceptions.h>
+
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+#include <dns/rdata/generic/detail/char_string.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <cctype>
+#include <cstring>
+#include <vector>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+namespace {
+// Convert a DDD form to the corresponding integer
+int
+decimalToNumber(const char* s, const char* s_end) {
+    if (s_end - s < 3) {
+        isc_throw(InvalidRdataText, "Escaped digits too short");
+    }
+
+    const std::string num_str(s, s + 3);
+    try {
+        const int i = boost::lexical_cast<int>(num_str);
+        if (i > 255) {
+            isc_throw(InvalidRdataText, "Escaped digits too large: "
+                      << num_str);
+        }
+        return (i);
+    } catch (const boost::bad_lexical_cast&) {
+        isc_throw(InvalidRdataText,
+                  "Invalid form for escaped digits: " << num_str);
+    }
+}
+}
+
+void
+strToCharString(const MasterToken::StringRegion& str_region,
+                CharString& result)
+{
+    // make a space for the 1-byte length field; filled in at the end
+    result.push_back(0);
+
+    bool escape = false;
+    const char* s = str_region.beg;
+    const char* const s_end = str_region.beg + str_region.len;
+
+    for (size_t n = str_region.len; n != 0; --n, ++s) {
+        int c = (*s & 0xff);
+        if (escape && std::isdigit(c) != 0) {
+            c = decimalToNumber(s, s_end);
+            assert(n >= 3);
+            n -= 2;
+            s += 2;
+        } else if (!escape && c == '\\') {
+            escape = true;
+            continue;
+        }
+        escape = false;
+        result.push_back(c);
+    }
+    if (escape) {               // terminated by non-escaped '\'
+        isc_throw(InvalidRdataText, "character-string ends with '\\'");
+    }
+    if (result.size() > MAX_CHARSTRING_LEN + 1) { // '+ 1' due to the len field
+        isc_throw(CharStringTooLong, "character-string is too long: " <<
+                  (result.size() - 1) << "(+1) characters");
+    }
+    result[0] = result.size() - 1;
+}
+
+} // end of detail
+} // end of generic
+} // end of rdata
+} // end of dns
+} // end of isc

+ 63 - 0
src/lib/dns/rdata/generic/detail/char_string.h

@@ -0,0 +1,63 @@
+// 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 DNS_RDATA_CHARSTRING_H
+#define DNS_RDATA_CHARSTRING_H 1
+
+#include <dns/master_lexer.h>
+
+#include <vector>
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief Type for DNS character string.
+///
+/// A character string can contain any unsigned 8-bit value, so this cannot
+/// be the bare char basis.
+typedef std::vector<uint8_t> CharString;
+
+/// \brief Convert a DNS character-string into corresponding binary data.
+///
+/// This helper function takes a string object that is expected to be a
+/// textual representation of a valid DNS character-string, and dumps
+/// the corresponding binary sequence in the given placeholder (passed
+/// via the \c result parameter).  It handles escape notations of
+/// character-strings with a backslash ('\'), and checks the length
+/// restriction.
+///
+/// \throw CharStringTooLong The resulting binary data are too large for a
+/// valid character-string.
+/// \throw InvalidRdataText Other syntax errors.
+///
+/// \brief str_region A string that represents a character-string.
+/// \brief result A placeholder vector where the resulting data are to be
+/// stored.  Expected to be empty, but it's not checked.
+void strToCharString(const MasterToken::StringRegion& str_region,
+                     CharString& result);
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif  // DNS_RDATA_CHARSTRING_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 74 - 49
src/lib/dns/rdata/generic/detail/txt_like.h

@@ -15,13 +15,20 @@
 #ifndef TXT_LIKE_H
 #define TXT_LIKE_H 1
 
+#include <dns/master_lexer.h>
+#include <dns/rdata/generic/detail/char_string.h>
+
 #include <stdint.h>
 
 #include <string>
+#include <sstream>
 #include <vector>
 
-using namespace std;
-using namespace isc::util;
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
 
 /// \brief \c rdata::TXTLikeImpl class represents the TXT-like RDATA for TXT
 /// and SPF types.
@@ -41,7 +48,7 @@ public:
     ///
     /// \c InvalidRdataLength is thrown if rdata_len exceeds the maximum.
     /// \c DNSMessageFORMERR is thrown if the RR is misformed.
-    TXTLikeImpl(InputBuffer& buffer, size_t rdata_len) {
+    TXTLikeImpl(util::InputBuffer& buffer, size_t rdata_len) {
         if (rdata_len > MAX_RDLENGTH) {
             isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len);
         }
@@ -59,7 +66,7 @@ public:
                           " RDATA: character string length is too large: " <<
                           static_cast<int>(len));
             }
-            vector<uint8_t> data(len + 1);
+            std::vector<uint8_t> data(len + 1);
             data[0] = len;
             buffer.readData(&data[0] + 1, len);
             string_list_.push_back(data);
@@ -70,46 +77,61 @@ public:
 
     /// \brief Constructor from string.
     ///
-    /// <b>Exceptions</b>
-    ///
-    /// \c CharStringTooLong is thrown if the parameter string length exceeds
-    /// maximum.
-    /// \c InvalidRdataText is thrown if the method cannot process the
-    /// parameter data.
+    /// \throw CharStringTooLong the parameter string length exceeds maximum.
+    /// \throw InvalidRdataText the method cannot process the parameter data
     explicit TXTLikeImpl(const std::string& txtstr) {
-        // TBD: this is a simple, incomplete implementation that only supports
-        // a single character-string.
+        std::istringstream ss(txtstr);
+        MasterLexer lexer;
+        lexer.pushSource(ss);
+
+        try {
+            buildFromTextHelper(lexer);
+            if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+                isc_throw(InvalidRdataText, "Failed to construct " <<
+                          RRType(typeCode) << " RDATA from '" << txtstr <<
+                          "': extra new line");
+            }
+        } catch (const MasterLexer::LexerError& ex) {
+            isc_throw(InvalidRdataText, "Failed to construct " <<
+                      RRType(typeCode) << " RDATA from '" << txtstr << "': "
+                      << ex.what());
+        }
+    }
 
-        size_t length = txtstr.size();
-        size_t pos_begin = 0;
+    /// \brief Constructor using the master lexer.
+    ///
+    /// \throw CharStringTooLong the parameter string length exceeds maximum.
+    /// \throw InvalidRdataText the method cannot process the parameter data
+    ///
+    /// \param lexer A \c MasterLexer object parsing a master file for this
+    /// RDATA.
+    TXTLikeImpl(MasterLexer& lexer) {
+        buildFromTextHelper(lexer);
+    }
 
-        if (length > 1 && txtstr[0] == '"' && txtstr[length - 1] == '"') {
-            pos_begin = 1;
-            length -= 2;
+private:
+    void buildFromTextHelper(MasterLexer& lexer) {
+        while (true) {
+            const MasterToken& token = lexer.getNextToken(
+                MasterToken::QSTRING, true);
+            if (token.getType() != MasterToken::STRING &&
+                token.getType() != MasterToken::QSTRING) {
+                break;
+            }
+            string_list_.push_back(std::vector<uint8_t>());
+            strToCharString(token.getStringRegion(), string_list_.back());
         }
 
-        if (length > MAX_CHARSTRING_LEN) {
-            isc_throw(CharStringTooLong, RRType(typeCode) <<
-                      " RDATA construction from text:"
-                      " string length is too long: " << length);
-        }
+        // Let upper layer handle eol/eof.
+        lexer.ungetToken();
 
-        // TBD: right now, we don't support escaped characters
-        if (txtstr.find('\\') != string::npos) {
-            isc_throw(InvalidRdataText, RRType(typeCode) <<
-                      " RDATA from text:"
-                      " escaped character is currently not supported: " <<
-                      txtstr);
+        if (string_list_.empty()) {
+            isc_throw(InvalidRdataText, "Failed to construct" <<
+                      RRType(typeCode) << " RDATA: empty input");
         }
-
-        vector<uint8_t> data;
-        data.reserve(length + 1);
-        data.push_back(length);
-        data.insert(data.end(), txtstr.begin() + pos_begin,
-                    txtstr.begin() + pos_begin + length);
-        string_list_.push_back(data);
     }
 
+public:
     /// \brief The copy constructor.
     ///
     /// Trivial for now, we could've used the default one.
@@ -122,9 +144,9 @@ public:
     ///
     /// \param buffer An output buffer to store the wire data.
     void
-    toWire(OutputBuffer& buffer) const {
-        for (vector<vector<uint8_t> >::const_iterator it =
-                                                          string_list_.begin();
+    toWire(util::OutputBuffer& buffer) const {
+        for (std::vector<std::vector<uint8_t> >::const_iterator it =
+                 string_list_.begin();
              it != string_list_.end();
              ++it)
         {
@@ -139,8 +161,8 @@ public:
     /// to.
     void
     toWire(AbstractMessageRenderer& renderer) const {
-        for (vector<vector<uint8_t> >::const_iterator it =
-                                                          string_list_.begin();
+        for (std::vector<std::vector<uint8_t> >::const_iterator it =
+                 string_list_.begin();
              it != string_list_.end();
              ++it)
         {
@@ -151,14 +173,14 @@ public:
     /// \brief Convert the TXT-like data to a string.
     ///
     /// \return A \c string object that represents the TXT-like data.
-    string
+    std::string
     toText() const {
-        string s;
+        std::string s;
 
         // XXX: this implementation is not entirely correct.  for example, it
         // should escape double-quotes if they appear in the character string.
-        for (vector<vector<uint8_t> >::const_iterator it =
-                                                          string_list_.begin();
+        for (std::vector<std::vector<uint8_t> >::const_iterator it =
+                 string_list_.begin();
              it != string_list_.end();
              ++it)
         {
@@ -189,7 +211,7 @@ public:
         OutputBuffer this_buffer(0);
         toWire(this_buffer);
         uint8_t const* const this_data = (uint8_t const*)this_buffer.getData();
-        size_t this_len = this_buffer.getLength();
+        const size_t this_len = this_buffer.getLength();
 
         OutputBuffer other_buffer(0);
         other.toWire(other_buffer);
@@ -214,11 +236,14 @@ private:
     std::vector<std::vector<uint8_t> > string_list_;
 };
 
-// END_RDATA_NAMESPACE
-// END_ISC_NAMESPACE
+}      // namespace detail
+}      // namespace generic
+}      // namespace rdata
+}      // namespace dns
+}      // namespace isc
 
 #endif //  TXT_LIKE_H
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:

+ 20 - 6
src/lib/dns/rdata/generic/spf_99.cc

@@ -24,18 +24,17 @@
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+#include <dns/rdata/generic/detail/txt_like.h>
+
 using namespace std;
 using namespace isc::util;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
-/// This class implements the basic interfaces inherited from the abstract
-/// \c rdata::Rdata class. The semantics of the class is provided by
-/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
-
-#include <dns/rdata/generic/detail/txt_like.h>
-
 /// \brief The assignment operator
 ///
 /// It internally allocates a resource, and if it fails a corresponding
@@ -67,6 +66,21 @@ SPF::SPF(InputBuffer& buffer, size_t rdata_len) :
     impl_(new SPFImpl(buffer, rdata_len))
 {}
 
+/// \brief Constructor using the master lexer.
+///
+/// This implementation only uses the \c lexer parameters; others are
+/// ignored.
+///
+/// \throw CharStringTooLong the parameter string length exceeds maximum.
+/// \throw InvalidRdataText the method cannot process the parameter data
+///
+/// \param lexer A \c MasterLexer object parsing a master file for this
+/// RDATA.
+SPF::SPF(MasterLexer& lexer, const Name*, MasterLoader::Options,
+         MasterLoaderCallbacks&) :
+    impl_(new SPFImpl(lexer))
+{}
+
 /// \brief Constructor from string.
 ///
 /// It internally allocates a resource, and if it fails a corresponding

+ 3 - 1
src/lib/dns/rdata/generic/spf_99.h

@@ -28,7 +28,9 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+namespace detail {
 template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
 
 /// \brief \c rdata::SPF class represents the SPF RDATA as defined %in
 /// RFC4408.
@@ -65,7 +67,7 @@ public:
     const std::vector<std::vector<uint8_t> >& getString() const;
 
 private:
-    typedef TXTLikeImpl<SPF, 99> SPFImpl;
+    typedef isc::dns::rdata::generic::detail::TXTLikeImpl<SPF, 99> SPFImpl;
     SPFImpl* impl_;
 };
 

+ 16 - 2
src/lib/dns/rdata/generic/txt_16.cc

@@ -23,6 +23,7 @@
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/txt_like.h>
 
 using namespace std;
 using namespace isc::util;
@@ -30,8 +31,6 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
-#include <dns/rdata/generic/detail/txt_like.h>
-
 TXT&
 TXT::operator=(const TXT& source) {
     if (impl_ == source.impl_) {
@@ -53,6 +52,21 @@ TXT::TXT(InputBuffer& buffer, size_t rdata_len) :
     impl_(new TXTImpl(buffer, rdata_len))
 {}
 
+/// \brief Constructor using the master lexer.
+///
+/// This implementation only uses the \c lexer parameters; others are
+/// ignored.
+///
+/// \throw CharStringTooLong the parameter string length exceeds maximum.
+/// \throw InvalidRdataText the method cannot process the parameter data
+///
+/// \param lexer A \c MasterLexer object parsing a master file for this
+/// RDATA.
+TXT::TXT(MasterLexer& lexer, const Name*, MasterLoader::Options,
+         MasterLoaderCallbacks&) :
+    impl_(new TXTImpl(lexer))
+{}
+
 TXT::TXT(const std::string& txtstr) :
     impl_(new TXTImpl(txtstr))
 {}

+ 3 - 1
src/lib/dns/rdata/generic/txt_16.h

@@ -28,7 +28,9 @@
 
 // BEGIN_RDATA_NAMESPACE
 
+namespace detail {
 template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
 
 class TXT : public Rdata {
 public:
@@ -39,7 +41,7 @@ public:
     ~TXT();
 
 private:
-    typedef TXTLikeImpl<TXT, 16> TXTImpl;
+    typedef isc::dns::rdata::generic::detail::TXTLikeImpl<TXT, 16> TXTImpl;
     TXTImpl* impl_;
 };
 

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

@@ -36,6 +36,7 @@ run_unittests_SOURCES += opcode_unittest.cc
 run_unittests_SOURCES += rcode_unittest.cc
 run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
 run_unittests_SOURCES += rdatafields_unittest.cc
+run_unittests_SOURCES += rdata_char_string_unittest.cc
 run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
 run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
 run_unittests_SOURCES += rdata_txt_like_unittest.cc

+ 147 - 0
src/lib/dns/tests/rdata_char_string_unittest.cc

@@ -0,0 +1,147 @@
+// 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 <util/unittests/wiredata.h>
+
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/char_string.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::dns::rdata::generic::detail::CharString;
+using isc::dns::rdata::generic::detail::strToCharString;
+using isc::util::unittests::matchWireData;
+
+namespace {
+const uint8_t test_charstr[] = {
+    sizeof("Test String") - 1,
+    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+class CharStringTest : public ::testing::Test {
+protected:
+    CharStringTest() :
+        // char-string representation for test data using two types of escape
+        // ('r' = 114)
+        test_str("Test\\ St\\114ing")
+    {
+        str_region.beg = &test_str[0];
+        str_region.len = test_str.size();
+    }
+    CharString chstr;           // place holder
+    const std::string test_str;
+    MasterToken::StringRegion str_region;
+};
+
+MasterToken::StringRegion
+createStringRegion(const std::string& str) {
+    MasterToken::StringRegion region;
+    region.beg = &str[0]; // note std ensures this works even if str is empty
+    region.len = str.size();
+    return (region);
+}
+
+TEST_F(CharStringTest, normalConversion) {
+    uint8_t tmp[3];             // placeholder for expected sequence
+
+    strToCharString(str_region, chstr);
+    matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
+
+    // Empty string
+    chstr.clear();
+    strToCharString(createStringRegion(""), chstr);
+    tmp[0] = 0;
+    matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+    // Possible largest char string
+    chstr.clear();
+    std::string long_str(255, 'x');
+    strToCharString(createStringRegion(long_str), chstr);
+    std::vector<uint8_t> expected;
+    expected.push_back(255);    // len of char string
+    expected.insert(expected.end(), long_str.begin(), long_str.end());
+    matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+    // Same data as the previous case, but the original string is longer than
+    // the max; this shouldn't be rejected
+    chstr.clear();
+    long_str.at(254) = '\\';    // replace the last 'x' with '\'
+    long_str.append("120");     // 'x' = 120
+    strToCharString(createStringRegion(long_str), chstr);
+    matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+    // Escaped '\'
+    chstr.clear();
+    tmp[0] = 1;
+    tmp[1] = '\\';
+    strToCharString(createStringRegion("\\\\"), chstr);
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+    // Boundary values for \DDD
+    chstr.clear();
+    tmp[0] = 1;
+    tmp[1] = 0;
+    strToCharString(createStringRegion("\\000"), chstr);
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+    chstr.clear();
+    strToCharString(createStringRegion("\\255"), chstr);
+    tmp[0] = 1;
+    tmp[1] = 255;
+    matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+    // Another digit follows DDD; it shouldn't cause confusion
+    chstr.clear();
+    strToCharString(createStringRegion("\\2550"), chstr);
+    tmp[0] = 2;                 // string len is now 2
+    tmp[2] = '0';
+    matchWireData(tmp, 3, &chstr[0], chstr.size());
+}
+
+TEST_F(CharStringTest, badConversion) {
+    // string cannot exceed 255 bytes
+    EXPECT_THROW(strToCharString(createStringRegion(std::string(256, 'a')),
+                                 chstr),
+                 CharStringTooLong);
+
+    // input string ending with (non escaped) '\'
+    chstr.clear();
+    EXPECT_THROW(strToCharString(createStringRegion("foo\\"), chstr),
+                 InvalidRdataText);
+}
+
+TEST_F(CharStringTest, badDDD) {
+    // Check various type of bad form of \DDD
+
+    // Not a number
+    EXPECT_THROW(strToCharString(createStringRegion("\\1a2"), chstr),
+                 InvalidRdataText);
+    EXPECT_THROW(strToCharString(createStringRegion("\\12a"), chstr),
+                 InvalidRdataText);
+
+    // Not in the range of uint8_t
+    EXPECT_THROW(strToCharString(createStringRegion("\\256"), chstr),
+                 InvalidRdataText);
+
+    // Short buffer
+    EXPECT_THROW(strToCharString(createStringRegion("\\42"), chstr),
+                 InvalidRdataText);
+}
+
+} // unnamed namespace

+ 173 - 39
src/lib/dns/tests/rdata_txt_like_unittest.cc

@@ -17,17 +17,25 @@
 #include <util/buffer.h>
 #include <dns/exceptions.h>
 #include <dns/rdataclass.h>
-#include <gtest/gtest.h>
 
 #include <dns/tests/unittest_util.h>
 #include <dns/tests/rdata_unittest.h>
 
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+
+#include <string>
+#include <sstream>
+#include <vector>
+
 using isc::UnitTestUtil;
 using namespace std;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::dns::rdata;
 
+namespace {
 
 template<class T>
 class RRTYPE : public RRType {
@@ -38,36 +46,42 @@ public:
 template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {}
 template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {}
 
-namespace {
 const uint8_t wiredata_txt_like[] = {
-    sizeof("Test String") - 1,
-    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+    sizeof("Test-String") - 1,
+    'T', 'e', 's', 't', '-', 'S', 't', 'r', 'i', 'n', 'g'
 };
 
 const uint8_t wiredata_nulltxt[] = { 0 };
-vector<uint8_t> wiredata_longesttxt(256, 'a');
+
+// For lexer-based constructor
+void
+dummyCallback(const string&, size_t, const string&) {
+}
 
 template<class TXT_LIKE>
 class Rdata_TXT_LIKE_Test : public RdataTest {
 protected:
-    Rdata_TXT_LIKE_Test() {
+    Rdata_TXT_LIKE_Test() :
+        callback(boost::bind(&dummyCallback, _1, _2, _3)),
+        loader_cb(callback, callback),
+        wiredata_longesttxt(256, 'a'),
+        rdata_txt_like("Test-String"),
+        rdata_txt_like_empty("\"\""),
+        rdata_txt_like_quoted("\"Test-String\"")
+    {
         wiredata_longesttxt[0] = 255; // adjust length
     }
 
-    static const TXT_LIKE rdata_txt_like;
-    static const TXT_LIKE rdata_txt_like_empty;
-    static const TXT_LIKE rdata_txt_like_quoted;
-};
-
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like("Test String");
-
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_empty("");
+private:
+    const MasterLoaderCallbacks::IssueCallback callback;
 
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_quoted
-                                                          ("\"Test String\"");
+protected:
+    MasterLoaderCallbacks loader_cb;
+    vector<uint8_t> wiredata_longesttxt;
+    const TXT_LIKE rdata_txt_like;
+    const TXT_LIKE rdata_txt_like_empty;
+    const TXT_LIKE rdata_txt_like_quoted;
+};
 
 // The list of types we want to test.
 typedef testing::Types<generic::TXT, generic::SPF> Implementations;
@@ -75,37 +89,155 @@ typedef testing::Types<generic::TXT, generic::SPF> Implementations;
 TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations);
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
-    // normal case is covered in toWireBuffer.
+    // Below we check the behavior for the "from text" constructors, both
+    // from std::string and with MasterLexer.  The underlying implementation
+    // is the same, so both should work exactly same, but we confirm both
+    // cases.
+
+    const std::string multi_line = "(\n \"Test-String\" )";
+    const std::string escaped_txt = "Test\\045Strin\\g";
+
+    // test input for the lexer version
+    std::stringstream ss;
+    ss << "Test-String\n";
+    ss << "\"Test-String\"\n";   // explicitly surrounded by '"'s
+    ss << multi_line << "\n";   // multi-line text with ()
+    ss << escaped_txt << "\n";   // using the two types of escape with '\'
+    ss << "\"\"\n";              // empty string (note: still valid char-str)
+    ss << string(255, 'a') << "\n"; // Longest possible character-string.
+    ss << string(256, 'a') << "\n"; // char-string too long
+    ss << "\"Test-String\\\"\n";    // unbalanced quote
+    ss << "\"Test-String\\\"\"\n";
+    this->lexer.pushSource(ss);
+
+    // commonly used Rdata to compare below, created from wire
+    ConstRdataPtr const rdata =
+        this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+                                   RRClass("IN"), "rdata_txt_fromWire1");
+
+    // normal case is covered in toWireBuffer.  First check the std::string
+    // case, then with MasterLexer.  For the latter, we need to read and skip
+    // '\n'.  These apply to most of the other cases below.
+    EXPECT_EQ(0, this->rdata_txt_like.compare(*rdata));
+    EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb).compare(*rdata));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // surrounding double-quotes shouldn't change the result.
-    EXPECT_EQ(0, this->rdata_txt_like.compare(this->rdata_txt_like_quoted));
+    EXPECT_EQ(0, this->rdata_txt_like_quoted.compare(*rdata));
+    EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb).compare(*rdata));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+    // multi-line input with ()
+    EXPECT_EQ(0, TypeParam(multi_line).compare(*rdata));
+    EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb).compare(*rdata));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+    // for the same data using escape
+    EXPECT_EQ(0, TypeParam(escaped_txt).compare(*rdata));
+    EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb).compare(*rdata));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // Null character-string.
     this->obuffer.clear();
-    TypeParam(string("")).toWire(this->obuffer);
+    TypeParam(string("\"\"")).toWire(this->obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
+                        this->obuffer.getData(), this->obuffer.getLength(),
                         wiredata_nulltxt, sizeof(wiredata_nulltxt));
+    this->obuffer.clear();
+    TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
+        toWire(this->obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(), this->obuffer.getLength(),
+                        wiredata_nulltxt, sizeof(wiredata_nulltxt));
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // Longest possible character-string.
     this->obuffer.clear();
     TypeParam(string(255, 'a')).toWire(this->obuffer);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        this->obuffer.getData(),
-                        this->obuffer.getLength(),
-                        &wiredata_longesttxt[0], wiredata_longesttxt.size());
+                        this->obuffer.getData(), this->obuffer.getLength(),
+                        &this->wiredata_longesttxt[0],
+                        this->wiredata_longesttxt.size());
+    this->obuffer.clear();
+    TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
+        toWire(this->obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(), this->obuffer.getLength(),
+                        &this->wiredata_longesttxt[0],
+                        this->wiredata_longesttxt.size());
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // Too long text for a valid character-string.
     EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong);
+    EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb), CharStringTooLong);
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
 
     // The escape character makes the double quote a part of character-string,
     // so this is invalid input and should be rejected.
-    EXPECT_THROW(TypeParam("\"Test String\\\""), InvalidRdataText);
+    EXPECT_THROW(TypeParam("\"Test-String\\\""), InvalidRdataText);
+    EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                           this->loader_cb), MasterLexer::LexerError);
+    EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createMultiStringsFromText) {
+    // Tests for "from text" variants construction with various forms of
+    // multi character-strings.
+
+    std::vector<std::string > texts;
+    texts.push_back("\"Test-String\" \"Test-String\""); // most common form
+    texts.push_back("\"Test-String\"\"Test-String\"");  // no space between'em
+    texts.push_back("\"Test-String\" Test-String");  // no '"' for one
+    texts.push_back("\"Test-String\"Test-String"); // and no space either
+    texts.push_back("Test-String \"Test-String\""); // no '"' for the other
+    // This one currently doesn't work
+    //texts.push_back("Test-String\"Test-String\""); // and no space either
+
+    std::stringstream ss;
+    for (std::vector<std::string >::const_iterator it = texts.begin();
+         it != texts.end(); ++it) {
+        ss << *it << "\n";
+    }
+    this->lexer.pushSource(ss);
+
+    // The corresponding Rdata built from wire to compare in the checks below.
+    ConstRdataPtr const rdata =
+        this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+                                   RRClass("IN"), "rdata_txt_fromWire3.wire");
+
+    // Confirm we can construct the Rdata from the test text, both from
+    // std::string and with lexer, and that matches the from-wire data.
+    for (std::vector<std::string >::const_iterator it = texts.begin();
+         it != texts.end(); ++it) {
+        SCOPED_TRACE(*it);
+        EXPECT_EQ(0, TypeParam(*it).compare(*rdata));
+
+        EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+                               this->loader_cb).compare(*rdata));
+        EXPECT_EQ(MasterToken::END_OF_LINE,
+                  this->lexer.getNextToken().getType());
+    }
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromTextExtra) {
+    // This is for the std::string version only: the input must end with EOF;
+    // an extra new-line will result in an exception.
+    EXPECT_THROW(TypeParam("\"Test-String\"\n"), InvalidRdataText);
+    // Same if there's a space before '\n'
+    EXPECT_THROW(TypeParam("\"Test-String\" \n"), InvalidRdataText);
+}
 
-    // Terminating double-quote is provided, so this is valid, but in this
-    // version of implementation we reject escaped characters.
-    EXPECT_THROW(TypeParam("\"Test String\\\"\""), InvalidRdataText);
+TYPED_TEST(Rdata_TXT_LIKE_Test, fromTextEmpty) {
+    // If the input text doesn't contain any character-string, it should be
+    // rejected
+    EXPECT_THROW(TypeParam(""), InvalidRdataText);
+    EXPECT_THROW(TypeParam(" "), InvalidRdataText); // even with a space
+    EXPECT_THROW(TypeParam("(\n)"), InvalidRdataText); // or multi-line with ()
 }
 
 void
@@ -129,13 +261,15 @@ makeLargest(vector<uint8_t>& data) {
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
     EXPECT_EQ(0, this->rdata_txt_like.compare(
-                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
-                                        "rdata_txt_fromWire1")));
+                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+                                              RRClass("IN"),
+                                              "rdata_txt_fromWire1")));
 
     // Empty character string
     EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
-                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
-                                        "rdata_txt_fromWire2.wire")));
+                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+                                              RRClass("IN"),
+                                              "rdata_txt_fromWire2.wire")));
 
     // Multiple character strings
     this->obuffer.clear();
@@ -188,7 +322,7 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
 TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) {
     EXPECT_EQ(0, this->rdata_txt_like.compare(
         *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
-                                     "Test String")));
+                                     "Test-String")));
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
@@ -208,7 +342,7 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
-    EXPECT_EQ("\"Test String\"", this->rdata_txt_like.toText());
+    EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
 }
 
 TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
@@ -238,8 +372,8 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, compare) {
 
     EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0);
 
-    EXPECT_LT(TypeParam("").compare(TypeParam(txt1)), 0);
-    EXPECT_GT(TypeParam(txt1).compare(TypeParam("")), 0);
+    EXPECT_LT(TypeParam("\"\"").compare(TypeParam(txt1)), 0);
+    EXPECT_GT(TypeParam(txt1).compare(TypeParam("\"\"")), 0);
 
     EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0);
     EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0);

+ 3 - 3
src/lib/dns/tests/testdata/rdata_txt_fromWire1

@@ -1,9 +1,9 @@
 #
 # various kinds of TXT RDATA stored in an input buffer
 #
-# Valid RDATA for "Test String"
+# Valid RDATA for "Test-String"
 #
 # RDLENGHT=12 bytes
  00 0c
-#    T  e  s  t     S  t  r  i  n  g
- 0b 54 65 73 74 20 53 74 72 69 6e 67
+#    T  e  s  t  -  S  t  r  i  n  g
+ 0b 54 65 73 74 2d 53 74 72 69 6e 67

+ 9 - 0
src/lib/dns/tests/tsigkey_unittest.cc

@@ -251,6 +251,15 @@ TEST_F(TSIGKeyRingTest, find) {
     const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name);
     EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code);
     EXPECT_EQ(static_cast<const TSIGKey*>(NULL), result3.key);
+
+    // But with just the name it should work
+    const TSIGKeyRing::FindResult result4(keyring.find(key_name));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, result4.code);
+    EXPECT_EQ(key_name, result4.key->getKeyName());
+    EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result4.key->getAlgorithmName());
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
+                        result4.key->getSecret(),
+                        result4.key->getSecretLength());
 }
 
 TEST_F(TSIGKeyRingTest, findFromSome) {

+ 11 - 1
src/lib/dns/tsigkey.cc

@@ -51,7 +51,7 @@ namespace {
         if (name == TSIGKey::HMACSHA512_NAME()) {
             return (isc::cryptolink::SHA512);
         }
- 
+
         return (isc::cryptolink::UNKNOWN_HASH);
     }
 }
@@ -270,6 +270,16 @@ TSIGKeyRing::remove(const Name& key_name) {
 }
 
 TSIGKeyRing::FindResult
+TSIGKeyRing::find(const Name& key_name) const {
+    TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
+        impl_->keys.find(key_name);
+    if (found == impl_->keys.end()) {
+        return (FindResult(NOTFOUND, NULL));
+    }
+    return (FindResult(SUCCESS, &((*found).second)));
+}
+
+TSIGKeyRing::FindResult
 TSIGKeyRing::find(const Name& key_name, const Name& algorithm_name) const {
     TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
         impl_->keys.find(key_name);

+ 22 - 0
src/lib/dns/tsigkey.h

@@ -327,6 +327,27 @@ public:
     /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
     ///
     /// It searches the internal storage for a \c TSIGKey whose name is
+    /// \c key_name.
+    /// It returns the result in the form of a \c FindResult
+    /// object as follows:
+    /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
+    /// - \c key: A pointer to the found \c TSIGKey object if one is found;
+    /// otherwise \c NULL.
+    ///
+    /// The pointer returned in the \c FindResult object is only valid until
+    /// the corresponding key is removed from the key ring.
+    /// The caller must ensure that the key is held in the key ring while
+    /// it needs to refer to it, or it must make a local copy of the key.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param key_name The name of the key to be found.
+    /// \return A \c FindResult object enclosing the search result (see above).
+    FindResult find(const Name& key_name) const;
+
+    /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
+    ///
+    /// It searches the internal storage for a \c TSIGKey whose name is
     /// \c key_name and that uses the hash algorithm identified by
     /// \c algorithm_name.
     /// It returns the result in the form of a \c FindResult
@@ -346,6 +367,7 @@ public:
     /// \param algorithm_name The name of the algorithm of the found key.
     /// \return A \c FindResult object enclosing the search result (see above).
     FindResult find(const Name& key_name, const Name& algorithm_name) const;
+
 private:
     struct TSIGKeyRingImpl;
     TSIGKeyRingImpl* impl_;

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

@@ -31,6 +31,7 @@ libb10_log_la_SOURCES += message_initializer.cc message_initializer.h
 libb10_log_la_SOURCES += message_reader.cc message_reader.h
 libb10_log_la_SOURCES += message_types.h
 libb10_log_la_SOURCES += output_option.cc output_option.h
+libb10_log_la_SOURCES += buffer_appender_impl.cc buffer_appender_impl.h
 
 EXTRA_DIST  = README
 EXTRA_DIST += logimpl_messages.mes

+ 13 - 1
src/lib/log/README

@@ -338,7 +338,8 @@ Variant #1, Used by Production Programs
 ---------------------------------------
 void isc::log::initLogger(const std::string& root,
                           isc::log::Severity severity = isc::log::INFO,
-                          int dbglevel = 0, const char* file = NULL);
+                          int dbglevel = 0, const char* file = NULL,
+                          bool buffer = false);
 
 This is the call that should be used by production programs:
 
@@ -359,6 +360,17 @@ file
 The name of a local message file.  This will be read and its definitions used
 to replace the compiled-in text of the messages.
 
+buffer
+If set to true, initial log messages will be internally buffered, until the
+first time a logger specification is processed. This way the program can
+use logging before even processing its logging configuration. As soon as any
+specification is processed (even an empty one), the buffered log messages will
+be flushed according to the specification. Note that if this option is used,
+the program SHOULD call one of the LoggerManager's process() calls (if you
+are using the built-in logging configuration handling in ModuleCCSession,
+this is automatically handled). If the program exits before this is done,
+all log messages are dumped in a raw format to stdout (so that no messages
+get lost).
 
 Variant #2, Used by Unit Tests
 ------------------------------

+ 96 - 0
src/lib/log/buffer_appender_impl.cc

@@ -0,0 +1,96 @@
+// 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 <log/buffer_appender_impl.h>
+
+#include <log4cplus/loglevel.h>
+#include <boost/scoped_ptr.hpp>
+#include <cstdio>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+BufferAppender::~BufferAppender() {
+    // If there is anything left in the buffer,
+    // it means no reconfig has been done, and
+    // we can assume the logging system was either
+    // never setup, or broke while doing so.
+    // So dump all that is left to stdout
+    try {
+        flushStdout();
+    } catch (...) {
+        // Ok if we can't even seem to dump to stdout, never mind.
+    }
+}
+
+void
+BufferAppender::flushStdout() {
+    // This does not show a bit of information normal log messages
+    // do, so perhaps we should try and setup a new logger here
+    // However, as this is called from a destructor, it may not
+    // be a good idea; as we can't reliably know whether in what
+    // state the logger instance is now (or what the specific logger's
+    // settings were).
+    LogEventList::const_iterator it;
+    for (it = stored_.begin(); it != stored_.end(); ++it) {
+        const std::string level(it->first);
+        LogEventPtr event(it->second);
+        std::printf("%s [%s]: %s\n", level.c_str(),
+                    event->getLoggerName().c_str(),
+                    event->getMessage().c_str());
+    }
+    stored_.clear();
+}
+
+void
+BufferAppender::flush() {
+    LogEventList stored_copy;
+    stored_.swap(stored_copy);
+
+    LogEventList::const_iterator it;
+    for (it = stored_copy.begin(); it != stored_copy.end(); ++it) {
+        LogEventPtr event(it->second);
+        log4cplus::Logger logger =
+            log4cplus::Logger::getInstance(event->getLoggerName());
+
+        logger.log(event->getLogLevel(), event->getMessage());
+    }
+    flushed_ = true;
+}
+
+size_t
+BufferAppender::getBufferSize() const {
+    return (stored_.size());
+}
+
+void
+BufferAppender::append(const log4cplus::spi::InternalLoggingEvent& event) {
+    if (flushed_) {
+        isc_throw(LogBufferAddAfterFlush,
+                  "Internal log buffer has been flushed already");
+    }
+    // get a clone, and put the pointer in a shared_ptr in the list
+    std::auto_ptr<log4cplus::spi::InternalLoggingEvent> event_aptr =
+        event.clone();
+    // Also store the string representation of the log level, to be
+    // used in flushStdout if necessary
+    stored_.push_back(LevelAndEvent(
+                log4cplus::LogLevelManager().toString(event.getLogLevel()),
+                LogEventPtr(event_aptr.release())));
+}
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc

+ 118 - 0
src/lib/log/buffer_appender_impl.h

@@ -0,0 +1,118 @@
+// 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 LOG_BUFFER_H
+#define LOG_BUFFER_H
+
+#include <exceptions/exceptions.h>
+
+#include <log4cplus/logger.h>
+#include <log4cplus/spi/loggingevent.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+/// \brief Buffer add after flush
+///
+/// This exception is thrown if the log buffer's add() method
+/// is called after the log buffer has been flushed; the buffer
+/// is only supposed to be used once (until the first time a
+/// logger specification is processed)
+class LogBufferAddAfterFlush : public isc::Exception {
+public:
+    LogBufferAddAfterFlush(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what)
+    {}
+};
+
+/// Convenience typedef for a pointer to a log event
+typedef boost::shared_ptr<log4cplus::spi::InternalLoggingEvent> LogEventPtr;
+
+/// Convenience typedef for a pair string/logeventptr, the string representing
+/// the logger level, as returned by LogLevelManager::toString() at the
+/// time of initial logging
+typedef std::pair<std::string, LogEventPtr> LevelAndEvent;
+
+/// Convenience typedef for a vector of LevelAndEvent instances
+typedef std::vector<LevelAndEvent> LogEventList;
+
+/// \brief Buffering Logger Appender
+///
+/// This class can be set as an Appender for log4cplus loggers,
+/// and is used to store logging events; it simply keeps any
+/// event that is passed to \c append(), and will replay them to the
+/// logger that they were originally created for when \c flush() is
+/// called.
+///
+/// The idea is that initially, a program may want to do some logging,
+/// but does not know where to yet (for instance because it has yet to
+/// read and parse its configuration). Any log messages before this time
+/// would normally go to some default (say, stdout), and be lost in the
+/// real logging destination. By buffering them (and flushing them once
+/// the logger has been configured), these log messages are kept in a
+/// consistent place, and are not lost.
+///
+/// Given this goal, this class has an extra check; it will raise
+/// an exception if \c append() is called after flush().
+///
+/// If the BufferAppender instance is destroyed before being flushed,
+/// it will dump any event it has left to stdout.
+class BufferAppender : public log4cplus::Appender {
+public:
+    /// \brief Constructor
+    ///
+    /// Constructs a BufferAppender that buffers log evens
+    BufferAppender() : flushed_(false) {}
+
+    /// \brief Destructor
+    ///
+    /// Any remaining events are flushed to stdout (there should
+    /// only be any events remaining if flush() was never called)
+    virtual ~BufferAppender();
+
+    /// \brief Close the appender
+    ///
+    /// This class has no specialized handling for this method
+    virtual void close() {}
+
+    /// \brief Flush the internal buffer
+    ///
+    /// Events that have been stored (after calls to \c append()
+    /// are replayed to the logger. Should only be called after
+    /// new appenders have been set to the logger.
+    void flush();
+
+    /// \brief Returns the number of stored logging events
+    ///
+    /// Mainly useful for testing
+    size_t getBufferSize() const;
+
+protected:
+    virtual void append(const log4cplus::spi::InternalLoggingEvent& event);
+private:
+    /// \brief Helper for the destructor, flush events to stdout
+    void flushStdout();
+
+    LogEventList stored_;
+    bool flushed_;
+};
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc
+
+#endif // LOG_BUFFER_H
+

+ 4 - 2
src/lib/log/logger_manager.cc

@@ -94,7 +94,7 @@ LoggerManager::processEnd() {
 
 void
 LoggerManager::init(const std::string& root, isc::log::Severity severity,
-                    int dbglevel, const char* file)
+                    int dbglevel, const char* file, bool buffer)
 {
     // Load in the messages declared in the program and registered by
     // statically-declared MessageInitializer objects.
@@ -114,7 +114,9 @@ LoggerManager::init(const std::string& root, isc::log::Severity severity,
 
     // Initialize the implementation logging.  After this point, some basic
     // logging has been set up and messages can be logged.
-    LoggerManagerImpl::init(severity, dbglevel);
+    // However, they will not appear until a logging specification has been
+    // processed (or the program exits), see TODO
+    LoggerManagerImpl::init(severity, dbglevel, buffer);
     setLoggingInitialized();
 
     // Check if there were any duplicate message IDs in the default dictionary

+ 22 - 2
src/lib/log/logger_manager.h

@@ -76,6 +76,21 @@ public:
         processEnd();
     }
 
+    /// \brief Process 'empty' specification
+    ///
+    /// This will disable any existing output options, and set
+    /// the logging to go to the built-in default (stdout).
+    /// If the logger has been initialized with buffering enabled,
+    /// all log messages up to now shall be printed to stdout.
+    ///
+    /// This is mainly useful in scenarios where buffering is needed,
+    /// but it turns out there are no logging specifications to
+    /// handle.
+    void process() {
+        processInit();
+        processEnd();
+    }
+
     /// \brief Run-Time Initialization
     ///
     /// Performs run-time initialization of the logger system, in particular
@@ -91,13 +106,18 @@ public:
     /// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
     /// \param file Name of the local message file.  This must be NULL if there
     ///        is no local message file.
+    /// \param buffer If true, all log messages will be buffered until one of
+    ///        the \c process() methods is called. If false, initial logging
+    ///        shall go to the default output (i.e. stdout)
     static void init(const std::string& root,
                     isc::log::Severity severity = isc::log::INFO,
-                    int dbglevel = 0, const char* file = NULL);
+                    int dbglevel = 0, const char* file = NULL,
+                    bool buffer = false);
 
     /// \brief Reset logging
     ///
-    /// Resets logging to whatever was set in the call to init().
+    /// Resets logging to whatever was set in the call to init(), expect for
+    /// the buffer option.
     static void reset();
 
     /// \brief Read local message file

+ 61 - 19
src/lib/log/logger_manager_impl.cc

@@ -23,12 +23,14 @@
 #include <log4cplus/syslogappender.h>
 
 #include <log/logger.h>
+#include <log/logger_support.h>
 #include <log/logger_level_impl.h>
 #include <log/logger_manager.h>
 #include <log/logger_manager_impl.h>
 #include <log/log_messages.h>
 #include <log/logger_name.h>
 #include <log/logger_specification.h>
+#include <log/buffer_appender_impl.h>
 
 using namespace std;
 
@@ -40,19 +42,24 @@ namespace log {
 // passed back to the parent) and resets the root logger to logging
 // informational messages.  (This last is not a log4cplus default, so we have to
 // explicitly reset the logging severity.)
-
 void
 LoggerManagerImpl::processInit() {
+    storeBufferAppenders();
+
     log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
     initRootLogger();
 }
 
+// Flush the BufferAppenders at the end of processing a new specification
+void
+LoggerManagerImpl::processEnd() {
+    flushBufferAppenders();
+}
+
 // Process logging specification.  Set up the common states then dispatch to
 // add output specifications.
-
 void
 LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
-
     log4cplus::Logger logger = log4cplus::Logger::getInstance(
                                    expandLoggerName(spec.getName()));
 
@@ -65,8 +72,7 @@ LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
 
     // Output options given?
     if (spec.optionCount() > 0) {
-
-        // Yes, so replace all appenders for this logger.
+        // Replace all appenders for this logger.
         logger.removeAllAppenders();
 
         // Now process output specifications.
@@ -134,7 +140,17 @@ LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
     logger.addAppender(fileapp);
 }
 
-// Syslog appender. 
+void
+LoggerManagerImpl::createBufferAppender(log4cplus::Logger& logger) {
+    log4cplus::SharedAppenderPtr bufferapp(new internal::BufferAppender());
+    bufferapp->setName("buffer");
+    logger.addAppender(bufferapp);
+    // Since we do not know at what level the loggers will end up
+    // running, set it to the highest for now
+    logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+}
+
+// Syslog appender.
 void
 LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
                                          const OutputOption& opt)
@@ -147,10 +163,10 @@ LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
 
 
 // One-time initialization of the log4cplus system
-
 void
-LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel) {
-
+LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel,
+                        bool buffer)
+{
     // Set up basic configurator.  This attaches a ConsoleAppender to the
     // root logger with suitable output.  This is used until we we have
     // actually read the logging configuration, in which case the output
@@ -161,22 +177,22 @@ LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel) {
     // Add the additional debug levels
     LoggerLevelImpl::init();
 
-    reset(severity, dbglevel);
+    initRootLogger(severity, dbglevel, buffer);
 }
 
 // Reset logging to default configuration.  This closes all appenders
 // and resets the root logger to output INFO messages to the console.
 // It is principally used in testing.
 void
-LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel) {
-
+LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel)
+{
     // Initialize the root logger
     initRootLogger(severity, dbglevel);
 }
 
 // Initialize the root logger
 void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
-                                       int dbglevel)
+                                       int dbglevel, bool buffer)
 {
     log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
 
@@ -191,14 +207,14 @@ void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
     b10root.setLogLevel(LoggerLevelImpl::convertFromBindLevel(
                                                     Level(severity, dbglevel)));
 
-    // Set the BIND 10 root to use a console logger.
-    OutputOption opt;
-    createConsoleAppender(b10root, opt);
+    if (buffer) {
+        createBufferAppender(b10root);
+    } else {
+        OutputOption opt;
+        createConsoleAppender(b10root, opt);
+    }
 }
 
-// Set the the "console" layout for the given appenders.  This layout includes
-// a date/time and the name of the logger.
-
 void LoggerManagerImpl::setConsoleAppenderLayout(
         log4cplus::SharedAppenderPtr& appender)
 {
@@ -225,5 +241,31 @@ void LoggerManagerImpl::setSyslogAppenderLayout(
     appender->setLayout(layout);
 }
 
+void LoggerManagerImpl::storeBufferAppenders() {
+    // Walk through all loggers, and find any buffer appenders there
+    log4cplus::LoggerList loggers = log4cplus::Logger::getCurrentLoggers();
+    log4cplus::LoggerList::iterator it;
+    for (it = loggers.begin(); it != loggers.end(); ++it) {
+        log4cplus::SharedAppenderPtr buffer_appender =
+            it->getAppender("buffer");
+        if (buffer_appender) {
+            buffer_appender_store_.push_back(buffer_appender);
+        }
+    }
+}
+
+void LoggerManagerImpl::flushBufferAppenders() {
+    std::vector<log4cplus::SharedAppenderPtr> copy;
+    buffer_appender_store_.swap(copy);
+
+    std::vector<log4cplus::SharedAppenderPtr>::iterator it;
+    for (it = copy.begin(); it != copy.end(); ++it) {
+        internal::BufferAppender* app =
+            dynamic_cast<internal::BufferAppender*>(it->get());
+        assert(app != NULL);
+        app->flush();
+    }
+}
+
 } // namespace log
 } // namespace isc

+ 41 - 9
src/lib/log/logger_manager_impl.h

@@ -51,15 +51,14 @@ class LoggerManagerImpl {
 public:
 
     /// \brief Constructor
-    LoggerManagerImpl()
-    {}
+    LoggerManagerImpl() {}
 
     /// \brief Initialize Processing
     ///
     /// This resets the hierachy of loggers back to their defaults.  This means
     /// that all non-root loggers (if they exist) are set to NOT_SET, and the
     /// root logger reset to logging informational messages.
-    static void processInit();
+    void processInit();
 
     /// \brief Process Specification
     ///
@@ -71,8 +70,7 @@ public:
     /// \brief End Processing
     ///
     /// Terminates the processing of the logging specifications.
-    static void processEnd()
-    {}
+    void processEnd();
 
     /// \brief Implementation-specific initialization
     ///
@@ -87,8 +85,11 @@ public:
     ///
     /// \param severity Severity to be associated with this logger
     /// \param dbglevel Debug level associated with the root logger
+    /// \param buffer If true, all log messages will be buffered until one of
+    ///        the \c process() methods is called. If false, initial logging
+    ///        shall go to the default output (i.e. stdout)
     static void init(isc::log::Severity severity = isc::log::INFO,
-                     int dbglevel = 0);
+                     int dbglevel = 0, bool buffer = false);
 
     /// \brief Reset logging
     ///
@@ -132,15 +133,27 @@ private:
     static void createSyslogAppender(log4cplus::Logger& logger,
                                      const OutputOption& opt);
 
+    /// \brief Create buffered appender
+    ///
+    /// Appends an object to the logger that will store the log events sent
+    /// to the logger. These log messages are replayed to the logger in
+    /// processEnd().
+    ///
+    /// \param logger Log4cplus logger to which the appender must be attached.
+    static void createBufferAppender(log4cplus::Logger& logger);
+
     /// \brief Set default layout and severity for root logger
     ///
-    /// Initializes the root logger to BIND 10 defaults - console output and
-    /// the passed severity/debug level.
+    /// Initializes the root logger to BIND 10 defaults - console or buffered
+    /// output and the passed severity/debug level.
     ///
     /// \param severity Severity of messages that the logger should output.
     /// \param dbglevel Debug level if severity = DEBUG
+    /// \param buffer If true, all log messages will be buffered until one of
+    ///        the \c process() methods is called. If false, initial logging
+    ///        shall go to the default output (i.e. stdout)
     static void initRootLogger(isc::log::Severity severity = isc::log::INFO,
-                               int dbglevel = 0);
+                               int dbglevel = 0, bool buffer = false);
 
     /// \brief Set layout for console appender
     ///
@@ -161,6 +174,25 @@ private:
     ///
     /// \param appender Appender for which this pattern is to be set.
     static void setSyslogAppenderLayout(log4cplus::SharedAppenderPtr& appender);
+
+    /// \brief Store all buffer appenders
+    ///
+    /// When processing a new specification, this method can be used
+    /// to keep a list of the buffer appenders; the caller can then
+    /// process the specification, and call \c flushBufferAppenders()
+    /// to flush and clear the list
+    void storeBufferAppenders();
+
+    /// \brief Flush the stored buffer appenders
+    ///
+    /// This flushes the list of buffer appenders stored in
+    /// \c storeBufferAppenders(), and clears it
+    void flushBufferAppenders();
+
+    /// Only used between processInit() and processEnd(), to temporarily
+    /// store the buffer appenders in order to flush them after
+    /// processSpecification() calls have been completed
+    std::vector<log4cplus::SharedAppenderPtr> buffer_appender_store_;
 };
 
 } // namespace log

+ 2 - 2
src/lib/log/logger_support.cc

@@ -46,8 +46,8 @@ setLoggingInitialized(bool state) {
 
 void
 initLogger(const string& root, isc::log::Severity severity, int dbglevel,
-    const char* file) {
-    LoggerManager::init(root, severity, dbglevel, file);
+           const char* file, bool buffer) {
+    LoggerManager::init(root, severity, dbglevel, file, buffer);
 }
 
 } // namespace log

+ 5 - 1
src/lib/log/logger_support.h

@@ -61,9 +61,13 @@ void setLoggingInitialized(bool state = true);
 /// \param severity Severity at which to log
 /// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
 /// \param file Name of the local message file.
+/// \param buffer If true, all log messages will be buffered until one of
+///        the \c process() methods is called. If false, initial logging
+///        shall go to the default output (i.e. stdout)
 void initLogger(const std::string& root,
                 isc::log::Severity severity = isc::log::INFO,
-                int dbglevel = 0, const char* file = NULL);
+                int dbglevel = 0, const char* file = NULL,
+                bool buffer = false);
 
 } // namespace log
 } // namespace isc

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

@@ -37,6 +37,15 @@ init_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
 
+noinst_PROGRAMS += buffer_logger_test
+buffer_logger_test_SOURCES = buffer_logger_test.cc
+buffer_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+buffer_logger_test_LDFLAGS = $(AM_LDFLAGS)
+buffer_logger_test_LDADD  = $(top_builddir)/src/lib/log/libb10-log.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+buffer_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+
 noinst_PROGRAMS += logger_lock_test
 logger_lock_test_SOURCES = logger_lock_test.cc
 nodist_logger_lock_test_SOURCES = log_test_messages.cc log_test_messages.h
@@ -82,11 +91,13 @@ run_unittests_SOURCES += logger_specification_unittest.cc
 run_unittests_SOURCES += message_dictionary_unittest.cc
 run_unittests_SOURCES += message_reader_unittest.cc
 run_unittests_SOURCES += output_option_unittest.cc
+run_unittests_SOURCES += buffer_appender_unittest.cc
 nodist_run_unittests_SOURCES = log_test_messages.cc log_test_messages.h
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
 run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
 run_unittests_LDADD    = $(AM_LDADD)
+run_unittests_LDADD    +=  $(LOG4CPLUS_LIBS)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)
 
 # logging initialization tests.  These are put in separate programs to
@@ -124,6 +135,7 @@ check-local:
 	$(SHELL) $(abs_builddir)/console_test.sh
 	$(SHELL) $(abs_builddir)/destination_test.sh
 	$(SHELL) $(abs_builddir)/init_logger_test.sh
+	$(SHELL) $(abs_builddir)/buffer_logger_test.sh
 	$(SHELL) $(abs_builddir)/local_file_test.sh
 	$(SHELL) $(abs_builddir)/logger_lock_test.sh
 	$(SHELL) $(abs_builddir)/severity_test.sh
@@ -131,6 +143,7 @@ check-local:
 noinst_SCRIPTS  = console_test.sh
 noinst_SCRIPTS += destination_test.sh
 noinst_SCRIPTS += init_logger_test.sh
+noinst_SCRIPTS += buffer_logger_test.sh
 noinst_SCRIPTS += local_file_test.sh
 noinst_SCRIPTS += logger_lock_test.sh
 noinst_SCRIPTS += severity_test.sh

+ 146 - 0
src/lib/log/tests/buffer_appender_unittest.cc

@@ -0,0 +1,146 @@
+// 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 <gtest/gtest.h>
+
+#include <log/macros.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include <log/buffer_appender_impl.h>
+
+#include <log4cplus/loggingmacros.h>
+#include <log4cplus/logger.h>
+#include <log4cplus/nullappender.h>
+#include <log4cplus/spi/loggingevent.h>
+
+using namespace isc::log;
+using namespace isc::log::internal;
+
+namespace isc {
+namespace log {
+
+/// \brief Specialized test class
+///
+/// In order to test append() somewhat directly, this
+/// class implements one more method (addEvent)
+class TestBufferAppender : public BufferAppender {
+public:
+    void addEvent(const log4cplus::spi::InternalLoggingEvent& event) {
+        append(event);
+    }
+};
+
+class BufferAppenderTest : public ::testing::Test {
+protected:
+    BufferAppenderTest() : buffer_appender1(new TestBufferAppender()),
+                      appender1(buffer_appender1),
+                      buffer_appender2(new TestBufferAppender()),
+                      appender2(buffer_appender2),
+                      logger(log4cplus::Logger::getInstance("buffer"))
+    {
+        logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+    }
+
+    ~BufferAppenderTest() {
+        // If any log messages are left, we don't care, get rid of them,
+        // by flushing them to a null appender
+        // Given the 'messages should not get lost' approach of the logging
+        // system, not flushing them to a null appender would cause them
+        // to be dumped to stdout as the test is destroyed, making
+        // unnecessarily messy test output.
+        log4cplus::SharedAppenderPtr null_appender(
+            new log4cplus::NullAppender());
+        logger.removeAllAppenders();
+        logger.addAppender(null_appender);
+        buffer_appender1->flush();
+        buffer_appender2->flush();
+    }
+
+    TestBufferAppender* buffer_appender1;
+    log4cplus::SharedAppenderPtr appender1;
+    TestBufferAppender* buffer_appender2;
+    log4cplus::SharedAppenderPtr appender2;
+    log4cplus::Logger logger;
+};
+
+// Test that log events are indeed stored, and that they are
+// flushed to the new appenders of their logger
+TEST_F(BufferAppenderTest, flush) {
+    ASSERT_EQ(0, buffer_appender1->getBufferSize());
+    ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+    // Create a Logger, log a few messages with the first appender
+    logger.addAppender(appender1);
+    LOG4CPLUS_INFO(logger, "Foo");
+    ASSERT_EQ(1, buffer_appender1->getBufferSize());
+    LOG4CPLUS_INFO(logger, "Foo");
+    ASSERT_EQ(2, buffer_appender1->getBufferSize());
+    LOG4CPLUS_INFO(logger, "Foo");
+    ASSERT_EQ(3, buffer_appender1->getBufferSize());
+
+    // Second buffer should still be empty
+    ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+    // Replace the appender by the second one, and call flush;
+    // this should cause all events to be moved to the second buffer
+    logger.removeAllAppenders();
+    logger.addAppender(appender2);
+    buffer_appender1->flush();
+    ASSERT_EQ(0, buffer_appender1->getBufferSize());
+    ASSERT_EQ(3, buffer_appender2->getBufferSize());
+}
+
+// Once flushed, logging new messages with the same buffer should fail
+TEST_F(BufferAppenderTest, addAfterFlush) {
+    logger.addAppender(appender1);
+    buffer_appender1->flush();
+    EXPECT_THROW(LOG4CPLUS_INFO(logger, "Foo"), LogBufferAddAfterFlush);
+    // It should not have been added
+    ASSERT_EQ(0, buffer_appender1->getBufferSize());
+
+    // But logging should work again as long as a different buffer is used
+    logger.removeAllAppenders();
+    logger.addAppender(appender2);
+    LOG4CPLUS_INFO(logger, "Foo");
+    ASSERT_EQ(1, buffer_appender2->getBufferSize());
+}
+
+TEST_F(BufferAppenderTest, addDirectly) {
+    // A few direct calls
+    log4cplus::spi::InternalLoggingEvent event("buffer",
+                                               log4cplus::INFO_LOG_LEVEL,
+                                               "Bar", "file", 123);
+    buffer_appender1->addEvent(event);
+    ASSERT_EQ(1, buffer_appender1->getBufferSize());
+
+    // Do one from a smaller scope to make sure destruction doesn't harm
+    {
+        log4cplus::spi::InternalLoggingEvent event2("buffer",
+                                                    log4cplus::INFO_LOG_LEVEL,
+                                                    "Bar", "file", 123);
+        buffer_appender1->addEvent(event2);
+    }
+    ASSERT_EQ(2, buffer_appender1->getBufferSize());
+
+    // And flush them to the next
+    logger.removeAllAppenders();
+    logger.addAppender(appender2);
+    buffer_appender1->flush();
+    ASSERT_EQ(0, buffer_appender1->getBufferSize());
+    ASSERT_EQ(2, buffer_appender2->getBufferSize());
+}
+
+}
+}

+ 71 - 0
src/lib/log/tests/buffer_logger_test.cc

@@ -0,0 +1,71 @@
+// 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 <log/macros.h>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+#include <log/log_messages.h>
+#include <util/interprocess_sync_null.h>
+
+using namespace isc::log;
+
+namespace {
+void usage() {
+    std::cout << "Usage: buffer_logger_test [-n]" << std::endl;
+}
+} // end unnamed namespace
+
+/// \brief Test InitLogger
+///
+/// A program used in testing the logger that initializes logging with
+/// buffering enabled, so that initial log messages are not immediately
+/// logged, but are not lost (they should be logged the moment process()
+/// is called.
+///
+/// If -n is given as an argument, process() is never called. In this
+/// case, upon exit, all leftover log messages should be printed to
+/// stdout, but without normal logging additions (such as time and
+/// logger name)
+int
+main(int argc, char** argv) {
+    bool do_process = true;
+    int opt;
+    while ((opt = getopt(argc, argv, "n")) != -1) {
+        switch (opt) {
+        case 'n':
+            do_process = false;
+            break;
+        default:
+            usage();
+            return (1);
+        }
+    }
+
+    // Note, level is INFO, so DEBUG should normally not show
+    // up. Unless process is never called (at which point it
+    // will end up in the dump at the end).
+    initLogger("buffertest", isc::log::INFO, 0, NULL, true);
+    Logger logger("log");
+    // No need for file interprocess locking in this test
+    logger.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+    LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+    LOG_DEBUG(logger, 50, LOG_BAD_DESTINATION).arg("debug-50");
+    LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+    // process should cause them to be logged
+    if (do_process) {
+        LoggerManager logger_manager;
+        logger_manager.process();
+    }
+    return (0);
+}

+ 61 - 0
src/lib/log/tests/buffer_logger_test.sh.in

@@ -0,0 +1,61 @@
+#!/bin/sh
+# 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.
+
+# Checks that the initLogger() call uses for unit tests respects the setting of
+# the buffer value
+#
+
+testname="bufferLogger test"
+echo $testname
+
+failcount=0
+tempfile=@abs_builddir@/buffer_logger_test_tempfile_$$
+
+passfail() {
+    if [ $1 -eq 0 ]; then
+        echo " pass"
+    else
+        echo " FAIL"
+        failcount=`expr $failcount + $1`
+    fi
+}
+
+echo "1. Checking that buffer initialization works"
+
+echo -n  "   - Buffer including process() call: "
+cat > $tempfile << .
+INFO  [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+INFO  [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test 2>&1 | \
+    sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+    cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n  "   - Buffer excluding process() call: "
+cat > $tempfile << .
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+DEBUG [buffertest.log]: LOG_BAD_DESTINATION unrecognized log destination: debug-50
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test -n 2>&1 | diff $tempfile -
+passfail $?
+
+
+
+# Tidy up.
+rm -f $tempfile
+
+exit $failcount

+ 7 - 2
src/lib/python/isc/config/cfgmgr.py

@@ -34,7 +34,7 @@ import bind10_config
 import isc.log
 from isc.log_messages.cfgmgr_messages import *
 
-logger = isc.log.Logger("cfgmgr")
+logger = isc.log.Logger("cfgmgr", buffer=True)
 
 class ConfigManagerDataReadError(Exception):
     """This exception is thrown when there is an error while reading
@@ -224,8 +224,13 @@ class ConfigManager:
 
     def check_logging_config(self, config):
         if self.log_module_name in config:
+            # If there is logging config, apply it.
             ccsession.default_logconfig_handler(config[self.log_module_name],
                                                 self.log_config_data)
+        else:
+            # If there is no logging config, we still need to trigger the
+            # handler, so make it use defaults (and flush any buffered logs)
+            ccsession.default_logconfig_handler({}, self.log_config_data)
 
     def notify_boss(self):
         """Notifies the Boss module that the Config Manager is running"""
@@ -313,11 +318,11 @@ class ConfigManager:
             self.config = ConfigManagerData.read_from_file(self.data_path,
                                                            self.\
                                                            database_filename)
-            self.check_logging_config(self.config.data);
         except ConfigManagerDataEmpty:
             # ok, just start with an empty config
             self.config = ConfigManagerData(self.data_path,
                                             self.database_filename)
+        self.check_logging_config(self.config.data);
 
     def write_config(self):
         """Write the current configuration to the file specificied at init()"""

+ 21 - 8
src/lib/python/isc/log/log.cc

@@ -166,17 +166,23 @@ reset(PyObject*, PyObject*) {
 }
 
 PyObject*
-init(PyObject*, PyObject* args) {
+init(PyObject*, PyObject* args, PyObject* arg_keywords) {
     const char* root;
     const char* file(NULL);
     const char* severity("INFO");
+    bool buffer = false;
     int dbglevel(0);
-    if (!PyArg_ParseTuple(args, "s|siz", &root, &severity, &dbglevel, &file)) {
+    const char* const keywords[] = { "name", "severity", "debuglevel", "file",
+                                     "buffer", NULL };
+    if (!PyArg_ParseTupleAndKeywords(args, arg_keywords, "s|sizb",
+                                     const_cast<char**>(keywords), &root,
+                                     &severity, &dbglevel, &file, &buffer)) {
         return (NULL);
     }
 
     try {
-        LoggerManager::init(root, getSeverity(severity), dbglevel, file);
+        LoggerManager::init(root, getSeverity(severity), dbglevel, file,
+                            buffer);
     }
     catch (const std::exception& e) {
         PyErr_SetString(PyExc_RuntimeError, e.what());
@@ -266,12 +272,19 @@ PyMethodDef methods[] = {
         "need to call it. It returns None if the message does not exist."},
     {"reset", reset, METH_NOARGS,
         "Reset all logging. For testing purposes only, do not use."},
-    {"init", init, METH_VARARGS,
+    {"init", reinterpret_cast<PyCFunction>(init), METH_VARARGS | METH_KEYWORDS,
         "Run-time initialization. You need to call this before you do any "
         "logging, to configure the root logger name. You may also provide "
-        "logging severity (one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
-        "'FATAL'), a debug level (integer in the range 0-99) and a file name "
-        "of a dictionary with message text translations."},
+        "Arguments:\n"
+        "name: root logger name\n"
+        "severity (optional): one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
+        "'FATAL'\n"
+        "debuglevel (optional): a debug level (integer in the range 0-99) "
+        "file (optional): a file name of a dictionary with message text "
+        "translations\n"
+        "buffer (optional), boolean, when True, causes all log messages "
+        "to be stored internally until log_config_update is called, at "
+        "which point they shall be logged."},
     {"resetUnitTestRootLogger", resetUnitTestRootLogger, METH_VARARGS,
         "Resets the configuration of the root logger to that set by the "
         "B10_XXX environment variables.  It is aimed at unit tests, where "
@@ -655,7 +668,7 @@ PyTypeObject logger_type = {
     NULL,                               // tp_as_number
     NULL,                               // tp_as_sequence
     NULL,                               // tp_as_mapping
-    NULL,                               // tp_hash 
+    NULL,                               // tp_hash
     NULL,                               // tp_call
     NULL,                               // tp_str
     NULL,                               // tp_getattro

+ 22 - 0
src/lib/python/isc/log/tests/log_test.py

@@ -56,6 +56,28 @@ class Manager(unittest.TestCase):
         # ignore errors like missing file?
         isc.log.init("root", "INFO", 0, "/no/such/file");
 
+    def test_init_keywords(self):
+        isc.log.init(name="root", severity="DEBUG", debuglevel=50, file=None,
+                     buffer=True)
+        # unknown keyword
+        self.assertRaises(TypeError, isc.log.init, name="root", foo="bar")
+        # Replace the values for each keyword by a wrong type, one by one
+        self.assertRaises(TypeError, isc.log.init, name=1,
+                          severity="DEBUG", debuglevel=50, file=None,
+                          buffer=True)
+        self.assertRaises(TypeError, isc.log.init, name="root",
+                          severity=2, debuglevel=50, file=None,
+                          buffer=True)
+        self.assertRaises(TypeError, isc.log.init, name="root",
+                          severity="DEBUG", debuglevel="50", file=None,
+                          buffer=True)
+        self.assertRaises(TypeError, isc.log.init, name="root",
+                          severity="DEBUG", debuglevel=50, file=1,
+                          buffer=True)
+        self.assertRaises(TypeError, isc.log.init, name="root",
+                          severity="DEBUG", debuglevel=50, file=None,
+                          buffer=None)
+
     def test_log_config_update(self):
         log_spec = json.dumps(isc.config.module_spec_from_file(path_search('logging.spec', bind10_config.PLUGIN_PATHS)).get_full_spec())
 

+ 0 - 0
src/lib/util/python/gen_wiredata.py.in


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