Browse Source

sync with trunk

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac268@2673 e5f2f494-b856-4b98-b285-d166d9295462
JINMEI Tatuya 14 years ago
parent
commit
91ab09d220
100 changed files with 3975 additions and 1181 deletions
  1. 39 0
      ChangeLog
  2. 2 2
      Makefile.am
  3. 3 0
      configure.ac
  4. 1 1
      doc/Doxyfile
  5. 5 0
      ext/asio/README
  6. 2 1
      src/bin/auth/Makefile.am
  7. 302 204
      src/bin/auth/asio_link.cc
  8. 412 2
      src/bin/auth/asio_link.h
  9. 1 1
      src/bin/auth/auth.spec.pre.in
  10. 221 32
      src/bin/auth/auth_srv.cc
  11. 37 8
      src/bin/auth/auth_srv.h
  12. 53 12
      src/bin/auth/main.cc
  13. 3 0
      src/bin/auth/tests/Makefile.am
  14. 357 0
      src/bin/auth/tests/asio_link_unittest.cc
  15. 495 39
      src/bin/auth/tests/auth_srv_unittest.cc
  16. 12 2
      src/bin/bindctl/bindcmd.py
  17. 5 0
      src/bin/bindctl/tests/bindctl_test.py
  18. 2 1
      src/bin/cmdctl/TODO
  19. 17 10
      src/bin/cmdctl/cmdctl.py.in
  20. 7 7
      src/bin/cmdctl/cmdctl.spec.pre.in
  21. 19 0
      src/bin/cmdctl/tests/cmdctl_test.py
  22. 19 3
      src/bin/xfrin/tests/xfrin_test.py
  23. 42 7
      src/bin/xfrin/xfrin.py.in
  24. 9 9
      src/bin/xfrin/xfrin.spec.pre.in
  25. 7 7
      src/bin/xfrout/xfrout.spec.pre.in
  26. 1 1
      src/lib/Makefile.am
  27. 10 0
      src/lib/bench/Makefile.am
  28. 403 0
      src/lib/bench/benchmark.h
  29. 116 0
      src/lib/bench/benchmark_util.cc
  30. 149 0
      src/lib/bench/benchmark_util.h
  31. 9 0
      src/lib/bench/example/Makefile.am
  32. 144 0
      src/lib/bench/example/search_bench.cc
  33. 24 0
      src/lib/bench/tests/Makefile.am
  34. 143 0
      src/lib/bench/tests/benchmark_unittest.cc
  35. 198 0
      src/lib/bench/tests/loadquery_unittest.cc
  36. 24 0
      src/lib/bench/tests/run_unittests.cc
  37. 6 0
      src/lib/bench/tests/testdata/query.txt
  38. 22 1
      src/lib/cc/session.h
  39. 1 0
      src/lib/config/Makefile.am
  40. 1 1
      src/lib/config/testdata/b10-config.db
  41. 2 2
      src/lib/config/testdata/data22_1.data
  42. 2 2
      src/lib/config/testdata/data22_2.data
  43. 3 3
      src/lib/config/testdata/data22_3.data
  44. 2 2
      src/lib/config/testdata/data22_4.data
  45. 1 1
      src/lib/config/testdata/data22_5.data
  46. 3 3
      src/lib/config/testdata/data22_6.data
  47. 2 2
      src/lib/config/testdata/data22_7.data
  48. 2 2
      src/lib/config/testdata/data22_8.data
  49. 1 1
      src/lib/config/testdata/spec10.spec
  50. 1 1
      src/lib/config/testdata/spec11.spec
  51. 1 1
      src/lib/config/testdata/spec12.spec
  52. 1 1
      src/lib/config/testdata/spec13.spec
  53. 1 1
      src/lib/config/testdata/spec14.spec
  54. 1 1
      src/lib/config/testdata/spec15.spec
  55. 1 1
      src/lib/config/testdata/spec17.spec
  56. 11 11
      src/lib/config/testdata/spec2.spec
  57. 1 1
      src/lib/config/testdata/spec20.spec
  58. 21 21
      src/lib/config/testdata/spec22.spec
  59. 1 1
      src/lib/config/testdata/spec23.spec
  60. 2 2
      src/lib/config/testdata/spec24.spec
  61. 23 23
      src/lib/config/testdata/spec27.spec
  62. 7 0
      src/lib/config/testdata/spec28.spec
  63. 1 1
      src/lib/config/testdata/spec3.spec
  64. 1 1
      src/lib/config/testdata/spec4.spec
  65. 1 1
      src/lib/config/testdata/spec6.spec
  66. 1 1
      src/lib/config/testdata/spec9.spec
  67. 3 3
      src/lib/datasrc/data_source.cc
  68. 1 1
      src/lib/datasrc/query.h
  69. 3 0
      src/lib/datasrc/static_datasrc.cc
  70. 3 0
      src/lib/datasrc/tests/static_unittest.cc
  71. 7 8
      src/lib/dns/Makefile.am
  72. 0 198
      src/lib/dns/base32.cc
  73. 0 54
      src/lib/dns/base32.h
  74. 0 189
      src/lib/dns/base64.cc
  75. 0 108
      src/lib/dns/hex.cc
  76. 1 2
      src/lib/dns/python/message_python.cc
  77. 1 2
      src/lib/dns/python/rrclass_python.cc
  78. 1 2
      src/lib/dns/python/rrttl_python.cc
  79. 1 2
      src/lib/dns/python/rrtype_python.cc
  80. 1 1
      src/lib/dns/rdata/generic/dnskey_48.cc
  81. 1 1
      src/lib/dns/rdata/generic/ds_43.cc
  82. 4 4
      src/lib/dns/rdata/generic/nsec3_50.cc
  83. 1 1
      src/lib/dns/rdata/generic/nsec3param_51.cc
  84. 1 1
      src/lib/dns/rdata/generic/nsec_47.cc
  85. 1 1
      src/lib/dns/rdata/generic/rrsig_46.cc
  86. 1 1
      src/lib/dns/tests/Makefile.am
  87. 0 107
      src/lib/dns/tests/base32_unittest.cc
  88. 161 0
      src/lib/dns/tests/base32hex_unittest.cc
  89. 9 6
      src/lib/dns/tests/base64_unittest.cc
  90. 46 12
      src/lib/dns/tests/hex_unittest.cc
  91. 4 3
      src/lib/dns/tests/rdata_dnskey_unittest.cc
  92. 19 12
      src/lib/dns/tests/rdata_nsec3_unittest.cc
  93. 6 3
      src/lib/dns/tests/rdata_nsec3param_unittest.cc
  94. 6 3
      src/lib/dns/tests/rdata_rrsig_unittest.cc
  95. 1 1
      src/lib/dns/tests/sha1_unittest.cc
  96. 31 0
      src/lib/dns/util/README
  97. 108 0
      src/lib/dns/util/base16_from_binary.h
  98. 26 17
      src/lib/dns/base64.h
  99. 110 0
      src/lib/dns/util/base32hex_from_binary.h
  100. 0 0
      src/lib/dns/util/base64.h

+ 39 - 0
ChangeLog

@@ -1,3 +1,42 @@
+  81.	[func]		jinmei
+	Added a C++ framework for micro benchmark tests.  A supplemental
+	library functions to build query data for the tests were also
+	provided. (Trac #241, svn r2664)
+
+  80.	[bug]		jelte
+	bindctl no longer accepts configuration changes for unknown or
+	non-running modules (for the latter, this is until we have a
+	way to verify those options, at which point it'll be allowed
+	again).
+	(Trac #99, r2657)
+
+  79.	[func]		feng, jinmei
+	Refactored the ASIO link interfaces to move incoming XFR and
+	NOTIFY processing to the auth server class.  Wrapper classes for
+	ASIO specific concepts were also provided, so that other BIND 10
+	modules can (eventually) use the interface without including the
+	ASIO header file directly.  On top of these changes, AXFR and
+	NOTIFY processing was massively improved in terms of message
+	validation and protocol conformance.  Detailed tests were provided
+	to confirm the behavior.
+	Note: Right now, NOTIFY doesn't actually trigger subsequent zone
+	transfer due to security reasons. (Trac #221, r2565)
+
+  78.	[bug]		jinmei
+	lib/dns: Fixed miscellaneous bugs in the base32 (hex) and hex
+	(base16) implementation, including incorrect padding handling,
+	parser failure in decoding with a SunStudio build, missing
+	validation on the length of encoded hex string.  Test cases were
+	more detailed to identify these bugs and confirm the fix.  Also
+	renamed the incorrect term of "base32" to "base32hex".  This
+	changed the API, but they are not intended to be used outside
+	libdns++, so we don't consider it a backward incompatible change.
+	(Trac #256, r2549)
+
+  77.	[func]		zhanglikun
+	Make error message be more friendly when running cmdctl and it's 
+	already running(listening on same port)(Trac #277, r2540)
+
   76.	[bug]		jelte
 	Fixed a bug in the handling of 'remote' config modules (i.e.
 	modules that peek at the configuration of other modules), where

+ 2 - 2
Makefile.am

@@ -41,8 +41,8 @@ report-coverage:
 coverage: clean-coverage perform-coverage report-coverage
 
 #### include external sources in the distributed tarball:
-# EXTRA_DIST = ext/asio/README
-EXTRA_DIST = ext/asio/asio/local/stream_protocol.hpp
+EXTRA_DIST = ext/asio/README
+EXTRA_DIST += ext/asio/asio/local/stream_protocol.hpp
 EXTRA_DIST += ext/asio/asio/local/basic_endpoint.hpp
 EXTRA_DIST += ext/asio/asio/local/datagram_protocol.hpp
 EXTRA_DIST += ext/asio/asio/local/connect_pair.hpp

+ 3 - 0
configure.ac

@@ -413,6 +413,9 @@ AC_CONFIG_FILES([Makefile
                  src/bin/xfrout/tests/Makefile
                  src/bin/usermgr/Makefile
                  src/lib/Makefile
+                 src/lib/bench/Makefile
+                 src/lib/bench/example/Makefile
+                 src/lib/bench/tests/Makefile
                  src/lib/cc/Makefile
                  src/lib/python/Makefile
                  src/lib/python/isc/Makefile

+ 1 - 1
doc/Doxyfile

@@ -568,7 +568,7 @@ WARN_LOGFILE           =
 # directories like "/usr/src/myproject". Separate the files or directories
 # with spaces.
 
-INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc
+INPUT                  = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/lib/bench
 
 # 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

+ 5 - 0
ext/asio/README

@@ -0,0 +1,5 @@
+ASIO library header files
+Version 1.4.5 (2010-05-12)
+Downloaded from http://sourceforge.net/projects/asio/files
+Project page: http://think-async.com/Asio
+No local modifications.

+ 2 - 1
src/bin/auth/Makefile.am

@@ -1,6 +1,7 @@
 SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
@@ -55,8 +56,8 @@ b10_auth_LDADD += $(top_builddir)/src/lib/config/.libs/libcfgclient.a
 b10_auth_LDADD += $(top_builddir)/src/lib/cc/.libs/libcc.a
 b10_auth_LDADD += $(top_builddir)/src/lib/exceptions/.libs/libexceptions.a
 b10_auth_LDADD += $(top_builddir)/src/bin/auth/libasio_link.a
-b10_auth_LDADD += $(SQLITE_LIBS)
 b10_auth_LDADD += $(top_builddir)/src/lib/xfr/.libs/libxfr.a
+b10_auth_LDADD += $(SQLITE_LIBS)
 
 # TODO: config.h.in is wrong because doesn't honor pkgdatadir
 # and can't use @datadir@ because doesn't expand default ${prefix}

+ 302 - 204
src/bin/auth/asio_link.cc

@@ -17,83 +17,167 @@
 #include <config.h>
 
 #include <unistd.h>             // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
 #include <asio.hpp>
 #include <boost/lexical_cast.hpp>
 #include <boost/bind.hpp>
 
+#include <boost/shared_ptr.hpp>
+
 #include <dns/buffer.h>
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
 
-#include <xfr/xfrout_client.h>
-
 #include <asio_link.h>
 
-#include <auth/spec_config.h>   // for XFROUT.  should not be here.
 #include <auth/auth_srv.h>
 #include <auth/common.h>
 
 using namespace asio;
-using ip::udp;
-using ip::tcp;
+using asio::ip::udp;
+using asio::ip::tcp;
 
 using namespace std;
 using namespace isc::dns;
-using namespace isc::xfr;
-
-namespace {
-// As a short term workaround, we have XFROUT specific code.  We should soon
-// refactor the code with some abstraction so that we can separate this level
-// details from the (AS)IO module.
-
-// This was contained in an ifdef USE_XFROUT, but we should really check
-// live if we do xfrout
-//TODO. The sample way for checking axfr query, the code should be merged to auth server class
-bool
-check_axfr_query(char* const msg_data, const uint16_t msg_len) {
-    if (msg_len < 15) {
-        return false;
-    }
 
-    const uint16_t query_type = *(uint16_t *)(msg_data + (msg_len - 4));
-    if ( query_type == 0xFC00) {
-        return true;
+namespace asio_link {
+IOAddress::IOAddress(const string& address_str)
+    // XXX: we cannot simply construct the address in the initialization list
+    // because we'd like to throw our own exception on failure.
+{
+    error_code err;
+    asio_address_ = ip::address::from_string(address_str, err);
+    if (err) {
+        isc_throw(IOError, "Failed to convert string to address '"
+                  << address_str << "': " << err.message());
     }
-    
-    return false;
 }
 
-//TODO. Send the xfr query to xfrout module, the code should be merged to auth server class
-//BIGGERTODO: stop using hardcoded install-path locations! 
-void
-dispatch_axfr_query(const int tcp_sock, char const axfr_query[],
-                    const uint16_t query_len)
-{
-    string path;
-    if (getenv("B10_FROM_BUILD")) {
-        path = string(getenv("B10_FROM_BUILD")) + "/auth_xfrout_conn";
-    } else {
-        path = UNIX_SOCKET_FILE;
-    }
-    
-    if (getenv("B10_FROM_BUILD")) {
-        path = string(getenv("B10_FROM_BUILD")) + "/auth_xfrout_conn";
+IOAddress::IOAddress(const ip::address& asio_address) :
+    asio_address_(asio_address)
+{}
+
+string
+IOAddress::toText() const {
+    return (asio_address_.to_string());
+}
+
+// Note: this implementation is optimized for the case where this object
+// is created from an ASIO endpoint object in a receiving code path
+// by avoiding to make a copy of the base endpoint.  For TCP it may not be
+// a bug deal, but when we receive UDP packets at a high rate, the copy
+// overhead might be significant.
+class TCPEndpoint : public IOEndpoint {
+public:
+    TCPEndpoint(const IOAddress& address, const unsigned short port) :
+        asio_endpoint_placeholder_(
+            new tcp::endpoint(ip::address::from_string(address.toText()),
+                              port)),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+    TCPEndpoint(const tcp::endpoint& asio_endpoint) :
+        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+    {}
+        
+    ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
+    virtual IOAddress getAddress() const {
+        return (asio_endpoint_.address());
     }
-    XfroutClient xfr_client(path);
-    try {
-        xfr_client.connect();
-        xfr_client.sendXfroutRequestInfo(tcp_sock, (uint8_t *)axfr_query,
-                                         query_len);
-        xfr_client.disconnect();
+private:
+    const tcp::endpoint* asio_endpoint_placeholder_;
+    const tcp::endpoint& asio_endpoint_;
+};
+
+class UDPEndpoint : public IOEndpoint {
+public:
+    UDPEndpoint(const IOAddress& address, const unsigned short port) :
+        asio_endpoint_placeholder_(
+            new udp::endpoint(ip::address::from_string(address.toText()),
+                              port)),
+        asio_endpoint_(*asio_endpoint_placeholder_)
+    {}
+    UDPEndpoint(const udp::endpoint& asio_endpoint) :
+        asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
+    {}
+    ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
+    virtual IOAddress getAddress() const {
+        return (asio_endpoint_.address());
     }
-    catch (const exception & err) {
-        //if (verbose_mode)
-        cerr << "error handle xfr query " << UNIX_SOCKET_FILE << ":" << err.what() << endl;
+private:
+    const udp::endpoint* asio_endpoint_placeholder_;
+    const udp::endpoint& asio_endpoint_;
+};
+
+const IOEndpoint*
+IOEndpoint::create(const int protocol, const IOAddress& address,
+                   const unsigned short port)
+{
+    if (protocol == IPPROTO_UDP) {
+        return (new UDPEndpoint(address, port));
+    } else if (protocol == IPPROTO_TCP) {
+        return (new TCPEndpoint(address, port));
     }
+    isc_throw(IOError,
+              "IOEndpoint creation attempt for unsupported protocol: " <<
+              protocol);
+}
+
+class TCPSocket : public IOSocket {
+private:
+    TCPSocket(const TCPSocket& source);
+    TCPSocket& operator=(const TCPSocket& source);
+public:
+    TCPSocket(tcp::socket& socket) : socket_(socket) {}
+    virtual int getNative() const { return (socket_.native()); }
+    virtual int getProtocol() const { return (IPPROTO_TCP); }
+private:
+    tcp::socket& socket_;
+};
+
+class UDPSocket : public IOSocket {
+private:
+    UDPSocket(const UDPSocket& source);
+    UDPSocket& operator=(const UDPSocket& source);
+public:
+    UDPSocket(udp::socket& socket) : socket_(socket) {}
+    virtual int getNative() const { return (socket_.native()); }
+    virtual int getProtocol() const { return (IPPROTO_UDP); }
+private:
+    udp::socket& socket_;
+};
+
+class DummySocket : public IOSocket {
+private:
+    DummySocket(const DummySocket& source);
+    DummySocket& operator=(const DummySocket& source);
+public:
+    DummySocket(const int protocol) : protocol_(protocol) {}
+    virtual int getNative() const { return (-1); }
+    virtual int getProtocol() const { return (protocol_); }
+private:
+    const int protocol_;
+};
+
+IOSocket&
+IOSocket::getDummyUDPSocket() {
+    static DummySocket socket(IPPROTO_UDP);
+    return (socket);
 }
+
+IOSocket&
+IOSocket::getDummyTCPSocket() {
+    static DummySocket socket(IPPROTO_TCP);
+    return (socket);
 }
 
-namespace asio_link {
+IOMessage::IOMessage(const void* data, const size_t data_size,
+                     IOSocket& io_socket, const IOEndpoint& remote_endpoint) :
+    data_(data), data_size_(data_size), io_socket_(io_socket),
+    remote_endpoint_(remote_endpoint)
+{}
+
 //
 // Helper classes for asynchronous I/O using asio
 //
@@ -102,15 +186,18 @@ public:
     TCPClient(AuthSrv* auth_server, io_service& io_service) :
         auth_server_(auth_server),
         socket_(io_service),
+        io_socket_(socket_),
         response_buffer_(0),
         responselen_buffer_(TCP_MESSAGE_LENGTHSIZE),
         response_renderer_(response_buffer_),
-        dns_message_(Message::PARSE)
+        dns_message_(Message::PARSE),
+        custom_callback_(NULL)
     {}
 
     void start() {
         // Check for queued configuration commands
-        if (auth_server_->configSession()->hasQueuedMsgs()) {
+        if (auth_server_ != NULL &&
+            auth_server_->configSession()->hasQueuedMsgs()) {
             auth_server_->configSession()->checkCommand();
         }
         async_read(socket_, asio::buffer(data_, TCP_MESSAGE_LENGTHSIZE),
@@ -129,7 +216,6 @@ public:
 
             uint16_t msglen = dnsbuffer.readUint16();
             async_read(socket_, asio::buffer(data_, msglen),
-
                        boost::bind(&TCPClient::requestRead, this,
                                    placeholders::error,
                                    placeholders::bytes_transferred));
@@ -142,25 +228,28 @@ public:
                      size_t bytes_transferred)
     {
         if (!error) {
-            InputBuffer dnsbuffer(data_, bytes_transferred);
-            if (check_axfr_query(data_, bytes_transferred)) {
-                dispatch_axfr_query(socket_.native(), data_, bytes_transferred); 
-                // start to get new query ?
+            const TCPEndpoint remote_endpoint(socket_.remote_endpoint());
+            const IOMessage io_message(data_, bytes_transferred, io_socket_,
+                                       remote_endpoint);
+            // currently, for testing purpose only
+            if (custom_callback_ != NULL) {
+                (*custom_callback_)(io_message);
                 start();
+                return;
+            }
+
+            if (auth_server_->processMessage(io_message, dns_message_,
+                                             response_renderer_)) {
+                responselen_buffer_.writeUint16(
+                    response_buffer_.getLength());
+                async_write(socket_,
+                            asio::buffer(
+                                responselen_buffer_.getData(),
+                                responselen_buffer_.getLength()),
+                            boost::bind(&TCPClient::responseWrite, this,
+                                        placeholders::error));
             } else {
-                if (auth_server_->processMessage(dnsbuffer, dns_message_,
-                                                response_renderer_, false)) {
-                    responselen_buffer_.writeUint16(
-                        response_buffer_.getLength());
-                    async_write(socket_,
-                                asio::buffer(
-                                    responselen_buffer_.getData(),
-                                    responselen_buffer_.getLength()),
-                                boost::bind(&TCPClient::responseWrite, this,
-                                            placeholders::error));
-                } else {
-                    delete this;
-                }
+                delete this;
             }
         } else {
             delete this;
@@ -171,9 +260,9 @@ public:
         if (!error) {
                 async_write(socket_,
                             asio::buffer(response_buffer_.getData(),
-                                                response_buffer_.getLength()),
-                        boost::bind(&TCPClient::handleWrite, this,
-                                    placeholders::error));
+                                         response_buffer_.getLength()),
+                            boost::bind(&TCPClient::handleWrite, this,
+                                        placeholders::error));
         } else {
             delete this;
         }
@@ -187,9 +276,15 @@ public:
       }
     }
 
+    // Currently this is for tests only
+    void setCallBack(const IOService::IOCallBack* callback) {
+        custom_callback_ = callback;
+    }
+
 private:
     AuthSrv* auth_server_;
     tcp::socket socket_;
+    TCPSocket io_socket_;
     OutputBuffer response_buffer_;
     OutputBuffer responselen_buffer_;
     MessageRenderer response_renderer_;
@@ -197,21 +292,25 @@ private:
     enum { MAX_LENGTH = 65535 };
     static const size_t TCP_MESSAGE_LENGTHSIZE = 2;
     char data_[MAX_LENGTH];
+
+    // currently, for testing purpose only.
+    const IOService::IOCallBack* custom_callback_;
 };
 
 class TCPServer {
 public:
     TCPServer(AuthSrv* auth_server, io_service& io_service,
-              int af, uint16_t port) :
+              const ip::address& addr, const uint16_t port) :
         auth_server_(auth_server), io_service_(io_service),
         acceptor_(io_service_), listening_(new TCPClient(auth_server_,
-                                                         io_service_))
+                                                         io_service_)),
+        custom_callback_(NULL)
     {
-        tcp::endpoint endpoint(af == AF_INET6 ? tcp::v6() : tcp::v4(), port);
+        tcp::endpoint endpoint(addr, port);
         acceptor_.open(endpoint.protocol());
         // Set v6-only (we use a different instantiation for v4,
         // otherwise asio will bind to both v4 and v6
-        if (af == AF_INET6) {
+        if (addr.is_v6()) {
             acceptor_.set_option(ip::v6_only(true));
         }
         acceptor_.set_option(tcp::acceptor::reuse_address(true));
@@ -222,23 +321,6 @@ public:
                                            listening_, placeholders::error));
     }
 
-    TCPServer(AuthSrv* auth_server, io_service& io_service,
-              asio::ip::address addr, uint16_t port) :
-        auth_server_(auth_server),
-        io_service_(io_service), acceptor_(io_service_),
-        listening_(new TCPClient(auth_server, io_service_))
-    {
-        tcp::endpoint endpoint(addr, port);
-        acceptor_.open(endpoint.protocol());
-
-        acceptor_.set_option(tcp::acceptor::reuse_address(true));
-        acceptor_.bind(endpoint);
-        acceptor_.listen();
-        acceptor_.async_accept(listening_->getSocket(),
-                               boost::bind(&TCPServer::handleAccept, this,
-                                           listening_, placeholders::error));
-    }
-
     ~TCPServer() { delete listening_; }
 
     void handleAccept(TCPClient* new_client,
@@ -246,6 +328,7 @@ public:
     {
         if (!error) {
             assert(new_client == listening_);
+            new_client->setCallBack(custom_callback_);
             new_client->start();
             listening_ = new TCPClient(auth_server_, io_service_);
             acceptor_.async_accept(listening_->getSocket(),
@@ -257,61 +340,68 @@ public:
         }
     }
 
+    // Currently this is for tests only
+    void setCallBack(const IOService::IOCallBack* callback) {
+        custom_callback_ = callback;
+    }
+
 private:
     AuthSrv* auth_server_;
     io_service& io_service_;
     tcp::acceptor acceptor_;
     TCPClient* listening_;
+
+    // currently, for testing purpose only.
+    const IOService::IOCallBack* custom_callback_;
 };
 
 class UDPServer {
 public:
     UDPServer(AuthSrv* auth_server, io_service& io_service,
-              int af, uint16_t port) :
+              const ip::address& addr, const uint16_t port) :
         auth_server_(auth_server),
         io_service_(io_service),
-        socket_(io_service, af == AF_INET6 ? udp::v6() : udp::v4()),
+        socket_(io_service, addr.is_v6() ? udp::v6() : udp::v4()),
+        io_socket_(socket_),
         response_buffer_(0),
         response_renderer_(response_buffer_),
-        dns_message_(Message::PARSE)
+        dns_message_(Message::PARSE),
+        custom_callback_(NULL)
     {
         // Set v6-only (we use a different instantiation for v4,
         // otherwise asio will bind to both v4 and v6
-        if (af == AF_INET6) {
+        if (addr.is_v6()) {
             socket_.set_option(asio::ip::v6_only(true));
-            socket_.bind(udp::endpoint(udp::v6(), port));
+            socket_.bind(udp::endpoint(addr, port));
         } else {
-            socket_.bind(udp::endpoint(udp::v4(), port));
+            socket_.bind(udp::endpoint(addr, port));
         }
         startReceive();
     }
 
-    UDPServer(AuthSrv* auth_server, io_service& io_service,
-              asio::ip::address addr, uint16_t port) :
-        auth_server_(auth_server), io_service_(io_service),
-        socket_(io_service, addr.is_v6() ? udp::v6() : udp::v4()),
-        response_buffer_(0),
-        response_renderer_(response_buffer_),
-        dns_message_(Message::PARSE)
-    {
-        socket_.bind(udp::endpoint(addr, port));
-        startReceive();
-    }
-
     void handleRequest(const asio::error_code& error,
                        size_t bytes_recvd)
     {
         // Check for queued configuration commands
-        if (auth_server_->configSession()->hasQueuedMsgs()) {
+        if (auth_server_ != NULL &&
+            auth_server_->configSession()->hasQueuedMsgs()) {
             auth_server_->configSession()->checkCommand();
         }
         if (!error && bytes_recvd > 0) {
-            InputBuffer request_buffer(data_, bytes_recvd);
+            const UDPEndpoint remote_endpoint(sender_endpoint_);
+            const IOMessage io_message(data_, bytes_recvd, io_socket_,
+                                       remote_endpoint);
+            // currently, for testing purpose only
+            if (custom_callback_ != NULL) {
+                (*custom_callback_)(io_message);
+                startReceive();
+                return;
+            }
 
             dns_message_.clear(Message::PARSE);
             response_renderer_.clear();
-            if (auth_server_->processMessage(request_buffer, dns_message_,
-                                            response_renderer_, true)) {
+            if (auth_server_->processMessage(io_message, dns_message_,
+                                             response_renderer_)) {
                 socket_.async_send_to(
                     asio::buffer(response_buffer_.getData(),
                                         response_buffer_.getLength()),
@@ -335,6 +425,11 @@ public:
         // the next request.
         startReceive();
     }
+
+    // Currently this is for tests only
+    void setCallBack(const IOService::IOCallBack* callback) {
+        custom_callback_ = callback;
+    }
 private:
     void startReceive() {
         socket_.async_receive_from(
@@ -348,121 +443,107 @@ private:
     AuthSrv* auth_server_;
     io_service& io_service_;
     udp::socket socket_;
+    UDPSocket io_socket_;
     OutputBuffer response_buffer_;
     MessageRenderer response_renderer_;
     Message dns_message_;
     udp::endpoint sender_endpoint_;
     enum { MAX_LENGTH = 4096 };
     char data_[MAX_LENGTH];
-};
 
-// This is a helper structure just to make the construction of IOServiceImpl
-// exception safe.  If the constructor of {UDP/TCP}Server throws an exception,
-// the destructor of this class will automatically perform the necessary
-// cleanup.
-struct ServerSet {
-    ServerSet() : udp4_server(NULL), udp6_server(NULL),
-                  tcp4_server(NULL), tcp6_server(NULL)
-    {}
-    ~ServerSet() {
-        delete udp4_server;
-        delete udp6_server;
-        delete tcp4_server;
-        delete tcp6_server;
-    }
-    UDPServer* udp4_server;
-    UDPServer* udp6_server;
-    TCPServer* tcp4_server;
-    TCPServer* tcp6_server;
+    // currently, for testing purpose only.
+    const IOService::IOCallBack* custom_callback_;
 };
 
 class IOServiceImpl {
 public:
-    IOServiceImpl(AuthSrv* auth_server, const char* address, const char* port,
-                  const bool use_ipv4, const bool use_ipv6);
-    ~IOServiceImpl();
+    IOServiceImpl(AuthSrv* auth_server, const char& port,
+                  const ip::address* v4addr, const ip::address* v6addr);
     asio::io_service io_service_;
     AuthSrv* auth_server_;
-    UDPServer* udp4_server_;
-    UDPServer* udp6_server_;
-    TCPServer* tcp4_server_;
-    TCPServer* tcp6_server_;
+
+    typedef boost::shared_ptr<UDPServer> UDPServerPtr;
+    typedef boost::shared_ptr<TCPServer> TCPServerPtr;
+    UDPServerPtr udp4_server_;
+    UDPServerPtr udp6_server_;
+    TCPServerPtr tcp4_server_;
+    TCPServerPtr tcp6_server_;
+
+    // This member is used only for testing at the moment.
+    IOService::IOCallBack callback_;
 };
 
-IOServiceImpl::IOServiceImpl(AuthSrv* auth_server, const char* const address,
-                             const char* const port, const bool use_ipv4,
-                             const bool use_ipv6) :
-    auth_server_(auth_server), udp4_server_(NULL), udp6_server_(NULL),
-    tcp4_server_(NULL), tcp6_server_(NULL)
+IOServiceImpl::IOServiceImpl(AuthSrv* auth_server, const char& port,
+                             const ip::address* const v4addr,
+                             const ip::address* const v6addr) :
+    auth_server_(auth_server),
+    udp4_server_(UDPServerPtr()), udp6_server_(UDPServerPtr()),
+    tcp4_server_(TCPServerPtr()), tcp6_server_(TCPServerPtr())
 {
-    ServerSet servers;
-    uint16_t portnum = atoi(port);
+    uint16_t portnum;
 
     try {
-        portnum = boost::lexical_cast<uint16_t>(port);
-    } catch (const std::exception& ex) {
-        isc_throw(FatalError, "[b10-auth] Invalid port number '"
-                              << port << "'");
-    }
-
-    if (address != NULL) {
-        asio::ip::address addr = asio::ip::address::from_string(address);
-
-        if ((addr.is_v6() && !use_ipv6)) {
-            isc_throw(FatalError,
-                      "[b10-auth] Error: -4 conflicts with " << addr);
-        }
-
-        if ((addr.is_v4() && !use_ipv4)) {
-            isc_throw(FatalError,
-                      "[b10-auth] Error: -6 conflicts with " << addr);
+        // XXX: SunStudio with stlport4 doesn't reject some invalid
+        // representation such as "-1" by lexical_cast<uint16_t>, so
+        // we convert it into a signed integer of a larger size and perform
+        // range check ourselves.
+        const int32_t portnum32 = boost::lexical_cast<int32_t>(&port);
+        if (portnum32 < 0 || portnum32 > 65535) {
+            isc_throw(IOError, "Invalid port number '" << &port);
         }
+        portnum = portnum32;
+    } catch (const boost::bad_lexical_cast& ex) {
+        isc_throw(IOError, "Invalid port number '" << &port << "': " <<
+                  ex.what());
+    }
 
-        if (addr.is_v4()) {
-            servers.udp4_server = new UDPServer(auth_server, io_service_,
-                                                addr, portnum);
-            servers.tcp4_server = new TCPServer(auth_server, io_service_,
-                                                addr, portnum);
-         } else {
-            servers.udp6_server = new UDPServer(auth_server, io_service_,
-                                                addr, portnum);
-            servers.tcp6_server = new TCPServer(auth_server, io_service_,
-                                                addr, portnum);
-        }
-    } else {
-        if (use_ipv4) {
-            servers.udp4_server = new UDPServer(auth_server, io_service_,
-                                                AF_INET, portnum);
-            servers.tcp4_server = new TCPServer(auth_server, io_service_,
-                                                AF_INET, portnum);
+    try {
+        if (v4addr != NULL) {
+            udp4_server_ = UDPServerPtr(new UDPServer(auth_server, io_service_,
+                                                      *v4addr, portnum));
+            tcp4_server_ = TCPServerPtr(new TCPServer(auth_server, io_service_,
+                                                      *v4addr, portnum));
         }
-        if (use_ipv6) {
-            servers.udp6_server = new UDPServer(auth_server, io_service_,
-                                                AF_INET6, portnum);
-            servers.tcp6_server = new TCPServer(auth_server, io_service_,
-                                                AF_INET6, portnum);
+        if (v6addr != NULL) {
+            udp6_server_ = UDPServerPtr(new UDPServer(auth_server, io_service_,
+                                                      *v6addr, portnum));
+            tcp6_server_ = TCPServerPtr(new TCPServer(auth_server, io_service_,
+                                                      *v6addr, portnum));
         }
+    } catch (const asio::system_error& err) {
+        // We need to catch and convert any ASIO level exceptions.
+        // This can happen for unavailable address, binding a privilege port
+        // without the privilege, etc.
+        isc_throw(IOError, "Failed to initialize network servers: " <<
+                  err.what());
     }
-
-    // Now we don't have to worry about exception, and need to make sure that
-    // the server objects won't be accidentally cleaned up.
-    servers.udp4_server = NULL;
-    servers.udp6_server = NULL;
-    servers.tcp4_server = NULL;
-    servers.tcp6_server = NULL;
 }
 
-IOServiceImpl::~IOServiceImpl() {
-    delete udp4_server_;
-    delete udp6_server_;
-    delete tcp4_server_;
-    delete tcp6_server_;
+IOService::IOService(AuthSrv* auth_server, const char& port,
+                     const char& address) :
+    impl_(NULL)
+{
+    error_code err;
+    const ip::address addr = ip::address::from_string(&address, err);
+    if (err) {
+        isc_throw(IOError, "Invalid IP address '" << &address << "': "
+                  << err.message());
+    }
+
+    impl_ = new IOServiceImpl(auth_server, port,
+                              addr.is_v4() ? &addr : NULL,
+                              addr.is_v6() ? &addr : NULL);
 }
 
-IOService::IOService(AuthSrv* auth_server, const char* const address,
-                     const char* const port, const bool use_ipv4,
-                     const bool use_ipv6) {
-    impl_ = new IOServiceImpl(auth_server, address, port, use_ipv4, use_ipv6);
+IOService::IOService(AuthSrv* auth_server, const char& port,
+                     const bool use_ipv4, const bool use_ipv6) :
+    impl_(NULL)
+{
+    const ip::address v4addr_any = ip::address(ip::address_v4::any());
+    const ip::address* const v4addrp = use_ipv4 ? &v4addr_any : NULL; 
+    const ip::address v6addr_any = ip::address(ip::address_v6::any());
+    const ip::address* const v6addrp = use_ipv6 ? &v6addr_any : NULL;
+    impl_ = new IOServiceImpl(auth_server, port, v4addrp, v6addrp);
 }
 
 IOService::~IOService() {
@@ -483,4 +564,21 @@ asio::io_service&
 IOService::get_io_service() {
     return impl_->io_service_;
 }
+
+void
+IOService::setCallBack(const IOCallBack callback) {
+    impl_->callback_ = callback;
+    if (impl_->udp4_server_ != NULL) {
+        impl_->udp4_server_->setCallBack(&impl_->callback_);
+    }
+    if (impl_->udp6_server_ != NULL) {
+        impl_->udp6_server_->setCallBack(&impl_->callback_);
+    }
+    if (impl_->tcp4_server_ != NULL) {
+        impl_->tcp4_server_->setCallBack(&impl_->callback_);
+    }
+    if (impl_->tcp6_server_ != NULL) {
+        impl_->tcp6_server_->setCallBack(&impl_->callback_);
+    }
+}
 }

+ 412 - 2
src/bin/auth/asio_link.h

@@ -17,20 +17,430 @@
 #ifndef __ASIO_LINK_H
 #define __ASIO_LINK_H 1
 
+// IMPORTANT NOTE: only very few ASIO headers files can be included in
+// this file.  In particular, asio.hpp should never be included here.
+// See the description of the namespace below.
+#include <unistd.h>             // for some network system calls
+#include <asio/ip/address.hpp>
+
+#include <functional>
+#include <string>
+
+#include <boost/function.hpp>
+
+#include <exceptions/exceptions.h>
+
+namespace asio {
+// forward declaration for IOService::get_io_service() below
+class io_service;
+}
+
 class AuthSrv;
 
+/// \namespace asio_link
+/// \brief A wrapper interface for the ASIO library.
+///
+/// The \c asio_link namespace is used to define a set of wrapper interfaces
+/// for the ASIO library.
+///
+/// BIND 10 uses the non-Boost version of ASIO because it's header-only,
+/// i.e., does not require a separate library object to be linked, and thus
+/// lowers the bar for introduction.
+///
+/// But the advantage comes with its own costs: since the header-only version
+/// includes more definitions in public header files, it tends to trigger
+/// more compiler warnings for our own sources, and, depending on the
+/// compiler options, may make the build fail.
+///
+/// We also found it may be tricky to use ASIO and standard C++ libraries
+/// in a single translation unit, i.e., a .cc file: depending on the order
+/// of including header files, ASIO may or may not work on some platforms.
+///
+/// This wrapper interface is intended to centralize these
+/// problematic issues in a single sub module.  Other BIND 10 modules should
+/// simply include \c asio_link.h and use the wrapper API instead of
+/// including ASIO header files and using ASIO-specific classes directly.
+///
+/// This wrapper may be used for other IO libraries if and when we want to
+/// switch, but generality for that purpose is not the primary goal of
+/// this module.  The resulting interfaces are thus straightforward mapping
+/// to the ASIO counterparts.
+///
+/// Notes to developers:
+/// Currently the wrapper interface is specific to the authoritative
+/// server implementation.  But the plan is to generalize it and have
+/// other modules use it.
+///
+/// One obvious drawback of this approach is performance overhead
+/// due to the additional layer.  We should eventually evaluate the cost
+/// of the wrapper abstraction in benchmark tests. Another drawback is
+/// that the wrapper interfaces don't provide all features of ASIO
+/// (at least for the moment).  We should also re-evaluate the
+/// maintenance overhead of providing necessary wrappers as we develop
+/// more.
+///
+/// On the other hand, we may be able to exploit the wrapper approach to
+/// simplify the interfaces (by limiting the usage) and unify performance
+/// optimization points.
+///
+/// As for optimization, we may want to provide a custom allocator for
+/// the placeholder of callback handlers:
+/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
+
 namespace asio_link {
 struct IOServiceImpl;
 
+/// \brief An exception that is thrown if an error occurs within the IO
+/// module.  This is mainly intended to be a wrapper exception class for
+/// ASIO specific exceptions.
+class IOError : public isc::Exception {
+public:
+    IOError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// \brief The \c IOAddress class represents an IP addresses (version
+/// agnostic)
+///
+/// This class is a wrapper for the ASIO \c ip::address class.
+class IOAddress {
+public:
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// This class is copyable.  We use default versions of copy constructor
+    /// and the assignment operator.
+    /// We use the default destructor.
+    //@{
+    /// \brief Constructor from string.
+    ///
+    /// This constructor converts a textual representation of IPv4 and IPv6
+    /// addresses into an IOAddress object.
+    /// If \c address_str is not a valid representation of any type of
+    /// address, an exception of class \c IOError will be thrown.
+    /// This constructor allocates memory for the object, and if that fails
+    /// a corresponding standard exception will be thrown.
+    ///
+    /// \param address_str Textual representation of address.
+    IOAddress(const std::string& address_str);
+
+    /// \brief Constructor from an ASIO \c ip::address object.
+    ///
+    /// This constructor is intended to be used within the wrapper
+    /// implementation; user applications of the wrapper API won't use it.
+    ///
+    /// This constructor never throws an exception.
+    ///
+    /// \param asio_address The ASIO \c ip::address to be converted.
+    IOAddress(const asio::ip::address& asio_adress);
+    //@}
+
+    /// \brief Convert the address to a string.
+    ///
+    /// This method is basically expected to be exception free, but
+    /// generating the string will involve resource allocation,
+    /// and if it fails the corresponding standard exception will be thrown.
+    ///
+    /// \return A string representation of the address.
+    std::string toText() const;
+private:
+    asio::ip::address asio_address_;
+};
+
+/// \brief The \c IOEndpoint class is an abstract base class to represent
+/// a communication endpoint.
+///
+/// This class is a wrapper for the ASIO endpoint classes such as
+/// \c ip::tcp::endpoint and \c ip::udp::endpoint.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation.  User applications only get access to concrete
+/// \c IOEndpoint objects via the abstract interfaces.
+class IOEndpoint {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOEndpoint(const IOEndpoint& source);
+    IOEndpoint& operator=(const IOEndpoint& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    IOEndpoint() {}
+public:
+    /// The destructor.
+    virtual ~IOEndpoint() {}
+    //@}
+
+    /// \brief Returns the address of the endpoint.
+    ///
+    /// This method returns an IOAddress object corresponding to \c this
+    /// endpoint.
+    /// Note that the return value is a real object, not a reference or
+    /// a pointer.
+    /// This is aligned with the interface of the ASIO counterpart:
+    /// the \c address() method of \c ip::xxx::endpoint classes returns
+    /// an \c ip::address object.
+    /// This also means handling the address of an endpoint using this method
+    /// can be expensive.  If the address information is necessary in a
+    /// performance sensitive context and there's a more efficient interface
+    /// for that purpose, it's probably better to avoid using this method.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A copy of \c IOAddress object corresponding to the endpoint.
+    virtual IOAddress getAddress() const = 0;
+
+    /// \brief A polymorphic factory of endpoint from address and port.
+    ///
+    /// This method creates a new instance of (a derived class of)
+    /// \c IOEndpoint object that identifies the pair of given address
+    /// and port.
+    /// The appropriate derived class is chosen based on the specified
+    /// transport protocol.  If the \c protocol doesn't specify a protocol
+    /// supported in this implementation, an exception of class \c IOError
+    /// will be thrown.
+    ///
+    /// Memory for the created object will be dynamically allocated.  It's
+    /// caller's responsibility to \c delete it later.
+    /// If resource allocation for the new object fails, a corresponding
+    /// standard exception will be thrown.
+    ///
+    /// \param protocol The transport protocol used for the endpoint.
+    /// Currently, only \c IPPROTO_UDP and \c IPPROTO_TCP can be specified.
+    /// \param address The (IP) address of the endpoint.
+    /// \param port The transport port number of the endpoint
+    /// \return A pointer to a newly created \c IOEndpoint object.
+    static const IOEndpoint* create(const int protocol,
+                                    const IOAddress& address,
+                                    const unsigned short port);
+};
+
+/// \brief The \c IOSocket class is an abstract base class to represent
+/// various types of network sockets.
+///
+/// This class is a wrapper for the ASIO socket classes such as
+/// \c ip::tcp::socket and \c ip::udp::socket.
+///
+/// Derived class implementations are completely hidden within the
+/// implementation.  User applications only get access to concrete
+/// \c IOSocket objects via the abstract interfaces.
+/// We may revisit this decision when we generalize the wrapper and more
+/// modules use it.  Also, at that point we may define a separate (visible)
+/// derived class for testing purposes rather than providing factory methods
+/// (i.e., getDummy variants below).
+class IOSocket {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOSocket(const IOSocket& source);
+    IOSocket& operator=(const IOSocket& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class
+    /// should never be instantiated (except as part of a derived class).
+    IOSocket() {}
+public:
+    /// The destructor.
+    virtual ~IOSocket() {}
+    //@}
+
+    /// \brief Return the "native" representation of the socket.
+    ///
+    /// In practice, this is the file descriptor of the socket for
+    /// UNIX-like systems so the current implementation simply uses
+    /// \c int as the type of the return value.
+    /// We may have to need revisit this decision later.
+    ///
+    /// In general, the application should avoid using this method;
+    /// it essentially discloses an implementation specific "handle" that
+    /// can change the internal state of the socket (consider the
+    /// application closes it, for example).
+    /// But we sometimes need to perform very low-level operations that
+    /// requires the native representation.  Passing the file descriptor
+    /// to a different process is one example.
+    /// This method is provided as a necessary evil for such limited purposes.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return The native representation of the socket.  This is the socket
+    /// file descriptor for UNIX-like systems.
+    virtual int getNative() const = 0;
+
+    /// \brief Return the transport protocol of the socket.
+    ///
+    /// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
+    /// \c IPPROTO_TCP for TCP sockets.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return IPPROTO_UDP for UDP sockets
+    /// \return IPPROTO_TCP for TCP sockets
+    virtual int getProtocol() const = 0;
+
+    /// \brief Return a non-usable "dummy" UDP socket for testing.
+    ///
+    /// This is a class method that returns a "mock" of UDP socket.
+    /// This is not associated with any actual socket, and its only
+    /// responsibility is to return \c IPPROTO_UDP from \c getProtocol().
+    /// The only feasible usage of this socket is for testing so that
+    /// the test code can prepare some "UDP data" even without opening any
+    /// actual socket.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \return A reference to an \c IOSocket object whose \c getProtocol()
+    /// returns \c IPPROTO_UDP.
+    static IOSocket& getDummyUDPSocket();
+
+    /// \brief Return a non-usable "dummy" TCP socket for testing.
+    ///
+    /// See \c getDummyUDPSocket().  This method is its TCP version.
+    ///
+    /// \return A reference to an \c IOSocket object whose \c getProtocol()
+    /// returns \c IPPROTO_TCP.
+    static IOSocket& getDummyTCPSocket();
+};
+
+/// \brief The \c IOMessage class encapsulates an incoming message received
+/// on a socket.
+///
+/// An \c IOMessage object represents a tuple of a chunk of data
+/// (a UDP packet or some segment of TCP stream), the socket over which the
+/// data is passed, the information about the other end point of the
+/// communication, and perhaps more.
+///
+/// The current design and interfaces of this class is tentative.
+/// It only provides a minimal level of support that is necessary for
+/// the current implementation of the authoritative server.
+/// A future version of this class will definitely support more.
+class IOMessage {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOMessage(const IOMessage& source);
+    IOMessage& operator=(const IOMessage& source);
+public:
+    /// \brief Constructor from message information.
+    ///
+    /// This constructor needs to handle the ASIO \c ip::address class,
+    /// and is intended to be used within this wrapper implementation.
+    /// Once the \c IOMessage object is created, the application can
+    /// get access to the information via the wrapper interface such as
+    /// \c getRemoteAddress().
+    ///
+    /// This constructor never throws an exception.
+    ///
+    /// \param data A pointer to the message data.
+    /// \param data_size The size of the message data in bytes.
+    /// \param io_socket The socket over which the data is given.
+    /// \param remote_endpoint The other endpoint of the socket, that is,
+    /// the sender of the message.
+    IOMessage(const void* data, const size_t data_size, IOSocket& io_socket,
+              const IOEndpoint& remote_endpoint);
+    //@}
+
+    /// \brief Returns a pointer to the received data.
+    const void* getData() const { return (data_); }
+
+    /// \brief Returns the size of the received data in bytes.
+    size_t getDataSize() const { return (data_size_); }
+
+    /// \brief Returns the socket on which the message arrives.
+    const IOSocket& getSocket() const { return (io_socket_); }
+
+    /// \brief Returns the endpoint that sends the message.
+    const IOEndpoint& getRemoteEndpoint() const { return (remote_endpoint_); }
+private:
+    const void* data_;
+    const size_t data_size_;
+    IOSocket& io_socket_;
+    const IOEndpoint& remote_endpoint_;
+};
+
+/// \brief The \c IOService class is a wrapper for the ASIO \c io_service
+/// class.
+///
+/// Currently, the interface of this class is very specific to the
+/// authoritative server implementation as indicated in the signature of
+/// the constructor, but the plan is to generalize it so that other BIND 10
+/// modules can use this interface, too.
 class IOService {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// These are currently very specific to the authoritative server
+    /// implementation.
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    //@{
+private:
+    IOService(const IOService& source);
+    IOService& operator=(const IOService& source);
 public:
-    IOService(AuthSrv* auth_server,
-              const char* const address, const char* const port,
+    /// \brief The constructor with a specific IP address and port on which
+    /// the services listen on.
+    IOService(AuthSrv* auth_server, const char& port, const char& address);
+    /// \brief The constructor with a specific port on which the services
+    /// listen on.
+    ///
+    /// It effectively listens on "any" IPv4 and/or IPv6 addresses.
+    /// IPv4/IPv6 services will be available if and only if \c use_ipv4
+    /// or \c use_ipv6 is \c true, respectively.
+    IOService(AuthSrv* auth_server, const char& port,
               const bool use_ipv4, const bool use_ipv6);
+    /// \brief The destructor.
     ~IOService();
+    //@}
+
+    /// \brief Start the underlying event loop.
+    ///
+    /// This method does not return control to the caller until
+    /// the \c stop() method is called via some handler.
     void run();
+
+    /// \brief Stop the underlying event loop.
+    ///
+    /// This will return the control to the caller of the \c run() method.
     void stop();
+
+    /// \brief Return the native \c io_service object used in this wrapper.
+    ///
+    /// This is a short term work around to support other BIND 10 modules
+    /// that share the same \c io_service with the authoritative server.
+    /// It will eventually be removed once the wrapper interface is
+    /// generalized.
     asio::io_service& get_io_service();
+
+    /// \brief A functor(-like) class that specifies a custom call back
+    /// invoked from the event loop instead of the embedded authoritative
+    /// server callbacks.
+    ///
+    /// Currently, the callback is intended to be used only for testing
+    /// purposes.  But we'll need a generic callback type like this to
+    /// generalize the wrapper interface.
+    typedef boost::function<void(const IOMessage& io_message)> IOCallBack;
+
+    /// \brief Set the custom call back invoked from the event loop.
+    ///
+    /// Right now this method is only for testing, but will eventually be
+    /// generalized.
+    void setCallBack(IOCallBack callback);
 private:
     IOServiceImpl* impl_;
 };

+ 1 - 1
src/bin/auth/auth.spec.pre.in

@@ -5,7 +5,7 @@
     "config_data": [
       { "item_name": "database_file",
         "item_type": "string",
-        "item_optional": True,
+        "item_optional": true,
         "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
       }
     ],

+ 221 - 32
src/bin/auth/auth_srv.cc

@@ -14,6 +14,8 @@
 
 // $Id$
 
+#include <netinet/in.h>
+
 #include <algorithm>
 #include <cassert>
 #include <iostream>
@@ -40,19 +42,23 @@
 
 #include <cc/data.h>
 
+#include <xfr/xfrout_client.h>
+
 #include <auth/common.h>
 #include <auth/auth_srv.h>
-
-#include <boost/lexical_cast.hpp>
+#include <auth/asio_link.h>
 
 using namespace std;
 
 using namespace isc;
+using namespace isc::cc;
 using namespace isc::datasrc;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::data;
 using namespace isc::config;
+using namespace isc::xfr;
+using namespace asio_link;
 
 class AuthSrvImpl {
 private:
@@ -60,12 +66,18 @@ private:
     AuthSrvImpl(const AuthSrvImpl& source);
     AuthSrvImpl& operator=(const AuthSrvImpl& source);
 public:
-    AuthSrvImpl(const bool use_cache);
-
+    AuthSrvImpl(const bool use_cache, AbstractXfroutClient& xfrout_client);
+    ~AuthSrvImpl();
     isc::data::ElementPtr setDbFile(const isc::data::ElementPtr config);
 
+    bool processNormalQuery(const IOMessage& io_message, Message& message,
+                            MessageRenderer& response_renderer);
+    bool processAxfrQuery(const IOMessage& io_message, Message& message,
+                            MessageRenderer& response_renderer);
+    bool processNotify(const IOMessage& io_message, Message& message, 
+                            MessageRenderer& response_renderer);
     std::string db_file_;
-    ModuleCCSession* cs_;
+    ModuleCCSession* config_session_;
     MetaDataSrc data_sources_;
     /// We keep a pointer to the currently running sqlite datasource
     /// so that we can specifically remove that one should the database
@@ -74,6 +86,11 @@ public:
 
     bool verbose_mode_;
 
+    AbstractSession* xfrin_session_;
+
+    bool xfrout_connected_;
+    AbstractXfroutClient& xfrout_client_;
+
     /// Currently non-configurable, but will be.
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
 
@@ -81,8 +98,12 @@ public:
     isc::datasrc::HotCache cache_;
 };
 
-AuthSrvImpl::AuthSrvImpl(const bool use_cache) :
-    cs_(NULL), verbose_mode_(false)
+AuthSrvImpl::AuthSrvImpl(const bool use_cache,
+                         AbstractXfroutClient& xfrout_client) :
+    config_session_(NULL), verbose_mode_(false),
+    xfrin_session_(NULL),
+    xfrout_connected_(false),
+    xfrout_client_(xfrout_client)
 {
     // cur_datasrc_ is automatically initialized by the default constructor,
     // effectively being an empty (sqlite) data source.  once ccsession is up
@@ -95,9 +116,17 @@ AuthSrvImpl::AuthSrvImpl(const bool use_cache) :
     cache_.setEnabled(use_cache);
 }
 
-AuthSrv::AuthSrv(const bool use_cache) : impl_(new AuthSrvImpl(use_cache)) {
+AuthSrvImpl::~AuthSrvImpl() {
+    if (xfrout_connected_) {
+        xfrout_client_.disconnect();
+        xfrout_connected_ = false;
+    }
 }
 
+AuthSrv::AuthSrv(const bool use_cache, AbstractXfroutClient& xfrout_client) :
+    impl_(new AuthSrvImpl(use_cache, xfrout_client))
+{}
+
 AuthSrv::~AuthSrv() {
     delete impl_;
 }
@@ -125,8 +154,9 @@ makeErrorMessage(Message& message, MessageRenderer& renderer,
     const Opcode& opcode = message.getOpcode();
     vector<QuestionPtr> questions;
 
-    // If this is an error to a query, we should also copy the question section.
-    if (opcode == Opcode::QUERY()) {
+    // If this is an error to a query or notify, we should also copy the
+    // question section.
+    if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) {
         questions.assign(message.beginQuestion(), message.endQuestion());
     }
 
@@ -147,8 +177,7 @@ makeErrorMessage(Message& message, MessageRenderer& renderer,
 
     if (verbose_mode) {
         cerr << "[b10-auth] sending an error response (" <<
-            boost::lexical_cast<string>(renderer.getLength())
-             << " bytes):\n" << message.toText() << endl;
+            renderer.getLength() << " bytes):\n" << message.toText() << endl;
     }
 }
 }
@@ -164,20 +193,26 @@ AuthSrv::getVerbose() const {
 }
 
 void
-AuthSrv::setConfigSession(ModuleCCSession* cs) {
-    impl_->cs_ = cs;
+AuthSrv::setXfrinSession(AbstractSession* xfrin_session) {
+    impl_->xfrin_session_ = xfrin_session;
+}
+
+void
+AuthSrv::setConfigSession(ModuleCCSession* config_session) {
+    impl_->config_session_ = config_session;
 }
 
 ModuleCCSession*
 AuthSrv::configSession() const {
-    return (impl_->cs_);
+    return (impl_->config_session_);
 }
 
 bool
-AuthSrv::processMessage(InputBuffer& request_buffer, Message& message,
-                        MessageRenderer& response_renderer,
-                        const bool udp_buffer)
+AuthSrv::processMessage(const IOMessage& io_message, Message& message,
+                        MessageRenderer& response_renderer)
 {
+    InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
+
     // First, check the header part.  If we fail even for the base header,
     // just drop the message.
     try {
@@ -186,7 +221,8 @@ AuthSrv::processMessage(InputBuffer& request_buffer, Message& message,
         // Ignore all responses.
         if (message.getHeaderFlag(MessageFlag::QR())) {
             if (impl_->verbose_mode_) {
-                cerr << "[b10-auth] received unexpected response, ignoring" << endl;
+                cerr << "[b10-auth] received unexpected response, ignoring"
+                     << endl;
             }
             return (false);
         }
@@ -199,8 +235,8 @@ AuthSrv::processMessage(InputBuffer& request_buffer, Message& message,
         message.fromWire(request_buffer);
     } catch (const DNSProtocolError& error) {
         if (impl_->verbose_mode_) {
-            cerr << "[b10-auth] returning " <<  error.getRcode().toText() << ": "
-                 << error.what() << endl;
+            cerr << "[b10-auth] returning " <<  error.getRcode().toText()
+                 << ": " << error.what() << endl;
         }
         makeErrorMessage(message, response_renderer, error.getRcode(),
                          impl_->verbose_mode_);
@@ -220,8 +256,9 @@ AuthSrv::processMessage(InputBuffer& request_buffer, Message& message,
 
     // Perform further protocol-level validation.
 
-    // In this implementation, we only support normal queries
-    if (message.getOpcode() != Opcode::QUERY()) {
+    if (message.getOpcode() == Opcode::NOTIFY()) {
+        return (impl_->processNotify(io_message, message, response_renderer));
+    } else if (message.getOpcode() != Opcode::QUERY()) {
         if (impl_->verbose_mode_) {
             cerr << "[b10-auth] unsupported opcode" << endl;
         }
@@ -236,6 +273,25 @@ AuthSrv::processMessage(InputBuffer& request_buffer, Message& message,
         return (true);
     }
 
+    ConstQuestionPtr question = *message.beginQuestion();
+    const RRType &qtype = question->getType();
+    if (qtype == RRType::AXFR()) {
+        return (impl_->processAxfrQuery(io_message, message,
+                                        response_renderer));
+    } else if (qtype == RRType::IXFR()) {
+        makeErrorMessage(message, response_renderer, Rcode::NOTIMP(),
+                         impl_->verbose_mode_);
+        return (true);
+    } else {
+        return (impl_->processNormalQuery(io_message, message,
+                                          response_renderer));
+    }
+}
+
+bool
+AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
+                                MessageRenderer& response_renderer)
+{
     const bool dnssec_ok = message.isDNSSECSupported();
     const uint16_t remote_bufsize = message.getUDPSize();
 
@@ -246,29 +302,162 @@ AuthSrv::processMessage(InputBuffer& request_buffer, Message& message,
     message.setUDPSize(AuthSrvImpl::DEFAULT_LOCAL_UDPSIZE);
 
     try {
-        Query query(message, impl_->cache_, dnssec_ok);
-        impl_->data_sources_.doQuery(query);
+        Query query(message, cache_, dnssec_ok);
+        data_sources_.doQuery(query);
     } catch (const Exception& ex) {
-        if (impl_->verbose_mode_) {
+        if (verbose_mode_) {
             cerr << "[b10-auth] Internal error, returning SERVFAIL: " <<
                 ex.what() << endl;
         }
         makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
-                         impl_->verbose_mode_);
+                         verbose_mode_);
         return (true);
     }
 
+    const bool udp_buffer =
+        (io_message.getSocket().getProtocol() == IPPROTO_UDP);
     response_renderer.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
     message.toWire(response_renderer);
-    if (impl_->verbose_mode_) {
-        cerr << "[b10-auth] sending a response (" <<
-            boost::lexical_cast<string>(response_renderer.getLength())
+    if (verbose_mode_) {
+        cerr << "[b10-auth] sending a response ("
+             << response_renderer.getLength()
              << " bytes):\n" << message.toText() << endl;
     }
 
     return (true);
 }
 
+
+bool
+AuthSrvImpl::processAxfrQuery(const IOMessage& io_message, Message& message,
+                            MessageRenderer& response_renderer)
+{
+    if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
+        if (verbose_mode_) {
+            cerr << "[b10-auth] AXFR query over UDP isn't allowed" << endl;
+        }
+        makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
+                         verbose_mode_);
+        return (true);
+    }
+
+    try {
+        if (!xfrout_connected_) {
+            xfrout_client_.connect();
+            xfrout_connected_ = true;
+        }
+        xfrout_client_.sendXfroutRequestInfo(
+            io_message.getSocket().getNative(),
+            io_message.getData(),
+            io_message.getDataSize());
+    } catch (const XfroutError& err) {
+        if (xfrout_connected_) {
+            // disconnect() may trigger an exception, but since we try it
+            // only if we've successfully opened it, it shouldn't happen in
+            // normal condition.  Should this occur, we'll propagate it to the
+            // upper layer.
+            xfrout_client_.disconnect();
+            xfrout_connected_ = false;
+        }
+        
+        if (verbose_mode_) {
+            cerr << "[b10-auth] Error in handling XFR request: " << err.what()
+                 << endl;
+        }
+        makeErrorMessage(message, response_renderer, Rcode::SERVFAIL(),
+                         verbose_mode_);
+        return (true);
+    }
+    return (false);
+}
+
+bool
+AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message, 
+                           MessageRenderer& response_renderer) 
+{
+    // The incoming notify must contain exactly one question for SOA of the
+    // zone name.
+    if (message.getRRCount(Section::QUESTION()) != 1) {
+        if (verbose_mode_) {
+                cerr << "[b10-auth] invalid number of questions in notify: "
+                     << message.getRRCount(Section::QUESTION()) << endl;
+        }
+        makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
+                         verbose_mode_);
+        return (true);
+    }
+    ConstQuestionPtr question = *message.beginQuestion();
+    if (question->getType() != RRType::SOA()) {
+        if (verbose_mode_) {
+                cerr << "[b10-auth] invalid question RR type in notify: "
+                     << question->getType() << endl;
+        }
+        makeErrorMessage(message, response_renderer, Rcode::FORMERR(),
+                         verbose_mode_);
+        return (true);
+    }
+
+    // According to RFC 1996, rcode should be "no error" and AA bit should be
+    // on, but we don't check these conditions.  This behavior is compatible
+    // with BIND 9.
+
+    // TODO check with the conf-mgr whether current server is the auth of the
+    // zone
+
+    // In the code that follows, we simply ignore the notify if any internal
+    // error happens rather than returning (e.g.) SERVFAIL.  RFC 1996 is
+    // silent about such cases, but there doesn't seem to be anything we can
+    // improve at the primary server side by sending an error anyway.
+    if (xfrin_session_ == NULL) {
+        if (verbose_mode_) {
+            cerr << "[b10-auth] "
+                "session interface for xfrin is not available" << endl;
+        }
+        return (false);
+    }
+    
+    const string remote_ip_address =
+        io_message.getRemoteEndpoint().getAddress().toText();
+    static const string command_template_start =
+        "{\"command\": [\"notify\", {\"zone_name\" : \"";
+    static const string command_template_master = "\", \"master\" : \"";
+    static const string command_template_rrclass = "\", \"rrclass\" : \"";
+    static const string command_template_end = "\"}]}";
+
+    try {
+        ElementPtr notify_command = Element::fromJSON(
+                command_template_start + question->getName().toText() + 
+                command_template_master + remote_ip_address +
+                command_template_rrclass + question->getClass().toText() +
+                command_template_end);
+        const unsigned int seq =
+            xfrin_session_->group_sendmsg(notify_command, "Xfrin",
+                                          "*", "*");
+        ElementPtr env, answer, parsed_answer;
+        xfrin_session_->group_recvmsg(env, answer, false, seq);
+        int rcode;
+        parsed_answer = parseAnswer(rcode, answer);
+        if (rcode != 0) {
+            if (verbose_mode_) {
+                cerr << "[b10-auth] failed to notify Xfrin: "
+                     << parsed_answer->str() << endl; 
+            }
+            return (false);
+        }
+    } catch (const Exception& ex) {
+        if (verbose_mode_) {
+            cerr << "[b10-auth] failed to notify Xfrin: " << ex.what() << endl;
+        }
+        return (false);
+    }
+
+    message.makeResponse();
+    message.setHeaderFlag(MessageFlag::AA());
+    message.setRcode(Rcode::NOERROR());
+    message.toWire(response_renderer);
+    return (true);
+}
+
 ElementPtr
 AuthSrvImpl::setDbFile(const isc::data::ElementPtr config) {
     ElementPtr answer = isc::config::createAnswer();
@@ -277,10 +466,10 @@ AuthSrvImpl::setDbFile(const isc::data::ElementPtr config) {
     if (config && config->contains("database_file")) {
         db_file_ = config->get("database_file")->stringValue();
         final = config;
-    } else if (cs_ != NULL) {
+    } else if (config_session_ != NULL) {
         bool is_default;
         string item("database_file");
-        ElementPtr value = cs_->getValue(is_default, item);
+        ElementPtr value = config_session_->getValue(is_default, item);
         final = Element::createMap();
 
         // If the value is the default, and we are running from

+ 37 - 8
src/bin/auth/auth_srv.h

@@ -28,6 +28,14 @@ class InputBuffer;
 class Message;
 class MessageRenderer;
 }
+
+namespace xfr {
+class AbstractXfroutClient;
+};
+}
+
+namespace asio_link {
+class IOMessage;
 }
 
 class AuthSrvImpl;
@@ -36,28 +44,49 @@ class AuthSrv {
     ///
     /// \name Constructors, Assignment Operator and Destructor.
     ///
-    /// Note: The copy constructor and the assignment operator are intentionally
-    /// defined as private.
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private.
     //@{
 private:
     AuthSrv(const AuthSrv& source);
     AuthSrv& operator=(const AuthSrv& source);
 public:
-    explicit AuthSrv(const bool use_cache);
+    /// The constructor.
+    ///
+    /// \param use_cache Whether to enable hot spot cache for lookup results.
+    /// \param xfrout_client Communication interface with a separate xfrout
+    /// process.  It's normally a reference to an xfr::XfroutClient object,
+    /// but can refer to a local mock object for testing (or other
+    /// experimental) purposes.
+    AuthSrv(const bool use_cache,
+            isc::xfr::AbstractXfroutClient& xfrout_client);
     ~AuthSrv();
     //@}
     /// \return \c true if the \message contains a response to be returned;
     /// otherwise \c false.
-    bool processMessage(isc::dns::InputBuffer& request_buffer,
+    bool processMessage(const asio_link::IOMessage& io_message,
                         isc::dns::Message& message,
-                        isc::dns::MessageRenderer& response_renderer,
-                        bool udp_buffer);
+                        isc::dns::MessageRenderer& response_renderer);
     void setVerbose(bool on);
     bool getVerbose() const;
-    void serve(std::string zone_name);
     isc::data::ElementPtr updateConfig(isc::data::ElementPtr config);
     isc::config::ModuleCCSession* configSession() const;
-    void setConfigSession(isc::config::ModuleCCSession* cs);
+    void setConfigSession(isc::config::ModuleCCSession* config_session);
+
+    ///
+    /// Note: this interface is tentative.  We'll revisit the ASIO and session
+    /// frameworks, at which point the session will probably be passed on
+    /// construction of the server.
+    ///
+    /// \param xfrin_session A Session object over which NOTIFY message
+    /// information is exchanged with a XFRIN handler.
+    /// The session must be established before setting in the server
+    /// object.
+    /// Ownership isn't transferred: the caller is responsible for keeping
+    /// this object to be valid while the server object is working and for
+    /// disconnecting the session and destroying the object when the server
+    ///
+    void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
 private:
     AuthSrvImpl* impl_;
 };

+ 53 - 12
src/bin/auth/main.cc

@@ -14,8 +14,6 @@
 
 // $Id$
 
-#include <config.h>
-
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/select.h>
@@ -39,6 +37,8 @@
 #include <cc/data.h>
 #include <config/ccsession.h>
 
+#include <xfr/xfrout_client.h>
+
 #include <auth/spec_config.h>
 #include <auth/common.h>
 #include <auth/change_user.h>
@@ -50,6 +50,7 @@ using namespace isc::data;
 using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::dns;
+using namespace isc::xfr;
 
 namespace {
 
@@ -87,7 +88,7 @@ my_command_handler(const string& command, const ElementPtr args) {
 
 void
 usage() {
-    cerr << "Usage: b10-auth [-p port] [-4|-6] [-nv]" << endl;
+    cerr << "Usage: b10-auth [-a address] [-p port] [-4|-6] [-nv]" << endl;
     exit(1);
 }
 } // end of anonymous namespace
@@ -143,9 +144,19 @@ main(int argc, char* argv[]) {
         usage();
     }
 
+    if ((!use_ipv4 || !use_ipv6) && address != NULL) {
+        cerr << "[b10-auth] Error: -4|-6 and -a can't coexist" << endl;
+        usage();
+    }
+
     int ret = 0;
+
+    // XXX: we should eventually pass io_service here.
     Session* cc_session = NULL;
-    ModuleCCSession* cs = NULL;
+    Session* xfrin_session = NULL;
+    bool xfrin_session_established = false; // XXX (see Trac #287)
+    ModuleCCSession* config_session = NULL;
+    XfroutClient xfrout_client(UNIX_SOCKET_FILE);
     try {
         string specfile;
         if (getenv("B10_FROM_BUILD")) {
@@ -155,26 +166,50 @@ main(int argc, char* argv[]) {
             specfile = string(AUTH_SPECFILE_LOCATION);
         }
 
-        auth_server = new AuthSrv(cache);
+        auth_server = new AuthSrv(cache, xfrout_client);
         auth_server->setVerbose(verbose_mode);
         cout << "[b10-auth] Server created." << endl;
 
-        io_service = new asio_link::IOService(auth_server, address, port,
-                                              use_ipv4, use_ipv6);
+        if (address != NULL) {
+            // XXX: we can only specify at most one explicit address.
+            // This also means the server cannot run in the dual address
+            // family mode if explicit addresses need to be specified.
+            // We don't bother to fix this problem, however.  The -a option
+            // is a short term workaround until we support dynamic listening
+            // port allocation.
+            io_service = new asio_link::IOService(auth_server, *port,
+                                                  *address);
+        } else {
+            io_service = new asio_link::IOService(auth_server, *port,
+                                                  use_ipv4, use_ipv6);
+        }
         cout << "[b10-auth] IOService created." << endl;
 
         cc_session = new Session(io_service->get_io_service());
-        cout << "[b10-auth] Session channel created." << endl;
+        cout << "[b10-auth] Configuration session channel created." << endl;
 
-        cs = new ModuleCCSession(specfile, *cc_session, my_config_handler,
-                                 my_command_handler);
+        config_session = new ModuleCCSession(specfile, *cc_session,
+                                             my_config_handler,
+                                             my_command_handler);
         cout << "[b10-auth] Configuration channel established." << endl;
 
         if (uid != NULL) {
             changeUser(uid);
         }
 
-        auth_server->setConfigSession(cs);
+        xfrin_session = new Session(io_service->get_io_service());
+        cout << "[b10-auth] Xfrin session channel created." << endl;
+        xfrin_session->establish(NULL);
+        xfrin_session_established = true;
+        cout << "[b10-auth] Xfrin session channel established." << endl;
+
+        // XXX: with the current interface to asio_link we have to create
+        // auth_server before io_service while Session needs io_service.
+        // In a next step of refactoring we should make asio_link independent
+        // from auth_server, and create io_service, auth_server, and
+        // sessions in that order.
+        auth_server->setXfrinSession(xfrin_session);
+        auth_server->setConfigSession(config_session);
         auth_server->updateConfig(ElementPtr());
 
         cout << "[b10-auth] Server started." << endl;
@@ -184,9 +219,15 @@ main(int argc, char* argv[]) {
         ret = 1;
     }
 
-    delete cs;
+    if (xfrin_session_established) {
+        xfrin_session->disconnect();
+    }
+
+    delete xfrin_session;
+    delete config_session;
     delete cc_session;
     delete io_service;
     delete auth_server;
+
     return (ret);
 }

+ 3 - 0
src/bin/auth/tests/Makefile.am

@@ -16,6 +16,7 @@ run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
+run_unittests_SOURCES += asio_link_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -26,6 +27,8 @@ run_unittests_LDADD +=  $(top_builddir)/src/lib/dns/.libs/libdns++.a
 run_unittests_LDADD += $(top_builddir)/src/lib/config/.libs/libcfgclient.a
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/.libs/libcc.a
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/.libs/libexceptions.a
+run_unittests_LDADD += $(top_builddir)/src/bin/auth/libasio_link.a
+run_unittests_LDADD += $(top_builddir)/src/lib/xfr/.libs/libxfr.a
 endif
 
 noinst_PROGRAMS = $(TESTS)

+ 357 - 0
src/bin/auth/tests/asio_link_unittest.cc

@@ -0,0 +1,357 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <stdint.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <auth/asio_link.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace asio_link;
+
+namespace {
+const char* const TEST_PORT = "53535";
+const char* const TEST_IPV6_ADDR = "::1";
+const char* const TEST_IPV4_ADDR = "127.0.0.1";
+// This data is intended to be valid as a DNS/TCP-like message: the first
+// two octets encode the length of the rest of the data.  This is crucial
+// for the tests below.
+const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+
+TEST(IOAddressTest, fromText) {
+    IOAddress io_address_v4("192.0.2.1");
+    EXPECT_EQ("192.0.2.1", io_address_v4.toText());
+
+    IOAddress io_address_v6("2001:db8::1234");
+    EXPECT_EQ("2001:db8::1234", io_address_v6.toText());
+
+    // bogus IPv4 address-like input
+    EXPECT_THROW(IOAddress("192.0.2.2.1"), IOError);
+
+    // bogus IPv6 address-like input
+    EXPECT_THROW(IOAddress("2001:db8:::1234"), IOError);
+}
+
+TEST(IOEndpointTest, create) {
+    const IOEndpoint* ep;
+    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300);
+    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+    delete ep;
+
+    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5300);
+    EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
+    delete ep;
+
+    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5300);
+    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+    delete ep;
+
+    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5300);
+    EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
+    delete ep;
+
+    EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
+                                    5300)->getAddress().toText(),
+                 IOError);
+}
+
+TEST(IOSocketTest, dummySockets) {
+    EXPECT_EQ(IPPROTO_UDP, IOSocket::getDummyUDPSocket().getProtocol());
+    EXPECT_EQ(IPPROTO_TCP, IOSocket::getDummyTCPSocket().getProtocol());
+    EXPECT_EQ(-1, IOSocket::getDummyUDPSocket().getNative());
+    EXPECT_EQ(-1, IOSocket::getDummyTCPSocket().getNative());
+}
+
+TEST(IOServiceTest, badPort) {
+    EXPECT_THROW(IOService(NULL, *"65536", true, false), IOError);
+    EXPECT_THROW(IOService(NULL, *"5300.0", true, false), IOError);
+    EXPECT_THROW(IOService(NULL, *"-1", true, false), IOError);
+    EXPECT_THROW(IOService(NULL, *"domain", true, false), IOError);
+}
+
+TEST(IOServiceTest, badAddress) {
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"192.0.2.1.1"),
+                 IOError);
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"2001:db8:::1"),
+                 IOError);
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"localhost"),
+                 IOError);
+}
+
+TEST(IOServiceTest, unavailableAddress) {
+    // These addresses should generally be unavailable as a valid local
+    // address, although there's no guarantee in theory.
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"255.255.0.0"), IOError);
+
+    // Some OSes would simply reject binding attempt for an AF_INET6 socket
+    // to an IPv4-mapped IPv6 address.  Even if those that allow it, since
+    // the corresponding IPv4 address is the same as the one used in the
+    // AF_INET socket case above, it should at least show the same result
+    // as the previous one.
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, *"::ffff:255.255.0.0"), IOError);
+}
+
+TEST(IOServiceTest, duplicateBind) {
+    // In each sub test case, second attempt should fail due to duplicate bind
+
+    // IPv6, "any" address
+    IOService* io_service = new IOService(NULL, *TEST_PORT, false, true);
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, false, true), IOError);
+    delete io_service;
+
+    // IPv6, specific address
+    io_service = new IOService(NULL, *TEST_PORT, *TEST_IPV6_ADDR);
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, *TEST_IPV6_ADDR), IOError);
+    delete io_service;
+
+    // IPv4, "any" address
+    io_service = new IOService(NULL, *TEST_PORT, true, false);
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, true, false), IOError);
+    delete io_service;
+
+    // IPv4, specific address
+    io_service = new IOService(NULL, *TEST_PORT, *TEST_IPV4_ADDR);
+    EXPECT_THROW(IOService(NULL, *TEST_PORT, *TEST_IPV4_ADDR), IOError);
+    delete io_service;
+}
+
+struct addrinfo*
+resolveAddress(const int family, const int sock_type, const int protocol) {
+    const char* const addr = (family == AF_INET6) ?
+        TEST_IPV6_ADDR : TEST_IPV4_ADDR;
+
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = family;
+    hints.ai_socktype = sock_type;
+    hints.ai_protocol = protocol;
+
+    struct addrinfo* res;
+    const int error = getaddrinfo(addr, TEST_PORT, &hints, &res);
+    if (error != 0) {
+        isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
+    }
+
+    return (res);
+}
+
+// This fixture is a framework for various types of network operations
+// using the ASIO interfaces.  Each test case creates an IOService object,
+// opens a local "client" socket for testing, sends data via the local socket
+// to the service that would run in the IOService object.
+// A mock callback function (an ASIOCallBack object) is registered with the
+// IOService object, so the test code should be able to examine the data
+// receives on the server side.  It then checks the received data matches
+// expected parameters.
+// If initialization parameters of the IOService should be modified, the test
+// case can do it using the setIOService() method.
+// Note: the set of tests in ASIOLinkTest use actual network services and may
+// involve undesirable side effect such as blocking.
+class ASIOLinkTest : public ::testing::Test {
+protected:
+    ASIOLinkTest();
+    ~ASIOLinkTest() {
+        if (res_ != NULL) {
+            freeaddrinfo(res_);
+        }
+        if (sock_ != -1) {
+            close(sock_);
+        }
+        delete io_service_;
+    }
+    void sendUDP(const int family) {
+        res_ = resolveAddress(family, SOCK_DGRAM, IPPROTO_UDP);
+
+        sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+        if (sock_ < 0) {
+            isc_throw(IOError, "failed to open test socket");
+        }
+        const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
+                              res_->ai_addr, res_->ai_addrlen);
+        if (cc != sizeof(test_data)) {
+            isc_throw(IOError, "unexpected sendto result: " << cc);
+        }
+        io_service_->run();
+    }
+    void sendTCP(const int family) {
+        res_ = resolveAddress(family, SOCK_STREAM, IPPROTO_TCP);
+
+        sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+        if (sock_ < 0) {
+            isc_throw(IOError, "failed to open test socket");
+        }
+        if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+            isc_throw(IOError, "failed to connect to the test server");
+        }
+        const int cc = send(sock_, test_data, sizeof(test_data), 0);
+        if (cc != sizeof(test_data)) {
+            isc_throw(IOError, "unexpected sendto result: " << cc);
+        }
+        io_service_->run();
+    }
+    void setIOService(const char& address) {
+        delete io_service_;
+        io_service_ = NULL;
+        io_service_ = new IOService(NULL, *TEST_PORT, address);
+        io_service_->setCallBack(ASIOCallBack(this));
+    }
+    void setIOService(const bool use_ipv4, const bool use_ipv6) {
+        delete io_service_;
+        io_service_ = NULL;
+        io_service_ = new IOService(NULL, *TEST_PORT, use_ipv4, use_ipv6);
+        io_service_->setCallBack(ASIOCallBack(this));
+    }
+    void doTest(const int family, const int protocol) {
+        if (protocol == IPPROTO_UDP) {
+            sendUDP(family);
+        } else {
+            sendTCP(family);
+        }
+
+        // There doesn't seem to be an effective test for the validity of
+        // 'native'.
+        // One thing we are sure is it must be different from our local socket.
+        EXPECT_NE(sock_, callback_native_);
+        EXPECT_EQ(protocol, callback_protocol_);
+        EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
+                  callback_address_);
+
+        const uint8_t* expected_data =
+            protocol == IPPROTO_UDP ? test_data : test_data + 2;
+        const size_t expected_datasize =
+            protocol == IPPROTO_UDP ? sizeof(test_data) :
+            sizeof(test_data) - 2;
+        EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
+                            callback_data_.size(),
+                            expected_data, expected_datasize);
+    }
+private:
+    class ASIOCallBack : public std::unary_function<IOMessage, void> {
+    public:
+        ASIOCallBack(ASIOLinkTest* test_obj) : test_obj_(test_obj) {}
+        void operator()(const IOMessage& io_message) const {
+            test_obj_->callBack(io_message);
+        }
+    private:
+        ASIOLinkTest* test_obj_;
+    };
+    void callBack(const IOMessage& io_message) {
+        callback_protocol_ = io_message.getSocket().getProtocol();
+        callback_native_ = io_message.getSocket().getNative();
+        callback_address_ =
+            io_message.getRemoteEndpoint().getAddress().toText();
+        callback_data_.assign(
+            static_cast<const uint8_t*>(io_message.getData()),
+            static_cast<const uint8_t*>(io_message.getData()) +
+            io_message.getDataSize());
+        io_service_->stop();
+    }
+protected:
+    IOService* io_service_;
+    int callback_protocol_;
+    int callback_native_;
+    string callback_address_;
+    vector<uint8_t> callback_data_;
+    int sock_;
+private:
+    struct addrinfo* res_;
+};
+
+ASIOLinkTest::ASIOLinkTest() :
+    io_service_(NULL), sock_(-1), res_(NULL)
+{
+    setIOService(true, true);
+}
+
+TEST_F(ASIOLinkTest, v6UDPSend) {
+    doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(ASIOLinkTest, v6TCPSend) {
+    doTest(AF_INET6, IPPROTO_TCP);
+}
+
+TEST_F(ASIOLinkTest, v4UDPSend) {
+    doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(ASIOLinkTest, v4TCPSend) {
+    doTest(AF_INET, IPPROTO_TCP);
+}
+
+TEST_F(ASIOLinkTest, v6UDPSendSpecific) {
+    // Explicitly set a specific address to be bound to the socket.
+    // The subsequent test does not directly ensures the underlying socket
+    // is bound to the expected address, but the success of the tests should
+    // reasonably suggest it works as intended.
+    // Specifying an address also implicitly means the service runs in a
+    // single address-family mode.  In tests using TCP we can confirm that
+    // by trying to make a connection and seeing a failure.  In UDP, it'd be
+    // more complicated because we need to use a connected socket and catch
+    // an error on a subsequent read operation.  We could do it, but for
+    // simplicity we only tests the easier cases for now.
+
+    setIOService(*TEST_IPV6_ADDR);
+    doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(ASIOLinkTest, v6TCPSendSpecific) {
+    setIOService(*TEST_IPV6_ADDR);
+    doTest(AF_INET6, IPPROTO_TCP);
+
+    EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(ASIOLinkTest, v4UDPSendSpecific) {
+    setIOService(*TEST_IPV4_ADDR);
+    doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(ASIOLinkTest, v4TCPSendSpecific) {
+    setIOService(*TEST_IPV4_ADDR);
+    doTest(AF_INET, IPPROTO_TCP);
+
+    EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(ASIOLinkTest, v6TCPOnly) {
+    // Open only IPv6 TCP socket.  A subsequent attempt of establishing an
+    // IPv4/TCP connection should fail.  See above for why we only test this
+    // for TCP.
+    setIOService(false, true);
+    EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(ASIOLinkTest, v4TCPOnly) {
+    setIOService(true, false);
+    EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+}

+ 495 - 39
src/bin/auth/tests/auth_srv_unittest.cc

@@ -14,6 +14,8 @@
 
 // $Id$
 
+#include <config.h>
+
 #include <gtest/gtest.h>
 
 #include <dns/buffer.h>
@@ -24,33 +26,105 @@
 #include <dns/rrtype.h>
 
 #include <cc/data.h>
+#include <cc/session.h>
+
+#include <xfr/xfrout_client.h>
 
 #include <auth/auth_srv.h>
+#include <auth/asio_link.h>
 
 #include <dns/tests/unittest_util.h>
 
 using isc::UnitTestUtil;
 using namespace std;
+using namespace isc::cc;
 using namespace isc::dns;
 using namespace isc::data;
+using namespace isc::xfr;
+using namespace asio_link;
 
 namespace {
-const char* CONFIG_TESTDB =
+const char* const CONFIG_TESTDB =
     "{\"database_file\": \"" TEST_DATA_DIR "/example.sqlite3\"}";
 // The following file must be non existent and must be non"creatable" (see
 // the sqlite3 test).
-const char* BADCONFIG_TESTDB =
+const char* const BADCONFIG_TESTDB =
     "{ \"database_file\": \"" TEST_DATA_DIR "/nodir/notexist\"}";
+const char* const DEFAULT_REMOTE_ADDRESS = "192.0.2.1";
 
 class AuthSrvTest : public ::testing::Test {
+private:
+    class MockXfroutClient : public AbstractXfroutClient {
+    public:
+        MockXfroutClient() :
+            is_connected_(false), connect_ok_(true), send_ok_(true),
+            disconnect_ok_(true)
+        {}
+        virtual void connect();
+        virtual void disconnect();
+        virtual int sendXfroutRequestInfo(int tcp_sock, const void* msg_data,
+                                          uint16_t msg_len);
+        bool isConnected() const { return (is_connected_); }
+        void disableConnect() { connect_ok_ = false; }
+        void disableDisconnect() { disconnect_ok_ = false; }
+        void enableDisconnect() { disconnect_ok_ = true; }
+        void disableSend() { send_ok_ = false; }
+    private:
+        bool is_connected_;
+        bool connect_ok_;
+        bool send_ok_;
+        bool disconnect_ok_;
+    };
+
+    class MockSession : public AbstractSession {
+    public:
+        MockSession() :
+            // by default we return a simple "success" message.
+            msg_(Element::fromJSON("{\"result\": [0, \"SUCCESS\"]}")),
+            send_ok_(true), receive_ok_(true)
+        {}
+        virtual void establish(const char* socket_file);
+        virtual void disconnect();
+        virtual int group_sendmsg(ElementPtr msg, string group,
+                                  string instance, string to);
+        virtual bool group_recvmsg(ElementPtr& envelope, ElementPtr& msg,
+                                   bool nonblock, int seq);
+        virtual void subscribe(string group, string instance);
+        virtual void unsubscribe(string group, string instance);
+        virtual void startRead(boost::function<void()> read_callback);
+        virtual int reply(ElementPtr& envelope, ElementPtr& newmsg);
+        virtual bool hasQueuedMsgs();
+
+        void setMessage(ElementPtr msg) { msg_ = msg; }
+        void disableSend() { send_ok_ = false; }
+        void disableReceive() { receive_ok_ = false; }
+
+        ElementPtr sent_msg;
+        string msg_destination;
+    private:
+        ElementPtr msg_;
+        bool send_ok_;
+        bool receive_ok_;
+    };
+
 protected:
-    AuthSrvTest() : server(true), request_message(Message::RENDER),
+    AuthSrvTest() : server(true, xfrout),
+                    request_message(Message::RENDER),
                     parse_message(Message::PARSE), default_qid(0x1035),
                     opcode(Opcode(Opcode::QUERY())), qname("www.example.com"),
-                    qclass(RRClass::IN()), qtype(RRType::A()), ibuffer(NULL),
-                    request_obuffer(0), request_renderer(request_obuffer),
+                    qclass(RRClass::IN()), qtype(RRType::A()),
+                    io_message(NULL), endpoint(NULL), request_obuffer(0),
+                    request_renderer(request_obuffer),
                     response_obuffer(0), response_renderer(response_obuffer)
-    {}
+    {
+        server.setXfrinSession(&notify_session);
+    }
+    ~AuthSrvTest() {
+        delete io_message;
+        delete endpoint;
+    }
+    MockSession notify_session;
+    MockXfroutClient xfrout;
     AuthSrv server;
     Message request_message;
     Message parse_message;
@@ -59,16 +133,114 @@ protected:
     const Name qname;
     const RRClass qclass;
     const RRType qtype;
-    InputBuffer* ibuffer;
+    IOMessage* io_message;
+    const IOEndpoint* endpoint;
     OutputBuffer request_obuffer;
     MessageRenderer request_renderer;
     OutputBuffer response_obuffer;
     MessageRenderer response_renderer;
     vector<uint8_t> data;
 
-    void createDataFromFile(const char* const datafile);
+    void createDataFromFile(const char* const datafile, int protocol);
+    void createRequestMessage(const Opcode& opcode, const Name& request_name,
+                              const RRClass& rrclass, const RRType& rrtype);
+    void createRequestPacket(const Opcode& opcode, const Name& request_name,
+                             const RRClass& rrclass, const RRType& rrtype,
+                             int protocol);
+    void createRequestPacket(int protocol);
 };
 
+void
+AuthSrvTest::MockSession::establish(const char* socket_file UNUSED_PARAM) {}
+
+void
+AuthSrvTest::MockSession::disconnect() {}
+
+void
+AuthSrvTest::MockSession::subscribe(string group UNUSED_PARAM,
+                                    string instance UNUSED_PARAM)
+{}
+
+void
+AuthSrvTest::MockSession::unsubscribe(string group UNUSED_PARAM,
+                                      string instance UNUSED_PARAM)
+{}
+
+void
+AuthSrvTest::MockSession::startRead(
+    boost::function<void()> read_callback UNUSED_PARAM)
+{}
+
+int
+AuthSrvTest::MockSession::reply(ElementPtr& envelope UNUSED_PARAM,
+                                ElementPtr& newmsg UNUSED_PARAM)
+{
+    return (-1);
+}
+
+bool
+AuthSrvTest::MockSession::hasQueuedMsgs() {
+    return (false);
+}
+
+int
+AuthSrvTest::MockSession::group_sendmsg(ElementPtr msg, string group,
+                                        string instance UNUSED_PARAM,
+                                        string to UNUSED_PARAM)
+{
+    if (!send_ok_) {
+        isc_throw(XfroutError, "mock session send is disabled for test");
+    }
+
+    sent_msg = msg;
+    msg_destination = group;
+    return (0);
+}
+
+bool
+AuthSrvTest::MockSession::group_recvmsg(ElementPtr& envelope UNUSED_PARAM,
+                                        ElementPtr& msg,
+                                        bool nonblock UNUSED_PARAM,
+                                        int seq UNUSED_PARAM)
+{
+    if (!receive_ok_) {
+        isc_throw(XfroutError, "mock session receive is disabled for test");
+    }
+
+    msg = msg_;
+    return (true);
+}
+
+void
+AuthSrvTest::MockXfroutClient::connect() {
+    if (!connect_ok_) {
+        isc_throw(XfroutError, "xfrout connection disabled for test");
+    }
+    is_connected_ = true;
+}
+
+void
+AuthSrvTest::MockXfroutClient::disconnect() {
+    if (!disconnect_ok_) {
+        isc_throw(XfroutError,
+                  "closing xfrout connection is disabled for test");
+    }
+    is_connected_ = false;
+}
+
+int
+AuthSrvTest::MockXfroutClient::sendXfroutRequestInfo(
+    const int tcp_sock UNUSED_PARAM,
+    const void* msg_data UNUSED_PARAM,
+    const uint16_t msg_len UNUSED_PARAM)
+{
+    if (!send_ok_) {
+        isc_throw(XfroutError, "xfrout connection send is disabled for test");
+    }
+    return (0);
+}
+
+
 // These are flags to indicate whether the corresponding flag bit of the
 // DNS header is to be set in the test cases.  (Note that the flag values
 // is irrelevant to their wire-format values)
@@ -81,12 +253,56 @@ const unsigned int AD_FLAG = 0x20;
 const unsigned int CD_FLAG = 0x40;
 
 void
-AuthSrvTest::createDataFromFile(const char* const datafile) {
-    delete ibuffer;
+AuthSrvTest::createDataFromFile(const char* const datafile,
+                                const int protocol = IPPROTO_UDP)
+{
+    delete io_message;
     data.clear();
 
+    delete endpoint;
+    endpoint = IOEndpoint::create(protocol,
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
     UnitTestUtil::readWireData(datafile, data);
-    ibuffer = new InputBuffer(&data[0], data.size());
+    io_message = new IOMessage(&data[0], data.size(),
+                               protocol == IPPROTO_UDP ?
+                               IOSocket::getDummyUDPSocket() :
+                               IOSocket::getDummyTCPSocket(), *endpoint);
+}
+
+void
+AuthSrvTest::createRequestMessage(const Opcode& opcode,
+                                  const Name& request_name,
+                                  const RRClass& rrclass,
+                                  const RRType& rrtype)
+{
+    request_message.clear(Message::RENDER);
+    request_message.setOpcode(opcode);
+    request_message.setQid(default_qid);
+    request_message.addQuestion(Question(request_name, rrclass, rrtype));
+}
+
+void
+AuthSrvTest::createRequestPacket(const Opcode& opcode,
+                                 const Name& request_name,
+                                 const RRClass& rrclass, const RRType& rrtype,
+                                 const int protocol = IPPROTO_UDP)
+{
+    createRequestMessage(opcode, request_name, rrclass, rrtype);
+    createRequestPacket(protocol);
+}
+
+void
+AuthSrvTest::createRequestPacket(const int protocol = IPPROTO_UDP) {
+    request_message.toWire(request_renderer);
+
+    delete io_message;
+    endpoint = IOEndpoint::create(protocol,
+                                  IOAddress(DEFAULT_REMOTE_ADDRESS), 5300);
+    io_message = new IOMessage(request_renderer.getData(),
+                               request_renderer.getLength(),
+                               protocol == IPPROTO_UDP ?
+                               IOSocket::getDummyUDPSocket() :
+                               IOSocket::getDummyTCPSocket(), *endpoint);
 }
 
 void
@@ -115,15 +331,19 @@ headerCheck(const Message& message, const qid_t qid, const Rcode& rcode,
 
 // Unsupported requests.  Should result in NOTIMP.
 TEST_F(AuthSrvTest, unsupportedRequest) {
-    for (unsigned int i = 1; i < 16; ++i) {
+    for (unsigned int i = 0; i < 16; ++i) {
         // set Opcode to 'i', which iterators over all possible codes except
-        // the standard query (0)
+        // the standard query and notify
+        if (i == Opcode::QUERY().getCode() ||
+            i == Opcode::NOTIFY().getCode()) {
+            continue;
+        }
         createDataFromFile("simplequery_fromWire");
         data[2] = ((i << 3) & 0xff);
 
         parse_message.clear(Message::PARSE);
-        EXPECT_EQ(true, server.processMessage(*ibuffer, parse_message,
-                                              response_renderer, true));
+        EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                              response_renderer));
         headerCheck(parse_message, default_qid, Rcode::NOTIMP(), i, QR_FLAG,
                     0, 0, 0, 0);
     }
@@ -141,8 +361,8 @@ TEST_F(AuthSrvTest, verbose) {
 // Multiple questions.  Should result in FORMERR.
 TEST_F(AuthSrvTest, multiQuestion) {
     createDataFromFile("multiquestion_fromWire");
-    EXPECT_EQ(true, server.processMessage(*ibuffer, parse_message,
-                                          response_renderer, true));
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
     headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
                 QR_FLAG, 2, 0, 0, 0);
 
@@ -162,8 +382,8 @@ TEST_F(AuthSrvTest, multiQuestion) {
 // dropped.
 TEST_F(AuthSrvTest, shortMessage) {
     createDataFromFile("shortmessage_fromWire");
-    EXPECT_EQ(false, server.processMessage(*ibuffer, parse_message,
-                                           response_renderer, true));
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
 }
 
 // Response messages.  Must be silently dropped, whether it's a valid response
@@ -171,26 +391,26 @@ TEST_F(AuthSrvTest, shortMessage) {
 TEST_F(AuthSrvTest, response) {
     // A valid (although unusual) response
     createDataFromFile("simpleresponse_fromWire");
-    EXPECT_EQ(false, server.processMessage(*ibuffer, parse_message,
-                                           response_renderer, true));
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
 
     // A response with a broken question section.  must be dropped rather than
     // returning FORMERR.
     createDataFromFile("shortresponse_fromWire");
-    EXPECT_EQ(false, server.processMessage(*ibuffer, parse_message,
-                                           response_renderer, true));
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
 
     // A response to iquery.  must be dropped rather than returning NOTIMP.
     createDataFromFile("iqueryresponse_fromWire");
-    EXPECT_EQ(false, server.processMessage(*ibuffer, parse_message,
-                                           response_renderer, true));
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
 }
 
 // Query with a broken question
 TEST_F(AuthSrvTest, shortQuestion) {
     createDataFromFile("shortquestion_fromWire");
-    EXPECT_EQ(true, server.processMessage(*ibuffer, parse_message,
-                                          response_renderer, true));
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
     // Since the query's question is broken, the question section of the
     // response should be empty.
     headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
@@ -200,8 +420,8 @@ TEST_F(AuthSrvTest, shortQuestion) {
 // Query with a broken answer section
 TEST_F(AuthSrvTest, shortAnswer) {
     createDataFromFile("shortanswer_fromWire");
-    EXPECT_EQ(true, server.processMessage(*ibuffer, parse_message,
-                                          response_renderer, true));
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
 
     // This is a bogus query, but question section is valid.  So the response
     // should copy the question section.
@@ -219,8 +439,8 @@ TEST_F(AuthSrvTest, shortAnswer) {
 // Query with unsupported version of EDNS.
 TEST_F(AuthSrvTest, ednsBadVers) {
     createDataFromFile("queryBadEDNS_fromWire");
-    EXPECT_EQ(true, server.processMessage(*ibuffer, parse_message,
-                                          response_renderer, true));
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
 
     // The response must have an EDNS OPT RR in the additional section.
     // Note that the DNSSEC DO bit is cleared even if this bit in the query
@@ -231,6 +451,242 @@ TEST_F(AuthSrvTest, ednsBadVers) {
     EXPECT_FALSE(parse_message.isDNSSECSupported());
 }
 
+TEST_F(AuthSrvTest, AXFROverUDP) {
+    // AXFR over UDP is invalid and should result in FORMERR.
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::AXFR(), IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::FORMERR(), opcode.getCode(),
+                QR_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(AuthSrvTest, AXFRSuccess) {
+    EXPECT_FALSE(xfrout.isConnected());
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::AXFR(), IPPROTO_TCP);
+    // On success, the AXFR query has been passed to a separate process,
+    // so we shouldn't have to respond.
+    EXPECT_EQ(false, server.processMessage(*io_message, parse_message,
+                                           response_renderer));
+    EXPECT_TRUE(xfrout.isConnected());
+}
+
+TEST_F(AuthSrvTest, AXFRConnectFail) {
+    EXPECT_FALSE(xfrout.isConnected()); // check prerequisite
+    xfrout.disableConnect();
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::AXFR(), IPPROTO_TCP);
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+    EXPECT_FALSE(xfrout.isConnected());
+}
+
+TEST_F(AuthSrvTest, AXFRSendFail) {
+    // first send a valid query, making the connection with the xfr process
+    // open.
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::AXFR(), IPPROTO_TCP);
+    server.processMessage(*io_message, parse_message, response_renderer);
+    EXPECT_TRUE(xfrout.isConnected());
+
+    xfrout.disableSend();
+    parse_message.clear(Message::PARSE);
+    response_renderer.clear();
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::AXFR(), IPPROTO_TCP);
+    EXPECT_TRUE(server.processMessage(*io_message, parse_message,
+                                      response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+    // The connection should have been closed due to the send failure.
+    EXPECT_FALSE(xfrout.isConnected());
+}
+
+TEST_F(AuthSrvTest, AXFRDisconnectFail) {
+    // In our usage disconnect() shouldn't fail.  So we'll see the exception
+    // should it be thrown.
+    xfrout.disableSend();
+    xfrout.disableDisconnect();
+    createRequestPacket(opcode, Name("example.com"), RRClass::IN(),
+                        RRType::AXFR(), IPPROTO_TCP);
+    EXPECT_THROW(server.processMessage(*io_message, parse_message,
+                                       response_renderer),
+                 XfroutError);
+    EXPECT_TRUE(xfrout.isConnected());
+    // XXX: we need to re-enable disconnect.  otherwise an exception would be
+    // thrown via the destructor of the server.
+    xfrout.enableDisconnect();
+}
+
+TEST_F(AuthSrvTest, notify) {
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+
+    // An internal command message should have been created and sent to an
+    // external module.  Check them.
+    EXPECT_EQ("Xfrin", notify_session.msg_destination);
+    EXPECT_EQ("notify",
+              notify_session.sent_msg->get("command")->get(0)->stringValue());
+    ElementPtr notify_args = notify_session.sent_msg->get("command")->get(1);
+    EXPECT_EQ("example.com.", notify_args->get("zone_name")->stringValue());
+    EXPECT_EQ(DEFAULT_REMOTE_ADDRESS,
+              notify_args->get("master")->stringValue());
+    EXPECT_EQ("IN", notify_args->get("rrclass")->stringValue());
+
+    // On success, the server should return a response to the notify.
+    headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+                Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
+
+    // The question must be identical to that of the received notify
+    ConstQuestionPtr question = *parse_message.beginQuestion();
+    EXPECT_EQ(Name("example.com"), question->getName());
+    EXPECT_EQ(RRClass::IN(), question->getClass());
+    EXPECT_EQ(RRType::SOA(), question->getType());
+}
+
+TEST_F(AuthSrvTest, notifyForCHClass) {
+    // Same as the previous test, but for the CH RRClass.
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::CH(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+
+    // Other conditions should be the same, so simply confirm the RR class is
+    // set correctly.
+    ElementPtr notify_args = notify_session.sent_msg->get("command")->get(1);
+    EXPECT_EQ("CH", notify_args->get("rrclass")->stringValue());
+}
+
+TEST_F(AuthSrvTest, notifyEmptyQuestion) {
+    request_message.clear(Message::RENDER);
+    request_message.setOpcode(Opcode::NOTIFY());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setQid(default_qid);
+    request_message.toWire(request_renderer);
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+                Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
+}
+
+TEST_F(AuthSrvTest, notifyMultiQuestions) {
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    // add one more SOA question
+    request_message.addQuestion(Question(Name("example.com"), RRClass::IN(),
+                                         RRType::SOA()));
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+                Opcode::NOTIFY().getCode(), QR_FLAG, 2, 0, 0, 0);
+}
+
+TEST_F(AuthSrvTest, notifyNonSOAQuestion) {
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::NS());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::FORMERR(),
+                Opcode::NOTIFY().getCode(), QR_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(AuthSrvTest, notifyWithoutAA) {
+    // implicitly leave the AA bit off.  our implementation will accept it.
+    createRequestPacket(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+                Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(AuthSrvTest, notifyWithErrorRcode) {
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    request_message.setRcode(Rcode::SERVFAIL());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::NOERROR(),
+                Opcode::NOTIFY().getCode(), QR_FLAG | AA_FLAG, 1, 0, 0, 0);
+}
+
+TEST_F(AuthSrvTest, notifyWithoutSession) {
+    server.setXfrinSession(NULL);
+
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+
+    // we simply ignore the notify and let it be resent if an internal error
+    // happens.
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
+}
+
+TEST_F(AuthSrvTest, notifySendFail) {
+    notify_session.disableSend();
+
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
+}
+
+TEST_F(AuthSrvTest, notifyReceiveFail) {
+    notify_session.disableReceive();
+
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
+}
+
+TEST_F(AuthSrvTest, notifyWithBogusSessionMessage) {
+    notify_session.setMessage(Element::fromJSON("{\"foo\": 1}"));
+
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
+}
+
+TEST_F(AuthSrvTest, notifyWithSessionMessageError) {
+    notify_session.setMessage(
+        Element::fromJSON("{\"result\": [1, \"FAIL\"]}"));
+
+    createRequestMessage(Opcode::NOTIFY(), Name("example.com"), RRClass::IN(),
+                        RRType::SOA());
+    request_message.setHeaderFlag(MessageFlag::AA());
+    createRequestPacket(IPPROTO_UDP);
+    EXPECT_FALSE(server.processMessage(*io_message, parse_message,
+                                       response_renderer));
+}
+
 void
 updateConfig(AuthSrv* server, const char* const dbfile,
              const bool expect_success)
@@ -253,8 +709,8 @@ TEST_F(AuthSrvTest, updateConfig) {
     // response should have the AA flag on, and have an RR in each answer
     // and authority section.
     createDataFromFile("examplequery_fromWire");
-    EXPECT_EQ(true, server.processMessage(*ibuffer, parse_message,
-                                          response_renderer, true));
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
     headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
@@ -267,10 +723,10 @@ TEST_F(AuthSrvTest, datasourceFail) {
     // in a SERVFAIL response, and the answer and authority sections should
     // be empty.
     createDataFromFile("badExampleQuery_fromWire");
-    EXPECT_EQ(true, server.processMessage(*ibuffer, parse_message,
-                                          response_renderer, true));
-    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(), opcode.getCode(),
-                QR_FLAG, 1, 0, 0, 0);
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
+    headerCheck(parse_message, default_qid, Rcode::SERVFAIL(),
+                opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
 }
 
 TEST_F(AuthSrvTest, updateConfigFail) {
@@ -282,8 +738,8 @@ TEST_F(AuthSrvTest, updateConfigFail) {
 
     // The original data source should still exist.
     createDataFromFile("examplequery_fromWire");
-    EXPECT_EQ(true, server.processMessage(*ibuffer, parse_message,
-                                          response_renderer, true));
+    EXPECT_EQ(true, server.processMessage(*io_message, parse_message,
+                                          response_renderer));
     headerCheck(parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
                 QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }

+ 12 - 2
src/bin/bindctl/bindcmd.py

@@ -35,7 +35,7 @@ import os, time, random, re
 import getpass
 from hashlib import sha1
 import csv
-import ast
+import json
 import pwd
 import getpass
 import traceback
@@ -543,6 +543,16 @@ class BindCmdInterpreter(Cmd):
                     identifier = cmd.params['identifier']
                 else:
                     identifier += cmd.params['identifier']
+
+                # Check if the module is known; for unknown modules
+                # we currently deny setting preferences, as we have
+                # no way yet to determine if they are ok.
+                module_name = identifier.split('/')[1]
+                if self.config_data is None or \
+                   not self.config_data.have_specification(module_name):
+                    print("Error: Module '" + module_name + "' unknown or not running")
+                    return
+
             if cmd.command == "show":
                 values = self.config_data.get_value_maps(identifier)
                 for value_map in values:
@@ -568,7 +578,7 @@ class BindCmdInterpreter(Cmd):
                 else:
                     parsed_value = None
                     try:
-                        parsed_value = ast.literal_eval(cmd.params['value'])
+                        parsed_value = json.loads(cmd.params['value'])
                     except Exception as exc:
                         # ok could be an unquoted string, interpret as such
                         parsed_value = cmd.params['value']

+ 5 - 0
src/bin/bindctl/tests/bindctl_test.py

@@ -237,6 +237,11 @@ class TestNameSequence(unittest.TestCase):
             assert self.random_names[i] == cmd_names[i+1]
             assert self.random_names[i] == module_names[i+1]
             i = i + 1
+
+    def test_apply_cfg_command(self):
+        self.tool.location = '/'
+        cmd = cmdparse.BindCmdParse("config set identifier=\"foo/bar\" value=\"5\"")
+        self.tool.apply_config_cmd(cmd)
     
 class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
     def __init__(self):

+ 2 - 1
src/bin/cmdctl/TODO

@@ -3,4 +3,5 @@
 . Add check for the content of key/certificate file
   (when cmdctl starts or is configured by bindctl).
 . Use only one msgq/session to communicate with other modules?
-
+. Add more test cases, especially about the cases where CmdctlException
+  is raised

+ 17 - 10
src/bin/cmdctl/cmdctl.py.in

@@ -441,7 +441,11 @@ class SecureHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
                  CommandControlClass,
                  idle_timeout = 1200, verbose = False):
         '''idle_timeout: the max idle time for login'''
-        http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass)
+        try:
+            http.server.HTTPServer.__init__(self, server_address, RequestHandlerClass)
+        except socket.error as err:
+            raise CmdctlException("Error creating server, because: %s \n" % str(err))
+
         self.user_sessions = {}
         self.idle_timeout = idle_timeout
         self.cmdctl = CommandControlClass(self, verbose)
@@ -587,20 +591,23 @@ def set_cmd_options(parser):
             help="display more about what is going on")
 
 if __name__ == '__main__':
+    set_signal_handler()
+    parser = OptionParser(version = __version__)
+    set_cmd_options(parser)
+    (options, args) = parser.parse_args()
+    result = 1                  # in case of failure
     try:
-        set_signal_handler()
-        parser = OptionParser(version = __version__)
-        set_cmd_options(parser)
-        (options, args) = parser.parse_args()
         run(options.addr, options.port, options.idle_timeout, options.verbose)
-    except isc.cc.SessionError as se:
+        result = 0
+    except isc.cc.SessionError as err:
         sys.stderr.write("[b10-cmdctl] Error creating b10-cmdctl, "
-                "is the command channel daemon running?\n")        
+                         "is the command channel daemon running?\n")        
     except KeyboardInterrupt:
-        sys.stderr.write("[b10-cmdctl] exit http server\n")
+        sys.stderr.write("[b10-cmdctl] exit from Cmdctl\n")
+    except CmdctlException as err:
+        sys.stderr.write("[b10-cmdctl] " + str(err) + "\n")
 
     if httpd:
         httpd.shutdown()
 
-
-
+    sys.exit(result)

+ 7 - 7
src/bin/cmdctl/cmdctl.spec.pre.in

@@ -6,20 +6,20 @@
       {
         "item_name": "key_file",
         "item_type": "string",
-        "item_optional": False,
-        "item_default": '@@SYSCONFDIR@@/@PACKAGE@/cmdctl-keyfile.pem'
+        "item_optional": false,
+        "item_default": "@@SYSCONFDIR@@/@PACKAGE@/cmdctl-keyfile.pem"
       },
       {
         "item_name": "cert_file",
         "item_type": "string",
-        "item_optional": False,
-        "item_default": '@@SYSCONFDIR@@/@PACKAGE@/cmdctl-certfile.pem'
+        "item_optional": false,
+        "item_default": "@@SYSCONFDIR@@/@PACKAGE@/cmdctl-certfile.pem"
       },
       {
         "item_name": "accounts_file",
         "item_type": "string",
-        "item_optional": False,
-        "item_default": '@@SYSCONFDIR@@/@PACKAGE@/cmdctl-accounts.csv'
+        "item_optional": false,
+        "item_default": "@@SYSCONFDIR@@/@PACKAGE@/cmdctl-accounts.csv"
       }
     ],
     "commands": [
@@ -32,7 +32,7 @@
         "command_name": "shutdown",
         "command_description": "shutdown cmdctl",
         "command_args": []
-      },
+      }
     ]
   }
 }

+ 19 - 0
src/bin/cmdctl/tests/cmdctl_test.py

@@ -17,6 +17,7 @@
 import unittest
 import socket
 import tempfile
+import sys
 from cmdctl import *
 
 SPEC_FILE_PATH = '..' + os.sep
@@ -383,13 +384,31 @@ class MySecureHTTPServer(SecureHTTPServer):
 class TestSecureHTTPServer(unittest.TestCase):
     def setUp(self):
         self.old_stdout = sys.stdout
+        self.old_stderr = sys.stderr
         sys.stdout = open(os.devnull, 'w')
+        sys.stderr = sys.stdout
         self.server = MySecureHTTPServer(('localhost', 8080), 
                                          MySecureHTTPRequestHandler,
                                          MyCommandControl, verbose=True)
 
     def tearDown(self):
         sys.stdout = self.old_stdout
+        sys.stderr = self.old_stderr
+
+    def test_addr_in_use(self):
+        server_one = None
+        try:
+            server_one = SecureHTTPServer(('localhost', 53531),
+                                        MySecureHTTPRequestHandler,
+                                        MyCommandControl)
+        except CmdctlException:
+            pass
+        else:
+            self.assertRaises(CmdctlException, SecureHTTPServer,
+                              ('localhost', 53531),
+                              MySecureHTTPRequestHandler, MyCommandControl)
+        if server_one:
+            server_one.server_close()
 
     def test_create_user_info(self):
         self.server._create_user_info('/local/not-exist')

+ 19 - 3
src/bin/xfrin/tests/xfrin_test.py

@@ -412,22 +412,31 @@ class TestXfrin(unittest.TestCase):
         return self.xfr._parse_cmd_params(self.args)
 
     def test_parse_cmd_params(self):
-        name, master_addrinfo, db_file = self._do_parse()
+        name, rrclass, master_addrinfo, db_file = self._do_parse()
         self.assertEqual(master_addrinfo[4][1], int(TEST_MASTER_PORT))
         self.assertEqual(name, TEST_ZONE_NAME)
+        self.assertEqual(rrclass, TEST_RRCLASS)
         self.assertEqual(master_addrinfo[4][0], TEST_MASTER_IPV4_ADDRESS)
         self.assertEqual(db_file, TEST_DB_FILE)
 
     def test_parse_cmd_params_default_port(self):
         del self.args['port']
-        master_addrinfo = self._do_parse()[1]
+        master_addrinfo = self._do_parse()[2]
         self.assertEqual(master_addrinfo[4][1], 53)
 
     def test_parse_cmd_params_ip6master(self):
         self.args['master'] = TEST_MASTER_IPV6_ADDRESS
-        master_addrinfo = self._do_parse()[1]
+        master_addrinfo = self._do_parse()[2]
         self.assertEqual(master_addrinfo[4][0], TEST_MASTER_IPV6_ADDRESS)
 
+    def test_parse_cmd_params_chclass(self):
+        self.args['rrclass'] = 'CH'
+        self.assertEqual(self._do_parse()[1], RRClass.CH())
+
+    def test_parse_cmd_params_bogusclass(self):
+        self.args['rrclass'] = 'XXX'
+        self.assertRaises(XfrinException, self._do_parse)
+
     def test_parse_cmd_params_nozone(self):
         # zone name is mandatory.
         del self.args['zone_name']
@@ -504,6 +513,13 @@ class TestXfrin(unittest.TestCase):
         self.assertEqual(self.xfr.command_handler("refresh",
                                                   self.args)['result'][0], 0)
 
+    def test_command_handler_notify(self):
+        # at this level, refresh is no different than retransfer.
+        self.args['master'] = TEST_MASTER_IPV6_ADDRESS
+        # ...but right now we disable the feature due to security concerns.
+        self.assertEqual(self.xfr.command_handler("notify",
+                                                  self.args)['result'][0], 1)
+
     def test_command_handler_unknown(self):
         self.assertEqual(self.xfr.command_handler("xxx", None)['result'][0], 1)
 

+ 42 - 7
src/bin/xfrin/xfrin.py.in

@@ -404,14 +404,37 @@ a separate method for the convenience of unit tests.
             if command == 'shutdown':
                 self._shutdown_event.set()
             elif command == 'retransfer' or command == 'refresh':
-                # The default RR class is IN.  We should fix this so that
-                # the class is passed in the command arg (where we specify
-                # the default)
-                rrclass = RRClass.IN()
-                zone_name, master_addr, db_file = self._parse_cmd_params(args)
-                ret = self.xfrin_start(zone_name, rrclass, db_file, master_addr,
+                (zone_name, rrclass,
+                 master_addr, db_file) = self._parse_cmd_params(args)
+                ret = self.xfrin_start(zone_name, rrclass, db_file,
+                                       master_addr,
                                    False if command == 'retransfer' else True)
                 answer = create_answer(ret[0], ret[1])
+            elif command == 'notify':
+                # This is the temporary implementation for notify.
+                # actually the notfiy command should be sent to the
+                # Zone Manager module.  Being temporary, we separate this case
+                # from refresh/retransfer while we could (and should otherwise)
+                # share the code.
+                (zone_name, rrclass,
+                 master_addr, db_file) = self._parse_cmd_params(args)
+
+                # XXX: master_addr is the sender of the notify message.
+                # It's very dangerous to naively trust it as the source of
+                # subsequent zone transfer; any remote node can easily exploit
+                # it to mount zone poisoning or DoS attacks.  We should
+                # locally identify the appropriate set of master servers.
+                # For now, we disable the code below.
+                master_is_valid = False
+
+                if master_is_valid:
+                    ret = self.xfrin_start(zone_name, rrclass, db_file,
+                                           master_addr, True)
+                else:
+                    errmsg = 'Failed to validate the master address ('
+                    errmsg += args['master'] + '), ignoring notify'
+                    ret = [1, errmsg]
+                answer = create_answer(ret[0], ret[1])
             else:
                 answer = create_answer(1, 'unknown command: ' + command)
 
@@ -425,6 +448,18 @@ a separate method for the convenience of unit tests.
         if not zone_name:
             raise XfrinException('zone name should be provided')
 
+        rrclass = args.get('rrclass')
+        if not rrclass:
+            # The default RR class is IN.  We should fix this so that
+            # the class is always passed in the command arg (where we specify
+            # the default)
+            rrclass = RRClass.IN()
+        else:
+            try:
+                rrclass = RRClass(rrclass)
+            except InvalidRRClass as e:
+                raise XfrinException('invalid RRClass: ' + rrclass)
+
         master = args.get('master')
         if not master:
             raise XfrinException('master address should be provided')
@@ -450,7 +485,7 @@ a separate method for the convenience of unit tests.
                 db_file = os.environ["B10_FROM_BUILD"] + os.sep + "bind10_zones.sqlite3"
             self._cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
 
-        return (zone_name, master_addrinfo, db_file)
+        return (zone_name, rrclass, master_addrinfo, db_file)
 
     def startup(self):
         while not self._shutdown_event.is_set():

+ 9 - 9
src/bin/xfrin/xfrin.spec.pre.in

@@ -6,37 +6,37 @@
       {
         "item_name": "transfers_in",
         "item_type": "integer",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 10
       }
     ],
     "commands": [
      {
-        'command_name': 'retransfer',
-        "command_description": 'retransfer a single zone without checking zone serial number',
-        'command_args': [ {
+        "command_name": "retransfer",
+        "command_description": "retransfer a single zone without checking zone serial number",
+        "command_args": [ {
             "item_name": "zone_name",
             "item_type": "string",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": ""
           },
           {
             "item_name": "master",
             "item_type": "string",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": ""
           },
           {
             "item_name": "port",
             "item_type": "integer",
-            "item_optional": True,
+            "item_optional": true,
             "item_default": 53
           },
           {
             "item_name": "db_file",
             "item_type": "string",
-            "item_optional": True,
-            "item_default": '@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3'
+            "item_optional": true,
+            "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
           }
         ]
       },

+ 7 - 7
src/bin/xfrout/xfrout.spec.pre.in

@@ -5,43 +5,43 @@
        {
          "item_name": "transfers_out",
          "item_type": "integer",
-         "item_optional": False,
+         "item_optional": false,
          "item_default": 10
        },
        {
          "item_name": "db_file",
          "item_type": "string",
-         "item_optional": False,
+         "item_optional": false,
          "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
        },
        {
          "item_name": "log_name",
          "item_type": "string",
-         "item_optional": False,
+         "item_optional": false,
          "item_default": "Xfrout"
        },
        {
          "item_name": "log_file",
     	 "item_type": "string",
-         "item_optional": False,
+         "item_optional": false,
          "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/log/Xfrout.log"
        },
        {
          "item_name": "log_severity",
     	 "item_type": "string",
-         "item_optional": False,
+         "item_optional": false,
     	 "item_default": "debug"
        },
        {
          "item_name": "log_versions",
     	 "item_type": "integer",
-         "item_optional": False,
+         "item_optional": false,
     	 "item_default": 5
        },
        {
          "item_name": "log_max_bytes",
     	 "item_type": "integer",
-         "item_optional": False,
+         "item_optional": false,
     	 "item_default": 1048576
        }
       ],

+ 1 - 1
src/lib/Makefile.am

@@ -1 +1 @@
-SUBDIRS = exceptions dns cc config datasrc python xfr
+SUBDIRS = exceptions dns cc config datasrc python xfr bench

+ 10 - 0
src/lib/bench/Makefile.am

@@ -0,0 +1,10 @@
+SUBDIRS = . tests example
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libbench.la
+libbench_la_SOURCES = benchmark_util.h benchmark_util.cc
+EXTRA_DIST = benchmark.h

+ 403 - 0
src/lib/bench/benchmark.h

@@ -0,0 +1,403 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __BENCHMARK_H
+#define __BENCHMARK_H 1
+
+#include <sys/time.h>
+
+#include <iostream>
+#include <ios>
+
+namespace isc {
+namespace bench {
+
+/// \brief Templated micro benchmark framework.
+///
+/// "Premature optimization is the root of all evil."
+/// But programmers are often tempted to focus on performance optimization
+/// too early.
+/// Likewise, it's not uncommon for an engineer to introduce a minor
+/// optimization with a lot of complicated code logic that actually improves
+/// performance only marginally.
+/// Making benchmark easier will help avoid such common pitfalls.
+/// Of course, it also helps when we really need to introduce optimization
+/// to identify where is the bottleneck and see how a particular optimization
+/// improves the performance.
+///
+/// The BenchMark class template provides a simple framework for so-called
+/// "stopwatch" micro benchmarking.
+/// It just encapsulates the common operations for this type of benchmark:
+/// repeat a specified operation for a given number of times,
+/// record the start and end times of the operations,
+/// and provide major accumulated results such as the number of iterations
+/// per second.
+/// The main goal of this class is to help developers write benchmark test
+/// cases with fewer strokes of typing.
+///
+/// The constructors of a \c BenchMark class is to take the number of
+/// iterations (which is referred to as \c niter below)
+/// and an object (or reference to it) of the type of the template
+/// parameter, \c T.  Class \c T implements the benchmark target code via
+/// its \c run() method, whose signature is as follows:
+/// \code  unsigned int T::run(); \endcode
+///
+/// A BenchMark class object, via its own \c run() method, calls \c T::run()
+/// for \c niter times.
+/// In the simplest form \c T::run() would perform a single operation to
+/// be benchmarked and returns 1.
+/// In some cases, however, the operation is very lightweight (e.g. performing
+/// a binary search on a moderate length of integer vector), and it may be
+/// desirable to have an internal iterations within \c T::run() to avoid
+/// the overhead of function calls to \c T::run().
+/// In that case, \c T::run() would return the number of internal iterations
+/// instead of 1.
+///
+/// The \c BenchMark::run() method records some statistics %data on the
+/// benchmarking, including the start and end times and the total number of
+/// iterations (which is the sum of the return value of \c T::run(), and,
+/// is equal to \c niter in the simplest case where \c T::run() always
+/// returns 1).
+/// This %data can be retried via other methods of \c BenchMark, but in
+/// the primarily intended use case the \c BenchMark object would calls its
+/// \c run() method at the end of its construction, and prints summarized
+/// statistics to the standard output.
+/// This way, the developer can only write a single line of code to perform
+/// everything other than the benchmark target code (see the example below).
+///
+/// \b Example
+///
+/// Suppose that we want to measure performance of the search (find)
+/// operation on STL set objects.  We'd first define the implementation
+/// class (to be the template parameter of the \c BenchMark class) as follows:
+///
+/// \code class SetSearchBenchMark {
+/// public:
+///    SetSearchBenchMark(const set<int>& data, const vector<int>& keys) :
+///        data_(data), keys_(keys)
+///    {}
+///    unsigned int run() {
+///        vector<int>::const_iterator iter;
+///        vector<int>::const_iterator end_key = keys_.end();
+///        for (iter = keys_.begin(); iter != end_key; ++iter) {
+///            data_.find(*iter);
+///        }        
+///        return (keys_.size());
+///    }
+///    const set<int>& data_;
+///    const vector<int>& keys_;
+/// }; \endcode
+///
+/// In its constructor the \c SetSearchBenchMark class takes a set of
+/// integers (\c %data) and a vector of integers (\c keys).  \c %data is
+/// the STL set to be searched, and \c keys stores the search keys.
+/// The constructor keeps local references to these objects.
+///
+/// The \c SetSearchBenchMark::run() method, which is called via
+/// \c BenchMark::run(), iterates over the key vector, and performs the
+/// \c find() method of the set class for each key.
+/// (This is only for performance measurement, so the result is ignored).
+/// Note that this \c run() method has its own internal iterations.
+/// This is because each invocation of \c find() would be pretty lightweight,
+/// and the function call overhead may be relatively heavier.
+/// Due to the internal iterations, this method returns the number of
+/// \c find() calls, which is equal to the size of the key vector.
+///
+/// Next, we should prepare test %data.  In this simple example, let's assume
+/// we use a fixed size: a set of 10,000 integers (from 0 to 9999), and use
+/// the same number of search keys randomly chosen from that range:
+/// \code
+///    set<int> data_set;
+///    vector<int> keys;
+///    for (int i = 0; i < 10000; ++i) {
+///        data_set.insert(i);
+///        keys.push_back(rand() % 10000);
+///    } \endcode
+///
+/// Then construct a \c BenchMark<SetSearchBenchMark> object with the
+/// test %data:
+/// \code
+///    BenchMark<SetSearchBenchMark>(100, SetSearchBenchMark(data_set, keys));
+/// \endcode
+/// Here we specify 100 for the number of iterations, which would cause
+/// 1 million search attempts in total.
+///
+/// That's it.  If we put these in a C++ source file with usual necessary
+/// stuff (such as \c %main()), compile it, and run the executable, then
+/// we'll see something like this:
+///
+/// \code Performed 1000000 iterations in 0.180172s (5550251.98ips) \endcode
+///
+/// It should be obvious what this means (ips stands for "iterations
+///  per second").
+///
+/// A complete example program of this measurement scenario (with some
+/// additional test cases and configurable parameters) can be found in
+/// example/search_bench.cc.
+///
+/// \b Customization
+///
+/// The above simple usage should be sufficient in many benchmark cases,
+/// but the \c BenchMark class provides some customization points by
+/// specializing some of its (templated) public methods.
+/// For example, assume you want to customize the output of benchmark result.
+/// It can be done by specializing \c BenchMark::printResult():
+/// \code namespace isc {
+/// namespace bench {
+/// template<>
+/// void
+/// BenchMark<SetSearchBenchMark>::printResult() const {
+///     cout << "Searched for " << target_.keys_.size() << " keys "
+///         << getIteration() << " times in " << getDuration() << "s" << endl;
+/// }
+/// }
+/// } \endcode
+///
+/// Then the Result would be something like this:
+///
+/// \code Searched for 10000 keys 1000000 times in 0.21s \endcode
+///
+/// Note that the specialization must be defined in the same namespace as
+/// that of the \c BenchMark class, that is, \c isc::bench.
+/// It should also be noted that the corresponding \c SetSearchBenchMark
+/// object can be accessed (through its public interfaces) via the \c target_
+/// member variable of \c BenchMark.
+///
+/// <b>Future Plans and Compatibility Notes</b>
+///
+/// Currently, benchmark developers need to write supplemental code that is
+/// not directly related to benchmarks (such as \c %main()) by hand.
+/// It would be better if we could minimize such development overhead.
+/// In future versions we may provide a common \c %main() function and
+/// option parsers, thereby allowing the developer to only write the benchmark
+/// classes and invoke them, just like what various unit test frameworks do.
+///
+/// If and when we implement it, some existing benchmark cases may need to be
+/// adjusted.
+template <typename T>
+class BenchMark {
+    ///
+    /// \name Constructors
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non-copyable.
+    /// We use the default destructor.
+    //@{
+private:
+    BenchMark(const BenchMark& source);
+    BenchMark& operator=(const BenchMark& source);
+public:
+    /// \bench Constructor for immediate run.
+    ///
+    /// This is the constructor that is expected to be used normally.
+    /// It runs the benchmark within the constructor and prints the result,
+    /// thereby making it possible to do everything with a single line of
+    /// code (see the above example).
+    ///
+    /// \param iterations The number of iterations.  The \c run() method will
+    /// be called this number of times.
+    /// \param target The templated class object that
+    /// implements the code to be benchmarked.
+    BenchMark(const int iterations, T target) :
+        iterations_(iterations), sub_iterations_(0), target_(target)
+    {
+        initialize(true);
+    }
+
+    /// \bench Constructor for finer-grained control.
+    ///
+    /// This constructor takes the third parameter, \c immediate, to control
+    /// whether to run the benchmark within the constructor.
+    /// It also takes a reference to the templated class object rather than
+    /// an object copy, so if the copy overhead isn't acceptable this
+    /// constructor must be used.
+    ///
+    /// \param iterations The number of iterations.  The \c run() method will
+    /// be called this number of times.
+    /// \param target A reference to the templated class object that
+    /// implements the code to be benchmarked.
+    /// \param immediate If \c true the benchmark will be performed within
+    /// the constructor; otherwise it only does initialization.
+    BenchMark(const int iterations, T& target, const bool immediate) :
+        iterations_(iterations), sub_iterations_(0), target_(target)
+    {
+        initialize(immediate);
+    }
+    //@}
+
+    /// \brief Hook to be called before starting benchmark.
+    ///
+    /// This method will be called from \c run() before starting the benchmark.
+    /// By default it's empty, but can be customized via template
+    /// specialization.
+    void setUp() {}
+
+    /// \brief Hook to be called after benchmark.
+    ///
+    /// This method will be called from \c run() when the benchmark completes.
+    /// By default it's empty, but can be customized via template
+    /// specialization.
+    void tearDown() {}
+
+    /// \brief Perform benchmark.
+    ///
+    /// This method first calls \c setUp().
+    /// It then records the current time, calls \c T::run() for the number
+    /// of times specified on construction, and records the time on completion.
+    /// Finally, it calls \c tearDown().
+    void run() {
+        setUp();
+
+        struct timeval beg, end;
+        gettimeofday(&beg, NULL);
+        for (int i = 0; i < iterations_; ++i) {
+            sub_iterations_ += target_.run();
+        }
+        gettimeofday(&end, NULL);
+        tv_diff_ = tv_subtract(end, beg);
+
+        tearDown();
+    }
+
+    /// \brief Print the benchmark result.
+    ///
+    /// This method prints the benchmark result in a common style to the
+    /// standard out.  The result contains the number of total iterations,
+    /// the duration of the test, and the number of iterations per second
+    /// calculated from the previous two parameters.
+    ///
+    /// A call to this method is only meaningful after the completion of
+    /// \c run().  The behavior is undefined in other cases.
+    void printResult() const {
+        std::cout.precision(6);
+        std::cout << "Performed " << getIteration() << " iterations in "
+                  << std::fixed << getDuration() << "s";
+        std::cout.precision(2);
+        std::cout << " (" << std::fixed << getIterationPerSecond() << "ips)"
+                  << std::endl;
+    }
+
+    /// \brief Return the number of iterations.
+    ///
+    /// It returns the total iterations of benchmark, which is the sum
+    /// of the return value of \c T::run() over all calls to it
+    /// (note that it may not equal to the number of calls to \c T::run(),
+    /// which was specified on construction of this class).
+    ///
+    /// A call to this method is only meaningful after the completion of
+    /// \c run().  The behavior is undefined in other cases.
+    unsigned int getIteration() const { return (sub_iterations_); }
+
+    /// \brief Return the duration of benchmark in seconds.
+    ///
+    /// The highest possible precision of this value is microseconds.
+    ///
+    /// A call to this method is only meaningful after the completion of
+    /// \c run().  The behavior is undefined in other cases.
+    double getDuration() const {
+        return (tv_diff_.tv_sec +
+                static_cast<double>(tv_diff_.tv_usec) / ONE_MILLION);
+    }
+
+    /// \brief Return the average duration per iteration in seconds.
+    ///
+    /// The highest possible precision of this value is microseconds.
+    /// The iteration is the sum of the return value of \c T::run() over
+    /// all calls to it (note that it may not equal to the number of calls
+    /// to \c T::run()).
+    ///
+    /// If it cannot calculate the average, it returns \c TIME_FAILURE.
+    ///
+    /// A call to this method is only meaningful after the completion of
+    /// \c run().  The behavior is undefined in other cases.
+    double getAverageTime() const {
+        if (sub_iterations_ == 0) {
+            return (TIME_FAILURE);
+        }
+        return ((tv_diff_.tv_sec +
+                 static_cast<double>(tv_diff_.tv_usec) / ONE_MILLION ) /
+                sub_iterations_);
+    }
+
+    /// \brief Return the number of possible iterations per second based on
+    /// the benchmark result.
+    ///
+    /// If it cannot calculate that number (e.g. because the duration is
+    /// too small) it returns \c ITERATION_FAILURE.
+    /// A call to this method is only meaningful after the completion of
+    /// \c run().  The behavior is undefined in other cases.
+    double getIterationPerSecond() const {
+        const double duration_usec = tv_diff_.tv_sec +
+            static_cast<double>(tv_diff_.tv_usec) / ONE_MILLION;
+        if (duration_usec == 0) {
+            return (ITERATION_FAILURE);
+        }
+        return (sub_iterations_ / duration_usec);
+    }
+public:
+    /// \brief A constant that indicates a failure in \c getAverageTime().
+    ///
+    /// This constant be used as double but is defined as int so that it can
+    /// be initialized within the class definition.  Type conversion will be
+    /// performed implicitly.
+    static const int TIME_FAILURE = -1;
+
+    /// \brief A constant that indicates a failure in
+    /// \c getIterationPerSecond().
+    ///
+    /// This constant be used as double but is defined as int so that it can
+    /// be initialized within the class definition.  Type conversion will be
+    /// performed implicitly.
+    static const int ITERATION_FAILURE = -1;
+private:
+    void initialize(const bool immediate) {
+        if (immediate) {
+            run();
+            printResult();
+        }
+    }
+private:
+    // return t1 - t2
+    struct timeval tv_subtract(const struct timeval& t1,
+                               const struct timeval& t2)
+    {
+        struct timeval result;
+
+        result.tv_sec = t1.tv_sec - t2.tv_sec;
+        if (t1.tv_usec >= t2.tv_usec) {
+            result.tv_usec = t1.tv_usec- t2.tv_usec;
+        } else {
+            result.tv_usec = ONE_MILLION + t1.tv_usec - t2.tv_usec;
+            --result.tv_sec;
+        }
+
+        return (result);
+    }
+private:
+    static const int ONE_MILLION = 1000000;
+    const unsigned int iterations_;
+    unsigned int sub_iterations_;
+    T& target_;
+    struct timeval tv_diff_;
+};
+
+}
+}
+#endif  // __BENCHMARK_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 116 - 0
src/lib/bench/benchmark_util.cc

@@ -0,0 +1,116 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/name.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/rrtype.h>
+#include <dns/rrclass.h>
+#include <dns/question.h>
+
+#include <bench/benchmark_util.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+
+namespace isc {
+namespace bench {
+void
+loadQueryData(const char* const input_file, BenchQueries& queries,
+              const RRClass& qclass, const bool strict)
+{
+    ifstream ifs;
+
+    ifs.open(input_file, ios_base::in);
+    if ((ifs.rdstate() & istream::failbit) != 0) {
+        isc_throw(BenchMarkError, "failed to load query data file: " +
+                  string(input_file));
+    }
+    loadQueryData(ifs, queries, qclass, strict);
+    ifs.close();
+}
+
+void
+loadQueryData(istream& input, BenchQueries& queries, const RRClass& qclass,
+              const bool strict)
+{
+    string line;
+    unsigned int linenum = 0;
+    Message query_message(Message::RENDER);
+    OutputBuffer buffer(128); // this should be sufficiently large
+    MessageRenderer renderer(buffer);
+    while (getline(input, line), !input.eof()) {
+        ++linenum;
+        if (input.bad() || input.fail()) {
+            isc_throw(BenchMarkError,
+                      "Unexpected line in query data file around line " <<
+                      linenum);
+        }
+        if (line.empty() || line[0] == '#') {
+            continue;           // skip comment and blank lines
+        }
+
+        istringstream iss(line);
+        string qname_string, qtype_string;
+        iss >> qname_string >> qtype_string;
+        if (iss.bad() || iss.fail()) {
+            if (strict) {
+                isc_throw(BenchMarkError,
+                          "load query: unexpected input around line " <<
+                          linenum);
+            }
+            continue;
+        }
+
+        // We expect broken lines of data, which will be ignored with a
+        // warning message.
+        try {
+            query_message.clear(Message::RENDER);
+            query_message.setQid(0);
+            query_message.setOpcode(Opcode::QUERY());
+            query_message.setRcode(Rcode::NOERROR());
+            query_message.addQuestion(Question(Name(qname_string), qclass,
+                                               RRType(qtype_string)));
+
+            renderer.clear();
+            query_message.toWire(renderer);
+            vector<unsigned char> query_data(
+                static_cast<const unsigned char*>(buffer.getData()),
+                static_cast<const unsigned char*>(buffer.getData()) +
+                buffer.getLength());
+            queries.push_back(query_data);
+        } catch (const Exception& error) {
+            if (strict) {
+                isc_throw(BenchMarkError,
+                          "failed to parse/create query around line " <<
+                          linenum);
+            }
+            continue;
+        }
+    }
+}
+}
+}

+ 149 - 0
src/lib/bench/benchmark_util.h

@@ -0,0 +1,149 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#ifndef __BENCHMARK_UTIL_H
+#define __BENCHMARK_UTIL_H 1
+
+/// \file
+/// Utilities to help write benchmark cases.
+///
+/// The initial version of this library only contains utilities for very
+/// specific benchmark cases, that is, building DNS query data.
+/// It's not clear if we have more utilities including scenario-independent
+/// ones in future, but we have them here for now.
+/// If we find we only need utilities specific to individual benchmark
+/// scenarios, we may move them to more specific places.
+/// For example, the query generator may go to benchmarks for DNS server
+/// implementations.
+
+#include <istream>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dns {
+class RRClass;
+}
+
+namespace bench {
+/// \brief An exception that is thrown if an error occurs within the benchmark
+/// module.
+class BenchMarkError : public Exception {
+public:
+    BenchMarkError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// \brief A convenient shortcut type to represent a sequence of query %data
+/// in wire format.
+typedef std::vector<std::vector<unsigned char> > BenchQueries; 
+
+/// \brief Load query %data from a file into a vector.
+///
+/// The format of the %data file is a sequence of tuples of query name and
+/// query type.  Each line specifies a single tuple.  Empty lines and
+/// lines beginning with a pound sign (#) are considered comments and will
+/// be ignored.  Example:
+/// \code
+/// # This is a comment line, will be ignored.  same for the next line.
+///
+/// www.example.com AAAA
+/// ftp.example.org NS
+/// text.dot.example TXT \endcode
+///
+/// For those who are familiar with BIND 9's queryperf tool, this is the
+/// same as the simplest form of the input file for queryperf.
+///
+/// For each tuple, this function builds a wire-format non recursive DNS
+/// query message, and appends it to the given vector in a form of
+/// a vector of <code>unsigned char</code>.
+///
+/// The resulting vector can be used, e.g., for benchmarking query processing
+/// code without involving disk access or network I/O.
+/// It would be handier than existing tool such as queryperf and can help
+/// measure the "bare" (or the best possible) performance of the query
+/// processing itself.
+///
+/// If this function fails to open the specified file to read the %data,
+/// an exception of class \c BenchMarkError will be thrown.
+/// If it fails to recognize an input line either as a comment or as
+/// a tuple of strings, an exception of class \c BenchMarkError will be
+/// thrown.
+///
+/// By default, this function does not require the strings be a valid
+/// domain name or a valid textual representation of an RR type.
+/// This is because the input %data may be built from a packet dump of
+/// real query samples without validation, which may contain bogus values.
+/// It would make more sense to just ignore the bogus %data than filter
+/// the sample beforehand.
+/// This behavior can be changed by setting the \c strict argument to
+/// \c true, in which case if this function fails to parse the query name
+/// or the type, it will throw an exception of class \c BenchMarkError.
+///
+/// If memory allocation fails during the processing, a corresponding standard
+/// exception will be thrown.
+///
+/// This function only offers the basic exception guarantee.  That is, if
+/// exception is thrown from this function, it is not guaranteed that
+/// \c queries keeps the content before this function is called.
+/// It is not so difficult to offer a stronger exception guarantee, but
+/// since this function is used in a limited usage, mainly for testing
+/// purposes, its benefit wouldn't outweigh the implementation complexity.
+///
+/// \param input_file A character string specifying the %data file name.
+/// \param queries A vector wherein the query %data is to be stored.
+/// \param qclass The RR class of the resulting queries.  The same RR class
+/// is used for all queries.
+/// \param strict If \c true, apply stricter validation on the query name and
+/// query RR types; otherwise invalid inputs will be ignored.
+void loadQueryData(const char* const input_file, BenchQueries& queries,
+                   const isc::dns::RRClass& qclass, const bool strict = false);
+
+/// \brief Load query %data from an input stream into a vector.
+///
+/// This version of function is same as
+/// loadQueryData(const char*,  BenchQueries&, const isc::dns::RRClass&, const bool)
+/// except it reads the input query sequence from a specified input stream.
+///
+/// This version will be used for a smaller scale test where query %data is
+///  hardcoded in the benchmark source code.  For example, we could build
+/// a sequence of wire-format queries via the following code:
+/// \code
+///    vector<QueryParam> queries;
+///    stringstream qstream;
+///    qstream << "www.example.com AAAA" << endl
+///            << "ftp.example.org NS" << endl
+///            << "text.dot.example TXT" << endl;
+///    loadQueryData(qstream, queries, RRClass::IN()); \endcode
+/// This will result in the same sequence of queries as the example using
+/// a %data file shown in the other version of the function.
+///
+/// \param input An input stream object that is to emit the query sequence.
+/// \param queries A vector wherein the query %data is to be stored.
+/// \param qclass The RR class of the resulting queries.  The same RR class
+/// is used for all queries.
+/// \param strict If \c true, apply stricter validation on the query name and
+/// query RR types; otherwise invalid inputs will be ignored.
+void loadQueryData(std::istream& input, BenchQueries& queries,
+                   const isc::dns::RRClass& qclass, const bool strict = false);
+}
+}
+#endif  // __BENCHMARK_UTIL_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 9 - 0
src/lib/bench/example/Makefile.am

@@ -0,0 +1,9 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+
+CLEANFILES = *.gcno *.gcda
+
+noinst_PROGRAMS = search_bench
+search_bench_SOURCES = search_bench.cc
+
+search_bench_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+

+ 144 - 0
src/lib/bench/example/search_bench.cc

@@ -0,0 +1,144 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <unistd.h>             // for getpid
+
+#include <cstdlib>              // for rand
+#include <algorithm>
+#include <iostream>
+#include <vector>
+#include <set>
+
+#include <exceptions/exceptions.h>
+
+#include <bench/benchmark.h>
+
+using namespace std;
+using namespace isc::bench;
+
+namespace {
+template <bool Sorted>
+class VectorSearchBenchMark {
+public:
+    VectorSearchBenchMark(const vector<int>& data,
+                          const vector<int>& keys) :
+        data_(data), keys_(keys)
+    {}
+    unsigned int run() {
+        vector<int>::const_iterator iter;
+        vector<int>::const_iterator end_key = keys_.end();
+        for (iter = keys_.begin(); iter != end_key; ++iter) {
+            if (Sorted) {
+                binary_search(data_.begin(), data_.end(), *iter);
+            } else {
+                find(data_.begin(), data_.end(), *iter);
+            }
+        }
+        return (keys_.size());
+    }
+private:
+    const vector<int>& data_;
+    const vector<int>& keys_;
+};
+
+class SetSearchBenchMark {
+public:
+    SetSearchBenchMark(const set<int>& data, const vector<int>& keys) :
+        data_(data), keys_(keys)
+    {}
+    unsigned int run() {
+        vector<int>::const_iterator iter;
+        vector<int>::const_iterator end_key = keys_.end();
+        for (iter = keys_.begin(); iter != end_key; ++iter) {
+            data_.find(*iter);
+        }        
+        return (keys_.size());
+    }
+public:   // make it visible to the BenchMark class
+    const set<int>& data_;
+private:
+    const vector<int>& keys_;
+};
+}
+
+namespace isc {
+namespace bench {
+template<>
+void
+BenchMark<SetSearchBenchMark>::setUp() {
+    cout << "Benchmark for searching std::set (size="
+         << target_.data_.size() << ")" << endl;    
+}
+}
+}
+
+namespace {
+const int DEFAULT_ITERATION = 100;
+const int DEFAULT_SIZE = 10000;
+
+void
+usage() {
+    cerr << "Usage: search_bench [-n iterations] [-s data_size]" << endl;
+    exit (1);
+}
+}
+
+int
+main(int argc, char* argv[]) {
+    int ch;
+    int iteration = DEFAULT_ITERATION;
+    int size = DEFAULT_SIZE;
+    while ((ch = getopt(argc, argv, "n:s:")) != -1) {
+        switch (ch) {
+        case 'n':
+            iteration = atoi(optarg);
+            break;
+        case 's':
+            size = atoi(optarg);
+            break;
+        case '?':
+        default:
+            usage();
+        }
+    }
+    argc -= optind;
+    argv += optind;
+    if (argc != 0) {
+        usage();
+    }
+
+    srand(getpid());
+    vector<int> data_vector;
+    set<int> data_set;
+    vector<int> keys;
+    for (int i = 0; i < size; ++i) {
+        data_vector.push_back(i);
+        data_set.insert(i);
+        keys.push_back(rand() % size);
+    }
+
+    cout << "Benchmark for linear search" << endl;
+    BenchMark<VectorSearchBenchMark<false> >(iteration,
+                                             VectorSearchBenchMark<false>(
+                                                 data_vector, keys));
+    cout << "Benchmark for binary search" << endl;
+    BenchMark<VectorSearchBenchMark<true> >(iteration,
+                                             VectorSearchBenchMark<true>(
+                                                 data_vector, keys));
+    BenchMark<SetSearchBenchMark>(iteration,
+                                  SetSearchBenchMark(data_set, keys));
+    return (0);
+}

+ 24 - 0
src/lib/bench/tests/Makefile.am

@@ -0,0 +1,24 @@
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += benchmark_unittest.cc
+run_unittests_SOURCES += loadquery_unittest.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/bench/libbench.la
+run_unittests_LDADD += $(GTEST_LDADD)
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = testdata/query.txt

+ 143 - 0
src/lib/bench/tests/benchmark_unittest.cc

@@ -0,0 +1,143 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <unistd.h>             // for usleep
+
+#include <bench/benchmark.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::bench;
+
+namespace {
+// Our "benchmark" simply sleeps for a short period, and reports a faked
+// number of iterations.
+class TestBenchMark {
+public:
+    TestBenchMark(const int sub_iterations, const int sleep_time) :
+        sub_iterations_(sub_iterations), sleep_time_(sleep_time),
+        setup_completed_(false), teardown_completed_(false)
+    {}
+    unsigned int run() {
+        usleep(sleep_time_);
+        return (sub_iterations_);
+    }
+    const int sub_iterations_;
+    const int sleep_time_;
+    bool setup_completed_;
+    bool teardown_completed_;
+};
+}
+
+namespace isc {
+namespace bench {
+template <>
+void
+BenchMark<TestBenchMark>::setUp() {
+    target_.setup_completed_ = true;
+};
+
+template <>
+void
+BenchMark<TestBenchMark>::tearDown() {
+    target_.teardown_completed_ = true;
+};
+
+// XXX: some compilers cannot find class static constants used in
+// EXPECT_xxx macross, for which we need an explicit definition.
+template <typename T>
+const int BenchMark<T>::TIME_FAILURE;
+}
+}
+
+namespace {
+TEST(BenchMarkTest, run) {
+    // use some uncommon iterations for testing purpose:
+    const int sub_iterations = 23;
+    const int sleep_time = 50000; // will sleep for 50ms
+    // we cannot expect particular accuracy on the measured duration, so
+    // we'll include some conservative margin (20%) and perform range
+    // comparison below.
+    const int duration_margin = 10000; // 10ms
+    const int ONE_MILLION = 1000000;
+
+    // Prerequisite check: since the tests in this case may depend on subtle
+    // timing, it may result in false positives.  There are reportedly systems
+    // where usleep() doesn't work as this test expects.  So we check the
+    // conditions before the tests, and if it fails skip the tests at the
+    // risk of overlooking possible bugs.
+    struct timeval check_begin, check_end;
+    gettimeofday(&check_begin, NULL);
+    usleep(sleep_time);
+    gettimeofday(&check_end, NULL);
+    check_end.tv_sec -= check_begin.tv_sec;
+    if (check_end.tv_usec >= check_begin.tv_usec) {
+        check_end.tv_usec = check_end.tv_usec - check_begin.tv_usec;
+    } else {
+        check_end.tv_usec = ONE_MILLION + check_begin.tv_usec -
+            check_end.tv_usec;
+        --check_end.tv_sec;
+    }
+    if (check_end.tv_sec != 0 ||
+        sleep_time - duration_margin > check_end.tv_usec ||
+        sleep_time + duration_margin < check_end.tv_usec) {
+        cerr << "Prerequisite check failed.  skipping test" << endl;
+        return;
+    }
+
+    TestBenchMark test_bench(sub_iterations, sleep_time);
+    BenchMark<TestBenchMark> bench(1, test_bench, false);
+    // Check pre-test conditions.
+    EXPECT_FALSE(test_bench.setup_completed_);
+    EXPECT_FALSE(test_bench.teardown_completed_);
+
+    bench.run();
+
+    // Check if specialized setup and teardown were performed.
+    EXPECT_TRUE(test_bench.setup_completed_);
+    EXPECT_TRUE(test_bench.teardown_completed_);
+
+    // Check accuracy of the measured statistics.
+    EXPECT_EQ(sub_iterations, bench.getIteration());
+    EXPECT_LT(sleep_time - duration_margin, bench.getDuration() * ONE_MILLION);
+    EXPECT_GT(sleep_time + duration_margin, bench.getDuration() * ONE_MILLION);
+    EXPECT_LT((sleep_time - duration_margin) /
+              static_cast<double>(sub_iterations),
+              bench.getAverageTime() * ONE_MILLION);
+    EXPECT_GT((sleep_time + duration_margin) /
+              static_cast<double>(sub_iterations),
+              bench.getAverageTime() * ONE_MILLION);
+    EXPECT_LT(static_cast<double>(sub_iterations) /
+              (sleep_time + duration_margin),
+              bench.getIterationPerSecond() / ONE_MILLION);
+    EXPECT_GT(static_cast<double>(sub_iterations) /
+              (sleep_time - duration_margin),
+              bench.getIterationPerSecond() / ONE_MILLION);
+}
+
+TEST(BenchMarkTest, runWithNoIteration) {
+    // we'll lie on the number of iteration (0).  it will result in
+    // meaningless result, but at least it shouldn't crash.
+    TestBenchMark test_bench(0, 0);
+    BenchMark<TestBenchMark> bench(1, test_bench, false);
+    bench.run();
+    EXPECT_EQ(0, bench.getIteration());
+    // Since the reported iteration is 0, naive calculation of the average
+    // time would cause a division by 0 failure.
+    EXPECT_EQ(bench.TIME_FAILURE, bench.getAverageTime());
+}
+}

+ 198 - 0
src/lib/bench/tests/loadquery_unittest.cc

@@ -0,0 +1,198 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+#include <sstream>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <bench/benchmark_util.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::bench;
+using namespace isc::dns;
+
+namespace {
+typedef pair<string, string> QueryParam;
+
+class LoadQueryTest : public ::testing::Test {
+protected:
+    LoadQueryTest() : query_rrclass(RRClass::IN()) {
+        queries.push_back(QueryParam("www.example.org", "AAAA"));
+        queries.push_back(QueryParam("www.example.com", "A"));
+        queries.push_back(QueryParam("test.example", "NS"));
+    }
+    RRClass query_rrclass;
+    BenchQueries result_queries;
+    vector<QueryParam> queries;
+    stringstream query_stream;
+    static const char* const DATA_DIR;
+};
+
+const char* const LoadQueryTest::DATA_DIR = TEST_DATA_DIR;
+
+class QueryInserter {
+public:
+    QueryInserter(stringstream& stream) : stream_(stream) {}
+    void operator()(const QueryParam& query) {
+        stream_ << query.first << " " << query.second << endl;
+    }
+private:
+    stringstream& stream_;
+};
+
+class QueryChecker {
+public:
+    QueryChecker(const vector<QueryParam>* expected, const RRClass& rrclass) :
+        expected_(expected), rrclass_(rrclass)
+    {
+        if (expected != NULL) {
+            iter_ = expected_->begin();
+        }
+    }
+    void operator()(const vector<unsigned char>& actual_data) {
+        InputBuffer buffer(&actual_data[0], actual_data.size());
+        Message message(Message::PARSE);
+        message.fromWire(buffer);
+
+        // Check if the header part indicates an expected standard query.
+        EXPECT_EQ(0, message.getQid());
+        EXPECT_EQ(Opcode::QUERY(), message.getOpcode());
+        EXPECT_EQ(Rcode::NOERROR(), message.getRcode());
+        EXPECT_EQ(Rcode::NOERROR(), message.getRcode());
+        EXPECT_FALSE(message.getHeaderFlag(MessageFlag::QR()));
+        EXPECT_FALSE(message.getHeaderFlag(MessageFlag::AA()));
+        EXPECT_EQ(1, message.getRRCount(Section::QUESTION()));
+        EXPECT_EQ(0, message.getRRCount(Section::ANSWER()));
+        EXPECT_EQ(0, message.getRRCount(Section::AUTHORITY()));
+        EXPECT_EQ(0, message.getRRCount(Section::ADDITIONAL()));
+
+        // Check if the question matches our original data, if the expected
+        // data is given.
+        if (expected_ != NULL) {
+            ConstQuestionPtr question = *message.beginQuestion();;
+            EXPECT_EQ(Name((*iter_).first), question->getName());
+            EXPECT_EQ(RRType((*iter_).second), question->getType());
+            EXPECT_EQ(rrclass_, question->getClass());
+        
+            ++iter_;
+        }
+    }
+private:
+    const vector<QueryParam>* expected_;
+    vector<QueryParam>::const_iterator iter_;
+    const RRClass rrclass_;
+};
+
+TEST_F(LoadQueryTest, load) {
+    for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+
+    loadQueryData(query_stream, result_queries, query_rrclass);
+
+    EXPECT_EQ(queries.size(), result_queries.size());
+    for_each(result_queries.begin(), result_queries.end(),
+             QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadForCHClass) {
+    for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+    query_rrclass = RRClass::CH();
+
+    loadQueryData(query_stream, result_queries, query_rrclass);
+
+    EXPECT_EQ(queries.size(), result_queries.size());
+    for_each(result_queries.begin(), result_queries.end(),
+             QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadWithComment) {
+    for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+    // add a comment line.  this shouldn't change the result.
+    query_stream << "# this is a comment" << endl;
+    query_stream << endl;       // empty line.  should be ignored, too.
+
+    loadQueryData(query_stream, result_queries, query_rrclass);
+    EXPECT_EQ(queries.size(), result_queries.size());
+    for_each(result_queries.begin(), result_queries.end(),
+             QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadWithIncompleteData) {
+    for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+    // RRType is missing.  It should be ignored by default.
+    query_stream << "type-is-missing" << endl;
+
+    loadQueryData(query_stream, result_queries, query_rrclass);
+    EXPECT_EQ(queries.size(), result_queries.size());
+    for_each(result_queries.begin(), result_queries.end(),
+             QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadWithIncompleteDataToBeRejected) {
+    for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+    // RRType is missing.  We're going to specify the "strict" check, so
+    // we should receive an exception.
+    query_stream << "type-is-missing" << endl;
+    EXPECT_THROW(loadQueryData(query_stream, result_queries, query_rrclass,
+                               true), BenchMarkError);
+}
+
+TEST_F(LoadQueryTest, loadWithBadData) {
+    for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+    // invalid RRType.  It should be ignored by default.
+    query_stream << "www.example.com NOSUCHRRTYPE" << endl;
+
+    loadQueryData(query_stream, result_queries, query_rrclass);
+    EXPECT_EQ(queries.size(), result_queries.size());
+    for_each(result_queries.begin(), result_queries.end(),
+             QueryChecker(&queries, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadWithBadDataToBeRejected) {
+    for_each(queries.begin(), queries.end(), QueryInserter(query_stream));
+    // invalid RRType, which should trigger an exception.
+    query_stream << "www.example.com NOSUCHRRTYPE" << endl;
+    EXPECT_THROW(loadQueryData(query_stream, result_queries, query_rrclass,
+                               true), BenchMarkError);
+}
+
+TEST_F(LoadQueryTest, loadFromFile) {
+    const string data_file = string(DATA_DIR) + string("/query.txt");
+    loadQueryData(data_file.c_str(), result_queries, query_rrclass);
+    EXPECT_LT(0, result_queries.size());
+
+    // We are going to skip matching the query data; we only check the header.
+    // We could check the data, too, but to do so we need to populate the
+    // expected data from the file (or prepare a consistent copy locally).
+    // Since the implementation is shared with the stringstream case, the
+    // additional setup wouldn't be worthwhile.
+    for_each(result_queries.begin(), result_queries.end(),
+             QueryChecker(NULL, query_rrclass));
+}
+
+TEST_F(LoadQueryTest, loadFromFileNotExist) {
+    EXPECT_THROW(loadQueryData("notexistent/query.data", result_queries,
+                               query_rrclass), BenchMarkError);
+}
+}

+ 24 - 0
src/lib/bench/tests/run_unittests.cc

@@ -0,0 +1,24 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    return (RUN_ALL_TESTS());
+}

+ 6 - 0
src/lib/bench/tests/testdata/query.txt

@@ -0,0 +1,6 @@
+# This is sample query data for benchmark.
+# The format is the same as BIND 9's queryperf.
+
+www.example.com TXT
+www.example.org SOA
+ftp.example.org RRSIG

+ 22 - 1
src/lib/cc/session.h

@@ -40,13 +40,34 @@ namespace isc {
                 isc::Exception(file, line, what) {}
         };
 
+        /// \brief The AbstractSession class is an abstract base class that
+        /// defines the interfaces of Session.
+        /// The intended primary usage of abstraction is to allow tests for the
+        /// user class of Session without requiring actual communication
+        /// channels.
+        /// For simplicity we only define the methods that are necessary for
+        /// existing test cases that use this base class.  Eventually we'll
+        /// probably have to extend them.
         class AbstractSession {
+            ///
+            /// \name Constructors, Assignment Operator and Destructor.
+            ///
+            /// Note: The copy constructor and the assignment operator are
+            /// intentionally defined as private to make it explicit that
+            /// this is a pure base class.
+            //@{
         private:
             AbstractSession(const AbstractSession& source);
             AbstractSession& operator=(const AbstractSession& source);
         protected:
+            /// \brief The default constructor.
+            ///
+            /// This is intentionally defined as \c protected as this base
+            /// class should never be instantiated (except as part of a
+            /// derived class).
             AbstractSession() {}
         public:
+            /// \brief The destructor.
             virtual ~AbstractSession() {}
             //@}
             virtual void establish(const char* socket_file) = 0;
@@ -79,7 +100,7 @@ namespace isc {
 
         public:
             Session(asio::io_service& ioservice);
-            ~Session();
+            virtual ~Session();
 
             virtual void startRead(boost::function<void()> read_callback);
 

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

@@ -51,3 +51,4 @@ EXTRA_DIST += testdata/spec24.spec
 EXTRA_DIST += testdata/spec25.spec
 EXTRA_DIST += testdata/spec26.spec
 EXTRA_DIST += testdata/spec27.spec
+EXTRA_DIST += testdata/spec28.spec

+ 1 - 1
src/lib/config/testdata/b10-config.db

@@ -1 +1 @@
-{'TestModule': {'test': 125}, 'version': 1}
+{"version": 1, "TestModule": {"test": 125}}

+ 2 - 2
src/lib/config/testdata/data22_1.data

@@ -1,9 +1,9 @@
 {
     "value1": 1,
     "value2": 2.3,
-    "value3": True,
+    "value3": true,
     "value4": "foo",
     "value5": [ 1, 2, 3 ],
-    "value6": { "v61": "bar", "v62": True },
+    "value6": { "v61": "bar", "v62": true },
     "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
 }

+ 2 - 2
src/lib/config/testdata/data22_2.data

@@ -1,8 +1,8 @@
 {
     "value1": "asdf",
     "value2": 2.3,
-    "value3": True,
+    "value3": true,
     "value4": "foo",
     "value5": [ 1, 2, 3 ],
-    "value6": { "v61": "bar", "v62": True }
+    "value6": { "v61": "bar", "v62": true }
 }

+ 3 - 3
src/lib/config/testdata/data22_3.data

@@ -1,8 +1,8 @@
 {
     "value1": 1,
-    "value2": False,
-    "value3": True,
+    "value2": false,
+    "value3": true,
     "value4": "foo",
     "value5": [ 1, 2, 3 ],
-    "value6": { "v61": "bar", "v62": True }
+    "value6": { "v61": "bar", "v62": true }
 }

+ 2 - 2
src/lib/config/testdata/data22_4.data

@@ -1,8 +1,8 @@
 {
     "value1": 1,
     "value2": 2.3,
-    "value3": True,
+    "value3": true,
     "value4": "foo",
     "value5": [ 1, 2, "a" ],
-    "value6": { "v61": "bar", "v62": True }
+    "value6": { "v61": "bar", "v62": true }
 }

+ 1 - 1
src/lib/config/testdata/data22_5.data

@@ -1,7 +1,7 @@
 {
     "value1": 1,
     "value2": 2.3,
-    "value3": True,
+    "value3": true,
     "value4": "foo",
     "value5": [ 1, 2, 3 ],
     "value6": { "v61": "bar", "v62": "Break" }

+ 3 - 3
src/lib/config/testdata/data22_6.data

@@ -1,10 +1,10 @@
 {
     "value1": 1,
     "value2": 2.3,
-    "value3": True,
+    "value3": true,
     "value4": "foo",
     "value5": [ 1, 2, 3 ],
-    "value6": { "v61": "bar", "v62": True },
-    "value7": [ 1, 2.2, "str", True ],
+    "value6": { "v61": "bar", "v62": true },
+    "value7": [ 1, 2.2, "str", true ],
     "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
 }

+ 2 - 2
src/lib/config/testdata/data22_7.data

@@ -1,10 +1,10 @@
 {
     "value1": 1,
     "value2": 2.3,
-    "value3": True,
+    "value3": true,
     "value4": "foo",
     "value5": [ 1, 2, 3 ],
-    "value6": { "v61": "bar", "v62": True },
+    "value6": { "v61": "bar", "v62": true },
     "value8": [ { "a": "d" }, { "a": "e" } ],
     "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
 }

+ 2 - 2
src/lib/config/testdata/data22_8.data

@@ -1,9 +1,9 @@
 {
     "value1": 1,
     "value2": 2.3,
-    "value3": True,
+    "value3": true,
     "value4": "foo",
     "value5": [ 1, 2, 3 ],
-    "value6": { "v61": "bar", "v62": True },
+    "value6": { "v61": "bar", "v62": true },
     "value8": [ { "a": "d" }, { "a": 1 } ]
 }

+ 1 - 1
src/lib/config/testdata/spec10.spec

@@ -4,7 +4,7 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "real",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       }
     ]

+ 1 - 1
src/lib/config/testdata/spec11.spec

@@ -4,7 +4,7 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "boolean",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       }
     ]

+ 1 - 1
src/lib/config/testdata/spec12.spec

@@ -4,7 +4,7 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "string",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       }
     ]

+ 1 - 1
src/lib/config/testdata/spec13.spec

@@ -4,7 +4,7 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "list",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       }
     ]

+ 1 - 1
src/lib/config/testdata/spec14.spec

@@ -4,7 +4,7 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "map",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       }
     ]

+ 1 - 1
src/lib/config/testdata/spec15.spec

@@ -4,7 +4,7 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "badname",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       }
     ]

+ 1 - 1
src/lib/config/testdata/spec17.spec

@@ -7,7 +7,7 @@
         "command_args": [ {
           "item_name": "message",
           "item_type": "string",
-          "item_optional": False,
+          "item_optional": false,
           "item_default": ""
         } ]
       }

+ 11 - 11
src/lib/config/testdata/spec2.spec

@@ -4,48 +4,48 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "integer",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       },
       { "item_name": "item2",
         "item_type": "real",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1.1
       },
       { "item_name": "item3",
         "item_type": "boolean",
-        "item_optional": False,
-        "item_default": True
+        "item_optional": false,
+        "item_default": true
       },
       { "item_name": "item4",
         "item_type": "string",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": "test"
       },
       { "item_name": "item5",
         "item_type": "list",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": [ "a", "b" ],
         "list_item_spec": {
           "item_name": "list_element",
           "item_type": "string",
-          "item_optional": False,
+          "item_optional": false,
           "item_default": ""
         }
       },
       { "item_name": "item6",
         "item_type": "map",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": {},
         "map_item_spec": [
           { "item_name": "value1",
             "item_type": "string",
-            "item_optional": True,
+            "item_optional": true,
             "item_default": "default"
           },
           { "item_name": "value2",
             "item_type": "integer",
-            "item_optional": True
+            "item_optional": true
           }
         ]
       }
@@ -57,7 +57,7 @@
         "command_args": [ {
           "item_name": "message",
           "item_type": "string",
-          "item_optional": False,
+          "item_optional": false,
           "item_default": ""
         } ]
       },

+ 1 - 1
src/lib/config/testdata/spec20.spec

@@ -8,7 +8,7 @@
         "command_args": [ {
           "item_name": "message",
           "item_type": "somethingbad",
-          "item_optional": False,
+          "item_optional": false,
           "item_default": ""
         } ]
       }

+ 21 - 21
src/lib/config/testdata/spec22.spec

@@ -4,75 +4,75 @@
     "config_data": [
       { "item_name": "value1",
         "item_type": "integer",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 9
       },
       { "item_name": "value2",
         "item_type": "real",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 9.9
       },
       { "item_name": "value3",
         "item_type": "boolean",
-        "item_optional": False,
-        "item_default": False
+        "item_optional": false,
+        "item_default": false
       },
       { "item_name": "value4",
         "item_type": "string",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": "default_string"
       },
       { "item_name": "value5",
         "item_type": "list",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": [ "a", "b" ],
         "list_item_spec": {
           "item_name": "list_element",
           "item_type": "integer",
-          "item_optional": False,
+          "item_optional": false,
           "item_default": 8
         }
       },
       { "item_name": "value6",
         "item_type": "map",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": {},
         "map_item_spec": [
           { "item_name": "v61",
             "item_type": "string",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": "def"
           },
           { "item_name": "v62",
             "item_type": "boolean",
-            "item_optional": False,
-            "item_default": False
+            "item_optional": false,
+            "item_default": false
           }
         ]
       },
       { "item_name": "value7",
         "item_type": "list",
-        "item_optional": True,
+        "item_optional": true,
         "item_default": [ ],
         "list_item_spec": {
           "item_name": "list_element",
           "item_type": "any",
-          "item_optional": True
+          "item_optional": true
         }
       },
       { "item_name": "value8",
         "item_type": "list",
-        "item_optional": True,
+        "item_optional": true,
         "item_default": [ ],
         "list_item_spec": {
           "item_name": "list_element",
           "item_type": "map",
-          "item_optional": True,
+          "item_optional": true,
           "item_default": { "a": "b" },
           "map_item_spec": [
             { "item_name": "a",
               "item_type": "string",
-              "item_optional": True,
+              "item_optional": true,
               "item_default": "empty"
             }
           ]
@@ -80,28 +80,28 @@
       },
       { "item_name": "value9",
         "item_type": "map",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": {},
         "map_item_spec": [
           { "item_name": "v91",
             "item_type": "string",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": "def"
           },
           { "item_name": "v92",
             "item_type": "map",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": {},
             "map_item_spec": [
               { "item_name": "v92a",
                 "item_type": "string",
-                "item_optional": False,
+                "item_optional": false,
                 "item_default": "Hello"
               } ,
               {
                 "item_name": "v92b",
                 "item_type": "integer",
-                "item_optional": False,
+                "item_optional": false,
                 "item_default": 47806
               }
             ]

+ 1 - 1
src/lib/config/testdata/spec23.spec

@@ -8,7 +8,7 @@
         "command_args": [ {
           "item_name": "message",
           "item_type": "string",
-          "item_optional": False,
+          "item_optional": false,
           "item_default": ""
         } ]
       }

+ 2 - 2
src/lib/config/testdata/spec24.spec

@@ -4,11 +4,11 @@
     "config_data": [
       { "item_name": "item",
         "item_type": "list",
-        "item_optional": True,
+        "item_optional": true,
         "list_item_spec": {
           "item_name": "list_element",
           "item_type": "string",
-          "item_optional": False,
+          "item_optional": false,
           "item_default": ""
         }
       }

+ 23 - 23
src/lib/config/testdata/spec27.spec

@@ -3,81 +3,81 @@
     "module_name": "Spec27",
     "commands": [
     {
-        'command_name': 'cmd1',
+        "command_name": "cmd1",
         "command_description": "command_for_unittest",
-        'command_args': [ 
+        "command_args": [ 
           {
             "item_name": "value1",
             "item_type": "integer",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": 9
           },
           { "item_name": "value2",
             "item_type": "real",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": 9.9
           },
           { "item_name": "value3",
             "item_type": "boolean",
-            "item_optional": False,
-            "item_default": False
+            "item_optional": false,
+            "item_default": false
           },
           { "item_name": "value4",
             "item_type": "string",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": "default_string"
           },
           { "item_name": "value5",
             "item_type": "list",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": [ "a", "b" ],
             "list_item_spec": {
               "item_name": "list_element",
               "item_type": "integer",
-              "item_optional": False,
+              "item_optional": false,
               "item_default": 8
             }
           },
           { "item_name": "value6",
             "item_type": "map",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": {},
             "map_item_spec": [
               { "item_name": "v61",
                 "item_type": "string",
-                "item_optional": False,
+                "item_optional": false,
                 "item_default": "def"
               },
               { "item_name": "v62",
                 "item_type": "boolean",
-                "item_optional": False,
-                "item_default": False
+                "item_optional": false,
+                "item_default": false
               }
             ]
           },
           { "item_name": "value7",
             "item_type": "list",
-            "item_optional": True,
+            "item_optional": true,
             "item_default": [ ],
             "list_item_spec": {
               "item_name": "list_element",
               "item_type": "any",
-              "item_optional": True
+              "item_optional": true
             }
           },
           { "item_name": "value8",
             "item_type": "list",
-            "item_optional": True,
+            "item_optional": true,
             "item_default": [ ],
             "list_item_spec": {
               "item_name": "list_element",
               "item_type": "map",
-              "item_optional": True,
+              "item_optional": true,
               "item_default": { "a": "b" },
               "map_item_spec": [
                 { "item_name": "a",
                   "item_type": "string",
-                  "item_optional": True,
+                  "item_optional": true,
                   "item_default": "empty"
                 }
               ]
@@ -85,28 +85,28 @@
           },
           { "item_name": "value9",
             "item_type": "map",
-            "item_optional": False,
+            "item_optional": false,
             "item_default": {},
             "map_item_spec": [
               { "item_name": "v91",
                 "item_type": "string",
-                "item_optional": False,
+                "item_optional": false,
                 "item_default": "def"
               },
               { "item_name": "v92",
                 "item_type": "map",
-                "item_optional": False,
+                "item_optional": false,
                 "item_default": {},
                 "map_item_spec": [
                   { "item_name": "v92a",
                     "item_type": "string",
-                    "item_optional": False,
+                    "item_optional": false,
                     "item_default": "Hello"
                   } ,
                   {
                     "item_name": "v92b",
                     "item_type": "integer",
-                    "item_optional": False,
+                    "item_optional": false,
                     "item_default": 47806
                   }
                 ]

+ 7 - 0
src/lib/config/testdata/spec28.spec

@@ -0,0 +1,7 @@
+{
+  "module_spec": {
+    "module_name": "Spec28",
+    'commands': [ ]
+  }
+}
+

+ 1 - 1
src/lib/config/testdata/spec3.spec

@@ -4,7 +4,7 @@
     "config_data": [
       {
         "item_type": "integer",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       }
     ]

+ 1 - 1
src/lib/config/testdata/spec4.spec

@@ -3,7 +3,7 @@
     "module_name": "Spec2",
     "config_data": [
       { "item_name": "item1",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": 1
       }
     ]

+ 1 - 1
src/lib/config/testdata/spec6.spec

@@ -4,7 +4,7 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "integer",
-        "item_optional": False
+        "item_optional": false
       }
     ]
   }

+ 1 - 1
src/lib/config/testdata/spec9.spec

@@ -4,7 +4,7 @@
     "config_data": [
       { "item_name": "item1",
         "item_type": "integer",
-        "item_optional": False,
+        "item_optional": false,
         "item_default": "asdf"
       }
     ]

+ 3 - 3
src/lib/datasrc/data_source.cc

@@ -28,14 +28,14 @@
 #include <datasrc/data_source.h>
 #include <datasrc/query.h>
 
-#include <dns/base32.h>
+#include <dns/util/base32hex.h>
 #include <dns/buffer.h>
 #include <dns/message.h>
 #include <dns/name.h>
 #include <dns/rdataclass.h>
 #include <dns/rrset.h>
 #include <dns/rrsetlist.h>
-#include <dns/sha1.h>
+#include <dns/util/sha1.h>
 
 #include <cc/data.h>
 
@@ -1234,7 +1234,7 @@ Nsec3Param::getHash(const Name& name) const {
         inlength = SHA1_HASHSIZE;
     } while (n++ < iterations_);
 
-    return (encodeBase32(vector<uint8_t>(digest, digest + SHA1_HASHSIZE)));
+    return (encodeBase32Hex(vector<uint8_t>(digest, digest + SHA1_HASHSIZE)));
 }
 
 //

+ 1 - 1
src/lib/datasrc/query.h

@@ -88,7 +88,7 @@ public:
         AUTH_QUERY,
         GLUE_QUERY,
         NOGLUE_QUERY,
-        REF_QUERY,
+        REF_QUERY
     } op;
 
     // The state field indicates the state of the query; it controls

+ 3 - 0
src/lib/datasrc/static_datasrc.cc

@@ -70,6 +70,7 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
 {
     authors = RRsetPtr(new RRset(authors_name, RRClass::CH(),
                                  RRType::TXT(), RRTTL(0)));
+    authors->addRdata(generic::TXT("Chen Zhengzhang")); // Jerry
     authors->addRdata(generic::TXT("Evan Hunt"));
     authors->addRdata(generic::TXT("Han Feng"));
     authors->addRdata(generic::TXT("Jelte Jansen"));
@@ -80,6 +81,8 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
     authors->addRdata(generic::TXT("Michael Graff"));
     authors->addRdata(generic::TXT("Naoki Kambe"));
     authors->addRdata(generic::TXT("Shane Kerr"));
+    authors->addRdata(generic::TXT("Shen Tingting"));
+    authors->addRdata(generic::TXT("Stephen Morris"));
     authors->addRdata(generic::TXT("Zhang Likun"));
 
     authors_ns = RRsetPtr(new RRset(authors_name, RRClass::CH(),

+ 3 - 0
src/lib/datasrc/tests/static_unittest.cc

@@ -54,6 +54,7 @@ protected:
         version_data.push_back(PACKAGE_STRING);
 
         // XXX: in addition, the order the following items matter.
+        authors_data.push_back("Chen Zhengzhang");
         authors_data.push_back("Evan Hunt");
         authors_data.push_back("Han Feng");
         authors_data.push_back("Jelte Jansen");
@@ -64,6 +65,8 @@ protected:
         authors_data.push_back("Michael Graff");
         authors_data.push_back("Naoki Kambe");
         authors_data.push_back("Shane Kerr");
+        authors_data.push_back("Shen Tingting");
+        authors_data.push_back("Stephen Morris");
         authors_data.push_back("Zhang Likun");
 
         version_ns_data.push_back("version.bind.");

+ 7 - 8
src/lib/dns/Makefile.am

@@ -57,12 +57,14 @@ BUILT_SOURCES = rrclass.h rrtype.h rrparamregistry.cc
 
 lib_LTLIBRARIES = libdns++.la
 
-libdns___la_SOURCES = base32.h base32.cc
-libdns___la_SOURCES += base64.h base64.cc
+libdns___la_SOURCES = util/base32hex.h util/base64.h util/base_n.cc
+libdns___la_SOURCES += util/base32hex_from_binary.h
+libdns___la_SOURCES += util/binary_from_base32hex.h
+libdns___la_SOURCES += util/base16_from_binary.h util/binary_from_base16.h
 libdns___la_SOURCES += buffer.h
 libdns___la_SOURCES += dnssectime.h dnssectime.cc
 libdns___la_SOURCES += exceptions.h exceptions.cc
-libdns___la_SOURCES += hex.h hex.cc
+libdns___la_SOURCES += util/hex.h
 libdns___la_SOURCES += message.h message.cc
 libdns___la_SOURCES += messagerenderer.h messagerenderer.cc
 libdns___la_SOURCES += name.h name.cc
@@ -74,7 +76,7 @@ libdns___la_SOURCES += rrsetlist.h rrsetlist.cc
 libdns___la_SOURCES += rrttl.h rrttl.cc
 libdns___la_SOURCES += rrtype.cc
 libdns___la_SOURCES += question.h question.cc
-libdns___la_SOURCES += sha1.h sha1.cc
+libdns___la_SOURCES += util/sha1.h util/sha1.cc
 libdns___la_SOURCES += tsig.h tsig.cc
 
 nodist_libdns___la_SOURCES = rdataclass.cc rrclass.h rrtype.h
@@ -105,9 +107,6 @@ libdns++_include_HEADERS = \
 	rrtype.h \
 	tsig.h
 # Purposely not installing these headers:
-# base32.h # used only internally, and not actually DNS specific
-# base64.h # used only internally, and not actually DNS specific
-# hex.h # used only internally, and not actually DNS specific
-# sha1.h # used only internally, and not actually DNS specific
+# util/*.h: used only internally, and not actually DNS specific
 # rrclass-placeholder.h
 # rrtype-placeholder.h

+ 0 - 198
src/lib/dns/base32.cc

@@ -1,198 +0,0 @@
-// Copyright (C) 2010  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.
-
-// $Id$
-
-#include <cassert>
-#include <iterator>
-#include <iomanip>
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <vector>
-
-#include <exceptions/exceptions.h>
-
-#include <ctype.h>
-#include <stdint.h>
-#include <string.h>
-
-#include <dns/base32.h>
-
-using namespace std;
-
-namespace isc {
-namespace dns {
-
-static const char base32hex[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
-
-string
-encodeBase32(const vector<uint8_t>& binary) {
-    ostringstream base32;
-    size_t len = binary.size();
-    size_t pos = 0;
-    while (len > 0) {
-        char buf[9];
-        memset(buf, '=', 8);
-        buf[8] = '\0';
-
-        uint8_t digit = (binary.at(pos) >> 3) & 0x1f;
-        buf[0] = base32hex[digit];
-
-        if (len == 1) {
-            digit = (binary.at(pos) << 2) & 0x1c;
-            buf[1] = base32hex[digit];
-            base32 << buf;
-            break;
-        }
-
-        digit = ((binary.at(pos) << 2) & 0x1c) |
-                ((binary.at(pos + 1) >> 6) & 0x03);
-        buf[1] = base32hex[digit];
-
-        digit = (binary.at(pos + 1) >> 1) & 0x1f;
-        buf[2] = base32hex[digit];
-
-        if (len == 2) {
-            digit = (binary.at(pos + 1) << 4) & 0x10;
-            buf[3] = base32hex[digit];
-            base32 << buf;
-            break;
-        }
-
-        digit = ((binary.at(pos + 1) << 4) & 0x10) |
-                ((binary.at(pos + 2) >> 4) & 0x0f);
-        buf[3] = base32hex[digit];
-        if (len == 3) {
-            digit = (binary.at(pos + 2) << 1) & 0x1e;
-            buf[4] = base32hex[digit];
-            base32 << buf;
-            break;
-        }
-
-        digit = ((binary.at(pos + 2) << 1) & 0x1e) |
-                ((binary.at(pos + 3) >> 7) & 0x01);
-        buf[4] = base32hex[digit];
-
-        digit = (binary.at(pos + 3) >> 2) & 0x1f;
-        buf[5] = base32hex[digit];
-
-        if (len == 4) {
-            digit = (binary.at(pos + 3) << 3) & 0x18;
-            buf[6] = base32hex[digit];
-            base32 << buf;
-            break;
-        }
-
-        digit = ((binary.at(pos + 3) << 3) & 0x18) |
-                ((binary.at(pos + 4) >> 5) & 0x07);
-        buf[6] = base32hex[digit];
-
-        digit = binary.at(pos + 4) & 0x1f;
-        buf[7] = base32hex[digit];
-
-        len -= 5;
-        pos += 5;
-
-        base32 << buf;
-    }
-
-    return (base32.str());
-}
-
-void
-decodeBase32(const string& base32, vector<uint8_t>& result) {
-    ostringstream comp;
-
-    // compress input by removing whitespace
-    const size_t len = base32.length();
-    for (int i = 0; i < len; ++i) {
-        const char c = base32[i];
-        if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
-            continue;
-        }
-        comp << c;
-    }
-
-    // base32 text should be a multiple of 8 bytes long
-    if (comp.str().length() % 8 != 0) {
-        isc_throw(BadBase32String, "Invalid length: " << comp.str().length());
-    }
-
-    istringstream iss(comp.str());
-    result.clear();
-    bool seenpad = false;
-    while (!iss.eof()) {
-        string group;
-
-        iss >> setw(8) >> group;
-        if (iss.bad() || iss.fail()) {
-            isc_throw(BadBase32String,
-                      "Could not parse base32 input: " << base32);
-        }
-
-        uint8_t octet = 0;
-        for (int i = 0; i < 8; ++i) {
-            char c = toupper(group.at(i));
-            int value;
-
-            if (c != '=' && seenpad) {
-                isc_throw(BadBase32String, "Invalid base32 input: " << base32);
-            } else 
-
-            if (c == '=' && !seenpad) {
-                value = 0;
-                seenpad = true;
-            } else {
-                const char* pos = strchr(base32hex, c);
-                if (!pos) {
-                    isc_throw(BadBase32String,
-                              "Invalid base32 input: " << base32);
-                }
-                value = pos - base32hex;
-                assert (value < 32);
-            }
-
-            switch (i) {
-            case 0: octet |= value << 3;
-                    break;
-            case 1: octet |= value >> 2;
-                    result.push_back(octet);
-                    octet = (value & 0x03) << 6;
-                    break;
-            case 2: octet |= value << 1;
-                    break;
-            case 3: octet |= value >> 4;
-                    result.push_back(octet);
-                    octet = (value & 0x0f) << 4;
-                    break;
-            case 4: octet |= value >> 1;
-                    result.push_back(octet);
-                    octet = (value & 0x01) << 7;
-                    break;
-            case 5: octet |= value << 2;
-                    break;
-            case 6: octet |= value >> 3;
-                    result.push_back(octet);
-                    octet = (value & 0x07) << 5;
-                    break;
-            case 7: octet |= value;
-                    result.push_back(octet);
-            }
-        }
-    }
-}
-
-}
-}

+ 0 - 54
src/lib/dns/base32.h

@@ -1,54 +0,0 @@
-// Copyright (C) 2010  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.
-
-// $Id$
-
-#ifndef __BASE32_H
-#define __BASE32_H 1
-
-#include <stdint.h>
-#include <string>
-#include <vector>
-
-#include <exceptions/exceptions.h>
-
-//
-// Note: this helper module isn't specific to the DNS protocol per se.
-// We should probably move this to somewhere else, possibly in some common
-// utility area.
-//
-
-namespace isc {
-namespace dns {
-
-///
-/// \brief A standard DNS (or ISC) module exception that is thrown if a
-/// base32 decoder encounters an invalid input.
-///
-class BadBase32String : public Exception {
-public:
-    BadBase32String(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-std::string encodeBase32(const std::vector<uint8_t>& binary);
-void decodeBase32(const std::string& hex, std::vector<uint8_t>& result);
-}
-}
-
-#endif  // __BASE32_H
-
-// Local Variables: 
-// mode: c++
-// End: 

+ 0 - 189
src/lib/dns/base64.cc

@@ -1,189 +0,0 @@
-// Copyright (C) 2010  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.
-
-// $Id$
-
-#include <stdint.h>
-#include <cassert>
-#include <iterator>
-#include <string>
-#include <vector>
-
-#include <boost/archive/iterators/base64_from_binary.hpp>
-#include <boost/archive/iterators/binary_from_base64.hpp>
-#include <boost/archive/iterators/transform_width.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/base64.h>
-
-using namespace std;
-using namespace boost::archive::iterators;
-
-namespace isc {
-namespace dns {
-
-namespace {
-const char BASE64_PADDING_CHAR = '=';
-const uint8_t BINARY_ZERO_CODE = 0;
-  
-class BinaryNormalizer : public iterator<input_iterator_tag, uint8_t> {
-public:
-    BinaryNormalizer(const vector<uint8_t>::const_iterator& base,
-                     const vector<uint8_t>::const_iterator& base_end) :
-        base_(base), base_end_(base_end), in_pad_(false)
-    {}
-    BinaryNormalizer& operator++()
-    {
-        if (!in_pad_) {
-            ++base_;
-        }
-        if (base_ == base_end_) {
-            in_pad_ = true;
-        }
-        return (*this);
-    }
-    const uint8_t& operator*() const {
-        if (in_pad_) {
-            return (BINARY_ZERO_CODE);
-        } else {
-            return (*base_);
-        }
-    }
-    bool operator==(const BinaryNormalizer& other) const
-    {
-        return (base_ == other.base_);
-    }
-private:
-    vector<uint8_t>::const_iterator base_;
-    const vector<uint8_t>::const_iterator base_end_;
-    bool in_pad_;
-};
-
-typedef
-base64_from_binary<transform_width<BinaryNormalizer, 6, 8> > base64_encoder;
-} // end of anonymous namespace
-
-string
-encodeBase64(const vector<uint8_t>& binary)
-{
-    // calculate the resulting length.  it's the smallest multiple of 4
-    // equal to or larger than 4/3 * original data length.
-    size_t len = ((binary.size() * 4 / 3) + 3) & ~3;
-
-    string base64;
-    base64.reserve(len);
-    base64.assign(base64_encoder(BinaryNormalizer(binary.begin(),
-                                                  binary.end())),
-                  base64_encoder(BinaryNormalizer(binary.end(), binary.end())));
-    assert(len >= base64.length());
-    base64.append(len - base64.length(), BASE64_PADDING_CHAR);
-    return (base64);
-}
-
-namespace {
-const size_t BASE64_MAX_PADDING_CHARS = 2;
-const char BASE64_ZERO_CODE = 'A'; // correspond to 000000(2)
-
-class Base64Normalizer : public iterator<input_iterator_tag, char> {
-public:
-    Base64Normalizer(const string::const_iterator& base,
-                     const string::const_iterator& base_beginpad,
-                     const string::const_iterator& base_end) :
-        base_(base), base_beginpad_(base_beginpad), base_end_(base_end),
-        in_pad_(false)
-    {}
-    Base64Normalizer& operator++()
-    {
-        ++base_;
-        while (base_ != base_end_ && isspace(*base_)) {
-            ++base_;
-        }
-        if (base_ == base_beginpad_) {
-            in_pad_ = true;
-        }
-        return (*this);
-    }
-    const char& operator*() const {
-        if (in_pad_ && *base_ == BASE64_PADDING_CHAR) {
-            return (BASE64_ZERO_CODE);
-        } else {
-            return (*base_);
-        }
-    }
-    bool operator==(const Base64Normalizer& other) const
-    {
-        return (base_ == other.base_);
-    }
-private:
-    string::const_iterator base_;
-    const string::const_iterator base_beginpad_;
-    const string::const_iterator base_end_;
-    bool in_pad_;
-};
-
-typedef
-transform_width<binary_from_base64<Base64Normalizer, char>, 8, 6, char>
-base64_decoder;
-} // end of anonymous namespace
-
-void
-decodeBase64(const string& base64, vector<uint8_t>& result)
-{
-    // enumerate the number of trailing padding characters (=), ignoring
-    // white spaces.  since base64_from_binary doesn't accept padding,
-    // we handle it explicitly.
-    size_t padlen = 0;
-    string::const_reverse_iterator srit = base64.rbegin();
-    string::const_reverse_iterator srit_end = base64.rend();
-    while (srit != srit_end) {
-        char ch = *srit;
-        if (ch == BASE64_PADDING_CHAR) {
-            if (++padlen > BASE64_MAX_PADDING_CHARS) {
-                isc_throw(BadBase64String,
-                          "Too many Base64 padding characters");
-            }
-        } else if (!isspace(ch)) {
-            break;
-        }
-        ++srit;
-    }
-
-    try {
-        result.assign(base64_decoder(Base64Normalizer(base64.begin(),
-                                                      srit.base(),
-                                                      base64.end())),
-                      base64_decoder(Base64Normalizer(base64.end(),
-                                                      base64.end(),
-                                                      base64.end())));
-    } catch (dataflow_exception& ex) {
-        isc_throw(BadBase64String, ex.what());
-    }
-
-    // Confirm the original base64 text is the canonical encoding of the
-    // data.
-    assert(result.size() >= padlen);
-    vector<uint8_t>::const_reverse_iterator rit = result.rbegin();
-    for (int i = 0; i < padlen; ++i, ++rit) {
-        if (*rit != 0) {
-            isc_throw(BadBase64String, "Non 0 bits included in padding");
-        }
-    }
-
-    // strip the padded zero-bit fields
-    result.resize(result.size() - padlen);
-}
-
-}
-}

+ 0 - 108
src/lib/dns/hex.cc

@@ -1,108 +0,0 @@
-// Copyright (C) 2010  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.
-
-// $Id$
-
-#include <cassert>
-#include <iterator>
-#include <iomanip>
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <vector>
-
-
-#include <exceptions/exceptions.h>
-#include <boost/foreach.hpp>
-#include <ctype.h>
-#include <stdint.h>
-
-#include <dns/hex.h>
-
-using namespace std;
-
-namespace isc {
-namespace dns {
-
-namespace {
-const char hexdigits[] = "0123456789ABCDEF";
-}
-
-std::string
-encodeHex(const std::vector<uint8_t>& binary) {
-    // calculate the resulting length.  it should be twice the
-    // original data length
-    const size_t len = (binary.size() * 2);
-    std::ostringstream hex;
-
-    BOOST_FOREACH(uint8_t octet, binary) {
-        hex << hexdigits[octet >> 4] << hexdigits[octet & 0xf];
-    }
-    assert(len >= hex.str().length());
-    return (hex.str());
-}
-
-void
-decodeHex(const std::string& hex, std::vector<uint8_t>& result) {
-    ostringstream comp;
-
-    // compress input by removing whitespace
-    const size_t len = hex.length();
-    for (int i = 0; i < len; ++i) {
-        char c = hex.at(i);
-        if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
-            continue;
-        }
-        comp << c;
-    }
-
-    istringstream iss(comp.str());
-    result.clear();
-    char c1, c2;
-
-    iss.width(1);
-    if ((comp.str().length() % 2) == 1) {
-        c2 = '0';
-        iss >> c2;
-
-        const char* pos = strchr(hexdigits, toupper(c2));
-        if (pos == NULL) {
-            isc_throw(BadHexString, "Invalid hex digit");
-        }
-
-        if (!iss.eof() && !iss.bad() && !iss.fail()) {
-            result.push_back(pos - hexdigits);
-        }
-    }
-    while (!iss.eof()) {
-        c1 = c2 = '0';
-        iss >> c1 >> c2;;
-
-        if (iss.eof() || iss.fail() || iss.bad()) {
-            break;
-        }
-
-        const char* pos1 = strchr(hexdigits, toupper(c1));
-        const char* pos2 = strchr(hexdigits, toupper(c2));
-        if (!pos1 || !pos2) {
-            isc_throw(BadHexString, "Invalid hex digit");
-        }
-
-        const uint8_t n = ((pos1 - hexdigits) << 4) | (pos2 - hexdigits);
-        result.push_back(n);
-    }
-}
-
-}
-}

+ 1 - 2
src/lib/dns/python/message_python.cc

@@ -1192,8 +1192,7 @@ Message_init(s_Message* self, PyObject* args) {
 
 static void
 Message_destroy(s_Message* self) {
-    if (self->message != NULL)
-        delete self->message;
+    delete self->message;
     self->message = NULL;
     Py_TYPE(self)->tp_free(self);
 }

+ 1 - 2
src/lib/dns/python/rrclass_python.cc

@@ -201,8 +201,7 @@ RRClass_init(s_RRClass* self, PyObject* args) {
 
 static void
 RRClass_destroy(s_RRClass* self) {
-    if (self->rrclass != NULL)
-        delete self->rrclass;
+    delete self->rrclass;
     self->rrclass = NULL;
     Py_TYPE(self)->tp_free(self);
 }

+ 1 - 2
src/lib/dns/python/rrttl_python.cc

@@ -199,8 +199,7 @@ RRTTL_init(s_RRTTL* self, PyObject* args) {
 
 static void
 RRTTL_destroy(s_RRTTL* self) {
-    if (self->rrttl != NULL)
-        delete self->rrttl;
+    delete self->rrttl;
     self->rrttl = NULL;
     Py_TYPE(self)->tp_free(self);
 }

+ 1 - 2
src/lib/dns/python/rrtype_python.cc

@@ -239,8 +239,7 @@ RRType_init(s_RRType* self, PyObject* args) {
 
 static void
 RRType_destroy(s_RRType* self) {
-    if (self->rrtype != NULL)
-        delete self->rrtype;
+    delete self->rrtype;
     self->rrtype = NULL;
     Py_TYPE(self)->tp_free(self);
 }

+ 1 - 1
src/lib/dns/rdata/generic/dnskey_48.cc

@@ -22,7 +22,7 @@
 #include <boost/lexical_cast.hpp>
 #include <boost/foreach.hpp>
 
-#include <dns/base64.h>
+#include <dns/util/base64.h>
 #include <dns/buffer.h>
 #include <dns/messagerenderer.h>
 #include <dns/name.h>

+ 1 - 1
src/lib/dns/rdata/generic/ds_43.cc

@@ -22,7 +22,7 @@
 #include <boost/lexical_cast.hpp>
 
 #include <dns/buffer.h>
-#include <dns/hex.h>
+#include <dns/util/hex.h>
 #include <dns/messagerenderer.h>
 #include <dns/name.h>
 #include <dns/rdata.h>

+ 4 - 4
src/lib/dns/rdata/generic/nsec3_50.cc

@@ -22,10 +22,10 @@
 
 #include <boost/lexical_cast.hpp>
 
-#include <dns/base32.h>
+#include <dns/util/base32hex.h>
 #include <dns/buffer.h>
 #include <dns/exceptions.h>
-#include <dns/hex.h>
+#include <dns/util/hex.h>
 #include <dns/messagerenderer.h>
 #include <dns/name.h>
 #include <dns/rrtype.h>
@@ -88,7 +88,7 @@ NSEC3::NSEC3(const string& nsec3_str) :
     if (iss.bad() || iss.fail()) {
         isc_throw(InvalidRdataText, "Invalid NSEC3 hash algorithm");
     }
-    decodeBase32(nextstr, next);
+    decodeBase32Hex(nextstr, next);
 
     uint8_t bitmap[8 * 1024];       // 64k bits
     vector<uint8_t> typebits;
@@ -237,7 +237,7 @@ NSEC3::toText() const
         " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
         " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
         " " + encodeHex(impl_->salt_) +
-        " " + encodeBase32(impl_->next_) + s.str());
+        " " + encodeBase32Hex(impl_->next_) + s.str());
 }
 
 void

+ 1 - 1
src/lib/dns/rdata/generic/nsec3param_51.cc

@@ -22,7 +22,7 @@
 #include <boost/lexical_cast.hpp>
 
 #include <dns/buffer.h>
-#include <dns/hex.h>
+#include <dns/util/hex.h>
 #include <dns/messagerenderer.h>
 #include <dns/name.h>
 #include <dns/rdata.h>

+ 1 - 1
src/lib/dns/rdata/generic/nsec_47.cc

@@ -19,7 +19,7 @@
 #include <sstream>
 #include <vector>
 
-#include <dns/base64.h>
+#include <dns/util/base64.h>
 #include <dns/buffer.h>
 #include <dns/exceptions.h>
 #include <dns/messagerenderer.h>

+ 1 - 1
src/lib/dns/rdata/generic/rrsig_46.cc

@@ -22,7 +22,7 @@
 
 #include <boost/lexical_cast.hpp>
 
-#include <dns/base64.h>
+#include <dns/util/base64.h>
 #include <dns/buffer.h>
 #include <dns/dnssectime.h>
 #include <dns/messagerenderer.h>

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

@@ -31,7 +31,7 @@ run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
 run_unittests_SOURCES += message_unittest.cc
-run_unittests_SOURCES += base32_unittest.cc
+run_unittests_SOURCES += base32hex_unittest.cc
 run_unittests_SOURCES += base64_unittest.cc
 run_unittests_SOURCES += hex_unittest.cc
 run_unittests_SOURCES += sha1_unittest.cc

+ 0 - 107
src/lib/dns/tests/base32_unittest.cc

@@ -1,107 +0,0 @@
-// Copyright (C) 2010  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.
-
-// $Id$
-
-#include <stdint.h>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <dns/base32.h>
-
-#include <gtest/gtest.h>
-
-using namespace std;
-using namespace isc::dns;
-
-namespace {
-
-typedef pair<string, string> StringPair;
-
-class Base32Test : public ::testing::Test {
-protected:
-    Base32Test() {
-        // test vectors from RFC4648
-#if 0   // the current implementation doesn't seem to handle '=' correctly
-        test_sequence.push_back(StringPair("", ""));
-        test_sequence.push_back(StringPair("f", "CO======"));
-        test_sequence.push_back(StringPair("fo", "CPNG===="));
-        test_sequence.push_back(StringPair("foo", "CPNMU==="));
-        test_sequence.push_back(StringPair("foob", "CPNMUOG="));
-#endif
-        test_sequence.push_back(StringPair("fooba", "CPNMUOJ1"));
-#if 0                           // this fails
-        test_sequence.push_back(StringPair("foobar", "CPNMUOJ1E8======"));
-#endif
-    }
-    vector<StringPair> test_sequence;
-    vector<uint8_t> decoded_data;
-};
-
-void
-decodeCheck(const string& input_string, vector<uint8_t>& output,
-            const string& expected)
-{
-    decodeBase32(input_string, output);
-    EXPECT_EQ(expected, string(&output[0], &output[0] + output.size()));
-}
-
-
-TEST_F(Base32Test, reversibility) {
-    vector<uint8_t> result;
-//    const string input("H9RSFB7FPF2L8HG35CMPC765TDK23RP6");
-    const string input("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
-    decodeBase32(input, result);
-    string output = encodeBase32(result);
-    EXPECT_EQ(input, output);
-}
-
-TEST_F(Base32Test, decode0) {
-    for (vector<StringPair>::const_iterator it = test_sequence.begin();
-         it != test_sequence.end();
-         ++it) {
-        decodeCheck((*it).second, decoded_data, (*it).first);
-    }
-}
-
-TEST_F(Base32Test, decode1) {
-    vector<uint8_t> result;
-    const std::string input("000G40O40K30E209185GO38E1S8124GJ");
-    decodeBase32(input, result);
-    EXPECT_EQ(20, result.size());
-    for (uint8_t i = 0; i < 20; i++) {
-        EXPECT_EQ((int) i, (int) result[i]);
-    }
-}
-
-TEST_F(Base32Test, encode0) {
-    for (vector<StringPair>::const_iterator it = test_sequence.begin();
-         it != test_sequence.end();
-         ++it) {
-        decoded_data.assign((*it).first.begin(), (*it).first.end());
-        EXPECT_EQ((*it).second, encodeBase32(decoded_data));
-    }
-}
-
-TEST_F(Base32Test, encode1) {
-    const std::string expect("000G40O40K30E209185GO38E1S8124GJ");
-    vector<uint8_t> binary;
-    for (uint8_t i = 0; i < 20; i++) {
-        binary.push_back(i);
-    }
-    string base32 = encodeBase32(binary);
-    EXPECT_EQ(expect, base32);
-}
-}

+ 161 - 0
src/lib/dns/tests/base32hex_unittest.cc

@@ -0,0 +1,161 @@
+// Copyright (C) 2010  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.
+
+// $Id$
+
+#include <stdint.h>
+
+#include <cctype>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/util/base32hex.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dns;
+
+namespace {
+
+typedef pair<string, string> StringPair;
+
+class Base32HexTest : public ::testing::Test {
+protected:
+    Base32HexTest() : encoding_chars("0123456789ABCDEFGHIJKLMNOPQRSTUV=") {
+        // test vectors from RFC4648
+        test_sequence.push_back(StringPair("", ""));
+        test_sequence.push_back(StringPair("f", "CO======"));
+        test_sequence.push_back(StringPair("fo", "CPNG===="));
+        test_sequence.push_back(StringPair("foo", "CPNMU==="));
+        test_sequence.push_back(StringPair("foob", "CPNMUOG="));
+        test_sequence.push_back(StringPair("fooba", "CPNMUOJ1"));
+        test_sequence.push_back(StringPair("foobar", "CPNMUOJ1E8======"));
+
+        // the same data, encoded using lower case chars (testable only
+        // for the decode side)
+        test_sequence_lower.push_back(StringPair("f", "co======"));
+        test_sequence_lower.push_back(StringPair("fo", "cpng===="));
+        test_sequence_lower.push_back(StringPair("foo", "cpnmu==="));
+        test_sequence_lower.push_back(StringPair("foob", "cpnmuog="));
+        test_sequence_lower.push_back(StringPair("fooba", "cpnmuoj1"));
+        test_sequence_lower.push_back(StringPair("foobar",
+                                                 "cpnmuoj1e8======"));
+    }
+    vector<StringPair> test_sequence;
+    vector<StringPair> test_sequence_lower;
+    vector<uint8_t> decoded_data;
+    const string encoding_chars;
+};
+
+void
+decodeCheck(const string& input_string, vector<uint8_t>& output,
+            const string& expected)
+{
+    decodeBase32Hex(input_string, output);
+    EXPECT_EQ(expected, string(&output[0], &output[0] + output.size()));
+}
+
+TEST_F(Base32HexTest, decode) {
+    for (vector<StringPair>::const_iterator it = test_sequence.begin();
+         it != test_sequence.end();
+         ++it) {
+        decodeCheck((*it).second, decoded_data, (*it).first);
+    }
+
+    // whitespace should be allowed
+    decodeCheck("CP NM\tUOG=", decoded_data, "foob");
+    decodeCheck("CPNMU===\n", decoded_data, "foo");
+
+    // invalid number of padding characters
+    EXPECT_THROW(decodeBase32Hex("CPNMU0==", decoded_data), BadValue);
+    EXPECT_THROW(decodeBase32Hex("CO0=====", decoded_data), BadValue);
+    EXPECT_THROW(decodeBase32Hex("CO=======", decoded_data), // too many ='s
+                 BadValue);
+
+    // intermediate padding isn't allowed
+    EXPECT_THROW(decodeBase32Hex("CPNMUOG=CPNMUOG=", decoded_data), BadValue);
+
+    // Non canonical form isn't allowed.
+    // P => 25(11001), so the padding byte would be 01000000
+    EXPECT_THROW(decodeBase32Hex("0P======", decoded_data), BadValue);
+}
+
+TEST_F(Base32HexTest, decodeLower) {
+    for (vector<StringPair>::const_iterator it = test_sequence_lower.begin();
+         it != test_sequence_lower.end();
+         ++it) {
+        decodeCheck((*it).second, decoded_data, (*it).first);
+    }
+}
+
+TEST_F(Base32HexTest, encode) {
+    for (vector<StringPair>::const_iterator it = test_sequence.begin();
+         it != test_sequence.end();
+         ++it) {
+        decoded_data.assign((*it).first.begin(), (*it).first.end());
+        EXPECT_EQ((*it).second, encodeBase32Hex(decoded_data));
+    }
+}
+
+// For Base32Hex we use handmade mappings, so it's prudent to test the
+// entire mapping table explicitly.
+TEST_F(Base32HexTest, decodeMap) {
+    string input(8, '0');       // input placeholder
+
+    // We're going to populate an input string with only the last character
+    // not equal to the zero character ('0') for each valid base32hex encoding
+    // character.  Decoding that input should result in a data stream with
+    // the last byte equal to the numeric value represented by the that
+    // character.  For example, we'll generate and confirm the following:
+    // "00000000" => should be 0 (as a 40bit integer)
+    // "00000001" => should be 1 (as a 40bit integer)
+    // ...
+    // "0000000V" => should be 31 (as a 40bit integer)
+    // We also check the use of an invalid character for the last character
+    // surely fails. '=' should be accepted as a valid padding in this
+    // context; space characters shouldn't be allowed in this context.
+
+    for (int i = 0; i < 256; ++i) {
+        input[7] = i;
+
+        const char ch = toupper(i);
+        const size_t pos = encoding_chars.find(ch);
+        if (pos == string::npos) {
+            EXPECT_THROW(decodeBase32Hex(input, decoded_data), BadValue);
+        } else {
+            decodeBase32Hex(input, decoded_data);
+            if (ch == '=') {
+                EXPECT_EQ(4, decoded_data.size());
+            } else {
+                EXPECT_EQ(5, decoded_data.size());
+                EXPECT_EQ(pos, decoded_data[4]);
+            }
+        }
+    }
+}
+
+TEST_F(Base32HexTest, encodeMap) {
+    for (int i = 0; i < 32; ++i) {
+        decoded_data.assign(4, 0);
+        decoded_data.push_back(i);
+        EXPECT_EQ(encoding_chars[i], encodeBase32Hex(decoded_data)[7]);
+    }
+}
+
+}

+ 9 - 6
src/lib/dns/tests/base64_unittest.cc

@@ -18,11 +18,14 @@
 #include <utility>
 #include <vector>
 
-#include <dns/base64.h>
+#include <exceptions/exceptions.h>
+
+#include <dns/util/base64.h>
 
 #include <gtest/gtest.h>
 
 using namespace std;
+using namespace isc;
 using namespace isc::dns;
 
 namespace {
@@ -67,18 +70,18 @@ TEST_F(Base64Test, decode) {
     decodeCheck("Zm9vYmE=\n", decoded_data, "fooba");
 
     // only up to 2 padding characters are allowed
-    EXPECT_THROW(decodeBase64("A===", decoded_data), BadBase64String);
-    EXPECT_THROW(decodeBase64("A= ==", decoded_data), BadBase64String);
+    EXPECT_THROW(decodeBase64("A===", decoded_data), BadValue);
+    EXPECT_THROW(decodeBase64("A= ==", decoded_data), BadValue);
 
     // intermediate padding isn't allowed
-    EXPECT_THROW(decodeBase64("YmE=YmE=", decoded_data), BadBase64String);
+    EXPECT_THROW(decodeBase64("YmE=YmE=", decoded_data), BadValue);
 
     // Non canonical form isn't allowed.
     // Z => 25(011001), m => 38(100110), 9 => 60(111101), so the padding
     // byte would be 0100 0000.
-    EXPECT_THROW(decodeBase64("Zm9=", decoded_data), BadBase64String);
+    EXPECT_THROW(decodeBase64("Zm9=", decoded_data), BadValue);
     // Same for the 1st padding byte.  This would make it 01100000.
-    EXPECT_THROW(decodeBase64("Zm==", decoded_data), BadBase64String);
+    EXPECT_THROW(decodeBase64("Zm==", decoded_data), BadValue);
 }
 
 TEST_F(Base64Test, encode) {

+ 46 - 12
src/lib/dns/tests/hex_unittest.cc

@@ -19,26 +19,28 @@
 #include <vector>
 #include <string>
 
-#include <dns/hex.h>
+#include <exceptions/exceptions.h>
 
-#include <gtest/gtest.h>
+#include <dns/util/hex.h>
 
-#include <dns/tests/unittest_util.h>
+#include <gtest/gtest.h>
 
-using isc::UnitTestUtil;
 using namespace std;
+using namespace isc;
 using namespace isc::dns;
 
 namespace {
+const string hex_txt("DEADBEEFDECADE");
+const string hex_txt_space("DEAD BEEF DECADE");
+const string hex_txt_lower("deadbeefdecade");
+
 class HexTest : public ::testing::Test {
 protected:
-    HexTest() {}
+    HexTest() : encoding_chars("0123456789ABCDEF") {}
+    vector<uint8_t> decoded_data;
+    const string encoding_chars;
 };
 
-const std::string hex_txt("DEADBEEFDECADE");
-const std::string hex_txt_space("DEAD BEEF DECADE");
-const std::string hex_txt_lower("deadbeefdecade");
-
 TEST_F(HexTest, encodeHex) {
     std::vector<uint8_t> data;
 
@@ -53,8 +55,7 @@ TEST_F(HexTest, encodeHex) {
 }
 
 void
-compareData(const std::vector<uint8_t>& data)
-{
+compareData(const std::vector<uint8_t>& data) {
     EXPECT_EQ(0xde, data[0]);
     EXPECT_EQ(0xad, data[1]);
     EXPECT_EQ(0xbe, data[2]);
@@ -82,7 +83,40 @@ TEST_F(HexTest, decodeHex) {
 
     // Bogus input: should fail
     result.clear();
-    EXPECT_THROW(decodeHex("1x", result), BadHexString);
+    EXPECT_THROW(decodeHex("1x", result), BadValue);
+
+    // Bogus input: encoded string must have an even number of characters.
+    result.clear();
+    EXPECT_THROW(decodeHex("dea", result), BadValue);
+}
+
+// For Hex encode/decode we use handmade mappings, so it's prudent to test the
+// entire mapping table explicitly.
+TEST_F(HexTest, decodeMap) {
+    string input("00");       // input placeholder
+
+    // See Base32HexTest.decodeMap for details of the following tests.
+    for (int i = 0; i < 256; ++i) {
+        input[1] = i;
+
+        const char ch = toupper(i);
+        const size_t pos = encoding_chars.find(ch);
+        if (pos == string::npos) {
+            EXPECT_THROW(decodeHex(input, decoded_data), BadValue);
+        } else {
+            decodeHex(input, decoded_data);
+            EXPECT_EQ(1, decoded_data.size());
+            EXPECT_EQ(pos, decoded_data[0]);
+        }
+    }
+}
+
+TEST_F(HexTest, encodeMap) {
+    for (int i = 0; i < 16; ++i) {
+        decoded_data.clear();
+        decoded_data.push_back(i);
+        EXPECT_EQ(encoding_chars[i], encodeHex(decoded_data)[1]);
+    }
 }
 
 }

+ 4 - 3
src/lib/dns/tests/rdata_dnskey_unittest.cc

@@ -16,7 +16,8 @@
 
 #include <string>
 
-#include <dns/base64.h>
+#include <exceptions/exceptions.h>
+
 #include <dns/buffer.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
@@ -31,6 +32,7 @@
 
 using isc::UnitTestUtil;
 using namespace std;
+using namespace isc;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
@@ -68,8 +70,7 @@ TEST_F(Rdata_DNSKEY_Test, badText) {
                  InvalidRdataText);
     EXPECT_THROW(generic::DNSKEY("257 3 500 BAAAAAAAAAAAD"),
                  InvalidRdataText);
-    EXPECT_THROW(generic::DNSKEY("257 3 5 BAAAAAAAAAAAD"),
-                 BadBase64String);
+    EXPECT_THROW(generic::DNSKEY("257 3 5 BAAAAAAAAAAAD"), BadValue);
 }
 
 TEST_F(Rdata_DNSKEY_Test, DISABLED_badText) {

+ 19 - 12
src/lib/dns/tests/rdata_nsec3_unittest.cc

@@ -16,10 +16,11 @@
 
 #include <string>
 
-#include <dns/base32.h>
+#include <exceptions/exceptions.h>
+
 #include <dns/buffer.h>
 #include <dns/exceptions.h>
-#include <dns/hex.h>
+#include <dns/util/hex.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
@@ -33,16 +34,19 @@
 
 using isc::UnitTestUtil;
 using namespace std;
+using namespace isc;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
 namespace {
 class Rdata_NSEC3_Test : public RdataTest {
     // there's nothing to specialize
+public:
+    Rdata_NSEC3_Test() :
+        nsec3_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
+                  "NS SOA RRSIG DNSKEY NSEC3PARAM") {}
+    string nsec3_txt;
 };
-string nsec3_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
-                 "NS SOA RRSIG DNSKEY NSEC3PARAM");
-
 
 TEST_F(Rdata_NSEC3_Test, toText) {
     const generic::NSEC3 rdata_nsec3(nsec3_txt);
@@ -50,30 +54,33 @@ TEST_F(Rdata_NSEC3_Test, toText) {
 }
 
 TEST_F(Rdata_NSEC3_Test, badText) {
-    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEE "
+    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEEE "
                                             "0123456789ABCDEFGHIJKLMNOPQRSTUV "
                                             "BIFF POW SPOON"),
                  InvalidRdataText);
     EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEE "
                                             "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW "
                                             "A NS SOA"),
-                 BadBase32String);
-    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1000000 1 1 ADDAFEE "
+                 BadValue);     // bad hex
+    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1 ADDAFEEE "
+                                            "WXYZWXYZWXYZ=WXYZWXYZ==WXYZWXYZW "
+                                            "A NS SOA"),
+                 BadValue);     // bad base32hex
+    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1000000 1 1 ADDAFEEE "
                                             "0123456789ABCDEFGHIJKLMNOPQRSTUV "
                                             "A NS SOA"),
                  InvalidRdataText);
-    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1000000 1 ADDAFEE "
+    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1000000 1 ADDAFEEE "
                                             "0123456789ABCDEFGHIJKLMNOPQRSTUV "
                                             "A NS SOA"),
                  InvalidRdataText);
-    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1000000 ADDAFEE "
+    EXPECT_THROW(generic::NSEC3 rdata_nsec3("1 1 1000000 ADDAFEEE "
                                             "0123456789ABCDEFGHIJKLMNOPQRSTUV "
                                             "A NS SOA"),
                  InvalidRdataText);
 }
 
-TEST_F(Rdata_NSEC3_Test, DISABLED_badText) {
-    // this currently fails
+TEST_F(Rdata_NSEC3_Test, DISABLED_badText) { // this currently fails
     EXPECT_THROW(generic::NSEC3(
                      "1 1 1D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
                      "NS SOA RRSIG DNSKEY NSEC3PARAM"), InvalidRdataText);

+ 6 - 3
src/lib/dns/tests/rdata_nsec3param_unittest.cc

@@ -16,9 +16,11 @@
 
 #include <string>
 
-#include <dns/base32.h>
+#include <exceptions/exceptions.h>
+
+#include <dns/util/base32hex.h>
 #include <dns/buffer.h>
-#include <dns/hex.h>
+#include <dns/util/hex.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
@@ -32,6 +34,7 @@
 
 using isc::UnitTestUtil;
 using namespace std;
+using namespace isc;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
@@ -47,7 +50,7 @@ TEST_F(Rdata_NSEC3PARAM_Test, toText) {
 }
 
 TEST_F(Rdata_NSEC3PARAM_Test, badText) {
-    EXPECT_THROW(generic::NSEC3PARAM("1 1 1 SPORK"), BadHexString);
+    EXPECT_THROW(generic::NSEC3PARAM("1 1 1 SPORK"), BadValue); // bad hex
     EXPECT_THROW(generic::NSEC3PARAM("100000 1 1 ADDAFEE"), InvalidRdataText);
     EXPECT_THROW(generic::NSEC3PARAM("1 100000 1 ADDAFEE"), InvalidRdataText);
     EXPECT_THROW(generic::NSEC3PARAM("1 1 100000 ADDAFEE"), InvalidRdataText);

+ 6 - 3
src/lib/dns/tests/rdata_rrsig_unittest.cc

@@ -14,7 +14,8 @@
 
 // $Id$
 
-#include <dns/base64.h>
+#include <exceptions/exceptions.h>
+
 #include <dns/buffer.h>
 #include <dns/dnssectime.h>
 #include <dns/messagerenderer.h>
@@ -30,6 +31,7 @@
 
 using isc::UnitTestUtil;
 using namespace std;
+using namespace isc;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
@@ -81,10 +83,11 @@ TEST_F(Rdata_RRSIG_Test, badText) {
                      "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
                      "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
                      "f49t+sXKPzbipN9g+s1ZPiIyofc="), InvalidRdataText);
-    EXPECT_THROW(const generic::RRSIG sig("A 5 4 43200 "
+    EXPECT_THROW(const generic::RRSIG sig(
+                     "A 5 4 43200 "
                      "20100223214617 20100222214617 8496 isc.org. "
                      "EEeeeeeeEEEeeeeeeGaaahAAAAAAAAHHHHHHHHHHH!="),
-                     BadBase64String);
+                 BadValue);     // bad base64 input
 }
 
 TEST_F(Rdata_RRSIG_Test, DISABLED_badText) {

+ 1 - 1
src/lib/dns/tests/sha1_unittest.cc

@@ -17,7 +17,7 @@
 #include <stdint.h>
 #include <string>
 
-#include <dns/sha1.h>
+#include <dns/util/sha1.h>
 
 #include <gtest/gtest.h>
 

+ 31 - 0
src/lib/dns/util/README

@@ -0,0 +1,31 @@
+This "util" directory is provided for utility header files and
+implementations that are internally used in the DNS library
+(libdns++).
+
+The functionality provided in these tools is generally available in
+other external or perhaps system supplied libraries.  The basic
+development policy of BIND 10 is to avoid "reinventing wheels" unless
+they belong to the exact technology area that BIND 10 targets (e.g.,
+DNS).  However, libdns++ is a very core part of BIND 10, and is also
+intended to be used as a public library, so dependency from libdns++
+to external libraries should be minimized.  The utilities in this
+directory are provided balancing two policies and as a kind of
+compromise.
+
+The header files in this directory are therefore not intended to be
+installed.  Likewise, classes and public functions defined in this
+directory are not intended to be used outside libdns++, although we
+cannot prohibit it at the language level.
+
+They are not even expected to be used in other modules of BIND 10 than
+libdns++ based on the basic policy explained above.  Other modules
+should only rely on the DNS specific interface that may internally
+rely on these utility interfaces, or should use external libraries if
+the other module really needs to use the utility feature directly.
+There seem to be some violations as of this writing, but we should
+eventually fix it.  A notable example is the SHA1 interfaces.  They
+are defined here in the context of NSEC3 processing, but, in fact,
+they are not even used from any of the other libdns++ classes or
+functions.  The SHA1 related interfaces should be moved to the
+application that needs it or the application should only access it
+through DNS specific interfaces defined in libdns++.

+ 108 - 0
src/lib/dns/util/base16_from_binary.h

@@ -0,0 +1,108 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP
+#define BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP
+
+// MS compatible compilers support #pragma once
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// base16_from_binary.h (derived from boost base64_from_binary.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com . 
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+//  See http://www.boost.org for updates, documentation, and revision history.
+
+#include <cassert>
+
+#include <cstddef> // size_t
+#include <boost/config.hpp> // for BOOST_DEDUCED_TYPENAME
+#if defined(BOOST_NO_STDC_NAMESPACE)
+namespace std{ 
+    using ::size_t; 
+} // namespace std
+#endif
+
+// See base32hex_from_binary.h for why we need base64_from...hpp here.
+#include <boost/archive/iterators/base64_from_binary.hpp>
+
+namespace boost { 
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert binary integers to base16 characters
+
+namespace detail {
+
+template<class CharType>
+struct from_4_bit {
+    typedef CharType result_type;
+    CharType operator()(CharType t) const{
+        const char * lookup_table = 
+            "0123456789"
+            "ABCDEF";
+        assert(t < 16);
+        return lookup_table[static_cast<size_t>(t)];
+    }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+//  typedef transform_iterator<
+//      from_4_bit<CharType>,
+//      transform_width<Base, 4, sizeof(Base::value_type) * 8, CharType>
+//  > base16_from_binary;
+// but C++ won't accept this.  Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor.  This makes it incompatible with the dataflow
+// ideal.  This is also addressed here.
+
+//template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+template<
+    class Base, 
+    class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class base16_from_binary : 
+    public transform_iterator<
+        detail::from_4_bit<CharType>,
+        Base
+    >
+{
+    friend class boost::iterator_core_access;
+    typedef transform_iterator<
+        BOOST_DEDUCED_TYPENAME detail::from_4_bit<CharType>,
+        Base
+    > super_t;
+
+public:
+    // make composible buy using templated constructor
+    template<class T>
+    base16_from_binary(BOOST_PFTO_WRAPPER(T) start) :
+        super_t(
+            Base(BOOST_MAKE_PFTO_WRAPPER(static_cast<T>(start))),
+            detail::from_4_bit<CharType>()
+        )
+    {}
+    // intel 7.1 doesn't like default copy constructor
+    base16_from_binary(const base16_from_binary & rhs) : 
+        super_t(
+            Base(rhs.base_reference()),
+            detail::from_4_bit<CharType>()
+        )
+    {}
+//    base16_from_binary(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BASE16_FROM_BINARY_HPP

+ 26 - 17
src/lib/dns/base64.h

@@ -14,15 +14,13 @@
 
 // $Id$
 
-#ifndef __BASE64_H
-#define __BASE64_H 1
+#ifndef __BASE32HEX_H
+#define __BASE32HEX_H 1
 
 #include <stdint.h>
 #include <string>
 #include <vector>
 
-#include <exceptions/exceptions.h>
-
 //
 // Note: this helper module isn't specific to the DNS protocol per se.
 // We should probably move this to somewhere else, possibly in some common
@@ -32,23 +30,34 @@
 namespace isc {
 namespace dns {
 
+/// \brief Encode binary data in the base32hex format.
+///
+/// The underlying implementation is shared with \c encodeBase64, and all
+/// description except the format (base32hex) equally applies.
+///
+/// Note: the encoding format is base32hex, not base32.
+///
+/// \param binary A vector object storing the data to be encoded. 
+/// \return A newly created string that stores base32hex encoded value for
+/// binary.
+std::string encodeBase32Hex(const std::vector<uint8_t>& binary);
+
+/// \brief Decode a text encoded in the base32hex format into the
+/// original %data.
+///
+/// The underlying implementation is shared with \c decodeBase64, and all
+/// description except the format (base32hex) equally applies.
 ///
-/// \brief A standard DNS (or ISC) module exception that is thrown a Base64
-/// decoder encounters an invalid input.
+/// Note: the encoding format is base32hex, not base32.
 ///
-class BadBase64String : public Exception {
-public:
-    BadBase64String(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-std::string encodeBase64(const std::vector<uint8_t>& binary);
-void decodeBase64(const std::string& base64, std::vector<uint8_t>& result);
+/// \param input A text encoded in the base32hex format.
+/// \param result A vector in which the decoded %data is to be stored.
+void decodeBase32Hex(const std::string& input, std::vector<uint8_t>& result);
 }
 }
 
-#endif  // __BASE64_H
+#endif  // __BASE32HEX_H
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:

+ 110 - 0
src/lib/dns/util/base32hex_from_binary.h

@@ -0,0 +1,110 @@
+#ifndef BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP
+#define BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP
+
+// MS compatible compilers support #pragma once
+#if defined(_MSC_VER) && (_MSC_VER >= 1020)
+# pragma once
+#endif
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// base32hex_from_binary.h (derived from boost base64_from_binary.hpp)
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com . 
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+//  See http://www.boost.org for updates, documentation, and revision history.
+
+#include <cassert>
+
+#include <cstddef> // size_t
+#include <boost/config.hpp> // for BOOST_DEDUCED_TYPENAME
+#if defined(BOOST_NO_STDC_NAMESPACE)
+namespace std{ 
+    using ::size_t; 
+} // namespace std
+#endif
+
+// We use the same boost header files used in "base64_from_".  Since the
+// precise path to these headers may vary depending on the boost version we
+// simply include the base64 header here.
+#include <boost/archive/iterators/base64_from_binary.hpp>
+
+namespace boost { 
+namespace archive {
+namespace iterators {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// convert binary integers to base32hex characters
+
+namespace detail {
+
+template<class CharType>
+struct from_5_bit {
+    typedef CharType result_type;
+    CharType operator()(CharType t) const{
+        const char * lookup_table = 
+            "0123456789"
+            "ABCDEFGHIJKLMNOPQRSTUV";
+        assert(t < 32);
+        return lookup_table[static_cast<size_t>(t)];
+    }
+};
+
+} // namespace detail
+
+// note: what we would like to do is
+// template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+//  typedef transform_iterator<
+//      from_5_bit<CharType>,
+//      transform_width<Base, 5, sizeof(Base::value_type) * 8, CharType>
+//  > base32hex_from_binary;
+// but C++ won't accept this.  Rather than using a "type generator" and
+// using a different syntax, make a derivation which should be equivalent.
+//
+// Another issue addressed here is that the transform_iterator doesn't have
+// a templated constructor.  This makes it incompatible with the dataflow
+// ideal.  This is also addressed here.
+
+//template<class Base, class CharType = BOOST_DEDUCED_TYPENAME Base::value_type>
+template<
+    class Base, 
+    class CharType = BOOST_DEDUCED_TYPENAME boost::iterator_value<Base>::type
+>
+class base32hex_from_binary : 
+    public transform_iterator<
+        detail::from_5_bit<CharType>,
+        Base
+    >
+{
+    friend class boost::iterator_core_access;
+    typedef transform_iterator<
+        BOOST_DEDUCED_TYPENAME detail::from_5_bit<CharType>,
+        Base
+    > super_t;
+
+public:
+    // make composible buy using templated constructor
+    template<class T>
+    base32hex_from_binary(BOOST_PFTO_WRAPPER(T) start) :
+        super_t(
+            Base(BOOST_MAKE_PFTO_WRAPPER(static_cast<T>(start))),
+            detail::from_5_bit<CharType>()
+        )
+    {}
+    // intel 7.1 doesn't like default copy constructor
+    base32hex_from_binary(const base32hex_from_binary & rhs) : 
+        super_t(
+            Base(rhs.base_reference()),
+            detail::from_5_bit<CharType>()
+        )
+    {}
+//    base32hex_from_binary(){};
+};
+
+} // namespace iterators
+} // namespace archive
+} // namespace boost
+
+#endif // BOOST_ARCHIVE_ITERATORS_BASE32HEX_FROM_BINARY_HPP

+ 0 - 0
src/lib/dns/util/base64.h


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