Browse Source

[1213] Merge branch 'master' into trac1213

Conflicts:
	tests/system/cleanall.sh
Stephen Morris 13 years ago
parent
commit
044381e03b
100 changed files with 5556 additions and 837 deletions
  1. 40 1
      ChangeLog
  2. 4 0
      Makefile.am
  3. 6 0
      configure.ac
  4. 1 1
      doc/Doxyfile
  5. 50 6
      doc/guide/bind10-guide.xml
  6. 6 0
      src/bin/auth/Makefile.am
  7. 6 0
      src/bin/auth/benchmarks/Makefile.am
  8. 8 1
      src/bin/auth/tests/Makefile.am
  9. 4 5
      src/bin/dhcp6/Makefile.am
  10. 2 2
      src/bin/dhcp6/b10-dhcp6.8
  11. 22 51
      src/bin/dhcp6/dhcp6.h
  12. 55 0
      src/bin/dhcp6/dhcp6_srv.cc
  13. 40 0
      src/bin/dhcp6/dhcp6_srv.h
  14. 581 0
      src/bin/dhcp6/iface_mgr.cc
  15. 103 0
      src/bin/dhcp6/iface_mgr.h
  16. 10 0
      src/bin/dhcp6/interfaces.txt
  17. 14 22
      src/bin/dhcp6/main.cc
  18. 46 0
      src/bin/dhcp6/pkt6.cc
  19. 62 0
      src/bin/dhcp6/pkt6.h
  20. 45 0
      src/bin/dhcp6/tests/Makefile.am
  21. 53 0
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  22. 28 0
      src/bin/dhcp6/tests/dhcp6_unittests.cc
  23. 263 0
      src/bin/dhcp6/tests/iface_mgr_unittest.cc
  24. 44 0
      src/bin/dhcp6/tests/pkt6_unittest.cc
  25. 1 3
      src/bin/resolver/tests/Makefile.am
  26. 1 1
      src/bin/sockcreator/tests/Makefile.am
  27. 10 5
      src/bin/xfrin/b10-xfrin.xml
  28. 7 0
      src/bin/xfrin/tests/Makefile.am
  29. 2 0
      src/bin/xfrin/tests/testdata/Makefile.am
  30. 17 0
      src/bin/xfrin/tests/testdata/example.com
  31. BIN
      src/bin/xfrin/tests/testdata/example.com.sqlite3
  32. 1140 126
      src/bin/xfrin/tests/xfrin_test.py
  33. 494 67
      src/bin/xfrin/xfrin.py.in
  34. 40 10
      src/bin/xfrin/xfrin_messages.mes
  35. 1 0
      src/cppcheck-suppress.lst
  36. 1 1
      src/lib/acl/tests/Makefile.am
  37. 1 1
      src/lib/asiodns/tests/Makefile.am
  38. 5 0
      src/lib/asiolink/io_address.cc
  39. 8 0
      src/lib/asiolink/io_address.h
  40. 1 1
      src/lib/asiolink/tests/Makefile.am
  41. 2 0
      src/lib/asiolink/tests/io_address_unittest.cc
  42. 1 1
      src/lib/asiolink/tests/io_endpoint_unittest.cc
  43. 1 1
      src/lib/bench/tests/Makefile.am
  44. 1 1
      src/lib/cache/tests/Makefile.am
  45. 1 1
      src/lib/cc/tests/Makefile.am
  46. 1 1
      src/lib/config/tests/Makefile.am
  47. 1 1
      src/lib/cryptolink/tests/Makefile.am
  48. 13 3
      src/lib/datasrc/Makefile.am
  49. 44 0
      src/lib/datasrc/client.h
  50. 6 6
      src/lib/datasrc/data_source.h
  51. 95 0
      src/lib/datasrc/factory.cc
  52. 170 0
      src/lib/datasrc/factory.h
  53. 150 1
      src/lib/datasrc/memory_datasrc.cc
  54. 35 1
      src/lib/datasrc/memory_datasrc.h
  55. 80 15
      src/lib/datasrc/sqlite3_accessor.cc
  56. 25 14
      src/lib/datasrc/sqlite3_accessor.h
  57. 15 4
      src/lib/datasrc/tests/Makefile.am
  58. 1 2
      src/lib/datasrc/tests/database_unittest.cc
  59. 175 0
      src/lib/datasrc/tests/factory_unittest.cc
  60. 19 21
      src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
  61. 4 0
      src/lib/dns/Makefile.am
  62. 4 4
      src/lib/dns/python/message_python.cc
  63. 225 0
      src/lib/dns/rdata/generic/detail/ds_like.h
  64. 54 2
      src/lib/dns/rdata/generic/detail/txt_like.h
  65. 121 0
      src/lib/dns/rdata/generic/dlv_32769.cc
  66. 77 0
      src/lib/dns/rdata/generic/dlv_32769.h
  67. 13 96
      src/lib/dns/rdata/generic/ds_43.cc
  68. 27 6
      src/lib/dns/rdata/generic/ds_43.h
  69. 44 0
      src/lib/dns/rdata/generic/spf_99.cc
  70. 26 0
      src/lib/dns/rdata/generic/spf_99.h
  71. 4 4
      src/lib/dns/rdata/in_1/dhcid_49.cc
  72. 1 1
      src/lib/dns/rdata/in_1/dhcid_49.h
  73. 5 3
      src/lib/dns/tests/Makefile.am
  74. 111 0
      src/lib/dns/tests/rdata_dhcid_unittest.cc
  75. 171 0
      src/lib/dns/tests/rdata_ds_like_unittest.cc
  76. 0 99
      src/lib/dns/tests/rdata_ds_unittest.cc
  77. 261 0
      src/lib/dns/tests/rdata_txt_like_unittest.cc
  78. 0 166
      src/lib/dns/tests/rdata_txt_unittest.cc
  79. 1 0
      src/lib/dns/tests/testdata/Makefile.am
  80. 12 0
      src/lib/dns/tests/testdata/rdata_dhcid_fromWire
  81. 7 0
      src/lib/dns/tests/testdata/rdata_dhcid_toWire
  82. 1 1
      src/lib/exceptions/tests/Makefile.am
  83. 1 1
      src/lib/log/tests/Makefile.am
  84. 1 1
      src/lib/nsas/tests/Makefile.am
  85. 1 1
      src/lib/python/isc/Makefile.am
  86. 2 2
      src/lib/python/isc/config/ccsession.py
  87. 3 0
      src/lib/python/isc/config/tests/ccsession_test.py
  88. 0 1
      src/lib/python/isc/datasrc/Makefile.am
  89. 14 0
      src/lib/python/isc/datasrc/__init__.py
  90. 15 2
      src/lib/python/isc/datasrc/client_inc.cc
  91. 35 22
      src/lib/python/isc/datasrc/client_python.cc
  92. 55 28
      src/lib/python/isc/datasrc/datasrc.cc
  93. 22 0
      src/lib/python/isc/datasrc/finder_inc.cc
  94. 46 8
      src/lib/python/isc/datasrc/finder_python.cc
  95. 9 1
      src/lib/python/isc/datasrc/finder_python.h
  96. 17 2
      src/lib/python/isc/datasrc/iterator_python.cc
  97. 9 1
      src/lib/python/isc/datasrc/iterator_python.h
  98. 6 1
      src/lib/python/isc/datasrc/tests/Makefile.am
  99. 93 7
      src/lib/python/isc/datasrc/tests/datasrc_test.py
  100. 0 0
      src/lib/python/isc/datasrc/updater_python.cc

+ 40 - 1
ChangeLog

@@ -1,3 +1,42 @@
+297.	[func]		dvv
+	Implement the SPF rrtype according to RFC4408.
+	(Trac #1140, git 146934075349f94ee27f23bf9ff01711b94e369e)
+
+296.	[build]		jreed
+	Do not install the unittest libraries. At this time, they
+	are not useful without source tree (and they may or may
+	not have googletest support). Also, convert several makefiles
+	to build tests at "check" time and not build time.
+	(Trac #1091, git 2adf4a90ad79754d52126e7988769580d20501c3)
+
+295.	[bug]		jinmei
+	__init__.py for isc.dns was installed in the wrong directory,
+	which would now make xfrin fail to start.  It was also bad
+	in that it replaced any existing __init__.py in th public
+	site-packages directory.  After applying this fix You may want to
+	check if the wrong init file is in the wrong place, in which
+	case it should be removed.
+	(Trac #1285, git af3b17472694f58b3d6a56d0baf64601b0f6a6a1)
+
+294.	[func]		jelte, jinmei, vorner
+	b10-xfrin now supports incoming IXFR.  See BIND 10 Guide for
+	how to configure it and operational notes.
+	(Trac #1212, multiple git merges)
+
+293.    [func]*		tomek
+	b10-dhcp6: Implemented DHCPv6 echo server. It joins DHCPv6
+	multicast groups and listens to incoming DHCPv6 client messages.
+	Received messages are then echoed back to clients. This
+	functionality is limited, but it can be used to test out client
+	resiliency to unexpected messages. Note that network interface
+	detection routines are not implemented yet, so interface name
+	and its address must be specified in interfaces.txt.
+	(Trac #878, git 3b1a604abf5709bfda7271fa94213f7d823de69d)
+
+292.	[func]		dvv
+	Implement the DLV rrtype according to RFC4431.
+	(Trac #1144, git d267c0511a07c41cd92e3b0b9ee9bf693743a7cf)
+
 291.    [func]          naokikambe
 	Statistics items are specified by each module's spec file.
 	Stats module can read these through the config manager. Stats
@@ -31,7 +70,7 @@
 	configuration.
 	(Trac #1165, git 698176eccd5d55759fe9448b2c249717c932ac31)
 
-288.    [bug]       stephen
+288.    [bug]		stephen
 	Fixed problem whereby the order in which component files appeared in
 	rdataclass.cc was system dependent, leading to problems on some
 	systems where data types were used before the header file in which

+ 4 - 0
Makefile.am

@@ -2,12 +2,16 @@ SUBDIRS = doc src tests
 USE_LCOV=@USE_LCOV@
 LCOV=@LCOV@
 GENHTML=@GENHTML@
+DISTCHECK_GTEST_CONFIGURE_FLAG=@DISTCHECK_GTEST_CONFIGURE_FLAG@
 
 DISTCLEANFILES = config.report
 
 # When running distcheck target, do not install the configurations
 DISTCHECK_CONFIGURE_FLAGS = --disable-install-configurations
 
+# Use same --with-gtest flag if set
+DISTCHECK_CONFIGURE_FLAGS += $(DISTCHECK_GTEST_CONFIGURE_FLAG)
+
 clean-cpp-coverage:
 	@if [ $(USE_LCOV) = yes ] ; then \
 		$(LCOV) --directory . --zerocounters; \

+ 6 - 0
configure.ac

@@ -650,6 +650,7 @@ fi
 #
 if test "$gtest_path" != "no"
 then
+	DISTCHECK_GTEST_CONFIGURE_FLAG="--with-gtest=\"$gtest_path\""
 	if test "$gtest_path" != "yes"; then
 		GTEST_PATHS=$gtest_path
 		if test -x "${gtest_path}/bin/gtest-config" ; then
@@ -690,8 +691,10 @@ else
 	GTEST_INCLUDES=
 	GTEST_LDFLAGS=
 	GTEST_LDADD=
+	DISTCHECK_GTEST_CONFIGURE_FLAG=
 fi
 AM_CONDITIONAL(HAVE_GTEST, test $gtest_path != "no")
+AC_SUBST(DISTCHECK_GTEST_CONFIGURE_FLAG)
 AC_SUBST(GTEST_INCLUDES)
 AC_SUBST(GTEST_LDFLAGS)
 AC_SUBST(GTEST_LDADD)
@@ -813,6 +816,7 @@ AC_CONFIG_FILES([Makefile
                  src/bin/sockcreator/tests/Makefile
                  src/bin/xfrin/Makefile
                  src/bin/xfrin/tests/Makefile
+                 src/bin/xfrin/tests/testdata/Makefile
                  src/bin/xfrout/Makefile
                  src/bin/xfrout/tests/Makefile
                  src/bin/zonemgr/Makefile
@@ -855,6 +859,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/testutils/Makefile
                  src/lib/python/isc/bind10/Makefile
                  src/lib/python/isc/bind10/tests/Makefile
+                 src/lib/python/isc/xfrin/Makefile
+                 src/lib/python/isc/xfrin/tests/Makefile
                  src/lib/config/Makefile
                  src/lib/config/tests/Makefile
                  src/lib/config/tests/testdata/Makefile

+ 1 - 1
doc/Doxyfile

@@ -574,7 +574,7 @@ INPUT                  = ../src/lib/exceptions ../src/lib/cc \
     ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
     ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
     ../src/bin/sockcreator/ ../src/lib/util/ \
-    ../src/lib/resolve ../src/lib/acl
+    ../src/lib/resolve ../src/lib/acl ../src/bin/dhcp6
 
 # 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

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

@@ -1257,21 +1257,65 @@ TODO
     <para>
       Incoming zones are transferred using the <command>b10-xfrin</command>
       process which is started by <command>bind10</command>.
-      When received, the zone is stored in the BIND 10
-      data store, and its records can be served by
+      When received, the zone is stored in the corresponding BIND 10
+      data source, and its records can be served by
       <command>b10-auth</command>.
       In combination with <command>b10-zonemgr</command> (for
       automated SOA checks), this allows the BIND 10 server to
       provide <quote>secondary</quote> service.
     </para>
 
+    <para>
+      The <command>b10-xfrin</command> process supports both AXFR and
+      IXFR.  Due to some implementation limitations of the current
+      development release, however, it only tries AXFR by default,
+      and care should be taken to enable IXFR.
+    </para>
+
     <note><simpara>
-     The current development release of BIND 10 only supports
-     AXFR. (IXFR is not supported.)
+     In the current development release of BIND 10, incoming zone
+     transfers are only available for SQLite3-based data sources,
+     that is, they don't work for an in-memory data source.
+    </simpara></note>
 
-<!-- TODO: sqlite3 data source only? -->
+    <para>
+      To enable IXFR, you need to
+      configure <command>b10-xfrin</command> with an explicit zone
+      configuration for the zone.
+      For example, to enable IXFR for a zone named "example.com"
+      (whose master address is assumed to be 2001:db8::53 here),
+      run the following at the <command>bindctl</command> prompt:
+
+      <screen>&gt; <userinput>config add Xfrin/zones</userinput>
+&gt; <userinput>config set Xfrin/zones[0]/name "<option>example.com</option>"</userinput>
+&gt; <userinput>config set Xfrin/zones[0]/master_addr "<option>2001:db8::53</option>"</userinput>
+&gt; <userinput>config commit</userinput></screen>
+
+      (We assume there has been no zone configuration before).
+      Note that you do NOT have to explicitly enable IXFR in the zone
+      configuration; once it's defined, IXFR is enabled by default.
+      This also means if you specify a zone configuration for some
+      other reason but don't want to use IXFR for that zone, you need
+      to disable it explicitly:
+
+      <screen>&gt; <userinput>config set Xfrin/zones[0]/ixfr_disabled true</userinput></screen>
+    </para>
 
-    </simpara></note>
+    <para>
+      One reason why IXFR is disabled by default in the current
+      release is because it does not support automatic fallback from IXFR to
+      AXFR when it encounters a primary server that doesn't support
+      outbound IXFR (and, not many existing implementations support
+      it).  Another, related reason is that it does not use AXFR even
+      if it has no knowledge about the zone (like at the very first
+      time the secondary server is set up).  IXFR requires the
+      "current version" of the zone, so obviously it doesn't work
+      in this situation and AXFR is the only workable choice.
+      The current release of <command>b10-xfrin</command> does not
+      make this selection automatically.
+      These features will be implemented in a near future
+      version, at which point we will enable IXFR by default.
+    </para>
 
 <!-- TODO:
 

+ 6 - 0
src/bin/auth/Makefile.am

@@ -50,6 +50,12 @@ b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += common.h common.cc
 b10_auth_SOURCES += statistics.cc statistics.h
 b10_auth_SOURCES += main.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+b10_auth_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
 
 nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
 EXTRA_DIST += auth_messages.mes

+ 6 - 0
src/bin/auth/benchmarks/Makefile.am

@@ -13,6 +13,12 @@ query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
 query_bench_SOURCES += ../auth_config.h ../auth_config.cc
 query_bench_SOURCES += ../statistics.h ../statistics.cc
 query_bench_SOURCES += ../auth_log.h ../auth_log.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+query_bench_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
 
 nodist_query_bench_SOURCES = ../auth_messages.h ../auth_messages.cc
 

+ 8 - 1
src/bin/auth/tests/Makefile.am

@@ -37,6 +37,13 @@ run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
+# This is a temporary workaround for #1206, where the InMemoryClient has been
+# moved to an ldopened library. We could add that library to LDADD, but that
+# is nonportable. When #1207 is done this becomes moot anyway, and the
+# specific workaround is not needed anymore, so we can then remove this
+# line again.
+run_unittests_SOURCES += ${top_srcdir}/src/lib/datasrc/memory_datasrc.cc
+
 
 nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
 
@@ -60,4 +67,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

+ 4 - 5
src/bin/dhcp6/Makefile.am

@@ -19,7 +19,7 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 CLEANFILES = *.gcno *.gcda spec_config.h
 
 man_MANS = b10-dhcp6.8
-EXTRA_DIST = $(man_MANS) dhcp6.spec
+EXTRA_DIST = $(man_MANS) dhcp6.spec interfaces.txt
 
 #if ENABLE_MAN
 #b10-dhcp6.8: b10-dhcp6.xml
@@ -31,8 +31,8 @@ spec_config.h: spec_config.h.pre
 
 BUILT_SOURCES = spec_config.h
 pkglibexec_PROGRAMS = b10-dhcp6
-b10_dhcp6_SOURCES = main.cc
-b10_dhcp6_SOURCES += dhcp6.h
+b10_dhcp6_SOURCES = main.cc iface_mgr.cc pkt6.cc dhcp6_srv.cc
+b10_dhcp6_SOURCES += iface_mgr.h pkt6.h dhcp6_srv.h dhcp6.h
 b10_dhcp6_LDADD =  $(top_builddir)/src/lib/datasrc/libdatasrc.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libutil.la
@@ -49,5 +49,4 @@ b10_dhcp6_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}
 b10_dhcp6dir = $(pkgdatadir)
-b10_dhcp6_DATA = dhcp6.spec
-
+b10_dhcp6_DATA = dhcp6.spec interfaces.txt

+ 2 - 2
src/bin/dhcp6/b10-dhcp6.8

@@ -21,8 +21,8 @@
 .SH "NAME"
 b10-dhcp6 \- DHCPv6 daemon in BIND10 architecture
 .SH "SYNOPSIS"
-.HP \w'\fBb10\-dhcp6\fR\ 'u
-\fBb10\-dhcp6\fR [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR]
+.HP \w'\fBb10\-dhcp6
+\fBb10\-dhcp6\fR [\fB\-v\fR]
 .SH "DESCRIPTION"
 .PP
 The

+ 22 - 51
src/bin/dhcp6/dhcp6.h

@@ -1,29 +1,19 @@
-/* dhcp6.h
-
-   DHCPv6 Protocol structures... */
-
-/*
- * Copyright (c) 2006-2011 by Internet Systems Consortium, Inc. ("ISC")
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND 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.
- *
- *   Internet Systems Consortium, Inc.
- *   950 Charter Street
- *   Redwood City, CA 94063
- *   <info@isc.org>
- *   https://www.isc.org/
- */
-
+// Copyright (C) 2006-2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCP6_H
+#define DHCP6_H
 
 /* DHCPv6 Option codes: */
 
@@ -136,8 +126,11 @@ extern const int dhcpv6_type_name_max;
 /* 
  * DHCPv6 well-known multicast addressess, from section 5.1 of RFC 3315 
  */
-#define All_DHCP_Relay_Agents_and_Servers "FF02::1:2"
-#define All_DHCP_Servers "FF05::1:3"
+#define ALL_DHCP_RELAY_AGENTS_AND_SERVERS "ff02::1:2"
+#define ALL_DHCP_SERVERS "ff05::1:3"
+
+#define DHCP6_CLIENT_PORT 546
+#define DHCP6_SERVER_PORT 547
 
 /*
  * DHCPv6 Retransmission Constants (RFC3315 section 5.5, RFC 5007)
@@ -171,29 +164,6 @@ extern const int dhcpv6_type_name_max;
 #define LQ6_MAX_RT       10
 #define LQ6_MAX_RC        5
 
-/* 
- * Normal packet format, defined in section 6 of RFC 3315 
- */
-struct dhcpv6_packet {
-	unsigned char msg_type;
-	unsigned char transaction_id[3];
-	unsigned char options[FLEXIBLE_ARRAY_MEMBER];
-};
-
-/* Offset into DHCPV6 Reply packets where Options spaces commence. */
-#define REPLY_OPTIONS_INDEX 4
-
-/* 
- * Relay packet format, defined in section 7 of RFC 3315 
- */
-struct dhcpv6_relay_packet {
-	unsigned char msg_type;
-	unsigned char hop_count;
-	unsigned char link_address[16];
-	unsigned char peer_address[16];
-	unsigned char options[FLEXIBLE_ARRAY_MEMBER];
-};
-
 /* Leasequery query-types (RFC 5007) */
 
 #define LQ6QT_BY_ADDRESS	1
@@ -211,3 +181,4 @@ struct dhcpv6_relay_packet {
 #define IRT_DEFAULT	86400
 #define IRT_MINIMUM	600
 
+#endif

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

@@ -0,0 +1,55 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "dhcp6/pkt6.h"
+#include "dhcp6/iface_mgr.h"
+#include "dhcp6/dhcp6_srv.h"
+
+using namespace std;
+using namespace isc;
+
+Dhcpv6Srv::Dhcpv6Srv() {
+    cout << "Initialization" << endl;
+
+    // first call to instance() will create IfaceMgr (it's a singleton)
+    // it may throw something if things go wrong
+    IfaceMgr::instance();
+}
+
+Dhcpv6Srv::~Dhcpv6Srv() {
+    cout << "DHCPv6 Srv shutdown." << endl;
+}
+
+bool
+Dhcpv6Srv::run() {
+    while (true) {
+        Pkt6* pkt;
+
+        pkt = IfaceMgr::instance().receive();
+
+        if (pkt) {
+            cout << "Received " << pkt->data_len_ << " bytes, echoing back."
+                 << endl;
+            IfaceMgr::instance().send(*pkt);
+            delete pkt;
+        }
+
+	// TODO add support for config session (see src/bin/auth/main.cc)
+	//      so this daemon can be controlled from bob
+        sleep(1);
+
+    }
+
+    return (true);
+}

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

@@ -0,0 +1,40 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPV6_SRV_H
+#define DHCPV6_SRV_H
+
+#include <iostream>
+
+namespace isc {
+    class Dhcpv6Srv {
+    private:
+        // defined private on purpose. We don't want to have more than
+        // one copy
+        Dhcpv6Srv(const Dhcpv6Srv& src);
+        Dhcpv6Srv& operator=(const Dhcpv6Srv& src);
+
+    public:
+        // default constructor
+        Dhcpv6Srv();
+        ~Dhcpv6Srv();
+
+        bool run();
+
+    protected:
+        bool shutdown;
+    };
+};
+
+#endif // DHCP6_SRV_H

+ 581 - 0
src/bin/dhcp6/iface_mgr.cc

@@ -0,0 +1,581 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <sstream>
+#include <fstream>
+#include <string.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "dhcp6/iface_mgr.h"
+#include "dhcp6/dhcp6.h"
+#include "exceptions/exceptions.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+
+namespace isc {
+
+// IfaceMgr is a singleton implementation
+IfaceMgr* IfaceMgr::instance_ = 0;
+
+void
+IfaceMgr::instanceCreate() {
+    if (instance_) {
+        // no need to do anything. Instance is already created.
+        // Who called it again anyway? Uh oh. Had to be us, as
+        // this is private method.
+        return;
+    }
+    instance_ = new IfaceMgr();
+}
+
+IfaceMgr&
+IfaceMgr::instance() {
+    if (instance_ == 0)
+        instanceCreate();
+    return (*instance_);
+}
+
+IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
+    :name_(name), ifindex_(ifindex), mac_len_(0) {
+    memset(mac_, 0, 20);
+}
+
+std::string
+IfaceMgr::Iface::getFullName() const {
+    ostringstream tmp;
+    tmp << name_ << "/" << ifindex_;
+    return (tmp.str());
+}
+
+std::string
+IfaceMgr::Iface::getPlainMac() const {
+    ostringstream tmp;
+    for (int i=0; i<mac_len_; i++) {
+        tmp.fill('0');
+        tmp.width(2);
+        tmp << (hex) << (int) mac_[i];
+        if (i<mac_len_-1) {
+            tmp << ":";
+        }
+    }
+    return (tmp.str());
+}
+
+IfaceMgr::IfaceMgr() {
+
+    cout << "IfaceMgr initialization." << endl;
+
+    try {
+        // required for sending/receiving packets
+        // let's keep it in front, just in case someone
+        // wants to send anything during initialization
+        control_buf_len_ = CMSG_SPACE(sizeof(struct in6_pktinfo));
+        control_buf_ = new char[control_buf_len_];
+
+        detectIfaces();
+
+        if (!openSockets()) {
+            isc_throw(Unexpected, "Failed to open/bind sockets.");
+        }
+    } catch (const std::exception& ex) {
+        cout << "IfaceMgr creation failed:" << ex.what() << endl;
+
+        // TODO Uncomment this (or call LOG_FATAL) once
+        // interface detection is implemented. Otherwise
+        // it is not possible to run tests in a portable
+        // way (see detectIfaces() method).
+        // throw ex;
+    }
+}
+
+IfaceMgr::~IfaceMgr() {
+    if (control_buf_) {
+        delete [] control_buf_;
+        control_buf_ = 0;
+        control_buf_len_ = 0;
+    }
+}
+
+void
+IfaceMgr::detectIfaces() {
+    string ifaceName, linkLocal;
+
+    // TODO do the actual detection. Currently interface detection is faked
+    //      by reading a text file.
+
+    cout << "Interface detection is not implemented yet. "
+         << "Reading interfaces.txt file instead." << endl;
+    cout << "Please use format: interface-name link-local-address" << endl;
+
+    try {
+        ifstream interfaces("interfaces.txt");
+
+        if (!interfaces.good()) {
+            cout << "Failed to read interfaces.txt file." << endl;
+            isc_throw(Unexpected, "Failed to read interfaces.txt");
+        }
+        interfaces >> ifaceName;
+        interfaces >> linkLocal;
+
+        cout << "Detected interface " << ifaceName << "/" << linkLocal << endl;
+
+        Iface iface(ifaceName, if_nametoindex( ifaceName.c_str() ) );
+        IOAddress addr(linkLocal);
+        iface.addrs_.push_back(addr);
+        ifaces_.push_back(iface);
+        interfaces.close();
+    } catch (const std::exception& ex) {
+        // TODO: deallocate whatever memory we used
+        // not that important, since this function is going to be
+        // thrown away as soon as we get proper interface detection
+        // implemented
+
+        // TODO Do LOG_FATAL here
+        std::cerr << "Interface detection failed." << std::endl;
+        throw ex;
+    }
+}
+
+bool
+IfaceMgr::openSockets() {
+    int sock;
+
+    for (IfaceLst::iterator iface=ifaces_.begin();
+         iface!=ifaces_.end();
+         ++iface) {
+
+        for (Addr6Lst::iterator addr=iface->addrs_.begin();
+             addr!=iface->addrs_.end();
+             ++addr) {
+
+            sock = openSocket(iface->name_, *addr,
+                              DHCP6_SERVER_PORT);
+            if (sock<0) {
+                cout << "Failed to open unicast socket." << endl;
+                return (false);
+            }
+            sendsock_ = sock;
+
+            sock = openSocket(iface->name_,
+                              IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
+                              DHCP6_SERVER_PORT);
+            if (sock<0) {
+                cout << "Failed to open multicast socket." << endl;
+                close(sendsock_);
+                return (false);
+            }
+            recvsock_ = sock;
+        }
+    }
+
+    return (true);
+}
+
+void
+IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
+    for (IfaceLst::const_iterator iface=ifaces_.begin();
+         iface!=ifaces_.end();
+         ++iface) {
+        out << "Detected interface " << iface->getFullName() << endl;
+        out << "  " << iface->addrs_.size() << " addr(s):" << endl;
+        for (Addr6Lst::const_iterator addr=iface->addrs_.begin();
+             addr != iface->addrs_.end();
+             ++addr) {
+            out << "  " << addr->toText() << endl;
+        }
+        out << "  mac: " << iface->getPlainMac() << endl;
+    }
+}
+
+IfaceMgr::Iface*
+IfaceMgr::getIface(int ifindex) {
+    for (IfaceLst::iterator iface=ifaces_.begin();
+         iface!=ifaces_.end();
+         ++iface) {
+        if (iface->ifindex_ == ifindex)
+            return (&(*iface));
+    }
+
+    return (NULL); // not found
+}
+
+IfaceMgr::Iface*
+IfaceMgr::getIface(const std::string& ifname) {
+    for (IfaceLst::iterator iface=ifaces_.begin();
+         iface!=ifaces_.end();
+         ++iface) {
+        if (iface->name_ == ifname)
+            return (&(*iface));
+    }
+
+    return (NULL); // not found
+}
+
+
+/**
+ * Opens UDP/IPv6 socket and binds it to specific address, interface and port.
+ *
+ * @param ifname name of the interface
+ * @param addr address to be bound.
+ * @param port UDP port.
+ * @param mcast Should multicast address also be bound?
+ *
+ * @return socket descriptor, if socket creation, binding and multicast
+ * group join were all successful. -1 otherwise.
+ */
+int
+IfaceMgr::openSocket(const std::string& ifname,
+                     const IOAddress& addr,
+                     int port) {
+    struct sockaddr_in6 addr6;
+
+    cout << "Creating socket on " << ifname << "/" << addr.toText()
+         << "/port=" << port << endl;
+
+    memset(&addr6, 0, sizeof(addr6));
+    addr6.sin6_family = AF_INET6;
+    addr6.sin6_port = htons(port);
+    addr6.sin6_scope_id = if_nametoindex(ifname.c_str());
+
+    memcpy(&addr6.sin6_addr,
+           addr.getAddress().to_v6().to_bytes().data(),
+           sizeof(addr6.sin6_addr));
+#ifdef HAVE_SA_LEN
+    addr6->sin6_len = sizeof(addr6);
+#endif
+
+    // TODO: use sockcreator once it becomes available
+
+    // make a socket
+    int sock = socket(AF_INET6, SOCK_DGRAM, 0);
+    if (sock < 0) {
+        cout << "Failed to create UDP6 socket." << endl;
+        return (-1);
+    }
+
+    /* Set the REUSEADDR option so that we don't fail to start if
+       we're being restarted. */
+    int flag = 1;
+    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+                   (char *)&flag, sizeof(flag)) < 0) {
+        cout << "Can't set SO_REUSEADDR option on dhcpv6 socket." << endl;
+        close(sock);
+        return (-1);
+    }
+
+    if (bind(sock, (struct sockaddr *)&addr6, sizeof(addr6)) < 0) {
+        cout << "Failed to bind socket " << sock << " to " << addr.toText()
+             << "/port=" << port << endl;
+        close(sock);
+        return (-1);
+    }
+#ifdef IPV6_RECVPKTINFO
+    /* RFC3542 - a new way */
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+                   &flag, sizeof(flag)) != 0) {
+        cout << "setsockopt: IPV6_RECVPKTINFO failed." << endl;
+        close(sock);
+        return (-1);
+    }
+#else
+    /* RFC2292 - an old way */
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_PKTINFO,
+                   &flag, sizeof(flag)) != 0) {
+        cout << "setsockopt: IPV6_PKTINFO: failed." << endl;
+        close(sock);
+        return (-1);
+    }
+#endif
+
+    // multicast stuff
+
+    if (addr.getAddress().to_v6().is_multicast()) {
+        // both mcast (ALL_DHCP_RELAY_AGENTS_AND_SERVERS and ALL_DHCP_SERVERS)
+        // are link and site-scoped, so there is no sense to join those groups
+        // with global addresses.
+
+        if ( !joinMcast( sock, ifname,
+                         string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
+            close(sock);
+            return (-1);
+        }
+    }
+
+    cout << "Created socket " << sock << " on " << ifname << "/" <<
+        addr.toText() << "/port=" << port << endl;
+
+    return (sock);
+}
+
+/**
+ * joins multicast group
+ *
+ * @param sock socket file descriptor
+ * @param ifname name of the interface (DHCPv6 uses link-scoped mc groups)
+ * @param mcast multicast address to join (string)
+ *
+ * @return true if joined successfully, false otherwise
+ */
+bool
+IfaceMgr::joinMcast(int sock, const std::string& ifname,
+const std::string & mcast) {
+
+    struct ipv6_mreq mreq;
+
+    if (inet_pton(AF_INET6, mcast.c_str(),
+                  &mreq.ipv6mr_multiaddr) <= 0) {
+        cout << "Failed to convert " << ifname
+             << " to IPv6 multicast address." << endl;
+        return (false);
+    }
+
+    mreq.ipv6mr_interface = if_nametoindex(ifname.c_str());
+    if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+                   &mreq, sizeof(mreq)) < 0) {
+        cout << "Failed to join " << mcast << " multicast group." << endl;
+        return (false);
+    }
+
+    cout << "Joined multicast " << mcast << " group." << endl;
+
+    return (true);
+}
+
+/**
+ * Sends UDP packet over IPv6.
+ *
+ * All parameters for actual transmission are specified in
+ * Pkt6 structure itself. That includes destination address,
+ * src/dst port and interface over which data will be sent.
+ *
+ * @param pkt A packet object that is going to be sent.
+ *
+ * @return True, if transmission was successful. False otherwise.
+ */
+bool
+IfaceMgr::send(Pkt6 &pkt) {
+    struct msghdr m;
+    struct iovec v;
+    int result;
+    struct in6_pktinfo *pktinfo;
+    struct cmsghdr *cmsg;
+    memset(control_buf_, 0, control_buf_len_);
+
+    /*
+     * Initialize our message header structure.
+     */
+    memset(&m, 0, sizeof(m));
+
+    /*
+     * Set the target address we're sending to.
+     */
+    sockaddr_in6 to;
+    memset(&to, 0, sizeof(to));
+    to.sin6_family = AF_INET6;
+    to.sin6_port = htons(pkt.remote_port_);
+    memcpy(&to.sin6_addr,
+           pkt.remote_addr_.getAddress().to_v6().to_bytes().data(),
+           16);
+    to.sin6_scope_id = pkt.ifindex_;
+
+    m.msg_name = &to;
+    m.msg_namelen = sizeof(to);
+
+    /*
+     * Set the data buffer we're sending. (Using this wacky
+     * "scatter-gather" stuff... we only have a single chunk
+     * of data to send, so we declare a single vector entry.)
+     */
+    v.iov_base = (char *) &pkt.data_[0];
+    v.iov_len = pkt.data_len_;
+    m.msg_iov = &v;
+    m.msg_iovlen = 1;
+
+    /*
+     * Setting the interface is a bit more involved.
+     *
+     * We have to create a "control message", and set that to
+     * define the IPv6 packet information. We could set the
+     * source address if we wanted, but we can safely let the
+     * kernel decide what that should be.
+     */
+    m.msg_control = control_buf_;
+    m.msg_controllen = control_buf_len_;
+    cmsg = CMSG_FIRSTHDR(&m);
+    cmsg->cmsg_level = IPPROTO_IPV6;
+    cmsg->cmsg_type = IPV6_PKTINFO;
+    cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
+    pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+    memset(pktinfo, 0, sizeof(*pktinfo));
+    pktinfo->ipi6_ifindex = pkt.ifindex_;
+    m.msg_controllen = cmsg->cmsg_len;
+
+    result = sendmsg(sendsock_, &m, 0);
+    if (result < 0) {
+        cout << "Send packet failed." << endl;
+    }
+    cout << "Sent " << result << " bytes." << endl;
+
+    cout << "Sent " << pkt.data_len_ << " bytes over "
+         << pkt.iface_ << "/" << pkt.ifindex_ << " interface: "
+         << " dst=" << pkt.remote_addr_.toText()
+         << ", src=" << pkt.local_addr_.toText()
+         << endl;
+
+    return (result);
+}
+
+
+/**
+ * Attempts to receive UDP/IPv6 packet over open sockets.
+ *
+ * TODO Start using select() and add timeout to be able
+ * to not wait infinitely, but rather do something useful
+ * (e.g. remove expired leases)
+ *
+ * @return Object prepresenting received packet.
+ */
+Pkt6*
+IfaceMgr::receive() {
+    struct msghdr m;
+    struct iovec v;
+    int result;
+    struct cmsghdr* cmsg;
+    struct in6_pktinfo* pktinfo;
+    struct sockaddr_in6 from;
+    struct in6_addr to_addr;
+    Pkt6* pkt;
+    char addr_str[INET6_ADDRSTRLEN];
+
+    try {
+        // RFC3315 states that server responses may be
+        // fragmented if they are over MTU. There is no
+        // text whether client's packets may be larger
+        // than 1500. Nevertheless to be on the safe side
+        // we use larger buffer. This buffer limit is checked
+        // during reception (see iov_len below), so we are
+        // safe
+        pkt = new Pkt6(65536);
+    } catch (const std::exception& ex) {
+        cout << "Failed to create new packet." << endl;
+        return (0);
+    }
+
+    memset(control_buf_, 0, control_buf_len_);
+
+    memset(&from, 0, sizeof(from));
+    memset(&to_addr, 0, sizeof(to_addr));
+
+    /*
+     * Initialize our message header structure.
+     */
+    memset(&m, 0, sizeof(m));
+
+    /*
+     * Point so we can get the from address.
+     */
+    m.msg_name = &from;
+    m.msg_namelen = sizeof(from);
+
+    /*
+     * Set the data buffer we're receiving. (Using this wacky
+     * "scatter-gather" stuff... but we that doesn't really make
+     * sense for us, so we use a single vector entry.)
+     */
+    v.iov_base = (void*)&pkt->data_[0];
+    v.iov_len = pkt->data_len_;
+    m.msg_iov = &v;
+    m.msg_iovlen = 1;
+
+    /*
+     * Getting the interface is a bit more involved.
+     *
+     * We set up some space for a "control message". We have
+     * previously asked the kernel to give us packet
+     * information (when we initialized the interface), so we
+     * should get the destination address from that.
+     */
+    m.msg_control = control_buf_;
+    m.msg_controllen = control_buf_len_;
+
+    result = recvmsg(recvsock_, &m, 0);
+
+    if (result >= 0) {
+        /*
+         * If we did read successfully, then we need to loop
+         * through the control messages we received and
+         * find the one with our destination address.
+         *
+         * We also keep a flag to see if we found it. If we
+         * didn't, then we consider this to be an error.
+         */
+        int found_pktinfo = 0;
+        cmsg = CMSG_FIRSTHDR(&m);
+        while (cmsg != NULL) {
+            if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
+                (cmsg->cmsg_type == IPV6_PKTINFO)) {
+                pktinfo = (struct in6_pktinfo*)CMSG_DATA(cmsg);
+                to_addr = pktinfo->ipi6_addr;
+                pkt->ifindex_ = pktinfo->ipi6_ifindex;
+                found_pktinfo = 1;
+            }
+            cmsg = CMSG_NXTHDR(&m, cmsg);
+        }
+        if (!found_pktinfo) {
+            cout << "Unable to find pktinfo" << endl;
+            delete pkt;
+            return (0);
+        }
+    } else {
+        cout << "Failed to receive data." << endl;
+        delete pkt;
+        return (0);
+    }
+
+    // That's ugly.
+    // TODO add IOAddress constructor that will take struct in6_addr*
+    inet_ntop(AF_INET6, &to_addr, addr_str,INET6_ADDRSTRLEN);
+    pkt->local_addr_ = IOAddress(string(addr_str));
+
+    inet_ntop(AF_INET6, &from.sin6_addr, addr_str, INET6_ADDRSTRLEN);
+    pkt->remote_addr_ = IOAddress(string(addr_str));
+
+    pkt->remote_port_ = ntohs(from.sin6_port);
+
+    Iface* received = getIface(pkt->ifindex_);
+    if (received) {
+        pkt->iface_ = received->name_;
+    } else {
+        cout << "Received packet over unknown interface (ifindex="
+             << pkt->ifindex_ << ")." << endl;
+        delete pkt;
+        return (0);
+    }
+
+    pkt->data_len_ = result;
+
+    // TODO Move this to LOG_DEBUG
+    cout << "Received " << pkt->data_len_ << " bytes over "
+         << pkt->iface_ << "/" << pkt->ifindex_ << " interface: "
+         << " src=" << pkt->remote_addr_.toText()
+         << ", dst=" << pkt->local_addr_.toText()
+         << endl;
+
+    return (pkt);
+}
+
+}

+ 103 - 0
src/bin/dhcp6/iface_mgr.h

@@ -0,0 +1,103 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef IFACE_MGR_H
+#define IFACE_MGR_H
+
+#include <list>
+#include "io_address.h"
+#include "dhcp6/pkt6.h"
+
+namespace isc {
+
+    /**
+     * IfaceMgr is an interface manager class that detects available network
+     * interfaces, configured addresses, link-local addresses, and provides
+     * API for using sockets.
+     *
+     */
+    class IfaceMgr {
+    public:
+        typedef std::list<isc::asiolink::IOAddress> Addr6Lst;
+        struct Iface { // TODO: could be a class as well
+            std::string name_; // network interface name
+            int ifindex_; // interface index (a value that uniquely indentifies
+                          // an interface
+            Addr6Lst addrs_;
+            char mac_[20]; // Infiniband used 20 bytes indentifiers
+            int mac_len_;
+
+            Iface(const std::string& name, int ifindex);
+            std::string getFullName() const;
+            std::string getPlainMac() const;
+
+            int sendsock_; // socket used to sending data
+            int recvsock_; // socket used for receiving data
+
+            // next field is not needed, let's keep it in cointainers
+        };
+
+        // TODO performance improvement: we may change this into
+        //      2 maps (ifindex-indexed and name-indexed) and
+        //      also hide it (make it public make tests easier for now)
+        typedef std::list<Iface> IfaceLst;
+
+        static IfaceMgr& instance();
+
+        Iface * getIface(int ifindex);
+        Iface * getIface(const std::string& ifname);
+
+        void printIfaces(std::ostream& out = std::cout);
+
+        bool send(Pkt6& pkt);
+        Pkt6* receive();
+
+        // don't use private, we need derived classes in tests
+    protected:
+        IfaceMgr(); // don't create IfaceMgr directly, use instance() method
+        ~IfaceMgr();
+
+        void detectIfaces();
+
+        int openSocket(const std::string& ifname,
+                       const isc::asiolink::IOAddress& addr,
+                       int port);
+
+        // TODO: having 2 maps (ifindex->iface and ifname->iface would)
+        //      probably be better for performance reasons
+        IfaceLst ifaces_;
+
+        static IfaceMgr * instance_;
+
+        // TODO: Also keep this interface on Iface once interface detection
+        // is implemented. We may need it e.g. to close all sockets on
+        // specific interface
+        int recvsock_; // TODO: should be fd_set eventually, but we have only
+        int sendsock_; // 2 sockets for now. Will do for until next release
+        // we can't use the same socket, as receiving socket
+        // is bound to multicast address. And we all know what happens
+        // to people who try to use multicast as source address.
+
+        char * control_buf_;
+        int control_buf_len_;
+
+    private:
+        bool openSockets();
+        static void instanceCreate();
+        bool joinMcast(int sock, const std::string& ifname,
+                       const std::string& mcast);
+    };
+};
+
+#endif

+ 10 - 0
src/bin/dhcp6/interfaces.txt

@@ -0,0 +1,10 @@
+eth0 fe80::21e:8cff:fe9b:7349
+
+#
+# only first line is read.
+# please use following format:
+# interface-name link-local-ipv6-address
+#
+# This file will become obsolete once proper interface detection 
+# is implemented.
+#

+ 14 - 22
src/bin/dhcp6/main.cc

@@ -33,7 +33,7 @@
 #include <log/dummylog.h>
 
 #include <dhcp6/spec_config.h>
-
+#include "dhcp6/dhcp6_srv.h"
 
 using namespace std;
 using namespace isc::util;
@@ -42,15 +42,16 @@ using namespace isc::cc;
 using namespace isc::config;
 using namespace isc::util;
 
+using namespace isc;
+
 namespace {
 
 bool verbose_mode = false;
 
 void
 usage() {
-    cerr << "Usage:  b10-dhcp6 [-u user] [-v]"
+    cerr << "Usage:  b10-dhcp6 [-v]"
          << endl;
-    cerr << "\t-u: change process UID to the specified user" << endl;
     cerr << "\t-v: verbose output" << endl;
     exit(1);
 }
@@ -59,40 +60,32 @@ usage() {
 int
 main(int argc, char* argv[]) {
     int ch;
-    const char* uid = NULL;
-    bool cache = true;
 
-    while ((ch = getopt(argc, argv, ":nu:v")) != -1) {
+    while ((ch = getopt(argc, argv, ":v")) != -1) {
         switch (ch) {
-        case 'n':
-            cache = false;
-            break;
-        case 'u':
-            uid = optarg;
-            break;
         case 'v':
             verbose_mode = true;
             isc::log::denabled = true;
             break;
-        case '?':
+        case ':':
         default:
             usage();
         }
     }
 
+    cout << "My pid=" << getpid() << endl;
+
     if (argc - optind > 0) {
         usage();
     }
 
     int ret = 0;
 
-    // XXX: we should eventually pass io_service here.
+    // TODO remainder of auth to dhcp6 code copy. We need to enable this in
+    //      dhcp6 eventually
 #if 0
     Session* cc_session = NULL;
-    Session* xfrin_session = NULL;
     Session* statistics_session = NULL;
-    bool xfrin_session_established = false; // XXX (see Trac #287)
-    bool statistics_session_established = false; // XXX (see Trac #287)
     ModuleCCSession* config_session = NULL;
 #endif
     try {
@@ -108,15 +101,14 @@ main(int argc, char* argv[]) {
         // auth_server->setVerbose(verbose_mode);
         cout << "[b10-dhcp6] Initiating DHCPv6 operation." << endl;
 
+        Dhcpv6Srv* srv = new Dhcpv6Srv();
+
+        srv->run();
+
     } catch (const std::exception& ex) {
         cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;
         ret = 1;
     }
 
-    while (true) {
-            sleep(10);
-            cout << "[b10-dhcp6] I'm alive." << endl;
-    }
-
     return (ret);
 }

+ 46 - 0
src/bin/dhcp6/pkt6.cc

@@ -0,0 +1,46 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+
+#include "dhcp6/dhcp6.h"
+#include "dhcp6/pkt6.h"
+#include <iostream>
+
+namespace isc {
+
+///
+/// constructor
+///
+/// \param dataLen - length of the data to be allocated
+///
+Pkt6::Pkt6(int dataLen)
+    :local_addr_("::"),
+     remote_addr_("::") {
+    try {
+	data_ = boost::shared_array<char>(new char[dataLen]);
+	data_len_ = dataLen;
+    } catch (const std::exception& ex) {
+	// TODO move to LOG_FATAL()
+	// let's continue with empty pkt for now
+        std::cout << "Failed to allocate " << dataLen << " bytes."
+                  << std::endl;
+        data_len_ = 0;
+    }
+}
+
+Pkt6::~Pkt6() {
+    // no need to delete anything shared_ptr will take care of data_
+}
+
+};

+ 62 - 0
src/bin/dhcp6/pkt6.h

@@ -0,0 +1,62 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PKT6_H
+#define PKT6_H
+
+#include <iostream>
+#include <boost/shared_array.hpp>
+#include "io_address.h"
+
+namespace isc {
+
+    class Pkt6 {
+    public:
+        Pkt6(int len);
+        ~Pkt6();
+
+        // XXX: probably need getter/setter wrappers
+        //      and hide fields as protected
+        // buffer that holds memory. It is shared_array as options may
+        // share pointer to this buffer
+        boost::shared_array<char> data_;
+
+        // length of the data
+        int data_len_;
+
+        // local address (destination if receiving packet, source if sending packet)
+        isc::asiolink::IOAddress local_addr_;
+
+        // remote address (source if receiving packet, destination if sending packet)
+        isc::asiolink::IOAddress remote_addr_;
+
+        // name of the network interface the packet was received/to be sent over
+        std::string iface_;
+
+        // interface index (each network interface has assigned unique ifindex
+        // it is functional equvalent of name, but sometimes more useful, e.g.
+        // when using crazy systems that allow spaces in interface names (Windows)
+        int ifindex_;
+
+        // local TDP or UDP port
+        int local_port_;
+
+        // remote TCP or UDP port
+        int remote_port_;
+
+        // XXX: add *a lot* here
+    };
+}
+
+#endif

+ 45 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -20,3 +20,48 @@ check-local:
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
+
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
+AM_CPPFLAGS += -I$(top_srcdir)/src/bin
+AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
+AM_CPPFLAGS += -I$(top_srcdir)/src/lib/asiolink
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/bin/dhcp6/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+CLEANFILES = $(builddir)/interfaces.txt
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS =
+if HAVE_GTEST
+
+TESTS += dhcp6_unittests
+
+dhcp6_unittests_SOURCES = ../pkt6.h ../pkt6.cc
+dhcp6_unittests_SOURCES += ../iface_mgr.h ../iface_mgr.cc
+dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
+dhcp6_unittests_SOURCES += dhcp6_unittests.cc
+dhcp6_unittests_SOURCES += pkt6_unittest.cc
+dhcp6_unittests_SOURCES += iface_mgr_unittest.cc
+dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+
+dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+dhcp6_unittests_LDADD = $(GTEST_LDADD)
+dhcp6_unittests_LDADD += $(SQLITE_LIBS)
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
+endif
+
+noinst_PROGRAMS = $(TESTS)

+ 53 - 0
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -0,0 +1,53 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+
+#include "dhcp6/dhcp6_srv.h"
+
+using namespace std;
+using namespace isc;
+
+namespace {
+class Dhcpv6SrvTest : public ::testing::Test {
+public:
+    Dhcpv6SrvTest() {
+    }
+};
+
+TEST_F(Dhcpv6SrvTest, basic) {
+    // there's almost no code now. What's there provides echo capability 
+    // that is just a proof of concept and will be removed soon
+    // No need to thoroughly test it
+
+    // srv has stubbed interface detection. It will read
+    // interfaces.txt instead. It will pretend to have detected
+    // fe80::1234 link-local address on eth0 interface. Obviously
+
+    // an attempt to bind this socket will fail.
+    EXPECT_NO_THROW( {
+        Dhcpv6Srv * srv = new Dhcpv6Srv();
+
+	delete srv;
+	});
+    
+}
+
+}

+ 28 - 0
src/bin/dhcp6/tests/dhcp6_unittests.cc

@@ -0,0 +1,28 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdio.h>
+#include <gtest/gtest.h>
+#include <log/logger_support.h>
+
+int
+main(int argc, char* argv[]) {
+
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+
+    int result = RUN_ALL_TESTS();
+
+    return result;
+}

+ 263 - 0
src/bin/dhcp6/tests/iface_mgr_unittest.cc

@@ -0,0 +1,263 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include "io_address.h"
+#include "dhcp6/pkt6.h"
+#include "dhcp6/iface_mgr.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+
+namespace {
+const char* const INTERFACE_FILE = TEST_DATA_BUILDDIR "/interfaces.txt";
+
+class NakedIfaceMgr: public IfaceMgr {
+    // "naked" Interface Manager, exposes internal fields
+public:
+    NakedIfaceMgr() { }
+    IfaceLst & getIfacesLst() { return ifaces_; }
+    void setSendSock(int sock) { sendsock_ = sock; }
+    void setRecvSock(int sock) { recvsock_ = sock; }
+
+    int openSocket(const std::string& ifname,
+                   const isc::asiolink::IOAddress& addr,
+                   int port) {
+        return IfaceMgr::openSocket(ifname, addr, port);
+    }
+
+};
+
+// dummy class for now, but this will be expanded when needed
+class IfaceMgrTest : public ::testing::Test {
+public:
+    IfaceMgrTest() {
+    }
+};
+
+TEST_F(IfaceMgrTest, basic) {
+    // checks that IfaceManager can be instantiated
+
+    IfaceMgr & ifacemgr = IfaceMgr::instance();
+    ASSERT_TRUE(&ifacemgr != 0);
+}
+
+TEST_F(IfaceMgrTest, ifaceClass) {
+    // basic tests for Iface inner class
+
+    IfaceMgr::Iface * iface = new IfaceMgr::Iface("eth5", 7);
+
+    EXPECT_STREQ("eth5/7", iface->getFullName().c_str());
+
+    delete iface;
+
+}
+
+// TODO: Implement getPlainMac() test as soon as interface detection is implemented.
+TEST_F(IfaceMgrTest, getIface) {
+
+    cout << "Interface checks. Please ignore socket binding errors." << endl;
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    // interface name, ifindex
+    IfaceMgr::Iface iface1("lo", 1);
+    IfaceMgr::Iface iface2("eth5", 2);
+    IfaceMgr::Iface iface3("en3", 5);
+    IfaceMgr::Iface iface4("e1000g0", 3);
+
+    ifacemgr->getIfacesLst().push_back(iface1);
+    ifacemgr->getIfacesLst().push_back(iface2);
+    ifacemgr->getIfacesLst().push_back(iface3);
+    ifacemgr->getIfacesLst().push_back(iface4);
+
+    // check that interface can be retrieved by ifindex
+    IfaceMgr::Iface * tmp = ifacemgr->getIface(5);
+    // ASSERT_NE(NULL, tmp); is not supported. hmmmm.
+    ASSERT_TRUE( tmp != NULL );
+
+    EXPECT_STREQ( "en3", tmp->name_.c_str() );
+    EXPECT_EQ(5, tmp->ifindex_);
+
+    // check that interface can be retrieved by name
+    tmp = ifacemgr->getIface("lo");
+    ASSERT_TRUE( tmp != NULL );
+
+    EXPECT_STREQ( "lo", tmp->name_.c_str() );
+    EXPECT_EQ(1, tmp->ifindex_);
+
+    // check that non-existing interfaces are not returned
+    EXPECT_EQ(static_cast<void*>(NULL), ifacemgr->getIface("wifi0") );
+
+    delete ifacemgr;
+}
+
+TEST_F(IfaceMgrTest, detectIfaces) {
+
+    // test detects that interfaces can be detected
+    // there is no code for that now, but interfaces are
+    // read from file
+    fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
+    fakeifaces << "eth0 fe80::1234";
+    fakeifaces.close();
+
+    // this is not usable on systems that don't have eth0
+    // interfaces. Nevertheless, this fake interface should
+    // be on list, but if_nametoindex() will fail.
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    ASSERT_TRUE( ifacemgr->getIface("eth0") != NULL );
+
+    IfaceMgr::Iface * eth0 = ifacemgr->getIface("eth0");
+
+    // there should be one address
+    EXPECT_EQ(1, eth0->addrs_.size());
+
+    IOAddress * addr = &(*eth0->addrs_.begin());
+    ASSERT_TRUE( addr != NULL );
+
+    EXPECT_STREQ( "fe80::1234", addr->toText().c_str() );
+
+    delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+// Fix for this is available on 1186 branch, will reenable
+// this test once 1186 is merged
+TEST_F(IfaceMgrTest, DISABLED_sockets) {
+    // testing socket operation in a portable way is tricky
+    // without interface detection implemented
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    IOAddress loAddr("::1");
+
+    // bind multicast socket to port 10547
+    int socket1 = ifacemgr->openSocket("lo", loAddr, 10547);
+    EXPECT_GT(socket1, 0); // socket > 0
+
+    // bind unicast socket to port 10548
+    int socket2 = ifacemgr->openSocket("lo", loAddr, 10548);
+    EXPECT_GT(socket2, 0);
+
+    // expect success. This address/port is already bound, but
+    // we are using SO_REUSEADDR, so we can bind it twice
+    int socket3 = ifacemgr->openSocket("lo", loAddr, 10547);
+    EXPECT_GT(socket3, 0); // socket > 0
+
+    // we now have 3 sockets open at the same time. Looks good.
+
+    close(socket1);
+    close(socket2);
+    close(socket3);
+
+    delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+TEST_F(IfaceMgrTest, DISABLED_socketsMcast) {
+    // testing socket operation in a portable way is tricky
+    // without interface detection implemented
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    IOAddress loAddr("::1");
+    IOAddress mcastAddr("ff02::1:2");
+
+    // bind multicast socket to port 10547
+    int socket1 = ifacemgr->openSocket("lo", mcastAddr, 10547);
+    EXPECT_GT(socket1, 0); // socket > 0
+
+    // expect success. This address/port is already bound, but
+    // we are using SO_REUSEADDR, so we can bind it twice
+    int socket2 = ifacemgr->openSocket("lo", mcastAddr, 10547);
+    EXPECT_GT(socket2, 0);
+
+    // there's no good way to test negative case here.
+    // we would need non-multicast interface. We will be able
+    // to iterate thru available interfaces and check if there
+    // are interfaces without multicast-capable flag.
+
+    close(socket1);
+    close(socket2);
+
+    delete ifacemgr;
+}
+
+// TODO: disabled due to other naming on various systems
+// (lo in Linux, lo0 in BSD systems)
+// Fix for this is available on 1186 branch, will reenable
+// this test once 1186 is merged
+TEST_F(IfaceMgrTest, DISABLED_sendReceive) {
+    // testing socket operation in a portable way is tricky
+    // without interface detection implemented
+
+    fstream fakeifaces(INTERFACE_FILE, ios::out|ios::trunc);
+    fakeifaces << "lo ::1";
+    fakeifaces.close();
+
+    NakedIfaceMgr * ifacemgr = new NakedIfaceMgr();
+
+    // let's assume that every supported OS have lo interface
+    IOAddress loAddr("::1");
+    int socket1 = ifacemgr->openSocket("lo", loAddr, 10547);
+    int socket2 = ifacemgr->openSocket("lo", loAddr, 10546);
+
+    ifacemgr->setSendSock(socket2);
+    ifacemgr->setRecvSock(socket1);
+
+    Pkt6 sendPkt(128);
+
+    // prepare dummy payload
+    for (int i=0;i<128; i++) {
+        sendPkt.data_[i] = i;
+    }
+
+    sendPkt.remote_port_ = 10547;
+    sendPkt.remote_addr_ = IOAddress("::1");
+    sendPkt.ifindex_ = 1;
+    sendPkt.iface_ = "lo";
+
+    Pkt6 * rcvPkt;
+
+    EXPECT_EQ(true, ifacemgr->send(sendPkt));
+
+    rcvPkt = ifacemgr->receive();
+
+    ASSERT_TRUE( rcvPkt != NULL ); // received our own packet
+
+    // let's check that we received what was sent
+    EXPECT_EQ(sendPkt.data_len_, rcvPkt->data_len_);
+    EXPECT_EQ(0, memcmp(&sendPkt.data_[0], &rcvPkt->data_[0],
+                        rcvPkt->data_len_) );
+
+    EXPECT_EQ(sendPkt.remote_addr_.toText(), rcvPkt->remote_addr_.toText());
+    EXPECT_EQ(rcvPkt->remote_port_, 10546);
+
+    delete rcvPkt;
+
+    delete ifacemgr;
+}
+
+}

+ 44 - 0
src/bin/dhcp6/tests/pkt6_unittest.cc

@@ -0,0 +1,44 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+
+#include "dhcp6/pkt6.h"
+
+using namespace std;
+using namespace isc;
+
+namespace {
+// empty class for now, but may be extended once Addr6 becomes bigger
+class Pkt6Test : public ::testing::Test {
+public:
+    Pkt6Test() {
+    }
+};
+
+TEST_F(Pkt6Test, constructor) {
+    Pkt6 * pkt1 = new Pkt6(17);
+    
+    ASSERT_EQ(pkt1->data_len_, 17);
+
+    delete pkt1;
+}
+
+}

+ 1 - 3
src/bin/resolver/tests/Makefile.am

@@ -60,6 +60,4 @@ run_unittests_CXXFLAGS += -Wno-unused-parameter
 endif
 endif
 
-
-
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

+ 1 - 1
src/bin/sockcreator/tests/Makefile.am

@@ -21,4 +21,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
 run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

+ 10 - 5
src/bin/xfrin/b10-xfrin.xml

@@ -59,7 +59,7 @@
       <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
       boss process.
       When triggered it can request and receive a zone transfer and store
-      the zone in a BIND 10 zone data store.
+      the zone in a BIND 10 zone data source.
     </para>
 
 <!-- TODO:
@@ -68,9 +68,13 @@ The logic for handling transfer triggers or zone management is handled
 in separate zonemgr process.
 -->
 
-    <note><simpara>
-      This prototype release only supports AXFR. IXFR is not implemented.
-    </simpara></note>
+    <para>
+      The <command>b10-xfrin</command> daemon supports both AXFR and
+      IXFR.  Due to some implementation limitations of the current
+      development release, however, it only tries AXFR by default,
+      and care should be taken to enable IXFR.
+      See the BIND 10 Guide for more details.
+    </para>
 
     <para>
       This daemon communicates with BIND 10 over a
@@ -105,7 +109,8 @@ in separate zonemgr process.
       <varname>name</varname> (the zone name),
       <varname>class</varname> (defaults to <quote>IN</quote>),
       <varname>master_addr</varname> (the zone master to transfer from),
-      <varname>master_port</varname> (defaults to 53), and
+      <varname>master_port</varname> (defaults to 53),
+      <varname>ixfr_disabled</varname> (defaults to false), and
       <varname>tsig_key</varname> (optional TSIG key to use).
       The <varname>tsig_key</varname> is specified using a full string
       colon-delimited name:key:algorithm representation (e.g.

+ 7 - 0
src/bin/xfrin/tests/Makefile.am

@@ -1,3 +1,5 @@
+SUBDIRS = testdata .
+
 PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
 PYTESTS = xfrin_test.py
 EXTRA_DIST = $(PYTESTS)
@@ -7,6 +9,9 @@ EXTRA_DIST = $(PYTESTS)
 LIBRARY_PATH_PLACEHOLDER =
 if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+else
+# sunstudio needs the ds path even if not all paths are necessary
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS
@@ -20,5 +25,7 @@ endif
 	echo Running test: $$pytest ; \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	PYTHONPATH=$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/bin/xfrin:$(COMMON_PYTHON_PATH) \
+	TESTDATASRCDIR=$(abs_top_srcdir)/src/bin/xfrin/tests/testdata/ \
+	TESTDATAOBJDIR=$(abs_top_builddir)/src/bin/xfrin/tests/testdata/ \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done

+ 2 - 0
src/bin/xfrin/tests/testdata/Makefile.am

@@ -0,0 +1,2 @@
+EXTRA_DIST = example.com # not necessarily needed, but for reference
+EXTRA_DIST += example.com.sqlite3

+ 17 - 0
src/bin/xfrin/tests/testdata/example.com

@@ -0,0 +1,17 @@
+;; This is a simplest form of zone file for 'example.com', which is the
+;; source of the corresponding sqlite3 DB file.  This file is provided
+;; for reference purposes only; it's not actually used anywhere.
+
+example.com.		3600	IN SOA	master.example.com. admin.example.com. (
+					1230       ; serial
+					3600       ; refresh (1 hour)
+					1800       ; retry (30 minutes)
+					2419200    ; expire (4 weeks)
+					7200       ; minimum (2 hours)
+					)
+			3600	NS	dns01.example.com.
+			3600	NS	dns02.example.com.
+			3600	NS	dns03.example.com.
+dns01.example.com.	3600	IN A	192.0.2.1
+dns02.example.com.	3600	IN A	192.0.2.2
+dns03.example.com.	3600	IN A	192.0.2.3

BIN
src/bin/xfrin/tests/testdata/example.com.sqlite3


File diff suppressed because it is too large
+ 1140 - 126
src/bin/xfrin/tests/xfrin_test.py


+ 494 - 67
src/bin/xfrin/xfrin.py.in

@@ -28,7 +28,9 @@ from optparse import OptionParser, OptionValueError
 from isc.config.ccsession import *
 from isc.notify import notify_out
 import isc.util.process
+from isc.datasrc import DataSourceClient, ZoneFinder
 import isc.net.parse
+from isc.xfrin.diff import Diff
 from isc.log_messages.xfrin_messages import *
 
 isc.log.init("b10-xfrin")
@@ -62,6 +64,9 @@ ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
 REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
 ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
 
+# Constants for debug levels, to be removed when we have #1074.
+DBG_XFRIN_TRACE = 3
+
 # These two default are currently hard-coded. For config this isn't
 # necessary, but we need these defaults for optional command arguments
 # (TODO: have similar support to get default values for command
@@ -77,6 +82,11 @@ XFRIN_FAIL = 1
 class XfrinException(Exception):
     pass
 
+class XfrinProtocolError(Exception):
+    '''An exception raised for errors encountered in xfrin protocol handling.
+    '''
+    pass
+
 class XfrinZoneInfoException(Exception):
     """This exception is raised if there is an error in the given
        configuration (part), or when a command does not have a required
@@ -112,29 +122,358 @@ def _check_zone_class(zone_class_str):
     except InvalidRRClass as irce:
         raise XfrinZoneInfoException("bad zone class: " + zone_class_str + " (" + str(irce) + ")")
 
+def get_soa_serial(soa_rdata):
+    '''Extract the serial field of an SOA RDATA and returns it as an intger.
+
+    We don't have to be very efficient here, so we first dump the entire RDATA
+    as a string and convert the first corresponding field.  This should be
+    sufficient in practice, but may not always work when the MNAME or RNAME
+    contains an (escaped) space character in their labels.  Ideally there
+    should be a more direct and convenient way to get access to the SOA
+    fields.
+    '''
+    return int(soa_rdata.to_text().split()[2])
+
+class XfrinState:
+    '''
+    The states of the incomding *XFR state machine.
+
+    We (will) handle both IXFR and AXFR with a single integrated state
+    machine because they cannot be distinguished immediately - an AXFR
+    response to an IXFR request can only be detected when the first two (2)
+    response RRs have already been received.
+
+    The following diagram summarizes the state transition.  After sending
+    the query, xfrin starts the process with the InitialSOA state (all
+    IXFR/AXFR response begins with an SOA).  When it reaches IXFREnd
+    or AXFREnd, the process successfully completes.
+
+                             (AXFR or
+            (recv SOA)        AXFR-style IXFR)  (SOA, add)
+    InitialSOA------->FirstData------------->AXFR--------->AXFREnd
+                          |                  |  ^         (post xfr
+                          |                  |  |        checks, then
+                          |                  +--+        commit)
+                          |            (non SOA, add)
+                          |
+                          |                     (non SOA, delete)
+               (pure IXFR,|                           +-------+
+            keep handling)|             (Delete SOA)  V       |
+                          + ->IXFRDeleteSOA------>IXFRDelete--+
+                                   ^                   |
+                (see SOA, not end, |          (see SOA)|
+            commit, keep handling) |                   |
+                                   |                   V
+                      +---------IXFRAdd<----------+IXFRAddSOA
+        (non SOA, add)|         ^  |    (Add SOA)
+                      ----------+  |
+                                   |(see SOA w/ end serial, commit changes)
+                                   V
+                                IXFREnd
+
+    Note that changes are committed for every "difference sequence"
+    (i.e. changes for one SOA update).  This means when an IXFR response
+    contains multiple difference sequences and something goes wrong
+    after several commits, these changes have been published and visible
+    to clients even if the IXFR session is subsequently aborted.
+    It is not clear if this is valid in terms of the protocol specification.
+    Section 4 of RFC 1995 states:
+
+       An IXFR client, should only replace an older version with a newer
+       version after all the differences have been successfully processed.
+
+    If this "replacement" is for the changes of one difference sequence
+    and "all the differences" mean the changes for that sequence, this
+    implementation strictly follows what RFC states.  If this is for
+    the entire IXFR response (that may contain multiple sequences),
+    we should implement it with one big transaction and one final commit
+    at the very end.
+
+    For now, we implement it with multiple smaller commits for two
+    reasons.  First, this is what BIND 9 does, and we generally port
+    the implementation logic here.  BIND 9 has been supporting IXFR
+    for many years, so the fact that it still behaves this way
+    probably means it at least doesn't cause a severe operational
+    problem in practice.  Second, especially because BIND 10 would
+    often uses a database backend, a larger transaction could cause an
+    undesirable effects, e.g. suspending normal lookups for a longer
+    period depending on the characteristics of the database.  Even if
+    we find something wrong in a later sequeunce and abort the
+    session, we can start another incremental update from what has
+    been validated, or we can switch to AXFR to replace the zone
+    completely.
+
+    This implementation uses the state design pattern, where each state
+    is represented as a subclass of the base XfrinState class.  Each concrete
+    subclass of XfrinState is assumed to define two methods: handle_rr() and
+    finish_message().  These methods handle specific part of XFR protocols
+    and (if necessary) perform the state transition.
+
+    Conceptually, XfrinState and its subclasses are a "friend" of
+    XfrinConnection and are assumed to be allowed to access its internal
+    information (even though Python does not have a strict access control
+    between different classes).
+
+    The XfrinState and its subclasses are designed to be stateless, and
+    can be used as singleton objects.  For now, however, we always instantiate
+    a new object for every state transition, partly because the introduction
+    of singleton will make a code bit complicated, and partly because
+    the overhead of object instantiotion wouldn't be significant for xfrin.
+
+    '''
+    def set_xfrstate(self, conn, new_state):
+        '''Set the XfrConnection to a given new state.
+
+        As a "friend" class, this method intentionally gets access to the
+        connection's "private" method.
+
+        '''
+        conn._XfrinConnection__set_xfrstate(new_state)
+
+    def handle_rr(self, conn):
+        '''Handle one RR of an XFR response message.
+
+        Depending on the state, the RR is generally added or deleted in the
+        corresponding data source, or in some special cases indicates
+        a specifi transition, such as starting a new IXFR difference
+        sequence or completing the session.
+
+        All subclass has their specific behaviors for this method, so
+        there is no default definition.  If the base class version
+        is called, it's a bug of the caller, and it's notified via
+        an XfrinException exception.
+
+        This method returns a boolean value: True if the given RR was
+        fully handled and the caller should go to the next RR; False
+        if the caller needs to call this method with the (possibly) new
+        state for the same RR again.
+
+        '''
+        raise XfrinException("Internal bug: " +
+                             "XfrinState.handle_rr() called directly")
+
+    def finish_message(self, conn):
+        '''Perform any final processing after handling all RRs of a response.
+
+        This method then returns a boolean indicating whether to continue
+        receiving the message.  Unless it's in the end of the entire XFR
+        session, we should continue, so this default method simply returns
+        True.
+
+        '''
+        return True
+
+class XfrinInitialSOA(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() != RRType.SOA():
+            raise XfrinProtocolError('First RR in zone transfer must be SOA ('
+                                     + rr.get_type().to_text() + ' received)')
+        conn._end_serial = get_soa_serial(rr.get_rdata()[0])
+
+        # FIXME: we need to check the serial is actually greater than ours.
+        # To do so, however, we need to implement serial number arithmetic.
+        # Although it wouldn't be a big task, we'll leave it for a separate
+        # task for now.  (Always performing xfr could be inefficient, but
+        # shouldn't do any harm otherwise)
+
+        self.set_xfrstate(conn, XfrinFirstData())
+        return True
+
+class XfrinFirstData(XfrinState):
+    def handle_rr(self, conn, rr):
+        '''Handle the first RR after initial SOA in an XFR session.
+
+        This state happens exactly once in an XFR session, where
+        we decide whether it's incremental update ("real" IXFR) or
+        non incremental update (AXFR or AXFR-style IXFR).
+        If we initiated IXFR and the transfer begins with two SOAs
+        (the serial of the second one being equal to our serial),
+        it's incremental; otherwise it's non incremental.
+
+        This method always return False (unlike many other handle_rr()
+        methods) because this first RR must be examined again in the
+        determined update context.
+
+        Note that in the non incremental case the RR should normally be
+        something other SOA, but it's still possible it's an SOA with a
+        different serial than ours.  The only possible interpretation at
+        this point is that it's non incremental update that only consists
+        of the SOA RR.  It will result in broken zone (for example, it
+        wouldn't even contain an apex NS) and should be rejected at post
+        XFR processing, but in terms of the XFR session processing we
+        accept it and move forward.
+
+        Note further that, in the half-broken SOA-only transfer case,
+        these two SOAs are supposed to be the same as stated in Section 2.2
+        of RFC 5936.  We don't check that condition here, either; we'll
+        leave whether and how to deal with that situation to the end of
+        the processing of non incremental update.  See also a related
+        discussion at the IETF dnsext wg:
+        http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
+
+        '''
+        if conn._request_type == RRType.IXFR() and \
+                rr.get_type() == RRType.SOA() and \
+                conn._request_serial == get_soa_serial(rr.get_rdata()[0]):
+            logger.debug(DBG_XFRIN_TRACE, XFRIN_GOT_INCREMENTAL_RESP,
+                         conn.zone_str())
+            self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
+        else:
+            logger.debug(DBG_XFRIN_TRACE, XFRIN_GOT_NONINCREMENTAL_RESP,
+                 conn.zone_str())
+            # We are now going to add RRs to the new zone.  We need create
+            # a Diff object.  It will be used throughtout the XFR session.
+            conn._diff = Diff(conn._datasrc_client, conn._zone_name, True)
+            self.set_xfrstate(conn, XfrinAXFR())
+        return False
+
+class XfrinIXFRDeleteSOA(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() != RRType.SOA():
+            # this shouldn't happen; should this occur it means an internal
+            # bug.
+            raise XfrinException(rr.get_type().to_text() +
+                                 ' RR is given in IXFRDeleteSOA state')
+        # This is the beginning state of one difference sequence (changes
+        # for one SOA update).  We need to create a new Diff object now.
+        conn._diff = Diff(conn._datasrc_client, conn._zone_name)
+        conn._diff.delete_data(rr)
+        self.set_xfrstate(conn, XfrinIXFRDelete())
+        return True
+
+class XfrinIXFRDelete(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() == RRType.SOA():
+            # This is the only place where current_serial is set
+            conn._current_serial = get_soa_serial(rr.get_rdata()[0])
+            self.set_xfrstate(conn, XfrinIXFRAddSOA())
+            return False
+        conn._diff.delete_data(rr)
+        return True
+
+class XfrinIXFRAddSOA(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() != RRType.SOA():
+            # this shouldn't happen; should this occur it means an internal
+            # bug.
+            raise XfrinException(rr.get_type().to_text() +
+                                 ' RR is given in IXFRAddSOA state')
+        conn._diff.add_data(rr)
+        self.set_xfrstate(conn, XfrinIXFRAdd())
+        return True
+
+class XfrinIXFRAdd(XfrinState):
+    def handle_rr(self, conn, rr):
+        if rr.get_type() == RRType.SOA():
+            soa_serial = get_soa_serial(rr.get_rdata()[0])
+            if soa_serial == conn._end_serial:
+                conn._diff.commit()
+                self.set_xfrstate(conn, XfrinIXFREnd())
+                return True
+            elif soa_serial != conn._current_serial:
+                raise XfrinProtocolError('IXFR out of sync: expected ' +
+                                         'serial ' +
+                                         str(conn._current_serial) +
+                                         ', got ' + str(soa_serial))
+            else:
+                conn._diff.commit()
+                self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
+                return False
+        conn._diff.add_data(rr)
+        return True
+
+class XfrinIXFREnd(XfrinState):
+    def handle_rr(self, conn, rr):
+        raise XfrinProtocolError('Extra data after the end of IXFR diffs: ' +
+                                 rr.to_text())
+
+    def finish_message(self, conn):
+        '''Final processing after processing an entire IXFR session.
+
+        There will be more actions here, but for now we simply return False,
+        indicating there will be no more message to receive.
+
+        '''
+        return False
+
+class XfrinAXFR(XfrinState):
+    def handle_rr(self, conn, rr):
+        """
+        Handle the RR by putting it into the zone.
+        """
+        conn._diff.add_data(rr)
+        if rr.get_type() == RRType.SOA():
+            # SOA means end.  Don't commit it yet - we need to perform
+            # post-transfer checks
+
+            soa_serial = get_soa_serial(rr.get_rdata()[0])
+            if conn._end_serial != soa_serial:
+                logger.warn(XFRIN_AXFR_INCONSISTENT_SOA, conn.zone_str(),
+                            conn._end_serial, soa_serial)
+
+            self.set_xfrstate(conn, XfrinAXFREnd())
+        # Yes, we've eaten this RR.
+        return True
+
+class XfrinAXFREnd(XfrinState):
+    def handle_rr(self, conn, rr):
+        raise XfrinProtocolError('Extra data after the end of AXFR: ' +
+                                 rr.to_text())
+
+    def finish_message(self, conn):
+        """
+        Final processing after processing an entire AXFR session.
+
+        In this process all the AXFR changes are committed to the
+        data source.
+
+        There might be more actions here, but for now we simply return False,
+        indicating there will be no more message to receive.
+
+        """
+        conn._diff.commit()
+        return False
+
 class XfrinConnection(asyncore.dispatcher):
     '''Do xfrin in this class. '''
 
     def __init__(self,
-                 sock_map, zone_name, rrclass, db_file, shutdown_event,
-                 master_addrinfo, tsig_key = None, verbose = False,
-                 idle_timeout = 60):
-        ''' idle_timeout: max idle time for read data from socket.
-            db_file: specify the data source file.
-            check_soa: when it's true, check soa first before sending xfr query
+                 sock_map, zone_name, rrclass, datasrc_client,
+                 shutdown_event, master_addrinfo, tsig_key=None,
+                 idle_timeout=60):
+        '''Constructor of the XfirnConnection class.
+
+        idle_timeout: max idle time for read data from socket.
+        datasrc_client: the data source client object used for the XFR session.
+                        This will eventually replace db_file completely.
+
         '''
 
         asyncore.dispatcher.__init__(self, map=sock_map)
-        self.create_socket(master_addrinfo[0], master_addrinfo[1])
+
+        # The XFR state.  Conceptually this is purely private, so we emphasize
+        # the fact by the double underscore.  Other classes are assumed to
+        # get access to this via get_xfrstate(), and only XfrinState classes
+        # are assumed to be allowed to modify it via __set_xfrstate().
+        self.__state = None
+
+        # Requested transfer type (RRType.AXFR or RRType.IXFR).  The actual
+        # transfer type may differ due to IXFR->AXFR fallback:
+        self._request_type = None
+
+        # Zone parameters
         self._zone_name = zone_name
-        self._sock_map = sock_map
         self._rrclass = rrclass
-        self._db_file = db_file
+
+        # Data source handler
+        self._datasrc_client = datasrc_client
+
+        self.create_socket(master_addrinfo[0], master_addrinfo[1])
+        self._sock_map = sock_map
         self._soa_rr_count = 0
         self._idle_timeout = idle_timeout
         self.setblocking(1)
         self._shutdown_event = shutdown_event
-        self._verbose = verbose
         self._master_address = master_addrinfo[2]
         self._tsig_key = tsig_key
         self._tsig_ctx = None
@@ -145,6 +484,16 @@ class XfrinConnection(asyncore.dispatcher):
     def __create_tsig_ctx(self, key):
         return TSIGContext(key)
 
+    def __set_xfrstate(self, new_state):
+        self.__state = new_state
+
+    def get_xfrstate(self):
+        return self.__state
+
+    def zone_str(self):
+        '''A convenient function for logging to include zone name and class'''
+        return self._zone_name.to_text() + '/' + str(self._rrclass)
+
     def connect_to_master(self):
         '''Connect to master in TCP.'''
 
@@ -155,17 +504,67 @@ class XfrinConnection(asyncore.dispatcher):
             logger.error(XFRIN_CONNECT_MASTER, self._master_address, str(e))
             return False
 
+    def _get_zone_soa(self):
+        result, finder = self._datasrc_client.find_zone(self._zone_name)
+        if result != DataSourceClient.SUCCESS:
+            raise XfrinException('Zone not found in the given data ' +
+                                 'source: ' + self.zone_str())
+        result, soa_rrset = finder.find(self._zone_name, RRType.SOA(),
+                                        None, ZoneFinder.FIND_DEFAULT)
+        if result != ZoneFinder.SUCCESS:
+            raise XfrinException('SOA RR not found in zone: ' +
+                                 self.zone_str())
+        # Especially for database-based zones, a working zone may be in
+        # a broken state where it has more than one SOA RR.  We proactively
+        # check the condition and abort the xfr attempt if we identify it.
+        if soa_rrset.get_rdata_count() != 1:
+            raise XfrinException('Invalid number of SOA RRs for ' +
+                                 self.zone_str() + ': ' +
+                                 str(soa_rrset.get_rdata_count()))
+        return soa_rrset
+
     def _create_query(self, query_type):
-        '''Create dns query message. '''
+        '''Create an XFR-related query message.
+
+        query_type is either SOA, AXFR or IXFR.  For type IXFR, it searches
+        the associated data source for the current SOA record to include
+        it in the query.  If the corresponding zone or the SOA record
+        cannot be found, it raises an XfrinException exception.  Note that
+        this may not necessarily a broken configuration; for the first attempt
+        of transfer the secondary may not have any boot-strap zone
+        information, in which case IXFR simply won't work.  The xfrin
+        should then fall back to AXFR.  _request_serial is recorded for
+        later use.
 
+        '''
         msg = Message(Message.RENDER)
         query_id = random.randint(0, 0xFFFF)
         self._query_id = query_id
         msg.set_qid(query_id)
         msg.set_opcode(Opcode.QUERY())
         msg.set_rcode(Rcode.NOERROR())
-        query_question = Question(Name(self._zone_name), self._rrclass, query_type)
-        msg.add_question(query_question)
+        msg.add_question(Question(self._zone_name, self._rrclass, query_type))
+        if query_type == RRType.IXFR():
+            # get the zone finder.  this must be SUCCESS (not even
+            # PARTIALMATCH) because we are specifying the zone origin name.
+            zone_soa_rr = self._get_zone_soa()
+            msg.add_rrset(Message.SECTION_AUTHORITY, zone_soa_rr)
+            self._request_serial = get_soa_serial(zone_soa_rr.get_rdata()[0])
+        else:
+            # For AXFR, we temporarily provide backward compatible behavior
+            # where xfrin is responsible for creating zone in the corresponding
+            # DB table.  Note that the code below uses the old data source
+            # API and assumes SQLite3 in an ugly manner.  We'll have to
+            # develop a better way of managing zones in a generic way and
+            # eliminate the code like the one here.
+            try:
+                self._get_zone_soa()
+            except XfrinException:
+                def empty_rr_generator():
+                    return []
+                isc.datasrc.sqlite3_ds.load(self._db_file,
+                                            self._zone_name.to_text(),
+                                            empty_rr_generator)
         return msg
 
     def _send_data(self, data):
@@ -256,39 +655,49 @@ class XfrinConnection(asyncore.dispatcher):
         # now.
         return XFRIN_OK
 
-    def do_xfrin(self, check_soa, ixfr_first = False):
-        '''Do xfr by sending xfr request and parsing response. '''
+    def do_xfrin(self, check_soa, request_type=RRType.AXFR()):
+        '''Do an xfr session by sending xfr request and parsing responses.'''
 
         try:
             ret = XFRIN_OK
+            self._request_type = request_type
+            # Right now RRType.[IA]XFR().to_text() is 'TYPExxx', so we need
+            # to hardcode here.
+            request_str = 'IXFR' if request_type == RRType.IXFR() else 'AXFR'
             if check_soa:
-                logstr = 'SOA check for \'%s\' ' % self._zone_name
                 ret =  self._check_soa_serial()
 
             if ret == XFRIN_OK:
-                logger.info(XFRIN_AXFR_TRANSFER_STARTED, self._zone_name)
-                self._send_query(RRType.AXFR())
-                isc.datasrc.sqlite3_ds.load(self._db_file, self._zone_name,
-                                            self._handle_xfrin_response)
-
-                logger.info(XFRIN_AXFR_TRANSFER_SUCCESS, self._zone_name)
-
-        except XfrinException as e:
-            logger.error(XFRIN_AXFR_TRANSFER_FAILURE, self._zone_name, str(e))
-            ret = XFRIN_FAIL
-            #TODO, recover data source.
-        except isc.datasrc.sqlite3_ds.Sqlite3DSError as e:
-            logger.error(XFRIN_AXFR_DATABASE_FAILURE, self._zone_name, str(e))
+                logger.info(XFRIN_XFR_TRANSFER_STARTED, request_str,
+                            self.zone_str())
+                self._send_query(self._request_type)
+                self.__state = XfrinInitialSOA()
+                self._handle_xfrin_responses()
+                logger.info(XFRIN_XFR_TRANSFER_SUCCESS, request_str,
+                            self.zone_str())
+
+        except (XfrinException, XfrinProtocolError) as e:
+            logger.error(XFRIN_XFR_TRANSFER_FAILURE, request_str,
+                         self.zone_str(), str(e))
             ret = XFRIN_FAIL
-        except UserWarning as e:
-            # XXX: this is an exception from our C++ library via the
-            # Boost.Python binding.  It would be better to have more more
-            # specific exceptions, but at this moment this is the finest
-            # granularity.
-            logger.error(XFRIN_AXFR_INTERNAL_FAILURE, self._zone_name, str(e))
+        except Exception as e:
+            # Catching all possible exceptions like this is generally not a
+            # good practice, but handling an xfr session could result in
+            # so many types of exceptions, including ones from the DNS library
+            # or from the data source library.  Eventually we'd introduce a
+            # hierarchy for exception classes from a base "ISC exception" and
+            # catch it here, but until then we need broadest coverage so that
+            # we won't miss anything.
+
+            logger.error(XFRIN_XFR_OTHER_FAILURE, request_str,
+                         self.zone_str(), str(e))
             ret = XFRIN_FAIL
         finally:
-           self.close()
+            # Make sure any remaining transaction in the diff is closed
+            # (if not yet - possible in case of xfr-level exception) as soon
+            # as possible
+            self._diff = None
+            self.close()
 
         return ret
 
@@ -318,9 +727,6 @@ class XfrinConnection(asyncore.dispatcher):
 
         self._check_response_header(msg)
 
-        if msg.get_rr_count(Message.SECTION_ANSWER) == 0:
-            raise XfrinException('answer section is empty')
-
         if msg.get_rr_count(Message.SECTION_QUESTION) > 1:
             raise XfrinException('query section count greater than 1')
 
@@ -351,14 +757,14 @@ class XfrinConnection(asyncore.dispatcher):
                 yield (rrset_name, rrset_ttl, rrset_class, rrset_type,
                        rdata_text)
 
-    def _handle_xfrin_response(self):
-        '''Return a generator for the response to a zone transfer. '''
-        while True:
+    def _handle_xfrin_responses(self):
+        read_next_msg = True
+        while read_next_msg:
             data_len = self._get_request_response(2)
             msg_len = socket.htons(struct.unpack('H', data_len)[0])
             recvdata = self._get_request_response(msg_len)
             msg = Message(Message.PARSE)
-            msg.from_wire(recvdata)
+            msg.from_wire(recvdata, Message.PRESERVE_ORDER)
 
             # TSIG related checks, including an unexpected signed response
             self._check_response_tsig(msg, recvdata)
@@ -366,12 +772,12 @@ class XfrinConnection(asyncore.dispatcher):
             # Perform response status validation
             self._check_response_status(msg)
 
-            answer_section = msg.get_section(Message.SECTION_ANSWER)
-            for rr in self._handle_answer_section(answer_section):
-                yield rr
+            for rr in msg.get_section(Message.SECTION_ANSWER):
+                rr_handled = False
+                while not rr_handled:
+                    rr_handled = self.__state.handle_rr(self, rr)
 
-            if self._soa_rr_count == 2:
-                break
+            read_next_msg = self.__state.finish_message(self)
 
             if self._shutdown_event.is_set():
                 raise XfrinException('xfrin is forced to stop')
@@ -393,16 +799,35 @@ class XfrinConnection(asyncore.dispatcher):
         pass
 
 def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
-                  shutdown_event, master_addrinfo, check_soa, verbose,
-                  tsig_key):
+                  shutdown_event, master_addrinfo, check_soa, tsig_key,
+                  request_type):
     xfrin_recorder.increment(zone_name)
+
+    # Create a data source client used in this XFR session.  Right now we
+    # still assume an sqlite3-based data source, and use both the old and new
+    # data source APIs.  We also need to use a mock client for tests.
+    # For a temporary workaround to deal with these situations, we skip the
+    # creation when the given file is none (the test case).  Eventually
+    # this code will be much cleaner.
+    datasrc_client = None
+    if db_file is not None:
+        # temporary hardcoded sqlite initialization. Once we decide on
+        # the config specification, we need to update this (TODO)
+        # this may depend on #1207, or any followup ticket created for #1207
+        datasrc_type = "sqlite3"
+        datasrc_config = "{ \"database_file\": \"" + db_file + "\"}"
+        datasrc_client = DataSourceClient(datasrc_type, datasrc_config)
+
+    # Create a TCP connection for the XFR session and perform the operation.
     sock_map = {}
-    conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
-                           shutdown_event, master_addrinfo,
-                           tsig_key, verbose)
+    conn = XfrinConnection(sock_map, zone_name, rrclass, datasrc_client,
+                           shutdown_event, master_addrinfo, tsig_key)
+    # XXX: We still need _db_file for temporary workaround in _create_query().
+    # This should be removed when we eliminate the need for the workaround.
+    conn._db_file = db_file
     ret = XFRIN_FAIL
     if conn.connect_to_master():
-        ret = conn.do_xfrin(check_soa)
+        ret = conn.do_xfrin(check_soa, request_type)
 
     # Publish the zone transfer result news, so zonemgr can reset the
     # zone timer, and xfrout can notify the zone's slaves if the result
@@ -541,13 +966,12 @@ class ZoneInfo:
                 (str(self.master_addr), self.master_port))
 
 class Xfrin:
-    def __init__(self, verbose = False):
+    def __init__(self):
         self._max_transfers_in = 10
         self._zones = {}
         self._cc_setup()
         self.recorder = XfrinRecorder()
         self._shutdown_event = threading.Event()
-        self._verbose = verbose
 
     def _cc_setup(self):
         '''This method is used only as part of initialization, but is
@@ -646,7 +1070,7 @@ class Xfrin:
                                            rrclass,
                                            self._get_db_file(),
                                            master_addr,
-                                           zone_info.tsig_key,
+                                           zone_info.tsig_key, RRType.AXFR(),
                                            True)
                     answer = create_answer(ret[0], ret[1])
 
@@ -659,14 +1083,17 @@ class Xfrin:
                                                           rrclass)
                 zone_info = self._get_zone_info(zone_name, rrclass)
                 tsig_key = None
+                request_type = RRType.AXFR()
                 if zone_info:
                     tsig_key = zone_info.tsig_key
+                    if not zone_info.ixfr_disabled:
+                        request_type = RRType.IXFR()
                 db_file = args.get('db_file') or self._get_db_file()
                 ret = self.xfrin_start(zone_name,
                                        rrclass,
                                        db_file,
                                        master_addr,
-                                       tsig_key,
+                                       tsig_key, request_type,
                                        (False if command == 'retransfer' else True))
                 answer = create_answer(ret[0], ret[1])
 
@@ -746,7 +1173,8 @@ class Xfrin:
         news(command: zone_new_data_ready) to zone manager and xfrout.
         if xfrin failed, just tell the bad news to zone manager, so that
         it can reset the refresh timer for that zone. '''
-        param = {'zone_name': zone_name, 'zone_class': zone_class.to_text()}
+        param = {'zone_name': zone_name.to_text(),
+                 'zone_class': zone_class.to_text()}
         if xfr_result == XFRIN_OK:
             msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
             # catch the exception, in case msgq has been killed.
@@ -783,8 +1211,8 @@ class Xfrin:
         while not self._shutdown_event.is_set():
             self._cc_check_command()
 
-    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo, tsig_key,
-                    check_soa = True):
+    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
+                    tsig_key, request_type, check_soa=True):
         if "pydnspp" not in sys.modules:
             return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
 
@@ -798,13 +1226,12 @@ class Xfrin:
         xfrin_thread = threading.Thread(target = process_xfrin,
                                         args = (self,
                                                 self.recorder,
-                                                zone_name.to_text(),
+                                                zone_name,
                                                 rrclass,
                                                 db_file,
                                                 self._shutdown_event,
                                                 master_addrinfo, check_soa,
-                                                self._verbose,
-                                                tsig_key))
+                                                tsig_key, request_type))
 
         xfrin_thread.start()
         return (0, 'zone xfrin is started')
@@ -823,9 +1250,9 @@ def set_signal_handler():
 
 def set_cmd_options(parser):
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
-            help="display more about what is going on")
+            help="This option is obsolete and has no effect.")
 
-def main(xfrin_class, use_signal = True):
+def main(xfrin_class, use_signal=True):
     """The main loop of the Xfrin daemon.
 
     @param xfrin_class: A class of the Xfrin object.  This is normally Xfrin,
@@ -842,7 +1269,7 @@ def main(xfrin_class, use_signal = True):
 
         if use_signal:
             set_signal_handler()
-        xfrind = xfrin_class(verbose = options.verbose)
+        xfrind = xfrin_class()
         xfrind.startup()
     except KeyboardInterrupt:
         logger.info(XFRIN_STOPPED_BY_KEYBOARD)

+ 40 - 10
src/bin/xfrin/xfrin_messages.mes

@@ -15,25 +15,26 @@
 # No namespace declaration - these constants go in the global namespace
 # of the xfrin messages python module.
 
-% XFRIN_AXFR_INTERNAL_FAILURE AXFR transfer of zone %1 failed: %2
-The AXFR transfer for the given zone has failed due to an internal
-problem in the bind10 python wrapper library.
-The error is shown in the log message.
+% XFRIN_XFR_OTHER_FAILURE %1 transfer of zone %2 failed: %3
+The XFR transfer for the given zone has failed due to a problem outside
+of the xfrin module.  Possible reasons are a broken DNS message or failure
+in database connection.  The error is shown in the log message.
 
 % XFRIN_AXFR_DATABASE_FAILURE AXFR transfer of zone %1 failed: %2
 The AXFR transfer for the given zone has failed due to a database problem.
-The error is shown in the log message.
+The error is shown in the log message.  Note: due to the code structure
+this can only happen for AXFR.
 
-% XFRIN_AXFR_TRANSFER_FAILURE AXFR transfer of zone %1 failed: %2
-The AXFR transfer for the given zone has failed due to a protocol error.
+% XFRIN_XFR_TRANSFER_FAILURE %1 transfer of zone %2 failed: %3
+The XFR transfer for the given zone has failed due to a protocol error.
 The error is shown in the log message.
 
-% XFRIN_AXFR_TRANSFER_STARTED AXFR transfer of zone %1 started
+% XFRIN_XFR_TRANSFER_STARTED %1 transfer of zone %2 started
 A connection to the master server has been made, the serial value in
 the SOA record has been checked, and a zone transfer has been started.
 
-% XFRIN_AXFR_TRANSFER_SUCCESS AXFR transfer of zone %1 succeeded
-The AXFR transfer of the given zone was successfully completed.
+% XFRIN_XFR_TRANSFER_SUCCESS %1 transfer of zone %2 succeeded
+The XFR transfer of the given zone was successfully completed.
 
 % XFRIN_BAD_MASTER_ADDR_FORMAT bad format for master address: %1
 The given master address is not a valid IP address.
@@ -89,3 +90,32 @@ daemon will now shut down.
 % XFRIN_UNKNOWN_ERROR unknown error: %1
 An uncaught exception was raised while running the xfrin daemon. The
 exception message is printed in the log message.
+
+% XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
+In an attempt of IXFR processing, the begenning SOA of the first difference
+(following the initial SOA that specified the final SOA for all the
+differences) was found.  This means a connection for xfrin tried IXFR
+and really aot a response for incremental updates.
+
+% XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
+Non incremental transfer was detected at the "first data" of a transfer,
+which is the RR following the initial SOA.  Non incremental transfer is
+either AXFR or AXFR-style IXFR.  In the latter case, it means that
+in a response to IXFR query the first data is not SOA or its SOA serial
+is not equal to the requested SOA serial.
+
+% XFRIN_AXFR_INCONSISTENT_SOA AXFR SOAs are inconsistent for %1: %2 expected, %3 received
+The serial fields of the first and last SOAs of AXFR (including AXFR-style
+IXFR) are not the same.  According to RFC 5936 these two SOAs must be the
+"same" (not only for the serial), but it is still not clear what the
+receiver should do if this condition does not hold.  There was a discussion
+about this at the IETF dnsext wg:
+http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
+and the general feeling seems that it would be better to reject the
+transfer if a mismatch is detected.  On the other hand, also as noted
+in that email thread, neither BIND 9 nor NSD performs any comparison
+on the SOAs.  For now, we only check the serials (ignoring other fields)
+and only leave a warning log message when a mismatch is found.  If it
+turns out to happen with a real world primary server implementation
+and that server actually feeds broken data (e.g. mixed versions of
+zone), we can consider a stricter action.

+ 1 - 0
src/cppcheck-suppress.lst

@@ -8,3 +8,4 @@ unreadVariable:src/lib/dns/rdata/template.cc:61
 selfAssignment:src/lib/dns/tests/name_unittest.cc:293
 selfAssignment:src/lib/dns/tests/rdata_unittest.cc:228
 selfAssignment:src/lib/dns/tests/tsigkey_unittest.cc:137
+selfAssignment:src/lib/dns/tests/rdata_txt_like_unittest.cc:222

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

@@ -37,4 +37,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/acl/libdnsacl.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

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

@@ -47,4 +47,4 @@ run_unittests_CXXFLAGS += -Wno-error
 endif
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

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

@@ -63,5 +63,10 @@ IOAddress::getFamily() const {
     }
 }
 
+const asio::ip::address& 
+IOAddress::getAddress() const {
+    return asio_address_;
+}
+
 } // namespace asiolink
 } // namespace isc

+ 8 - 0
src/lib/asiolink/io_address.h

@@ -74,6 +74,14 @@ public:
     /// \return A string representation of the address.
     std::string toText() const;
 
+    /// \brief Returns const reference to the underlying address object.
+    ///
+    /// This is useful, when access to interface offerted by
+    //  asio::ip::address_v4 and asio::ip::address_v6 is beneficial.
+    /// 
+    /// \return A const reference to asio::ip::address object
+    const asio::ip::address& getAddress() const;
+
     /// \brief Returns the address family
     ///
     /// \return AF_INET for IPv4 or AF_INET6 for IPv6.

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

@@ -53,4 +53,4 @@ run_unittests_CXXFLAGS += -Wno-error
 endif
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

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

@@ -18,6 +18,8 @@
 #include <asiolink/io_error.h>
 #include <asiolink/io_address.h>
 
+#include <cstring>
+
 using namespace isc::asiolink;
 
 TEST(IOAddressTest, fromText) {

+ 1 - 1
src/lib/asiolink/tests/io_endpoint_unittest.cc

@@ -219,7 +219,7 @@ sockAddrMatch(const struct sockaddr& actual_sa,
     res->ai_addr->sa_len = actual_sa.sa_len;
 #endif
     EXPECT_EQ(0, memcmp(res->ai_addr, &actual_sa, res->ai_addrlen));
-    free(res);
+    freeaddrinfo(res);
 }
 
 TEST(IOEndpointTest, getSockAddr) {

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

@@ -22,6 +22,6 @@ run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 run_unittests_LDADD += $(GTEST_LDADD)
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
 
 EXTRA_DIST = testdata/query.txt

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

@@ -62,7 +62,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
 
 EXTRA_DIST  = testdata/message_cname_referral.wire
 EXTRA_DIST += testdata/message_example_com_soa.wire

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

@@ -32,4 +32,4 @@ run_unittests_LDADD +=  $(top_builddir)/src/lib/exceptions/libexceptions.la
 
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

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

@@ -31,4 +31,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
 
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

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

@@ -24,4 +24,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

+ 13 - 3
src/lib/datasrc/Makefile.am

@@ -9,7 +9,7 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
 
-lib_LTLIBRARIES = libdatasrc.la
+lib_LTLIBRARIES = libdatasrc.la sqlite3_ds.la memory_ds.la
 libdatasrc_la_SOURCES = data_source.h data_source.cc
 libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
@@ -17,15 +17,25 @@ libdatasrc_la_SOURCES += query.h query.cc
 libdatasrc_la_SOURCES += cache.h cache.cc
 libdatasrc_la_SOURCES += rbtree.h
 libdatasrc_la_SOURCES += zonetable.h zonetable.cc
-libdatasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
 libdatasrc_la_SOURCES += zone.h
 libdatasrc_la_SOURCES += result.h
 libdatasrc_la_SOURCES += logger.h logger.cc
 libdatasrc_la_SOURCES += client.h iterator.h
 libdatasrc_la_SOURCES += database.h database.cc
-libdatasrc_la_SOURCES += sqlite3_accessor.h sqlite3_accessor.cc
+libdatasrc_la_SOURCES += factory.h factory.cc
 nodist_libdatasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
 
+sqlite3_ds_la_SOURCES = sqlite3_accessor.h sqlite3_accessor.cc
+sqlite3_ds_la_LDFLAGS = -module
+sqlite3_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+sqlite3_ds_la_LIBADD += libdatasrc.la
+sqlite3_ds_la_LIBADD += $(SQLITE_LIBS)
+
+memory_ds_la_SOURCES = memory_datasrc.h memory_datasrc.cc
+memory_ds_la_LDFLAGS = -module
+memory_ds_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
+memory_ds_la_LIBADD += libdatasrc.la
+
 libdatasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 libdatasrc_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la

+ 44 - 0
src/lib/datasrc/client.h

@@ -22,6 +22,47 @@
 
 #include <datasrc/zone.h>
 
+/// \file
+/// Datasource clients
+///
+/// The data source client API is specified in client.h, and provides the
+/// functionality to query and modify data in the data sources. There are
+/// multiple datasource implementations, and by subclassing DataSourceClient or
+/// DatabaseClient, more can be added.
+///
+/// All datasources are implemented as loadable modules, with a name of the
+/// form "<type>_ds.so". This has been chosen intentionally, to minimize
+/// confusion and potential mistakes.
+///
+/// In order to use a datasource client backend, the class
+/// DataSourceClientContainer is provided in factory.h; this will load the
+/// library, set up the instance, and clean everything up once it is destroyed.
+///
+/// Access to the actual instance is provided with the getInstance() method
+/// in DataSourceClientContainer
+///
+/// \note Depending on actual usage, we might consider making the container
+/// a transparent abstraction layer, so it can be used as a DataSourceClient
+/// directly. This has some other implications though so for now the only access
+/// provided is through getInstance()).
+///
+/// For datasource backends, we use a dynamically loaded library system (with
+/// dlopen()). This library must contain the following things;
+/// - A subclass of DataSourceClient or DatabaseClient (which itself is a
+///   subclass of DataSourceClient)
+/// - A creator function for an instance of that subclass, of the form:
+/// \code
+/// extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr cfg);
+/// \endcode
+/// - A destructor for said instance, of the form:
+/// \code
+/// extern "C" void destroyInstance(isc::data::DataSourceClient* instance);
+/// \endcode
+///
+/// See the documentation for the \link DataSourceClient \endlink class for
+/// more information on implementing subclasses of it.
+///
+
 namespace isc {
 namespace datasrc {
 
@@ -39,6 +80,9 @@ typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
 /// operations to other classes; in general methods of this class act as
 /// factories of these other classes.
 ///
+/// See \link datasrc/client.h datasrc/client.h \endlink for more information
+/// on adding datasource implementations.
+///
 /// The following derived classes are currently (expected to be) provided:
 /// - \c InMemoryClient: A client of a conceptual data source that stores
 /// all necessary data in memory for faster lookups

+ 6 - 6
src/lib/datasrc/data_source.h

@@ -184,9 +184,9 @@ public:
     void setClass(isc::dns::RRClass& c) { rrclass = c; }
     void setClass(const isc::dns::RRClass& c) { rrclass = c; }
 
-    Result init() { return (NOT_IMPLEMENTED); }
-    Result init(isc::data::ConstElementPtr config);
-    Result close() { return (NOT_IMPLEMENTED); }
+    virtual Result init() { return (NOT_IMPLEMENTED); }
+    virtual Result init(isc::data::ConstElementPtr config);
+    virtual Result close() { return (NOT_IMPLEMENTED); }
 
     virtual Result findRRset(const isc::dns::Name& qname,
                              const isc::dns::RRClass& qclass,
@@ -351,7 +351,7 @@ public:
 
     /// \brief Returns the best enclosing zone name found for the given
     // name and RR class so far.
-    /// 
+    ///
     /// \return A pointer to the zone apex \c Name, NULL if none found yet.
     ///
     /// This method never throws an exception.
@@ -413,6 +413,6 @@ private:
 
 #endif
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:

+ 95 - 0
src/lib/datasrc/factory.cc

@@ -0,0 +1,95 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "factory.h"
+
+#include "data_source.h"
+#include "database.h"
+#include "sqlite3_accessor.h"
+#include "memory_datasrc.h"
+
+#include <datasrc/logger.h>
+
+#include <dlfcn.h>
+
+using namespace isc::data;
+using namespace isc::datasrc;
+
+namespace isc {
+namespace datasrc {
+
+LibraryContainer::LibraryContainer(const std::string& name) {
+    // use RTLD_GLOBAL so that shared symbols (e.g. exceptions)
+    // are recognized as such
+    ds_lib_ = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
+    if (ds_lib_ == NULL) {
+        isc_throw(DataSourceLibraryError, dlerror());
+    }
+}
+
+LibraryContainer::~LibraryContainer() {
+    dlclose(ds_lib_);
+}
+
+void*
+LibraryContainer::getSym(const char* name) {
+    // Since dlsym can return NULL on success, we check for errors by
+    // first clearing any existing errors with dlerror(), then calling dlsym,
+    // and finally checking for errors with dlerror()
+    dlerror();
+
+    void *sym = dlsym(ds_lib_, name);
+
+    const char* dlsym_error = dlerror();
+    if (dlsym_error != NULL) {
+        isc_throw(DataSourceLibrarySymbolError, dlsym_error);
+    }
+
+    return (sym);
+}
+
+DataSourceClientContainer::DataSourceClientContainer(const std::string& type,
+                                                     ConstElementPtr config)
+: ds_lib_(type + "_ds.so")
+{
+    // We are casting from a data to a function pointer here
+    // Some compilers (rightfully) complain about that, but
+    // c-style casts are accepted the most here. If we run
+    // into any that also don't like this, we might need to
+    // use some form of union cast or memory copy to get
+    // from the void* to the function pointer.
+    ds_creator* ds_create = (ds_creator*)ds_lib_.getSym("createInstance");
+    destructor_ = (ds_destructor*)ds_lib_.getSym("destroyInstance");
+
+    std::string error;
+    try {
+        instance_ = ds_create(config, error);
+        if (instance_ == NULL) {
+            isc_throw(DataSourceError, error);
+        }
+    } catch (const std::exception& exc) {
+        isc_throw(DataSourceError, "Unknown uncaught exception from " + type +
+                                   " createInstance: " + exc.what());
+    } catch (...) {
+        isc_throw(DataSourceError, "Unknown uncaught exception from " + type);
+    }
+}
+
+DataSourceClientContainer::~DataSourceClientContainer() {
+    destructor_(instance_);
+}
+
+} // end namespace datasrc
+} // end namespace isc
+

+ 170 - 0
src/lib/datasrc/factory.h

@@ -0,0 +1,170 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __DATA_SOURCE_FACTORY_H
+#define __DATA_SOURCE_FACTORY_H 1
+
+#include <boost/noncopyable.hpp>
+
+#include <datasrc/data_source.h>
+#include <datasrc/client.h>
+#include <exceptions/exceptions.h>
+
+#include <cc/data.h>
+
+namespace isc {
+namespace datasrc {
+
+
+/// \brief Raised if there is an error loading the datasource implementation
+///        library
+class DataSourceLibraryError : public DataSourceError {
+public:
+    DataSourceLibraryError(const char* file, size_t line, const char* what) :
+        DataSourceError(file, line, what) {}
+};
+
+/// \brief Raised if there is an error reading a symbol from the datasource
+///        implementation library
+class DataSourceLibrarySymbolError : public DataSourceError {
+public:
+    DataSourceLibrarySymbolError(const char* file, size_t line,
+                                 const char* what) :
+        DataSourceError(file, line, what) {}
+};
+
+typedef DataSourceClient* ds_creator(isc::data::ConstElementPtr config,
+                                     std::string& error);
+typedef void ds_destructor(DataSourceClient* instance);
+
+/// \brief Container class for dynamically loaded libraries
+///
+/// This class is used to dlopen() a library, provides access to dlsym(),
+/// and cleans up the dlopened library when the instance of this class is
+/// destroyed.
+///
+/// Its main function is to provide RAII-style access to dlopen'ed libraries.
+///
+/// \note Currently it is Datasource-backend specific. If we have need for this
+///       in other places than for dynamically loading datasources, then, apart
+///       from moving it to another location, we also need to make the
+///       exceptions raised more general.
+class LibraryContainer : boost::noncopyable {
+public:
+    /// \brief Constructor
+    ///
+    /// \param name The name of the library (.so) file. This file must be in
+    ///             the library path.
+    ///
+    /// \exception DataSourceLibraryError If the library cannot be found or
+    ///            cannot be loaded.
+    LibraryContainer(const std::string& name);
+
+    /// \brief Destructor
+    ///
+    /// Cleans up the library by calling dlclose()
+    ~LibraryContainer();
+
+    /// \brief Retrieve a symbol
+    ///
+    /// This retrieves a symbol from the loaded library.
+    ///
+    /// \exception DataSourceLibrarySymbolError if the symbol cannot be found,
+    ///            or if another error (as reported by dlerror() occurs.
+    ///
+    /// \param name The name of the symbol to retrieve
+    /// \return A pointer to the symbol. This may be NULL, and if so, indicates
+    ///         the symbol does indeed exist, but has the value NULL itself.
+    ///         If the symbol does not exist, a DataSourceLibrarySymbolError is
+    ///         raised.
+    ///
+    /// \note The argument is a const char* (and not a std::string like the
+    ///       argument in the constructor). This argument is always a fixed
+    ///       string in the code, while the other can be read from
+    ///       configuration, and needs modification
+    void* getSym(const char* name);
+private:
+    /// Pointer to the dynamically loaded library structure
+    void *ds_lib_;
+};
+
+
+/// \brief Container for a specific instance of a dynamically loaded
+///        DataSourceClient implementation
+///
+/// Given a datasource type and a type-specific set of configuration data,
+/// the corresponding dynamic library is loaded (if it hadn't been already),
+/// and an instance is created. This instance is stored within this structure,
+/// and can be accessed through getInstance(). Upon destruction of this
+/// container, the stored instance of the DataSourceClient is deleted with
+/// the destructor function provided by the loaded library.
+///
+/// The 'type' is actually the name of the library, minus the '_ds.so' postfix
+/// Datasource implementation libraries therefore have a fixed name, both for
+/// easy recognition and to reduce potential mistakes.
+/// For example, the sqlite3 implementation has the type 'sqlite3', and the
+/// derived filename 'sqlite3_ds.so'
+///
+/// There are of course some demands to an implementation, not all of which
+/// can be verified compile-time. It must provide a creator and destructor
+/// functions. The creator function must return an instance of a subclass of
+/// DataSourceClient. The prototypes of these functions are as follows:
+/// \code
+/// extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr cfg);
+///
+/// extern "C" void destroyInstance(isc::data::DataSourceClient* instance);
+/// \endcode
+class DataSourceClientContainer : boost::noncopyable {
+public:
+    /// \brief Constructor
+    ///
+    /// \exception DataSourceLibraryError if there is an error loading the
+    ///            backend library
+    /// \exception DataSourceLibrarySymbolError if the library does not have
+    ///            the needed symbols, or if there is an error reading them
+    /// \exception DataError if the given config is not correct
+    ///            for the given type, or if there was a problem during
+    ///            initialization
+    ///
+    /// \param type The type of the datasource client. Based on the value of
+    ///             type, a specific backend library is used, by appending the
+    ///             string '_ds.so' to the given type, and loading that as the
+    ///             implementation library
+    /// \param config Type-specific configuration data, see the documentation
+    ///               of the datasource backend type for information on what
+    ///               configuration data to pass.
+    DataSourceClientContainer(const std::string& type,
+                              isc::data::ConstElementPtr config);
+
+    /// \brief Destructor
+    ~DataSourceClientContainer();
+
+    /// \brief Accessor to the instance
+    ///
+    /// \return Reference to the DataSourceClient instance contained in this
+    ///         container.
+    DataSourceClient& getInstance() { return *instance_; }
+
+private:
+    DataSourceClient* instance_;
+    ds_destructor* destructor_;
+    LibraryContainer ds_lib_;
+};
+
+} // end namespace datasrc
+} // end namespace isc
+#endif  // DATA_SOURCE_FACTORY_H
+// Local Variables:
+// mode: c++
+// End:

+ 150 - 1
src/lib/datasrc/memory_datasrc.cc

@@ -16,6 +16,7 @@
 #include <cassert>
 #include <boost/shared_ptr.hpp>
 #include <boost/bind.hpp>
+#include <boost/foreach.hpp>
 
 #include <exceptions/exceptions.h>
 
@@ -29,9 +30,13 @@
 #include <datasrc/logger.h>
 #include <datasrc/iterator.h>
 #include <datasrc/data_source.h>
+#include <datasrc/factory.h>
+
+#include <cc/data.h>
 
 using namespace std;
 using namespace isc::dns;
+using namespace isc::data;
 
 namespace isc {
 namespace datasrc {
@@ -805,5 +810,149 @@ ZoneUpdaterPtr
 InMemoryClient::getUpdater(const isc::dns::Name&, bool) const {
     isc_throw(isc::NotImplemented, "Update attempt on in memory data source");
 }
+
+
+namespace {
+// convencience function to add an error message to a list of those
+// (TODO: move functions like these to some util lib?)
+void
+addError(ElementPtr errors, const std::string& error) {
+    if (errors != ElementPtr() && errors->getType() == Element::list) {
+        errors->add(Element::create(error));
+    }
+}
+
+/// Check if the given element exists in the map, and if it is a string
+bool
+checkConfigElementString(ConstElementPtr config, const std::string& name,
+                         ElementPtr errors)
+{
+    if (!config->contains(name)) {
+        addError(errors,
+                 "Config for memory backend does not contain a '"
+                 +name+
+                 "' value");
+        return false;
+    } else if (!config->get(name) ||
+               config->get(name)->getType() != Element::string) {
+        addError(errors, "value of " + name +
+                 " in memory backend config is not a string");
+        return false;
+    } else {
+        return true;
+    }
+}
+
+bool
+checkZoneConfig(ConstElementPtr config, ElementPtr errors) {
+    bool result = true;
+    if (!config || config->getType() != Element::map) {
+        addError(errors, "Elements in memory backend's zone list must be maps");
+        result = false;
+    } else {
+        if (!checkConfigElementString(config, "origin", errors)) {
+            result = false;
+        }
+        if (!checkConfigElementString(config, "file", errors)) {
+            result = false;
+        }
+        // we could add some existence/readabilty/parsability checks here
+        // if we want
+    }
+    return result;
+}
+
+bool
+checkConfig(ConstElementPtr config, ElementPtr errors) {
+    /* Specific configuration is under discussion, right now this accepts
+     * the 'old' configuration, see [TODO]
+     * So for memory datasource, we get a structure like this:
+     * { "type": string ("memory"),
+     *   "class": string ("IN"/"CH"/etc),
+     *   "zones": list
+     * }
+     * Zones list is a list of maps:
+     * { "origin": string,
+     *     "file": string
+     * }
+     *
+     * At this moment we cannot be completely sure of the contents of the
+     * structure, so we have to do some more extensive tests than should
+     * strictly be necessary (e.g. existence and type of elements)
+     */
+    bool result = true;
+
+    if (!config || config->getType() != Element::map) {
+        addError(errors, "Base config for memory backend must be a map");
+        result = false;
+    } else {
+        if (!checkConfigElementString(config, "type", errors)) {
+            result = false;
+        } else {
+            if (config->get("type")->stringValue() != "memory") {
+                addError(errors,
+                         "Config for memory backend is not of type \"memory\"");
+                result = false;
+            }
+        }
+        if (!checkConfigElementString(config, "class", errors)) {
+            result = false;
+        } else {
+            try {
+                RRClass rrc(config->get("class")->stringValue());
+            } catch (const isc::Exception& rrce) {
+                addError(errors,
+                         "Error parsing class config for memory backend: " +
+                         std::string(rrce.what()));
+                result = false;
+            }
+        }
+        if (!config->contains("zones")) {
+            addError(errors, "No 'zones' element in memory backend config");
+            result = false;
+        } else if (!config->get("zones") ||
+                   config->get("zones")->getType() != Element::list) {
+            addError(errors, "'zones' element in memory backend config is not a list");
+            result = false;
+        } else {
+            BOOST_FOREACH(ConstElementPtr zone_config,
+                          config->get("zones")->listValue()) {
+                if (!checkZoneConfig(zone_config, errors)) {
+                    result = false;
+                }
+            }
+        }
+    }
+
+    return (result);
+    return true;
+}
+
+} // end anonymous namespace
+
+DataSourceClient *
+createInstance(isc::data::ConstElementPtr config, std::string& error) {
+    ElementPtr errors(Element::createList());
+    if (!checkConfig(config, errors)) {
+        error = "Configuration error: " + errors->str();
+        return (NULL);
+    }
+    try {
+        return (new InMemoryClient());
+    } catch (const std::exception& exc) {
+        error = std::string("Error creating memory datasource: ") + exc.what();
+        return (NULL);
+    } catch (...) {
+        error = std::string("Error creating memory datasource, "
+                            "unknown exception");
+        return (NULL);
+    }
+}
+
+void destroyInstance(DataSourceClient* instance) {
+    delete instance;
+}
+
+
 } // end of namespace datasrc
-} // end of namespace dns
+} // end of namespace isc

+ 35 - 1
src/lib/datasrc/memory_datasrc.h

@@ -22,6 +22,8 @@
 #include <datasrc/zonetable.h>
 #include <datasrc/client.h>
 
+#include <cc/data.h>
+
 namespace isc {
 namespace dns {
 class Name;
@@ -219,7 +221,7 @@ private:
 /// while it wouldn't be safe to delete unnecessary zones inside the dedicated
 /// backend.
 ///
-/// The findZone() method takes a domain name and returns the best matching 
+/// The findZone() method takes a domain name and returns the best matching
 /// \c InMemoryZoneFinder in the form of (Boost) shared pointer, so that it can
 /// provide the general interface for all data sources.
 class InMemoryClient : public DataSourceClient {
@@ -289,6 +291,38 @@ private:
     class InMemoryClientImpl;
     InMemoryClientImpl* impl_;
 };
+
+/// \brief Creates an instance of the Memory datasource client
+///
+/// Currently the configuration passed here must be a MapElement, formed as
+/// follows:
+/// \code
+/// { "type": string ("memory"),
+///   "class": string ("IN"/"CH"/etc),
+///   "zones": list
+/// }
+/// Zones list is a list of maps:
+/// { "origin": string,
+///   "file": string
+/// }
+/// \endcode
+/// (i.e. the configuration that was used prior to the datasource refactor)
+///
+/// This configuration setup is currently under discussion and will change in
+/// the near future.
+///
+/// \param config The configuration for the datasource instance
+/// \param error This string will be set to an error message if an error occurs
+///              during initialization
+/// \return An instance of the memory datasource client, or NULL if there was
+///         an error
+extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
+                                            std::string& error);
+
+/// \brief Destroy the instance created by createInstance()
+extern "C" void destroyInstance(DataSourceClient* instance);
+
+
 }
 }
 #endif  // __DATA_SOURCE_MEMORY_H

+ 80 - 15
src/lib/datasrc/sqlite3_accessor.cc

@@ -22,12 +22,16 @@
 #include <datasrc/sqlite3_accessor.h>
 #include <datasrc/logger.h>
 #include <datasrc/data_source.h>
+#include <datasrc/factory.h>
 #include <util/filename.h>
 
 using namespace std;
+using namespace isc::data;
 
 #define SQLITE_SCHEMA_VERSION 1
 
+#define CONFIG_ITEM_DATABASE_FILE "database_file"
+
 namespace isc {
 namespace datasrc {
 
@@ -134,19 +138,6 @@ private:
 };
 
 SQLite3Accessor::SQLite3Accessor(const std::string& filename,
-                                 const isc::dns::RRClass& rrclass) :
-    dbparameters_(new SQLite3Parameters),
-    filename_(filename),
-    class_(rrclass.toText()),
-    database_name_("sqlite3_" +
-                   isc::util::Filename(filename).nameAndExtension())
-{
-    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_SQLITE_NEWCONN);
-
-    open(filename);
-}
-
-SQLite3Accessor::SQLite3Accessor(const std::string& filename,
                                  const string& rrclass) :
     dbparameters_(new SQLite3Parameters),
     filename_(filename),
@@ -637,8 +628,12 @@ doUpdate(SQLite3Parameters& dbparams, StatementID stmt_id,
     const size_t column_count =
         sizeof(update_params) / sizeof(update_params[0]);
     for (int i = 0; i < column_count; ++i) {
-        if (sqlite3_bind_text(stmt, ++param_id, update_params[i].c_str(), -1,
-                              SQLITE_TRANSIENT) != SQLITE_OK) {
+        // The old sqlite3 data source API assumes NULL for an empty column.
+        // We need to provide compatibility at least for now.
+        if (sqlite3_bind_text(stmt, ++param_id,
+                              update_params[i].empty() ? NULL :
+                              update_params[i].c_str(),
+                              -1, SQLITE_TRANSIENT) != SQLITE_OK) {
             isc_throw(DataSourceError, "failed to bind SQLite3 parameter: " <<
                       sqlite3_errmsg(dbparams.db_));
         }
@@ -711,5 +706,75 @@ SQLite3Accessor::findPreviousName(int zone_id, const std::string& rname)
     return (result);
 }
 
+namespace {
+void
+addError(ElementPtr errors, const std::string& error) {
+    if (errors != ElementPtr() && errors->getType() == Element::list) {
+        errors->add(Element::create(error));
+    }
+}
+
+bool
+checkConfig(ConstElementPtr config, ElementPtr errors) {
+    /* Specific configuration is under discussion, right now this accepts
+     * the 'old' configuration, see header file
+     */
+    bool result = true;
+
+    if (!config || config->getType() != Element::map) {
+        addError(errors, "Base config for SQlite3 backend must be a map");
+        result = false;
+    } else {
+        if (!config->contains(CONFIG_ITEM_DATABASE_FILE)) {
+            addError(errors,
+                     "Config for SQlite3 backend does not contain a '"
+                     CONFIG_ITEM_DATABASE_FILE
+                     "' value");
+            result = false;
+        } else if (!config->get(CONFIG_ITEM_DATABASE_FILE) ||
+                   config->get(CONFIG_ITEM_DATABASE_FILE)->getType() !=
+                   Element::string) {
+            addError(errors, "value of " CONFIG_ITEM_DATABASE_FILE
+                     " in SQLite3 backend is not a string");
+            result = false;
+        } else if (config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue() ==
+                   "") {
+            addError(errors, "value of " CONFIG_ITEM_DATABASE_FILE
+                     " in SQLite3 backend is empty");
+            result = false;
+        }
+    }
+
+    return (result);
+}
+
+} // end anonymous namespace
+
+DataSourceClient *
+createInstance(isc::data::ConstElementPtr config, std::string& error) {
+    ElementPtr errors(Element::createList());
+    if (!checkConfig(config, errors)) {
+        error = "Configuration error: " + errors->str();
+        return (NULL);
+    }
+    std::string dbfile = config->get(CONFIG_ITEM_DATABASE_FILE)->stringValue();
+    try {
+        boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
+            new SQLite3Accessor(dbfile, "IN")); // XXX: avoid hardcode RR class
+        return (new DatabaseClient(isc::dns::RRClass::IN(), sqlite3_accessor));
+    } catch (const std::exception& exc) {
+        error = std::string("Error creating sqlite3 datasource: ") + exc.what();
+        return (NULL);
+    } catch (...) {
+        error = std::string("Error creating sqlite3 datasource, "
+                            "unknown exception");
+        return (NULL);
+    }
 }
+
+void destroyInstance(DataSourceClient* instance) {
+    delete instance;
 }
+
+} // end of namespace datasrc
+} // end of namespace isc

+ 25 - 14
src/lib/datasrc/sqlite3_accessor.h

@@ -24,6 +24,8 @@
 #include <boost/scoped_ptr.hpp>
 #include <string>
 
+#include <cc/data.h>
+
 namespace isc {
 namespace dns {
 class RRClass;
@@ -65,20 +67,10 @@ public:
      * doesn't work (it is broken, doesn't exist and can't be created, etc).
      *
      * \param filename The database file to be used.
-     * \param rrclass Which class of data it should serve (while the database
-     *     file can contain multiple classes of data, single database can
-     *     provide only one class).
-     */
-    SQLite3Accessor(const std::string& filename,
-                    const isc::dns::RRClass& rrclass);
-
-    /**
-     * \brief Constructor
-     *
-     * Same as the other version, but takes rrclass as a bare string.
-     * we should obsolete the other version and unify the constructor to
-     * this version; the SQLite3Accessor is expected to be "dumb" and
-     * shouldn't care about DNS specific information such as RRClass.
+     * \param rrclass Textual representation of RR class ("IN", "CH", etc),
+     *     specifying which class of data it should serve (while the database
+     *     file can contain multiple classes of data, a single accessor can
+     *     work with only one class).
      */
     SQLite3Accessor(const std::string& filename, const std::string& rrclass);
 
@@ -191,6 +183,25 @@ private:
     const std::string database_name_;
 };
 
+/// \brief Creates an instance of the SQlite3 datasource client
+///
+/// Currently the configuration passed here must be a MapElement, containing
+/// one item called "database_file", whose value is a string
+///
+/// This configuration setup is currently under discussion and will change in
+/// the near future.
+///
+/// \param config The configuration for the datasource instance
+/// \param error This string will be set to an error message if an error occurs
+///              during initialization
+/// \return An instance of the sqlite3 datasource client, or NULL if there was
+///         an error
+extern "C" DataSourceClient* createInstance(isc::data::ConstElementPtr config,
+                                            std::string& error);
+
+/// \brief Destroy the instance created by createInstance()
+extern "C" void destroyInstance(DataSourceClient* instance);
+
 }
 }
 

+ 15 - 4
src/lib/datasrc/tests/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . testdata
+SUBDIRS = testdata
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/lib/dns
@@ -29,12 +29,23 @@ run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += cache_unittest.cc
 run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
 run_unittests_SOURCES += rbtree_unittest.cc
-run_unittests_SOURCES += zonetable_unittest.cc
-run_unittests_SOURCES += memory_datasrc_unittest.cc
+#run_unittests_SOURCES += zonetable_unittest.cc
+#run_unittests_SOURCES += memory_datasrc_unittest.cc
 run_unittests_SOURCES += logger_unittest.cc
 run_unittests_SOURCES += database_unittest.cc
 run_unittests_SOURCES += client_unittest.cc
 run_unittests_SOURCES += sqlite3_accessor_unittest.cc
+if !USE_STATIC_LINK
+# This test uses dynamically loadable module.  It will cause various
+# troubles with static link such as "missing" symbols in the static object
+# for the module.  As a workaround we disable this particualr test
+# in this case.
+run_unittests_SOURCES += factory_unittest.cc
+endif
+# for the dlopened types we have tests for, we also need to include the
+# sources
+run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/sqlite3_accessor.cc
+#run_unittests_SOURCES += $(top_srcdir)/src/lib/datasrc/memory_datasrc.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
@@ -51,7 +62,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)
 
 EXTRA_DIST =  testdata/brokendb.sqlite3
 EXTRA_DIST += testdata/example.com.signed

+ 1 - 2
src/lib/datasrc/tests/database_unittest.cc

@@ -830,8 +830,7 @@ public:
 class TestSQLite3Accessor : public SQLite3Accessor {
 public:
     TestSQLite3Accessor() : SQLite3Accessor(
-        TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied",
-        RRClass::IN())
+        TEST_DATA_BUILDDIR "/rwtest.sqlite3.copied", "IN")
     {
         startUpdateZone("example.org.", true);
         string columns[ADD_COLUMN_COUNT];

+ 175 - 0
src/lib/datasrc/tests/factory_unittest.cc

@@ -0,0 +1,175 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <boost/scoped_ptr.hpp>
+
+#include <datasrc/factory.h>
+#include <datasrc/data_source.h>
+#include <datasrc/sqlite3_accessor.h>
+
+#include <dns/rrclass.h>
+#include <cc/data.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::datasrc;
+using namespace isc::data;
+
+std::string SQLITE_DBFILE_EXAMPLE_ORG = TEST_DATA_DIR "/example.org.sqlite3";
+
+namespace {
+
+TEST(FactoryTest, sqlite3ClientBadConfig) {
+    // We start out by building the configuration data bit by bit,
+    // testing each form of 'bad config', until we have a good one.
+    // Then we do some very basic operation on the client (detailed
+    // tests are left to the implementation-specific backends)
+    ElementPtr config;
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config = Element::create("asdf");
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config = Element::createMap();
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config->set("class", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config->set("class", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config->set("class", Element::create("FOO"));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config->set("class", Element::create("IN"));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config->set("database_file", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config->set("database_file", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config->set("database_file", Element::create("/foo/bar/doesnotexist"));
+    ASSERT_THROW(DataSourceClientContainer("sqlite3", config),
+                 DataSourceError);
+
+    config->set("database_file", Element::create(SQLITE_DBFILE_EXAMPLE_ORG));
+    DataSourceClientContainer dsc("sqlite3", config);
+
+    DataSourceClient::FindResult result1(
+        dsc.getInstance().findZone(isc::dns::Name("example.org.")));
+    ASSERT_EQ(result::SUCCESS, result1.code);
+
+    DataSourceClient::FindResult result2(
+        dsc.getInstance().findZone(isc::dns::Name("no.such.zone.")));
+    ASSERT_EQ(result::NOTFOUND, result2.code);
+
+    ZoneIteratorPtr iterator(dsc.getInstance().getIterator(
+        isc::dns::Name("example.org.")));
+
+    ZoneUpdaterPtr updater(dsc.getInstance().getUpdater(
+        isc::dns::Name("example.org."), false));
+}
+
+TEST(FactoryTest, memoryClient) {
+    // We start out by building the configuration data bit by bit,
+    // testing each form of 'bad config', until we have a good one.
+    // Then we do some very basic operation on the client (detailed
+    // tests are left to the implementation-specific backends)
+    ElementPtr config;
+    ASSERT_THROW(DataSourceClientContainer client("memory", config),
+                 DataSourceError);
+
+    config = Element::create("asdf");
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config = Element::createMap();
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("type", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("type", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("type", Element::create("FOO"));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("type", Element::create("memory"));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("class", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("class", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("class", Element::create("FOO"));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("class", Element::create("IN"));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("zones", ElementPtr());
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("zones", Element::create(1));
+    ASSERT_THROW(DataSourceClientContainer("memory", config),
+                 DataSourceError);
+
+    config->set("zones", Element::createList());
+    DataSourceClientContainer dsc("memory", config);
+
+    // Once it is able to load some zones, we should add a few tests
+    // here to see that it does.
+    DataSourceClient::FindResult result(
+        dsc.getInstance().findZone(isc::dns::Name("no.such.zone.")));
+    ASSERT_EQ(result::NOTFOUND, result.code);
+
+    ASSERT_THROW(dsc.getInstance().getIterator(isc::dns::Name("example.org.")),
+                 DataSourceError);
+
+    ASSERT_THROW(dsc.getInstance().getUpdater(isc::dns::Name("no.such.zone."),
+                                              false), isc::NotImplemented);
+}
+
+TEST(FactoryTest, badType) {
+    ASSERT_THROW(DataSourceClientContainer("foo", ElementPtr()),
+                                           DataSourceError);
+}
+
+} // end anonymous namespace
+

+ 19 - 21
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc

@@ -57,36 +57,34 @@ const char* SQLITE_NEW_DBFILE = TEST_DATA_BUILDDIR "/newdb.sqlite3";
 
 // Opening works (the content is tested in different tests)
 TEST(SQLite3Open, common) {
-    EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE,
-                                             RRClass::IN()));
+    EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE, "IN"));
 }
 
 // The file can't be opened
 TEST(SQLite3Open, notExist) {
-    EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST,
-                                          RRClass::IN()), SQLite3Error);
+    EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_NOTEXIST, "IN"),
+                 SQLite3Error);
 }
 
 // It rejects broken DB
 TEST(SQLite3Open, brokenDB) {
-    EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB,
-                                          RRClass::IN()), SQLite3Error);
+    EXPECT_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_BROKENDB, "IN"),
+                 SQLite3Error);
 }
 
 // Test we can create the schema on the fly
 TEST(SQLite3Open, memoryDB) {
-    EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY,
-                                             RRClass::IN()));
+    EXPECT_NO_THROW(SQLite3Accessor accessor(SQLITE_DBFILE_MEMORY, "IN"));
 }
 
 // Test fixture for querying the db
 class SQLite3AccessorTest : public ::testing::Test {
 public:
     SQLite3AccessorTest() {
-        initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::IN());
+        initAccessor(SQLITE_DBFILE_EXAMPLE, "IN");
     }
     // So it can be re-created with different data
-    void initAccessor(const std::string& filename, const RRClass& rrclass) {
+    void initAccessor(const std::string& filename, const string& rrclass) {
         accessor.reset(new SQLite3Accessor(filename, rrclass));
     }
     // The tested accessor
@@ -112,14 +110,14 @@ TEST_F(SQLite3AccessorTest, noZone) {
 
 // This zone is there, but in different class
 TEST_F(SQLite3AccessorTest, noClass) {
-    initAccessor(SQLITE_DBFILE_EXAMPLE, RRClass::CH());
+    initAccessor(SQLITE_DBFILE_EXAMPLE, "CH");
     EXPECT_FALSE(accessor->getZone("example.com.").first);
 }
 
 // This tests the iterator context
 TEST_F(SQLite3AccessorTest, iterator) {
     // Our test zone is conveniently small, but not empty
-    initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, RRClass::IN());
+    initAccessor(SQLITE_DBFILE_EXAMPLE_ORG, "IN");
 
     const std::pair<bool, int> zone_info(accessor->getZone("example.org."));
     ASSERT_TRUE(zone_info.first);
@@ -207,12 +205,12 @@ TEST_F(SQLite3AccessorTest, iterator) {
 }
 
 TEST(SQLite3Open, getDBNameExample2) {
-    SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, RRClass::IN());
+    SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE2, "IN");
     EXPECT_EQ(SQLITE_DBNAME_EXAMPLE2, accessor.getDBName());
 }
 
 TEST(SQLite3Open, getDBNameExampleROOT) {
-    SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, RRClass::IN());
+    SQLite3Accessor accessor(SQLITE_DBFILE_EXAMPLE_ROOT, "IN");
     EXPECT_EQ(SQLITE_DBNAME_EXAMPLE_ROOT, accessor.getDBName());
 }
 
@@ -410,7 +408,7 @@ bool isReadable(const char* filename) {
 TEST_F(SQLite3Create, creationtest) {
     ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
     // Should simply be created
-    SQLite3Accessor accessor(SQLITE_NEW_DBFILE, RRClass::IN());
+    SQLite3Accessor accessor(SQLITE_NEW_DBFILE, "IN");
     ASSERT_TRUE(isReadable(SQLITE_NEW_DBFILE));
 }
 
@@ -422,12 +420,12 @@ TEST_F(SQLite3Create, emptytest) {
     ASSERT_EQ(SQLITE_OK, sqlite3_open(SQLITE_NEW_DBFILE, &db));
 
     // empty, but not locked, so creating it now should work
-    SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN());
+    SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, "IN");
 
     sqlite3_close(db);
 
     // should work now that we closed it
-    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, "IN");
 }
 
 TEST_F(SQLite3Create, lockedtest) {
@@ -439,13 +437,13 @@ TEST_F(SQLite3Create, lockedtest) {
     sqlite3_exec(db, "BEGIN EXCLUSIVE TRANSACTION", NULL, NULL, NULL);
 
     // should not be able to open it
-    EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, RRClass::IN()),
+    EXPECT_THROW(SQLite3Accessor accessor2(SQLITE_NEW_DBFILE, "IN"),
                  SQLite3Error);
 
     sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL);
 
     // should work now that we closed it
-    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, RRClass::IN());
+    SQLite3Accessor accessor3(SQLITE_NEW_DBFILE, "IN");
 }
 
 TEST_F(SQLite3AccessorTest, clone) {
@@ -508,11 +506,11 @@ protected:
             isc_throw(isc::Exception,
                       "Error setting up; command failed: " << install_cmd);
         };
-        initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", RRClass::IN());
+        initAccessor(TEST_DATA_BUILDDIR "/test.sqlite3.copied", "IN");
         zone_id = accessor->getZone("example.com.").second;
         another_accessor.reset(new SQLite3Accessor(
                                    TEST_DATA_BUILDDIR "/test.sqlite3.copied",
-                                   RRClass::IN()));
+                                   "IN"));
         expected_stored.push_back(common_expected_data);
     }
 

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

@@ -24,6 +24,9 @@ EXTRA_DIST += rdata/generic/cname_5.h
 EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
 EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
 EXTRA_DIST += rdata/generic/detail/txt_like.h
+EXTRA_DIST += rdata/generic/detail/ds_like.h
+EXTRA_DIST += rdata/generic/dlv_32769.cc
+EXTRA_DIST += rdata/generic/dlv_32769.h
 EXTRA_DIST += rdata/generic/dname_39.cc
 EXTRA_DIST += rdata/generic/dname_39.h
 EXTRA_DIST += rdata/generic/dnskey_48.cc
@@ -107,6 +110,7 @@ libdns___la_SOURCES += character_string.h character_string.cc
 libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
 libdns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
 libdns___la_SOURCES += rdata/generic/detail/txt_like.h
+libdns___la_SOURCES += rdata/generic/detail/ds_like.h
 
 libdns___la_CPPFLAGS = $(AM_CPPFLAGS)
 # Most applications of libdns++ will only implicitly rely on libcryptolink,

+ 4 - 4
src/lib/dns/python/message_python.cc

@@ -78,7 +78,7 @@ PyObject* Message_makeResponse(s_Message* self);
 PyObject* Message_toText(s_Message* self);
 PyObject* Message_str(PyObject* self);
 PyObject* Message_toWire(s_Message* self, PyObject* args);
-PyObject* Message_fromWire(PyObject* const pyself, PyObject* args);
+PyObject* Message_fromWire(PyObject* pyself, PyObject* args);
 
 // This list contains the actual set of functions we have in
 // python. Each entry has
@@ -160,7 +160,7 @@ PyMethodDef Message_methods[] = {
       "If the given message is not in RENDER mode, an "
       "InvalidMessageOperation is raised.\n"
        },
-    { "from_wire", reinterpret_cast<PyCFunction>(Message_fromWire), METH_VARARGS, Message_fromWire_doc },
+    { "from_wire", Message_fromWire, METH_VARARGS, Message_fromWire_doc },
     { NULL, NULL, 0, NULL }
 };
 
@@ -642,8 +642,8 @@ Message_toWire(s_Message* self, PyObject* args) {
 }
 
 PyObject*
-Message_fromWire(PyObject* const pyself, PyObject* args) {
-    s_Message* self = static_cast<s_Message*>(pyself);
+Message_fromWire(PyObject* pyself, PyObject* args) {
+    s_Message* const self = static_cast<s_Message*>(pyself);
     const char* b;
     Py_ssize_t len;
     unsigned int options = Message::PARSE_DEFAULT;

+ 225 - 0
src/lib/dns/rdata/generic/detail/ds_like.h

@@ -0,0 +1,225 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __DS_LIKE_H
+#define __DS_LIKE_H 1
+
+#include <stdint.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief \c rdata::DSLikeImpl class represents the DS-like RDATA for DS
+/// and DLV types.
+///
+/// This class implements the basic interfaces inherited by the DS and DLV
+/// classes from the abstract \c rdata::Rdata class, and provides trivial
+/// accessors to DS-like RDATA.
+template <class Type, uint16_t typeCode> class DSLikeImpl {
+    // Common sequence of toWire() operations used for the two versions of
+    // toWire().
+    template <typename Output>
+    void
+    toWireCommon(Output& output) const {
+        output.writeUint16(tag_);
+        output.writeUint8(algorithm_);
+        output.writeUint8(digest_type_);
+        output.writeData(&digest_[0], digest_.size());
+    }
+
+public:
+    /// \brief Constructor from string.
+    ///
+    /// <b>Exceptions</b>
+    ///
+    /// \c InvalidRdataText is thrown if the method cannot process the
+    /// parameter data for any of the number of reasons.
+    DSLikeImpl(const std::string& ds_str) {
+        std::istringstream iss(ds_str);
+        // peekc should be of iss's char_type for isspace to work
+        std::istringstream::char_type peekc;
+        std::stringbuf digestbuf;
+        uint32_t tag, algorithm, digest_type;
+
+        iss >> tag >> algorithm >> digest_type;
+        if (iss.bad() || iss.fail()) {
+            isc_throw(InvalidRdataText,
+                      "Invalid " << RRType(typeCode) << " text");
+        }
+        if (tag > 0xffff) {
+            isc_throw(InvalidRdataText,
+                      RRType(typeCode) << " tag out of range");
+        }
+        if (algorithm > 0xff) {
+            isc_throw(InvalidRdataText,
+                      RRType(typeCode) << " algorithm out of range");
+        }
+        if (digest_type > 0xff) {
+            isc_throw(InvalidRdataText,
+                      RRType(typeCode) << " digest type out of range");
+        }
+
+        iss.read(&peekc, 1);
+        if (!iss.good() || !isspace(peekc, iss.getloc())) {
+            isc_throw(InvalidRdataText,
+                      RRType(typeCode) << " presentation format error");
+        }
+
+        iss >> &digestbuf;
+
+        tag_ = tag;
+        algorithm_ = algorithm;
+        digest_type_ = digest_type;
+        decodeHex(digestbuf.str(), digest_);
+    }
+
+    /// \brief Constructor from wire-format data.
+    ///
+    /// \param buffer A buffer storing the wire format data.
+    /// \param rdata_len The length of the RDATA in bytes, normally expected
+    /// to be the value of the RDLENGTH field of the corresponding RR.
+    ///
+    /// <b>Exceptions</b>
+    ///
+    /// \c InvalidRdataLength is thrown if the input data is too short for the
+    /// type.
+    DSLikeImpl(InputBuffer& buffer, size_t rdata_len) {
+        if (rdata_len < 4) {
+            isc_throw(InvalidRdataLength, RRType(typeCode) << " too short");
+        }
+
+        tag_ = buffer.readUint16();
+        algorithm_ = buffer.readUint8();
+        digest_type_ = buffer.readUint8();
+
+        rdata_len -= 4;
+        digest_.resize(rdata_len);
+        buffer.readData(&digest_[0], rdata_len);
+    }
+
+    /// \brief The copy constructor.
+    ///
+    /// Trivial for now, we could've used the default one.
+    DSLikeImpl(const DSLikeImpl& source) {
+        digest_ = source.digest_;
+        tag_ = source.tag_;
+        algorithm_ = source.algorithm_;
+        digest_type_ = source.digest_type_;
+    }
+
+    /// \brief Convert the DS-like data to a string.
+    ///
+    /// \return A \c string object that represents the DS-like data.
+    std::string
+    toText() const {
+        using namespace boost;
+        return (lexical_cast<string>(static_cast<int>(tag_)) +
+            " " + lexical_cast<string>(static_cast<int>(algorithm_)) +
+            " " + lexical_cast<string>(static_cast<int>(digest_type_)) +
+            " " + encodeHex(digest_));
+    }
+
+    /// \brief Render the DS-like data in the wire format to an OutputBuffer
+    /// object.
+    ///
+    /// \param buffer An output buffer to store the wire data.
+    void
+    toWire(OutputBuffer& buffer) const {
+        toWireCommon(buffer);
+    }
+
+    /// \brief Render the DS-like data in the wire format to an
+    /// AbstractMessageRenderer object.
+    ///
+    /// \param renderer A renderer object to send the wire data to.
+    void
+    toWire(AbstractMessageRenderer& renderer) const {
+        toWireCommon(renderer);
+    }
+
+    /// \brief Compare two instances of DS-like RDATA.
+    ///
+    /// It is up to the caller to make sure that \c other is an object of the
+    /// same \c DSLikeImpl class.
+    ///
+    /// \param other the right-hand operand to compare against.
+    /// \return < 0 if \c this would be sorted before \c other.
+    /// \return 0 if \c this is identical to \c other in terms of sorting
+    /// order.
+    /// \return > 0 if \c this would be sorted after \c other.
+    int
+    compare(const DSLikeImpl& other_ds) const {
+        if (tag_ != other_ds.tag_) {
+            return (tag_ < other_ds.tag_ ? -1 : 1);
+        }
+        if (algorithm_ != other_ds.algorithm_) {
+            return (algorithm_ < other_ds.algorithm_ ? -1 : 1);
+        }
+        if (digest_type_ != other_ds.digest_type_) {
+            return (digest_type_ < other_ds.digest_type_ ? -1 : 1);
+        }
+
+        size_t this_len = digest_.size();
+        size_t other_len = other_ds.digest_.size();
+        size_t cmplen = min(this_len, other_len);
+        int cmp = memcmp(&digest_[0], &other_ds.digest_[0], cmplen);
+        if (cmp != 0) {
+            return (cmp);
+        } else {
+            return ((this_len == other_len)
+                    ? 0 : (this_len < other_len) ? -1 : 1);
+        }
+    }
+
+    /// \brief Accessors
+    uint16_t
+    getTag() const {
+        return (tag_);
+    }
+
+private:
+    // straightforward representation of DS RDATA fields
+    uint16_t tag_;
+    uint8_t algorithm_;
+    uint8_t digest_type_;
+    std::vector<uint8_t> digest_;
+};
+
+}
+}
+}
+}
+}
+#endif //  __DS_LIKE_H
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 54 - 2
src/lib/dns/rdata/generic/detail/txt_like.h

@@ -23,8 +23,24 @@
 using namespace std;
 using namespace isc::util;
 
+/// \brief \c rdata::TXTLikeImpl class represents the TXT-like RDATA for TXT
+/// and SPF types.
+///
+/// This class implements the basic interfaces inherited by the TXT and SPF
+/// classes from the abstract \c rdata::Rdata class, and provides trivial
+/// accessors to TXT-like RDATA.
 template<class Type, uint16_t typeCode>class TXTLikeImpl {
 public:
+    /// \brief Constructor from wire-format data.
+    ///
+    /// \param buffer A buffer storing the wire format data.
+    /// \param rdata_len The length of the RDATA in bytes, normally expected
+    /// to be the value of the RDLENGTH field of the corresponding RR.
+    ///
+    /// <b>Exceptions</b>
+    ///
+    /// \c InvalidRdataLength is thrown if rdata_len exceeds the maximum.
+    /// \c DNSMessageFORMERR is thrown if the RR is misformed.
     TXTLikeImpl(InputBuffer& buffer, size_t rdata_len) {
         if (rdata_len > MAX_RDLENGTH) {
             isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len);
@@ -52,6 +68,14 @@ public:
         } while (rdata_len > 0);
     }
 
+    /// \brief Constructor from string.
+    ///
+    /// <b>Exceptions</b>
+    ///
+    /// \c CharStringTooLong is thrown if the parameter string length exceeds
+    /// maximum.
+    /// \c InvalidRdataText is thrown if the method cannot process the
+    /// parameter data.
     explicit TXTLikeImpl(const std::string& txtstr) {
         // TBD: this is a simple, incomplete implementation that only supports
         // a single character-string.
@@ -86,10 +110,17 @@ public:
         string_list_.push_back(data);
     }
 
+    /// \brief The copy constructor.
+    ///
+    /// Trivial for now, we could've used the default one.
     TXTLikeImpl(const TXTLikeImpl& other) :
         string_list_(other.string_list_)
     {}
 
+    /// \brief Render the TXT-like data in the wire format to an OutputBuffer
+    /// object.
+    ///
+    /// \param buffer An output buffer to store the wire data.
     void
     toWire(OutputBuffer& buffer) const {
         for (vector<vector<uint8_t> >::const_iterator it =
@@ -101,6 +132,11 @@ public:
         }
     }
 
+    /// \brief Render the TXT-like data in the wire format to an
+    /// AbstractMessageRenderer object.
+    ///
+    /// \param buffer An output AbstractMessageRenderer to send the wire data
+    /// to.
     void
     toWire(AbstractMessageRenderer& renderer) const {
         for (vector<vector<uint8_t> >::const_iterator it =
@@ -112,6 +148,9 @@ public:
         }
     }
 
+    /// \brief Convert the TXT-like data to a string.
+    ///
+    /// \return A \c string object that represents the TXT-like data.
     string
     toText() const {
         string s;
@@ -134,20 +173,33 @@ public:
         return (s);
     }
 
+    /// \brief Compare two instances of TXT-like RDATA.
+    ///
+    /// It is up to the caller to make sure that \c other is an object of the
+    /// same \c TXTLikeImpl class.
+    ///
+    /// \param other the right-hand operand to compare against.
+    /// \return < 0 if \c this would be sorted before \c other.
+    /// \return 0 if \c this is identical to \c other in terms of sorting
+    /// order.
+    /// \return > 0 if \c this would be sorted after \c other.
     int
     compare(const TXTLikeImpl& other) const {
         // This implementation is not efficient.  Revisit this (TBD).
         OutputBuffer this_buffer(0);
         toWire(this_buffer);
+        uint8_t const* const this_data = (uint8_t const*)this_buffer.getData();
         size_t this_len = this_buffer.getLength();
 
         OutputBuffer other_buffer(0);
         other.toWire(other_buffer);
+        uint8_t const* const other_data
+                                      = (uint8_t const*)other_buffer.getData();
         const size_t other_len = other_buffer.getLength();
 
         const size_t cmplen = min(this_len, other_len);
-        const int cmp = memcmp(this_buffer.getData(), other_buffer.getData(),
-                               cmplen);
+        const int cmp = memcmp(this_data, other_data, cmplen);
+
         if (cmp != 0) {
             return (cmp);
         } else {

+ 121 - 0
src/lib/dns/rdata/generic/dlv_32769.cc

@@ -0,0 +1,121 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+
+#include <util/buffer.h>
+#include <util/encode/hex.h>
+
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/rdata/generic/detail/ds_like.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(const string& ds_str) :
+    impl_(new DLVImpl(ds_str))
+{}
+
+/// \brief Constructor from wire-format data.
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(InputBuffer& buffer, size_t rdata_len) :
+    impl_(new DLVImpl(buffer, rdata_len))
+{}
+
+/// \brief Copy constructor
+///
+/// A copy of the implementation object is allocated and constructed.
+DLV::DLV(const DLV& source) :
+    Rdata(), impl_(new DLVImpl(*source.impl_))
+{}
+
+/// \brief Assignment operator
+///
+/// PIMPL-induced logic
+DLV&
+DLV::operator=(const DLV& source) {
+    if (impl_ == source.impl_) {
+        return (*this);
+    }
+
+    DLVImpl* newimpl = new DLVImpl(*source.impl_);
+    delete impl_;
+    impl_ = newimpl;
+
+    return (*this);
+}
+
+/// \brief Destructor
+///
+/// Deallocates an internal resource.
+DLV::~DLV() {
+    delete impl_;
+}
+
+/// \brief Convert the \c DLV to a string.
+///
+/// A pass-thru to the corresponding implementation method.
+string
+DLV::toText() const {
+    return (impl_->toText());
+}
+
+/// \brief Render the \c DLV in the wire format to a OutputBuffer object
+///
+/// A pass-thru to the corresponding implementation method.
+void
+DLV::toWire(OutputBuffer& buffer) const {
+    impl_->toWire(buffer);
+}
+
+/// \brief Render the \c DLV in the wire format to a AbstractMessageRenderer
+/// object
+///
+/// A pass-thru to the corresponding implementation method.
+void
+DLV::toWire(AbstractMessageRenderer& renderer) const {
+    impl_->toWire(renderer);
+}
+
+/// \brief Compare two instances of \c DLV RDATA.
+///
+/// The type check is performed here. Otherwise, a pass-thru to the
+/// corresponding implementation method.
+int
+DLV::compare(const Rdata& other) const {
+    const DLV& other_ds = dynamic_cast<const DLV&>(other);
+
+    return (impl_->compare(*other_ds.impl_));
+}
+
+/// \brief Tag accessor
+uint16_t
+DLV::getTag() const {
+    return (impl_->getTag());
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE

+ 77 - 0
src/lib/dns/rdata/generic/dlv_32769.h

@@ -0,0 +1,77 @@
+// Copyright (C) 2011  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.
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+namespace detail {
+template <class Type, uint16_t typeCode> class DSLikeImpl;
+}
+
+/// \brief \c rdata::generic::DLV class represents the DLV RDATA as defined in
+/// RFC4431.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DLV RDATA.
+class DLV : public Rdata {
+public:
+    // BEGIN_COMMON_MEMBERS
+    // END_COMMON_MEMBERS
+
+    /// \brief Assignment operator.
+    ///
+    /// It internally allocates a resource, and if it fails a corresponding
+    /// standard exception will be thrown.
+    /// This operator never throws an exception otherwise.
+    ///
+    /// This operator provides the strong exception guarantee: When an
+    /// exception is thrown the content of the assignment target will be
+    /// intact.
+    DLV& operator=(const DLV& source);
+
+    /// \brief The destructor.
+    ~DLV();
+
+    /// \brief Return the value of the Tag field.
+    ///
+    /// This method never throws an exception.
+    uint16_t getTag() const;
+private:
+    typedef detail::DSLikeImpl<DLV, 32769> DLVImpl;
+    DLVImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables: 
+// mode: c++
+// End: 

+ 13 - 96
src/lib/dns/rdata/generic/ds_43.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011  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
@@ -12,87 +12,32 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <iostream>
 #include <string>
-#include <sstream>
-#include <vector>
-
-#include <boost/lexical_cast.hpp>
 
 #include <util/buffer.h>
 #include <util/encode/hex.h>
 
 #include <dns/messagerenderer.h>
-#include <dns/name.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 
-#include <stdio.h>
-#include <time.h>
+#include <dns/rdata/generic/detail/ds_like.h>
 
 using namespace std;
 using namespace isc::util;
 using namespace isc::util::encode;
+using namespace isc::dns::rdata::generic::detail;
 
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
-struct DSImpl {
-    // straightforward representation of DS RDATA fields
-    DSImpl(uint16_t tag, uint8_t algorithm, uint8_t digest_type,
-           const vector<uint8_t>& digest) :
-        tag_(tag), algorithm_(algorithm), digest_type_(digest_type),
-        digest_(digest)
-    {}
-
-    uint16_t tag_;
-    uint8_t algorithm_;
-    uint8_t digest_type_;
-    const vector<uint8_t> digest_;
-};
-
 DS::DS(const string& ds_str) :
-    impl_(NULL)
-{
-    istringstream iss(ds_str);
-    unsigned int tag, algorithm, digest_type;
-    stringbuf digestbuf;
-
-    iss >> tag >> algorithm >> digest_type >> &digestbuf;
-    if (iss.bad() || iss.fail()) {
-        isc_throw(InvalidRdataText, "Invalid DS text");
-    }
-    if (tag > 0xffff) {
-        isc_throw(InvalidRdataText, "DS tag out of range");
-    }
-    if (algorithm > 0xff) {
-        isc_throw(InvalidRdataText, "DS algorithm out of range");
-    }
-    if (digest_type > 0xff) {
-        isc_throw(InvalidRdataText, "DS digest type out of range");
-    }
-
-    vector<uint8_t> digest;
-    decodeHex(digestbuf.str(), digest);
-
-    impl_ = new DSImpl(tag, algorithm, digest_type, digest);
-}
-
-DS::DS(InputBuffer& buffer, size_t rdata_len) {
-    if (rdata_len < 4) {
-        isc_throw(InvalidRdataLength, "DS too short");
-    }
-
-    uint16_t tag = buffer.readUint16();
-    uint16_t algorithm = buffer.readUint8();
-    uint16_t digest_type = buffer.readUint8();
-
-    rdata_len -= 4;
-    vector<uint8_t> digest(rdata_len);
-    buffer.readData(&digest[0], rdata_len);
+    impl_(new DSImpl(ds_str))
+{}
 
-    impl_ = new DSImpl(tag, algorithm, digest_type, digest);
-}
+DS::DS(InputBuffer& buffer, size_t rdata_len) :
+    impl_(new DSImpl(buffer, rdata_len))
+{}
 
 DS::DS(const DS& source) :
     Rdata(), impl_(new DSImpl(*source.impl_))
@@ -117,57 +62,29 @@ DS::~DS() {
 
 string
 DS::toText() const {
-    using namespace boost;
-    return (lexical_cast<string>(static_cast<int>(impl_->tag_)) +
-        " " + lexical_cast<string>(static_cast<int>(impl_->algorithm_)) +
-        " " + lexical_cast<string>(static_cast<int>(impl_->digest_type_)) +
-        " " + encodeHex(impl_->digest_));
+    return (impl_->toText());
 }
 
 void
 DS::toWire(OutputBuffer& buffer) const {
-    buffer.writeUint16(impl_->tag_);
-    buffer.writeUint8(impl_->algorithm_);
-    buffer.writeUint8(impl_->digest_type_);
-    buffer.writeData(&impl_->digest_[0], impl_->digest_.size());
+    impl_->toWire(buffer);
 }
 
 void
 DS::toWire(AbstractMessageRenderer& renderer) const {
-    renderer.writeUint16(impl_->tag_);
-    renderer.writeUint8(impl_->algorithm_);
-    renderer.writeUint8(impl_->digest_type_);
-    renderer.writeData(&impl_->digest_[0], impl_->digest_.size());
+    impl_->toWire(renderer);
 }
 
 int
 DS::compare(const Rdata& other) const {
     const DS& other_ds = dynamic_cast<const DS&>(other);
 
-    if (impl_->tag_ != other_ds.impl_->tag_) {
-        return (impl_->tag_ < other_ds.impl_->tag_ ? -1 : 1);
-    }
-    if (impl_->algorithm_ != other_ds.impl_->algorithm_) {
-        return (impl_->algorithm_ < other_ds.impl_->algorithm_ ? -1 : 1);
-    }
-    if (impl_->digest_type_ != other_ds.impl_->digest_type_) {
-        return (impl_->digest_type_ < other_ds.impl_->digest_type_ ? -1 : 1);
-    }
-
-    size_t this_len = impl_->digest_.size();
-    size_t other_len = other_ds.impl_->digest_.size();
-    size_t cmplen = min(this_len, other_len);
-    int cmp = memcmp(&impl_->digest_[0], &other_ds.impl_->digest_[0], cmplen);
-    if (cmp != 0) {
-        return (cmp);
-    } else {
-        return ((this_len == other_len) ? 0 : (this_len < other_len) ? -1 : 1);
-    }
+    return (impl_->compare(*other_ds.impl_));
 }
 
 uint16_t
 DS::getTag() const {
-    return (impl_->tag_);
+    return (impl_->getTag());
 }
 
 // END_RDATA_NAMESPACE

+ 27 - 6
src/lib/dns/rdata/generic/ds_43.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011  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
@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+// BEGIN_HEADER_GUARD
+
 #include <stdint.h>
 
 #include <string>
@@ -21,8 +23,6 @@
 #include <dns/rrttl.h>
 #include <dns/rdata.h>
 
-// BEGIN_HEADER_GUARD
-
 // BEGIN_ISC_NAMESPACE
 
 // BEGIN_COMMON_DECLARATIONS
@@ -30,20 +30,41 @@
 
 // BEGIN_RDATA_NAMESPACE
 
-struct DSImpl;
+namespace detail {
+template <class Type, uint16_t typeCode> class DSLikeImpl;
+}
 
+/// \brief \c rdata::generic::DS class represents the DS RDATA as defined in
+/// RFC3658.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// DS RDATA.
 class DS : public Rdata {
 public:
     // BEGIN_COMMON_MEMBERS
     // END_COMMON_MEMBERS
+
+    /// \brief Assignment operator.
+    ///
+    /// It internally allocates a resource, and if it fails a corresponding
+    /// standard exception will be thrown.
+    /// This operator never throws an exception otherwise.
+    ///
+    /// This operator provides the strong exception guarantee: When an
+    /// exception is thrown the content of the assignment target will be
+    /// intact.
     DS& operator=(const DS& source);
+
+    /// \brief The destructor.
     ~DS();
 
+    /// \brief Return the value of the Tag field.
     ///
-    /// Specialized methods
-    ///
+    /// This method never throws an exception.
     uint16_t getTag() const;
 private:
+    typedef detail::DSLikeImpl<DS, 43> DSImpl;
     DSImpl* impl_;
 };
 

+ 44 - 0
src/lib/dns/rdata/generic/spf_99.cc

@@ -30,8 +30,17 @@ using namespace isc::util;
 // BEGIN_ISC_NAMESPACE
 // BEGIN_RDATA_NAMESPACE
 
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+
 #include <dns/rdata/generic/detail/txt_like.h>
 
+/// \brief The assignment operator
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
 SPF&
 SPF::operator=(const SPF& source) {
     if (impl_ == source.impl_) {
@@ -45,37 +54,72 @@ SPF::operator=(const SPF& source) {
     return (*this);
 }
 
+/// \brief The destructor
 SPF::~SPF() {
     delete impl_;
 }
 
+/// \brief Constructor from wire-format data.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
 SPF::SPF(InputBuffer& buffer, size_t rdata_len) :
     impl_(new SPFImpl(buffer, rdata_len))
 {}
 
+/// \brief Constructor from string.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
 SPF::SPF(const std::string& txtstr) :
     impl_(new SPFImpl(txtstr))
 {}
 
+/// \brief Copy constructor
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
 SPF::SPF(const SPF& other) :
     Rdata(), impl_(new SPFImpl(*other.impl_))
 {}
 
+/// \brief Render the \c SPF in the wire format to a OutputBuffer object
+///
+/// \return is the return of the corresponding implementation method.
 void
 SPF::toWire(OutputBuffer& buffer) const {
     impl_->toWire(buffer);
 }
 
+/// \brief Render the \c SPF in the wire format to an AbstractMessageRenderer
+/// object
+///
+/// \return is the return of the corresponding implementation method.
 void
 SPF::toWire(AbstractMessageRenderer& renderer) const {
     impl_->toWire(renderer);
 }
 
+/// \brief Convert the \c SPF to a string.
+///
+/// \return is the return of the corresponding implementation method.
 string
 SPF::toText() const {
     return (impl_->toText());
 }
 
+/// \brief Compare two instances of \c SPF RDATA.
+///
+/// This method compares \c this and the \c other \c SPF objects.
+///
+/// This method is expected to be used in a polymorphic way, and the
+/// parameter to compare against is therefore of the abstract \c Rdata class.
+/// However, comparing two \c Rdata objects of different RR types
+/// is meaningless, and \c other must point to a \c SPF object;
+/// otherwise, the standard \c bad_cast exception will be thrown.
+///
+/// \param other the right-hand operand to compare against.
+/// \return is the return of the corresponding implementation method.
 int
 SPF::compare(const Rdata& other) const {
     const SPF& other_txt = dynamic_cast<const SPF&>(other);

+ 26 - 0
src/lib/dns/rdata/generic/spf_99.h

@@ -30,14 +30,40 @@
 
 template<class Type, uint16_t typeCode> class TXTLikeImpl;
 
+/// \brief \c rdata::SPF class represents the SPF RDATA as defined %in
+/// RFC4408.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
 class SPF : public Rdata {
 public:
     // BEGIN_COMMON_MEMBERS
     // END_COMMON_MEMBERS
 
+    /// \brief Assignment operator.
+    ///
+    /// It internally allocates a resource, and if it fails a corresponding
+    /// standard exception will be thrown.
+    /// This operator never throws an exception otherwise.
+    ///
+    /// This operator provides the strong exception guarantee: When an
+    /// exception is thrown the content of the assignment target will be
+    /// intact.
     SPF& operator=(const SPF& source);
+
+    /// \brief The destructor.
     ~SPF();
 
+    ///
+    /// Specialized methods
+    ///
+
+    /// \brief Return a reference to the data strings
+    ///
+    /// This method never throws an exception.
+    const std::vector<std::vector<uint8_t> >& getString() const;
+
 private:
     typedef TXTLikeImpl<SPF, 99> SPFImpl;
     SPFImpl* impl_;

+ 4 - 4
src/lib/dns/rdata/in_1/dhcid_49.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011  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
@@ -20,7 +20,7 @@
 #include <exceptions/exceptions.h>
 
 #include <util/buffer.h>
-#include <util/encode/hex.h>
+#include <util/encode/base64.h>
 #include <dns/exceptions.h>
 #include <dns/messagerenderer.h>
 #include <dns/rdata.h>
@@ -52,7 +52,7 @@ DHCID::DHCID(const string& dhcid_str) {
     stringbuf digestbuf;
 
     iss >> &digestbuf;
-    isc::util::encode::decodeHex(digestbuf.str(), digest_);
+    isc::util::encode::decodeBase64(digestbuf.str(), digest_);
 
     // RFC4701 states DNS software should consider the RDATA section to
     // be opaque, but there must be at least three bytes in the data:
@@ -112,7 +112,7 @@ DHCID::toWire(AbstractMessageRenderer& renderer) const {
 /// \return A string representation of \c DHCID.
 string
 DHCID::toText() const {
-    return (isc::util::encode::encodeHex(digest_));
+    return (isc::util::encode::encodeBase64(digest_));
 }
 
 /// \brief Compare two instances of \c DHCID RDATA.

+ 1 - 1
src/lib/dns/rdata/in_1/dhcid_49.h

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

+ 5 - 3
src/lib/dns/tests/Makefile.am

@@ -29,13 +29,15 @@ run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
 run_unittests_SOURCES += rdatafields_unittest.cc
 run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
 run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
-run_unittests_SOURCES += rdata_txt_unittest.cc rdata_mx_unittest.cc
+run_unittests_SOURCES += rdata_txt_like_unittest.cc
+run_unittests_SOURCES += rdata_mx_unittest.cc
 run_unittests_SOURCES += rdata_ptr_unittest.cc rdata_cname_unittest.cc
 run_unittests_SOURCES += rdata_dname_unittest.cc
 run_unittests_SOURCES += rdata_afsdb_unittest.cc
 run_unittests_SOURCES += rdata_opt_unittest.cc
+run_unittests_SOURCES += rdata_dhcid_unittest.cc
 run_unittests_SOURCES += rdata_dnskey_unittest.cc
-run_unittests_SOURCES += rdata_ds_unittest.cc
+run_unittests_SOURCES += rdata_ds_like_unittest.cc
 run_unittests_SOURCES += rdata_nsec_unittest.cc
 run_unittests_SOURCES += rdata_nsec3_unittest.cc
 run_unittests_SOURCES += rdata_nsecbitmap_unittest.cc
@@ -71,4 +73,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

+ 111 - 0
src/lib/dns/tests/rdata_dhcid_unittest.cc

@@ -0,0 +1,111 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/buffer.h>
+#include <dns/rdataclass.h>
+#include <util/encode/base64.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::dns::rdata;
+
+namespace {
+
+const string string_dhcid(
+                   "0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=");
+
+const in::DHCID rdata_dhcid(string_dhcid);
+
+class Rdata_DHCID_Test : public RdataTest {
+};
+
+TEST_F(Rdata_DHCID_Test, createFromString) {
+    const in::DHCID rdata_dhcid2(string_dhcid);
+    EXPECT_EQ(0, rdata_dhcid2.compare(rdata_dhcid));
+}
+
+TEST_F(Rdata_DHCID_Test, badBase64) {
+    EXPECT_THROW(const in::DHCID rdata_dhcid_bad("00"), isc::BadValue);
+}
+
+TEST_F(Rdata_DHCID_Test, badLength) {
+    EXPECT_THROW(const in::DHCID rdata_dhcid_bad("MDA="), InvalidRdataLength);
+}
+
+TEST_F(Rdata_DHCID_Test, copy) {
+    const in::DHCID rdata_dhcid2(rdata_dhcid);
+    EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid2));
+}
+
+TEST_F(Rdata_DHCID_Test, createFromWire) {
+    EXPECT_EQ(0, rdata_dhcid.compare(
+                  *rdataFactoryFromFile(RRType("DHCID"), RRClass("IN"),
+                                        "rdata_dhcid_fromWire")));
+    // TBD: more tests
+}
+
+TEST_F(Rdata_DHCID_Test, toWireRenderer) {
+    rdata_dhcid.toWire(renderer);
+
+    vector<unsigned char> data;
+    UnitTestUtil::readWireData("rdata_dhcid_toWire", data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+                        renderer.getLength(), &data[0], data.size());
+}
+
+TEST_F(Rdata_DHCID_Test, toWireBuffer) {
+    rdata_dhcid.toWire(obuffer);
+
+    vector<unsigned char> data;
+    UnitTestUtil::readWireData("rdata_dhcid_toWire", data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
+                        obuffer.getLength(), &data[0], data.size());
+}
+
+TEST_F(Rdata_DHCID_Test, toText) {
+    EXPECT_EQ(string_dhcid, rdata_dhcid.toText());
+}
+
+TEST_F(Rdata_DHCID_Test, getDHCIDDigest) {
+    const string string_dhcid1(encodeBase64(rdata_dhcid.getDigest()));
+
+    EXPECT_EQ(string_dhcid, string_dhcid1);
+}
+
+TEST_F(Rdata_DHCID_Test, compare) {
+    // trivial case: self equivalence
+    EXPECT_EQ(0, rdata_dhcid.compare(rdata_dhcid));
+
+    in::DHCID rdata_dhcid1("0YLQvtC/0L7Qu9GPINC00LLQsCDRgNGD0LHQu9GP");
+    in::DHCID rdata_dhcid2("0YLQvtC/0L7Qu9GPINGC0YDQuCDRgNGD0LHQu9GP");
+    in::DHCID rdata_dhcid3("0YLQvtC/0L7Qu9GPINGH0LXRgtGL0YDQtSDRgNGD0LHQu9GP");
+
+    EXPECT_LT(rdata_dhcid1.compare(rdata_dhcid2), 0);
+    EXPECT_GT(rdata_dhcid2.compare(rdata_dhcid1), 0);
+
+    EXPECT_LT(rdata_dhcid2.compare(rdata_dhcid3), 0);
+    EXPECT_GT(rdata_dhcid3.compare(rdata_dhcid2), 0);
+
+    // comparison attempt between incompatible RR types should be rejected
+    EXPECT_THROW(rdata_dhcid.compare(*rdata_nomatch), bad_cast); 
+}
+}

+ 171 - 0
src/lib/dns/tests/rdata_ds_like_unittest.cc

@@ -0,0 +1,171 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <algorithm>
+#include <string>
+
+#include <util/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+namespace {
+// hacks to make templates work
+template <class T>
+class RRTYPE : public RRType {
+public:
+    RRTYPE();
+};
+
+template<> RRTYPE<generic::DS>::RRTYPE() : RRType(RRType::DS()) {}
+template<> RRTYPE<generic::DLV>::RRTYPE() : RRType(RRType::DLV()) {}
+
+template <class DS_LIKE>
+class Rdata_DS_LIKE_Test : public RdataTest {
+protected:
+    static DS_LIKE const rdata_ds_like;
+};
+
+string ds_like_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                   "5F0EB5C777586DE18DA6B5");
+
+template <class DS_LIKE>
+DS_LIKE const Rdata_DS_LIKE_Test<DS_LIKE>::rdata_ds_like(ds_like_txt);
+
+// The list of types we want to test.
+typedef testing::Types<generic::DS, generic::DLV> Implementations;
+
+TYPED_TEST_CASE(Rdata_DS_LIKE_Test, Implementations);
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toText_DS_LIKE) {
+    EXPECT_EQ(ds_like_txt, this->rdata_ds_like.toText());
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, badText_DS_LIKE) {
+    EXPECT_THROW(const TypeParam ds_like2("99999 5 2 BEEF"), InvalidRdataText);
+    EXPECT_THROW(const TypeParam ds_like2("11111 555 2 BEEF"),
+                 InvalidRdataText);
+    EXPECT_THROW(const TypeParam ds_like2("11111 5 22222 BEEF"),
+                 InvalidRdataText);
+    EXPECT_THROW(const TypeParam ds_like2("11111 5 2"), InvalidRdataText);
+    EXPECT_THROW(const TypeParam ds_like2("GARBAGE IN"), InvalidRdataText);
+    // no space between the digest type and the digest.
+    EXPECT_THROW(const TypeParam ds_like2(
+                     "12892 5 2F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                     "5F0EB5C777586DE18DA6B5"), InvalidRdataText);
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, createFromWire_DS_LIKE) {
+    EXPECT_EQ(0, this->rdata_ds_like.compare(
+              *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass::IN(),
+                                          "rdata_ds_fromWire")));
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, assignment_DS_LIKE) {
+    TypeParam copy((string(ds_like_txt)));
+    copy = this->rdata_ds_like;
+    EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
+
+    // Check if the copied data is valid even after the original is deleted
+    TypeParam* copy2 = new TypeParam(this->rdata_ds_like);
+    TypeParam copy3((string(ds_like_txt)));
+    copy3 = *copy2;
+    delete copy2;
+    EXPECT_EQ(0, copy3.compare(this->rdata_ds_like));
+
+    // Self assignment
+    copy = copy;
+    EXPECT_EQ(0, copy.compare(this->rdata_ds_like));
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, getTag_DS_LIKE) {
+    EXPECT_EQ(12892, this->rdata_ds_like.getTag());
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toWireRenderer) {
+    Rdata_DS_LIKE_Test<TypeParam>::renderer.skip(2);
+    TypeParam rdata_ds_like(ds_like_txt);
+    rdata_ds_like.toWire(this->renderer);
+
+    vector<unsigned char> data;
+    UnitTestUtil::readWireData("rdata_ds_fromWire", data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        static_cast<const uint8_t*>
+                        (this->obuffer.getData()) + 2,
+                        this->obuffer.getLength() - 2,
+                        &data[2], data.size() - 2);
+}
+
+TYPED_TEST(Rdata_DS_LIKE_Test, toWireBuffer) {
+    TypeParam rdata_ds_like(ds_like_txt);
+    rdata_ds_like.toWire(this->obuffer);
+}
+
+string ds_like_txt1("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                   "5F0EB5C777586DE18DA6B5");
+// different tag
+string ds_like_txt2("12893 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                   "5F0EB5C777586DE18DA6B5");
+// different algorithm
+string ds_like_txt3("12892 6 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                   "5F0EB5C777586DE18DA6B5");
+// different digest type
+string ds_like_txt4("12892 5 3 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                   "5F0EB5C777586DE18DA6B5");
+// different digest
+string ds_like_txt5("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                   "5F0EB5C777586DE18DA6B5");
+// different digest length
+string ds_like_txt6("12892 5 2 F2E184C0E1D615D20EB3C223ACED3B03C773DD952D"
+                   "5F0EB5C777586DE18DA6B555");
+
+TYPED_TEST(Rdata_DS_LIKE_Test, compare) {
+    // trivial case: self equivalence
+    EXPECT_EQ(0, TypeParam(ds_like_txt).compare(TypeParam(ds_like_txt)));
+
+    // non-equivalence tests
+    EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt2)), 0);
+    EXPECT_GT(TypeParam(ds_like_txt2).compare(TypeParam(ds_like_txt1)), 0);
+
+    EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt3)), 0);
+    EXPECT_GT(TypeParam(ds_like_txt3).compare(TypeParam(ds_like_txt1)), 0);
+
+    EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt4)), 0);
+    EXPECT_GT(TypeParam(ds_like_txt4).compare(TypeParam(ds_like_txt1)), 0);
+
+    EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt5)), 0);
+    EXPECT_GT(TypeParam(ds_like_txt5).compare(TypeParam(ds_like_txt1)), 0);
+
+    EXPECT_LT(TypeParam(ds_like_txt1).compare(TypeParam(ds_like_txt6)), 0);
+    EXPECT_GT(TypeParam(ds_like_txt6).compare(TypeParam(ds_like_txt1)), 0);
+
+    // comparison attempt between incompatible RR types should be rejected
+    EXPECT_THROW(this->rdata_ds_like.compare(*this->rdata_nomatch),
+                 bad_cast);
+}
+
+}

+ 0 - 99
src/lib/dns/tests/rdata_ds_unittest.cc

@@ -1,99 +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.
-
-#include <string>
-
-#include <util/buffer.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-#include <dns/rrclass.h>
-#include <dns/rrtype.h>
-
-#include <gtest/gtest.h>
-
-#include <dns/tests/unittest_util.h>
-#include <dns/tests/rdata_unittest.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace isc::dns;
-using namespace isc::util;
-using namespace isc::dns::rdata;
-
-namespace {
-class Rdata_DS_Test : public RdataTest {
-    // there's nothing to specialize
-};
-
-string ds_txt("12892 5 2 F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
-              "5F0EB5C777586DE18DA6B5");
-const generic::DS rdata_ds(ds_txt);
-
-TEST_F(Rdata_DS_Test, toText_DS) {
-    EXPECT_EQ(ds_txt, rdata_ds.toText());
-}
-
-TEST_F(Rdata_DS_Test, badText_DS) {
-    EXPECT_THROW(const generic::DS ds2("99999 5 2 BEEF"), InvalidRdataText);
-    EXPECT_THROW(const generic::DS ds2("11111 555 2 BEEF"), InvalidRdataText);
-    EXPECT_THROW(const generic::DS ds2("11111 5 22222 BEEF"), InvalidRdataText);
-    EXPECT_THROW(const generic::DS ds2("11111 5 2"), InvalidRdataText);
-    EXPECT_THROW(const generic::DS ds2("GARBAGE IN"), InvalidRdataText);
-}
-
-// this test currently fails; we must fix it, and then migrate the test to
-// badText_DS
-TEST_F(Rdata_DS_Test, DISABLED_badText_DS) {
-    // no space between the digest type and the digest.
-    EXPECT_THROW(const generic::DS ds2(
-                     "12892 5 2F1E184C0E1D615D20EB3C223ACED3B03C773DD952D"
-                     "5F0EB5C777586DE18DA6B5"), InvalidRdataText);
-}
-
-TEST_F(Rdata_DS_Test, createFromWire_DS) {
-    EXPECT_EQ(0, rdata_ds.compare(
-                  *rdataFactoryFromFile(RRType::DS(), RRClass::IN(),
-                                        "rdata_ds_fromWire")));
-}
-
-TEST_F(Rdata_DS_Test, getTag_DS) {
-    EXPECT_EQ(12892, rdata_ds.getTag());
-}
-
-TEST_F(Rdata_DS_Test, toWireRenderer) {
-    renderer.skip(2);
-    generic::DS rdata_ds(ds_txt);
-    rdata_ds.toWire(renderer);
-
-    vector<unsigned char> data;
-    UnitTestUtil::readWireData("rdata_ds_fromWire", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(obuffer.getData()) + 2,
-                        obuffer.getLength() - 2, &data[2], data.size() - 2);
-}
-
-TEST_F(Rdata_DS_Test, toWireBuffer) {
-    generic::DS rdata_ds(ds_txt);
-    rdata_ds.toWire(obuffer);
-}
-
-TEST_F(Rdata_DS_Test, compare) {
-    // trivial case: self equivalence
-    EXPECT_EQ(0, generic::DS(ds_txt).compare(generic::DS(ds_txt)));
-
-    // TODO: need more tests
-}
-
-}

+ 261 - 0
src/lib/dns/tests/rdata_txt_like_unittest.cc

@@ -0,0 +1,261 @@
+// Copyright (C) 2011  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.
+
+// This is the common code for TXT and SPF tests.
+
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/rdataclass.h>
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::util;
+using namespace isc::dns::rdata;
+
+
+template<class T>
+class RRTYPE : public RRType {
+public:
+    RRTYPE();
+};
+
+template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {}
+template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {}
+
+namespace {
+const uint8_t wiredata_txt_like[] = {
+    sizeof("Test String") - 1,
+    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+const uint8_t wiredata_nulltxt[] = { 0 };
+vector<uint8_t> wiredata_longesttxt(256, 'a');
+
+template<class TXT_LIKE>
+class Rdata_TXT_LIKE_Test : public RdataTest {
+protected:
+    Rdata_TXT_LIKE_Test() {
+        wiredata_longesttxt[0] = 255; // adjust length
+    }
+
+    static const TXT_LIKE rdata_txt_like;
+    static const TXT_LIKE rdata_txt_like_empty;
+    static const TXT_LIKE rdata_txt_like_quoted;
+};
+
+template<class TXT_LIKE>
+const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like("Test String");
+
+template<class TXT_LIKE>
+const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_empty("");
+
+template<class TXT_LIKE>
+const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_quoted
+                                                          ("\"Test String\"");
+
+// The list of types we want to test.
+typedef testing::Types<generic::TXT, generic::SPF> Implementations;
+
+TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations);
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
+    // normal case is covered in toWireBuffer.
+
+    // surrounding double-quotes shouldn't change the result.
+    EXPECT_EQ(0, this->rdata_txt_like.compare(this->rdata_txt_like_quoted));
+
+    // Null character-string.
+    this->obuffer.clear();
+    TypeParam(string("")).toWire(this->obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(),
+                        this->obuffer.getLength(),
+                        wiredata_nulltxt, sizeof(wiredata_nulltxt));
+
+    // Longest possible character-string.
+    this->obuffer.clear();
+    TypeParam(string(255, 'a')).toWire(this->obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(),
+                        this->obuffer.getLength(),
+                        &wiredata_longesttxt[0], wiredata_longesttxt.size());
+
+    // Too long text for a valid character-string.
+    EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong);
+
+    // The escape character makes the double quote a part of character-string,
+    // so this is invalid input and should be rejected.
+    EXPECT_THROW(TypeParam("\"Test String\\\""), InvalidRdataText);
+
+    // Terminating double-quote is provided, so this is valid, but in this
+    // version of implementation we reject escaped characters.
+    EXPECT_THROW(TypeParam("\"Test String\\\"\""), InvalidRdataText);
+}
+
+void
+makeLargest(vector<uint8_t>& data) {
+    uint8_t ch = 0;
+
+    // create 255 sets of character-strings, each of which has the longest
+    // length (255bytes string + 1-byte length field)
+    for (int i = 0; i < 255; ++i, ++ch) {
+        data.push_back(255);
+        data.insert(data.end(), 255, ch);
+    }
+    // the last character-string should be 255 bytes (including the one-byte
+    // length field) in length so that the total length should be in the range
+    // of 16-bit integers.
+    data.push_back(254);
+    data.insert(data.end(), 254, ch);
+
+    assert(data.size() == 65535);
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
+    EXPECT_EQ(0, this->rdata_txt_like.compare(
+                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+                                        "rdata_txt_fromWire1")));
+
+    // Empty character string
+    EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
+                  *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+                                        "rdata_txt_fromWire2.wire")));
+
+    // Multiple character strings
+    this->obuffer.clear();
+    this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+                         "rdata_txt_fromWire3.wire")->toWire(this->obuffer);
+    // the result should be 'wiredata_txt' repeated twice
+    vector<uint8_t> expected_data(wiredata_txt_like, wiredata_txt_like +
+                                  sizeof(wiredata_txt_like));
+    expected_data.insert(expected_data.end(), wiredata_txt_like,
+                         wiredata_txt_like + sizeof(wiredata_txt_like));
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(),
+                        this->obuffer.getLength(),
+                        &expected_data[0], expected_data.size());
+
+    // Largest length of data.  There's nothing special, but should be
+    // constructed safely, and the content should be identical to the original
+    // data.
+    vector<uint8_t> largest_txt_like_data;
+    makeLargest(largest_txt_like_data);
+    InputBuffer ibuffer(&largest_txt_like_data[0],
+                        largest_txt_like_data.size());
+    TypeParam largest_txt_like(ibuffer, largest_txt_like_data.size());
+    this->obuffer.clear();
+    largest_txt_like.toWire(this->obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(),
+                        this->obuffer.getLength(),
+                        &largest_txt_like_data[0],
+                        largest_txt_like_data.size());
+
+    // rdlen parameter is out of range.  This is a rare event because we'd
+    // normally call the constructor via a polymorphic wrapper, where the
+    // length is validated.  But this should be checked explicitly.
+    InputBuffer ibuffer2(&largest_txt_like_data[0],
+                         largest_txt_like_data.size());
+    EXPECT_THROW(TypeParam(ibuffer2, 65536), InvalidRdataLength);
+
+    // RDATA is empty, which is invalid for TXT_LIKE.
+    EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+                                      "rdata_txt_fromWire4.wire"),
+                 DNSMessageFORMERR);
+
+    // character-string length is too large, which could cause overrun.
+    EXPECT_THROW(this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
+                                      "rdata_txt_fromWire5.wire"),
+                 DNSMessageFORMERR);
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
+    this->rdata_txt_like.toWire(this->obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->obuffer.getData(),
+                        this->obuffer.getLength(),
+                        wiredata_txt_like, sizeof(wiredata_txt_like));
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
+    this->rdata_txt_like.toWire(this->renderer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        this->renderer.getData(),
+                        this->renderer.getLength(),
+                        wiredata_txt_like, sizeof(wiredata_txt_like));
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
+    EXPECT_EQ("\"Test String\"", this->rdata_txt_like.toText());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
+    TypeParam rdata1("assignment1");
+    TypeParam rdata2("assignment2");
+    rdata1 = rdata2;
+    EXPECT_EQ(0, rdata2.compare(rdata1));
+
+    // Check if the copied data is valid even after the original is deleted
+    TypeParam* rdata3 = new TypeParam(rdata1);
+    TypeParam rdata4("assignment3");
+    rdata4 = *rdata3;
+    delete rdata3;
+    EXPECT_EQ(0, rdata4.compare(rdata1));
+
+    // Self assignment
+    rdata2 = rdata2;
+    EXPECT_EQ(0, rdata2.compare(rdata1));
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, compare) {
+    string const txt1("aaaaaaaa");
+    string const txt2("aaaaaaaaaa");
+    string const txt3("bbbbbbbb");
+    string const txt4(129, 'a');
+    string const txt5(128, 'b');
+
+    EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0);
+
+    EXPECT_LT(TypeParam("").compare(TypeParam(txt1)), 0);
+    EXPECT_GT(TypeParam(txt1).compare(TypeParam("")), 0);
+
+    EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0);
+    EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0);
+
+    EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt3)), 0);
+    EXPECT_GT(TypeParam(txt3).compare(TypeParam(txt1)), 0);
+
+    // we're comparing the data raw, starting at the length octet, so a shorter
+    // string sorts before a longer one no matter the lexicopraphical order
+    EXPECT_LT(TypeParam(txt3).compare(TypeParam(txt2)), 0);
+    EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt3)), 0);
+
+    // to make sure the length octet compares unsigned
+    EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt4)), 0);
+    EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt1)), 0);
+
+    EXPECT_LT(TypeParam(txt5).compare(TypeParam(txt4)), 0);
+    EXPECT_GT(TypeParam(txt4).compare(TypeParam(txt5)), 0);
+
+    // comparison attempt between incompatible RR types should be rejected
+    EXPECT_THROW(TypeParam(txt1).compare(*this->rdata_nomatch),
+                 bad_cast);
+}
+
+}

+ 0 - 166
src/lib/dns/tests/rdata_txt_unittest.cc

@@ -1,166 +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.
-
-#include <util/buffer.h>
-#include <dns/exceptions.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-#include <dns/rrclass.h>
-#include <dns/rrtype.h>
-
-#include <gtest/gtest.h>
-
-#include <dns/tests/unittest_util.h>
-#include <dns/tests/rdata_unittest.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace isc::dns;
-using namespace isc::util;
-using namespace isc::dns::rdata;
-
-namespace {
-const generic::TXT rdata_txt("Test String");
-const generic::TXT rdata_txt_empty("");
-const generic::TXT rdata_txt_quoated("\"Test String\"");
-const uint8_t wiredata_txt[] = {
-    sizeof("Test String") - 1,
-    'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
-};
-const uint8_t wiredata_nulltxt[] = { 0 };
-vector<uint8_t> wiredata_longesttxt(256, 'a');
-
-class Rdata_TXT_Test : public RdataTest {
-protected:
-    Rdata_TXT_Test() {
-        wiredata_longesttxt[0] = 255; // adjust length
-    }
-};
-
-TEST_F(Rdata_TXT_Test, createFromText) {
-    // normal case is covered in toWireBuffer.
-
-    // surrounding double-quotes shouldn't change the result.
-    EXPECT_EQ(0, rdata_txt.compare(rdata_txt_quoated));
-
-    // Null character-string.
-    obuffer.clear();
-    generic::TXT(string("")).toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_nulltxt, sizeof(wiredata_nulltxt));
-
-    // Longest possible character-string.
-    obuffer.clear();
-    generic::TXT(string(255, 'a')).toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        &wiredata_longesttxt[0], wiredata_longesttxt.size());
-
-    // Too long text for a valid character-string.
-    EXPECT_THROW(generic::TXT(string(256, 'a')), CharStringTooLong);
-
-    // The escape character makes the double quote a part of character-string,
-    // so this is invalid input and should be rejected.
-    EXPECT_THROW(generic::TXT("\"Test String\\\""), InvalidRdataText);
-
-    // Terminating double-quote is provided, so this is valid, but in this
-    // version of implementation we reject escaped characters.
-    EXPECT_THROW(generic::TXT("\"Test String\\\"\""), InvalidRdataText);
-}
-
-void
-makeLargest(vector<uint8_t>& data) {
-    uint8_t ch = 0;
-
-    // create 255 sets of character-strings, each of which has the longest
-    // length (255bytes string + 1-byte length field)
-    for (int i = 0; i < 255; ++i, ++ch) {
-        data.push_back(255);
-        data.insert(data.end(), 255, ch);
-    }
-    // the last character-string should be 255 bytes (including the one-byte
-    // length field) in length so that the total length should be in the range
-    // of 16-bit integers.
-    data.push_back(254);
-    data.insert(data.end(), 254, ch);
-
-    assert(data.size() == 65535);
-}
-
-TEST_F(Rdata_TXT_Test, createFromWire) {
-    EXPECT_EQ(0, rdata_txt.compare(
-                  *rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
-                                        "rdata_txt_fromWire1")));
-
-    // Empty character string
-    EXPECT_EQ(0, rdata_txt_empty.compare(
-                  *rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
-                                        "rdata_txt_fromWire2.wire")));
-
-    // Multiple character strings
-    obuffer.clear();
-    rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
-                         "rdata_txt_fromWire3.wire")->toWire(obuffer);
-    // the result should be 'wiredata_txt' repeated twice
-    vector<uint8_t> expected_data(wiredata_txt, wiredata_txt +
-                                  sizeof(wiredata_txt));
-    expected_data.insert(expected_data.end(), wiredata_txt,
-                         wiredata_txt + sizeof(wiredata_txt));
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        &expected_data[0], expected_data.size());
-
-    // Largest length of data.  There's nothing special, but should be
-    // constructed safely, and the content should be identical to the original
-    // data.
-    vector<uint8_t> largest_txt_data;
-    makeLargest(largest_txt_data);
-    InputBuffer ibuffer(&largest_txt_data[0], largest_txt_data.size());
-    generic::TXT largest_txt(ibuffer, largest_txt_data.size());
-    obuffer.clear();
-    largest_txt.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        &largest_txt_data[0], largest_txt_data.size());
-
-    // rdlen parameter is out of range.  This is a rare event because we'd
-    // normally call the constructor via a polymorphic wrapper, where the
-    // length is validated.  But this should be checked explicitly.
-    InputBuffer ibuffer2(&largest_txt_data[0], largest_txt_data.size());
-    EXPECT_THROW(generic::TXT(ibuffer2, 65536), InvalidRdataLength);
-
-    // RDATA is empty, which is invalid for TXT.
-    EXPECT_THROW(rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
-                                      "rdata_txt_fromWire4.wire"),
-                 DNSMessageFORMERR);
-
-    // character-string length is too large, which could cause overrun.
-    EXPECT_THROW(rdataFactoryFromFile(RRType("TXT"), RRClass("IN"),
-                                      "rdata_txt_fromWire5.wire"),
-                 DNSMessageFORMERR);
-}
-
-TEST_F(Rdata_TXT_Test, toWireBuffer) {
-    rdata_txt.toWire(obuffer);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        obuffer.getData(), obuffer.getLength(),
-                        wiredata_txt, sizeof(wiredata_txt));
-}
-
-TEST_F(Rdata_TXT_Test, toText) {
-    EXPECT_EQ("\"Test String\"", rdata_txt.toText());
-}
-}

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

@@ -90,6 +90,7 @@ EXTRA_DIST += question_fromWire question_toWire1 question_toWire2
 EXTRA_DIST += rdatafields1.spec rdatafields2.spec rdatafields3.spec
 EXTRA_DIST += rdatafields4.spec rdatafields5.spec rdatafields6.spec
 EXTRA_DIST += rdata_cname_fromWire rdata_dname_fromWire rdata_dnskey_fromWire
+EXTRA_DIST += rdata_dhcid_fromWire rdata_dhcid_toWire
 EXTRA_DIST += rdata_ds_fromWire rdata_in_a_fromWire rdata_in_aaaa_fromWire
 EXTRA_DIST += rdata_mx_fromWire rdata_mx_toWire1 rdata_mx_toWire2
 EXTRA_DIST += rdata_ns_fromWire

+ 12 - 0
src/lib/dns/tests/testdata/rdata_dhcid_fromWire

@@ -0,0 +1,12 @@
+#
+# DHCID RDATA stored in an input buffer
+#
+# Valid RDATA for 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+#
+# RDLENGHT=41 bytes
+# 0  1
+ 00 29
+# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+d0 b2 20 d0 bb d0 b5 d1 81 d1 83 20 d1 80 d0 be
+d0 b4 d0 b8 d0 bb d0 b0 d1 81 d1 8c 20 d1 91 d0
+bb d0 be d1 87 d0 ba d0 b0

+ 7 - 0
src/lib/dns/tests/testdata/rdata_dhcid_toWire

@@ -0,0 +1,7 @@
+#
+# DHCID RDATA stored in an output buffer
+#
+# 0LIg0LvQtdGB0YMg0YDQvtC00LjQu9Cw0YHRjCDRkdC70L7Rh9C60LA=
+d0 b2 20 d0 bb d0 b5 d1 81 d1 83 20 d1 80 d0 be
+d0 b4 d0 b8 d0 bb d0 b0 d1 81 d1 8c 20 d1 91 d0
+bb d0 be d1 87 d0 ba d0 b0

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

@@ -20,4 +20,4 @@ run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

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

@@ -61,7 +61,7 @@ init_logger_test_LDADD  = $(top_builddir)/src/lib/log/liblog.la
 init_logger_test_LDADD += $(top_builddir)/src/lib/util/libutil.la
 init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS += $(TESTS)
 
 # Additional test using the shell.  These are principally tests
 # where the global logging environment is affected, and where the

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

@@ -60,4 +60,4 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 endif
 
-noinst_PROGRAMS = $(TESTS)
+check_PROGRAMS = $(TESTS)

+ 1 - 1
src/lib/python/isc/Makefile.am

@@ -1,5 +1,5 @@
 SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
-SUBDIRS += log_messages
+SUBDIRS += xfrin log_messages
 
 python_PYTHON = __init__.py
 

+ 2 - 2
src/lib/python/isc/config/ccsession.py

@@ -510,10 +510,10 @@ class UIModuleCCSession(MultiConfigData):
 
     def _remove_value_from_list(self, identifier, value):
         if value is None:
-            # we are directly removing an list index
+            # we are directly removing a list index
             id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
             if list_indices is None:
-                raise DataTypeError("identifier in remove_value() does not contain a list index, and no value to remove")
+                raise isc.cc.data.DataTypeError("identifier in remove_value() does not contain a list index, and no value to remove")
             else:
                 self.set_value(identifier, None)
         else:

+ 3 - 0
src/lib/python/isc/config/tests/ccsession_test.py

@@ -747,6 +747,9 @@ class TestUIModuleCCSession(unittest.TestCase):
         self.assertEqual({'Spec2': {'item5': []}}, uccs._local_changes)
         uccs.add_value("Spec2/item5", None);
         self.assertEqual({'Spec2': {'item5': ['']}}, uccs._local_changes)
+        # Intending to empty a list element, but forget specifying the index.
+        self.assertRaises(isc.cc.data.DataTypeError,
+                          uccs.remove_value, "Spec2/item5", None)
 
     def test_add_remove_value_named_set(self):
         fake_conn = fakeUIConn()

+ 0 - 1
src/lib/python/isc/datasrc/Makefile.am

@@ -24,7 +24,6 @@ datasrc_la_LDFLAGS += -module
 datasrc_la_LIBADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
 datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/python/libpydnspp.la
 datasrc_la_LIBADD += $(PYTHON_LIB)
-#datasrc_la_LIBADD += $(SQLITE_LIBS)
 
 EXTRA_DIST = client_inc.cc
 EXTRA_DIST += finder_inc.cc

+ 14 - 0
src/lib/python/isc/datasrc/__init__.py

@@ -1,6 +1,16 @@
 import sys
 import os
 
+# The datasource factory loader uses dlopen, as does python
+# for its modules. Some dynamic linkers do not play nice if 
+# modules are not loaded with RTLD_GLOBAL, a symptom of which
+# is that exceptions are not recognized by type. So to make
+# sure this doesn't happen, we temporarily set RTLD_GLOBAL
+# during the loading of the datasource wrappers.
+import ctypes
+flags = sys.getdlopenflags()
+sys.setdlopenflags(flags | ctypes.RTLD_GLOBAL)
+
 # this setup is a temporary workaround to deal with the problem of
 # having both 'normal' python modules and a wrapper module
 # Once all programs use the new interface, we should remove the
@@ -16,6 +26,10 @@ if intree:
     from datasrc import *
 else:
     from isc.datasrc.datasrc import *
+
+# revert to the default dlopen flags
+sys.setdlopenflags(flags)
+
 from isc.datasrc.sqlite3_ds import *
 from isc.datasrc.master import *
 

+ 15 - 2
src/lib/python/isc/datasrc/client_inc.cc

@@ -7,7 +7,20 @@ This is the python wrapper for the abstract base class that defines\n\
 the common interface for various types of data source clients. A data\n\
 source client is a top level access point to a data source, allowing \n\
 various operations on the data source such as lookups, traversing or \n\
-updates. The client class itself has limited focus and delegates \n\
+updates.\n\
+This class serves as both the factory and the main interface to those \n\
+classes.\n\
+\n\
+The constructor takes two arguments; a type (string), and\n\
+configuration data for a datasource client of that type. The configuration\n\
+data is currently passed as a JSON in string form, and its contents depend\n\
+on the type of datasource from the first argument. For instance, a\n\
+datasource of type \"sqlite3\" takes the config \n\
+{ \"database_file\": \"/var/example.org\" }\n\
+We may in the future add support for passing configuration data,\n\
+but right now we limit it to a JSON-formatted string\n\
+\n\
+The client class itself has limited focus and delegates \n\
 the responsibility for these specific operations to other (c++) classes;\n\
 in general methods of this class act as factories of these other classes.\n\
 \n\
@@ -110,7 +123,7 @@ Return an updater to make updates to a specific zone.\n\
 The RR class of the zone is the one that the client is expected to\n\
 handle (see the detailed description of this class).\n\
 \n\
-If the specified zone is not found via the client, a NULL pointer will\n\
+If the specified zone is not found via the client, a None object will\n\
 be returned; in other words a completely new zone cannot be created\n\
 using an updater. It must be created beforehand (even if it's an empty\n\
 placeholder) in a way specific to the underlying data source.\n\

+ 35 - 22
src/lib/python/isc/datasrc/client_python.cc

@@ -23,6 +23,7 @@
 #include <util/python/pycppwrapper_util.h>
 
 #include <datasrc/client.h>
+#include <datasrc/factory.h>
 #include <datasrc/database.h>
 #include <datasrc/data_source.h>
 #include <datasrc/sqlite3_accessor.h>
@@ -50,13 +51,9 @@ namespace {
 class s_DataSourceClient : public PyObject {
 public:
     s_DataSourceClient() : cppobj(NULL) {};
-    DataSourceClient* cppobj;
+    DataSourceClientContainer* cppobj;
 };
 
-// Shortcut type which would be convenient for adding class variables safely.
-typedef CPPPyObjectContainer<s_DataSourceClient, DataSourceClient>
-    DataSourceClientContainer;
-
 PyObject*
 DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
     s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
@@ -64,12 +61,12 @@ DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
     if (PyArg_ParseTuple(args, "O!", &name_type, &name)) {
         try {
             DataSourceClient::FindResult find_result(
-                self->cppobj->findZone(PyName_ToName(name)));
+                self->cppobj->getInstance().findZone(PyName_ToName(name)));
 
             result::Result r = find_result.code;
             ZoneFinderPtr zfp = find_result.zone_finder;
             // Use N instead of O so refcount isn't increased twice
-            return (Py_BuildValue("IN", r, createZoneFinderObject(zfp)));
+            return (Py_BuildValue("IN", r, createZoneFinderObject(zfp, po_self)));
         } catch (const std::exception& exc) {
             PyErr_SetString(getDataSourceException("Error"), exc.what());
             return (NULL);
@@ -90,7 +87,8 @@ DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
     if (PyArg_ParseTuple(args, "O!", &name_type, &name_obj)) {
         try {
             return (createZoneIteratorObject(
-                        self->cppobj->getIterator(PyName_ToName(name_obj))));
+                self->cppobj->getInstance().getIterator(PyName_ToName(name_obj)),
+                po_self));
         } catch (const isc::NotImplemented& ne) {
             PyErr_SetString(getDataSourceException("NotImplemented"),
                             ne.what());
@@ -120,9 +118,13 @@ DataSourceClient_getUpdater(PyObject* po_self, PyObject* args) {
         PyBool_Check(replace_obj)) {
         bool replace = (replace_obj != Py_False);
         try {
-            return (createZoneUpdaterObject(
-                        self->cppobj->getUpdater(PyName_ToName(name_obj),
-                                                 replace)));
+            ZoneUpdaterPtr updater =
+                self->cppobj->getInstance().getUpdater(PyName_ToName(name_obj),
+                                                       replace);
+            if (!updater) {
+                return (Py_None);
+            }
+            return (createZoneUpdaterObject(updater, po_self));
         } catch (const isc::NotImplemented& ne) {
             PyErr_SetString(getDataSourceException("NotImplemented"),
                             ne.what());
@@ -162,22 +164,33 @@ PyMethodDef DataSourceClient_methods[] = {
 
 int
 DataSourceClient_init(s_DataSourceClient* self, PyObject* args) {
-    // TODO: we should use the factory function which hasn't been written
-    // yet. For now we hardcode the sqlite3 initialization, and pass it one
-    // string for the database file. (similar to how the 'old direct'
-    // sqlite3_ds code works)
+    char* ds_type_str;
+    char* ds_config_str;
     try {
-        char* db_file_name;
-        if (PyArg_ParseTuple(args, "s", &db_file_name)) {
-            boost::shared_ptr<DatabaseAccessor> sqlite3_accessor(
-                new SQLite3Accessor(db_file_name, isc::dns::RRClass::IN()));
-            self->cppobj = new DatabaseClient(isc::dns::RRClass::IN(),
-                                              sqlite3_accessor);
+        // Turn the given argument into config Element; then simply call
+        // factory class to do its magic
+
+        // for now, ds_config must be JSON string
+        if (PyArg_ParseTuple(args, "ss", &ds_type_str, &ds_config_str)) {
+            isc::data::ConstElementPtr ds_config =
+                isc::data::Element::fromJSON(ds_config_str);
+            self->cppobj = new DataSourceClientContainer(ds_type_str,
+                                                         ds_config);
             return (0);
         } else {
             return (-1);
         }
-
+    } catch (const isc::data::JSONError& je) {
+        const string ex_what = "JSON parse error in data source configuration "
+                               "data for type " +
+                               string(ds_type_str) + ":" + je.what();
+        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+        return (-1);
+    } catch (const DataSourceError& dse) {
+        const string ex_what = "Failed to create DataSourceClient of type " +
+                               string(ds_type_str) + ":" + dse.what();
+        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
+        return (-1);
     } catch (const exception& ex) {
         const string ex_what = "Failed to construct DataSourceClient object: " +
             string(ex.what());

+ 55 - 28
src/lib/python/isc/datasrc/datasrc.cc

@@ -77,14 +77,26 @@ initModulePart_DataSourceClient(PyObject* mod) {
     }
     Py_INCREF(&datasourceclient_type);
 
-    addClassVariable(datasourceclient_type, "SUCCESS",
-                     Py_BuildValue("I", result::SUCCESS));
-    addClassVariable(datasourceclient_type, "EXIST",
-                     Py_BuildValue("I", result::EXIST));
-    addClassVariable(datasourceclient_type, "NOTFOUND",
-                     Py_BuildValue("I", result::NOTFOUND));
-    addClassVariable(datasourceclient_type, "PARTIALMATCH",
-                     Py_BuildValue("I", result::PARTIALMATCH));
+    try {
+        installClassVariable(datasourceclient_type, "SUCCESS",
+                             Py_BuildValue("I", result::SUCCESS));
+        installClassVariable(datasourceclient_type, "EXIST",
+                             Py_BuildValue("I", result::EXIST));
+        installClassVariable(datasourceclient_type, "NOTFOUND",
+                             Py_BuildValue("I", result::NOTFOUND));
+        installClassVariable(datasourceclient_type, "PARTIALMATCH",
+                             Py_BuildValue("I", result::PARTIALMATCH));
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in DataSourceClient initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+            "Unexpected failure in DataSourceClient initialization");
+        return (false);
+    }
 
     return (true);
 }
@@ -103,26 +115,41 @@ initModulePart_ZoneFinder(PyObject* mod) {
     }
     Py_INCREF(&zonefinder_type);
 
-    addClassVariable(zonefinder_type, "SUCCESS",
-                     Py_BuildValue("I", ZoneFinder::SUCCESS));
-    addClassVariable(zonefinder_type, "DELEGATION",
-                     Py_BuildValue("I", ZoneFinder::DELEGATION));
-    addClassVariable(zonefinder_type, "NXDOMAIN",
-                     Py_BuildValue("I", ZoneFinder::NXDOMAIN));
-    addClassVariable(zonefinder_type, "NXRRSET",
-                     Py_BuildValue("I", ZoneFinder::NXRRSET));
-    addClassVariable(zonefinder_type, "CNAME",
-                     Py_BuildValue("I", ZoneFinder::CNAME));
-    addClassVariable(zonefinder_type, "DNAME",
-                     Py_BuildValue("I", ZoneFinder::DNAME));
-
-    addClassVariable(zonefinder_type, "FIND_DEFAULT",
-                     Py_BuildValue("I", ZoneFinder::FIND_DEFAULT));
-    addClassVariable(zonefinder_type, "FIND_GLUE_OK",
-                     Py_BuildValue("I", ZoneFinder::FIND_GLUE_OK));
-    addClassVariable(zonefinder_type, "FIND_DNSSEC",
-                     Py_BuildValue("I", ZoneFinder::FIND_DNSSEC));
-
+    try {
+        installClassVariable(zonefinder_type, "SUCCESS",
+                             Py_BuildValue("I", ZoneFinder::SUCCESS));
+        installClassVariable(zonefinder_type, "DELEGATION",
+                             Py_BuildValue("I", ZoneFinder::DELEGATION));
+        installClassVariable(zonefinder_type, "NXDOMAIN",
+                             Py_BuildValue("I", ZoneFinder::NXDOMAIN));
+        installClassVariable(zonefinder_type, "NXRRSET",
+                             Py_BuildValue("I", ZoneFinder::NXRRSET));
+        installClassVariable(zonefinder_type, "CNAME",
+                             Py_BuildValue("I", ZoneFinder::CNAME));
+        installClassVariable(zonefinder_type, "DNAME",
+                             Py_BuildValue("I", ZoneFinder::DNAME));
+        installClassVariable(zonefinder_type, "WILDCARD",
+                             Py_BuildValue("I", ZoneFinder::WILDCARD));
+        installClassVariable(zonefinder_type, "WILDCARD_NXRRSET",
+                             Py_BuildValue("I", ZoneFinder::WILDCARD_NXRRSET));
+
+        installClassVariable(zonefinder_type, "FIND_DEFAULT",
+                             Py_BuildValue("I", ZoneFinder::FIND_DEFAULT));
+        installClassVariable(zonefinder_type, "FIND_GLUE_OK",
+                             Py_BuildValue("I", ZoneFinder::FIND_GLUE_OK));
+        installClassVariable(zonefinder_type, "FIND_DNSSEC",
+                             Py_BuildValue("I", ZoneFinder::FIND_DNSSEC));
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Unexpected failure in ZoneFinder initialization: " +
+            std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in ZoneFinder initialization");
+        return (false);
+    }
 
     return (true);
 }

+ 22 - 0
src/lib/python/isc/datasrc/finder_inc.cc

@@ -62,6 +62,10 @@ Search the zone for a given pair of domain name and RR type.\n\
   and the code of SUCCESS will be returned.\n\
 - If the search name matches a delegation point of DNAME, it returns\n\
   the code of DNAME and that DNAME RR.\n\
+- If the result was synthesized by a wildcard match, it returns the\n\
+  code WILDCARD and the synthesized RRset\n\
+- If the query matched a wildcard name, but not its type, it returns the\n\
+  code WILDCARD_NXRRSET, and None\n\
 - If the target is a list, all RRsets under the domain are inserted\n\
   there and SUCCESS (or NXDOMAIN, in case of empty domain) is returned\n\
   instead of normall processing. This is intended to handle ANY query.\n\
@@ -93,4 +97,22 @@ Parameters:\n\
 Return Value(s): A tuple of a result code an a FindResult object enclosing\n\
 the search result (see above).\n\
 ";
+
+const char* const ZoneFinder_find_previous_name_doc = "\
+find_previous_name(isc.dns.Name) -> isc.dns.Name\n\
+\n\
+Gets the previous name in the DNSSEC order. This can be used\n\
+to find the correct NSEC records for proving nonexistence\n\
+of domains.\n\
+\n\
+This method does not include under-zone-cut data (glue data).\n\
+\n\
+Raises isc.datasrc.NotImplemented in case the data source backend\n\
+doesn't support DNSSEC or there is no previous in the zone (NSEC\n\
+records might be missing in the DB, the queried name is less or\n\
+equal to the apex).\n\
+\n\
+Raises isc.datasrc.Error for low-level or internal datasource errors\n\
+(like broken connection to database, wrong data living there).\n\
+";
 } // unnamed namespace

+ 46 - 8
src/lib/python/isc/datasrc/finder_python.cc

@@ -103,8 +103,14 @@ namespace {
 // The s_* Class simply covers one instantiation of the object
 class s_ZoneFinder : public PyObject {
 public:
-    s_ZoneFinder() : cppobj(ZoneFinderPtr()) {};
+    s_ZoneFinder() : cppobj(ZoneFinderPtr()), base_obj(NULL) {};
     ZoneFinderPtr cppobj;
+    // This is a reference to a base object; if the object of this class
+    // depends on another object to be in scope during its lifetime,
+    // we use INCREF the base object upon creation, and DECREF it at
+    // the end of the destructor
+    // This is an optional argument to createXXX(). If NULL, it is ignored.
+    PyObject* base_obj;
 };
 
 // Shortcut type which would be convenient for adding class variables safely.
@@ -125,6 +131,9 @@ ZoneFinder_destroy(s_ZoneFinder* const self) {
     // cppobj is a shared ptr, but to make sure things are not destroyed in
     // the wrong order, we reset it here.
     self->cppobj.reset();
+    if (self->base_obj != NULL) {
+        Py_DECREF(self->base_obj);
+    }
     Py_TYPE(self)->tp_free(self);
 }
 
@@ -160,6 +169,31 @@ ZoneFinder_find(PyObject* po_self, PyObject* args) {
     return (isc_datasrc_internal::ZoneFinder_helper(self->cppobj.get(), args));
 }
 
+PyObject*
+ZoneFinder_findPreviousName(PyObject* po_self, PyObject* args) {
+    s_ZoneFinder* const self = static_cast<s_ZoneFinder*>(po_self);
+    PyObject* name_obj;
+    if (PyArg_ParseTuple(args, "O!", &name_type, &name_obj)) {
+        try {
+            return (createNameObject(
+                self->cppobj->findPreviousName(PyName_ToName(name_obj))));
+        } catch (const isc::NotImplemented& nie) {
+            PyErr_SetString(getDataSourceException("NotImplemented"),
+                            nie.what());
+            return (NULL);
+        } catch (const std::exception& exc) {
+            PyErr_SetString(getDataSourceException("Error"), exc.what());
+            return (NULL);
+        } catch (...) {
+            PyErr_SetString(getDataSourceException("Error"),
+                            "Unexpected exception");
+            return (NULL);
+        }
+    } else {
+        return (NULL);
+    }
+}
+
 // This list contains the actual set of functions we have in
 // python. Each entry has
 // 1. Python method name
@@ -167,12 +201,12 @@ ZoneFinder_find(PyObject* po_self, PyObject* args) {
 // 3. Argument type
 // 4. Documentation
 PyMethodDef ZoneFinder_methods[] = {
-    { "get_origin", reinterpret_cast<PyCFunction>(ZoneFinder_getOrigin),
-      METH_NOARGS, ZoneFinder_getOrigin_doc },
-    { "get_class", reinterpret_cast<PyCFunction>(ZoneFinder_getClass),
-      METH_NOARGS, ZoneFinder_getClass_doc },
-    { "find", reinterpret_cast<PyCFunction>(ZoneFinder_find), METH_VARARGS,
-      ZoneFinder_find_doc },
+    { "get_origin", ZoneFinder_getOrigin, METH_NOARGS,
+       ZoneFinder_getOrigin_doc },
+    { "get_class", ZoneFinder_getClass, METH_NOARGS, ZoneFinder_getClass_doc },
+    { "find", ZoneFinder_find, METH_VARARGS, ZoneFinder_find_doc },
+    { "find_previous_name", ZoneFinder_findPreviousName, METH_VARARGS,
+      ZoneFinder_find_previous_name_doc },
     { NULL, NULL, 0, NULL }
 };
 
@@ -233,11 +267,15 @@ PyTypeObject zonefinder_type = {
 };
 
 PyObject*
-createZoneFinderObject(isc::datasrc::ZoneFinderPtr source) {
+createZoneFinderObject(isc::datasrc::ZoneFinderPtr source, PyObject* base_obj) {
     s_ZoneFinder* py_zi = static_cast<s_ZoneFinder*>(
         zonefinder_type.tp_alloc(&zonefinder_type, 0));
     if (py_zi != NULL) {
         py_zi->cppobj = source;
+        py_zi->base_obj = base_obj;
+    }
+    if (base_obj != NULL) {
+        Py_INCREF(base_obj);
     }
     return (py_zi);
 }

+ 9 - 1
src/lib/python/isc/datasrc/finder_python.h

@@ -24,7 +24,15 @@ namespace python {
 
 extern PyTypeObject zonefinder_type;
 
-PyObject* createZoneFinderObject(isc::datasrc::ZoneFinderPtr source);
+/// \brief Create a ZoneFinder python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneFinder depends on
+///                 Its refcount is increased, and will be decreased when
+///                 this zone iterator is destroyed, making sure that the
+///                 base object is never destroyed before this zonefinder.
+PyObject* createZoneFinderObject(isc::datasrc::ZoneFinderPtr source,
+                                 PyObject* base_obj = NULL);
 
 } // namespace python
 } // namespace datasrc

+ 17 - 2
src/lib/python/isc/datasrc/iterator_python.cc

@@ -45,8 +45,14 @@ namespace {
 // The s_* Class simply covers one instantiation of the object
 class s_ZoneIterator : public PyObject {
 public:
-    s_ZoneIterator() : cppobj(ZoneIteratorPtr()) {};
+    s_ZoneIterator() : cppobj(ZoneIteratorPtr()), base_obj(NULL) {};
     ZoneIteratorPtr cppobj;
+    // This is a reference to a base object; if the object of this class
+    // depends on another object to be in scope during its lifetime,
+    // we use INCREF the base object upon creation, and DECREF it at
+    // the end of the destructor
+    // This is an optional argument to createXXX(). If NULL, it is ignored.
+    PyObject* base_obj;
 };
 
 // Shortcut type which would be convenient for adding class variables safely.
@@ -68,6 +74,9 @@ ZoneIterator_destroy(s_ZoneIterator* const self) {
     // cppobj is a shared ptr, but to make sure things are not destroyed in
     // the wrong order, we reset it here.
     self->cppobj.reset();
+    if (self->base_obj != NULL) {
+        Py_DECREF(self->base_obj);
+    }
     Py_TYPE(self)->tp_free(self);
 }
 
@@ -187,11 +196,17 @@ PyTypeObject zoneiterator_type = {
 };
 
 PyObject*
-createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source) {
+createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source,
+                         PyObject* base_obj)
+{
     s_ZoneIterator* py_zi = static_cast<s_ZoneIterator*>(
         zoneiterator_type.tp_alloc(&zoneiterator_type, 0));
     if (py_zi != NULL) {
         py_zi->cppobj = source;
+        py_zi->base_obj = base_obj;
+    }
+    if (base_obj != NULL) {
+        Py_INCREF(base_obj);
     }
     return (py_zi);
 }

+ 9 - 1
src/lib/python/isc/datasrc/iterator_python.h

@@ -25,7 +25,15 @@ namespace python {
 
 extern PyTypeObject zoneiterator_type;
 
-PyObject* createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source);
+/// \brief Create a ZoneIterator python object
+///
+/// \param source The zone iterator pointer to wrap
+/// \param base_obj An optional PyObject that this ZoneIterator depends on
+///                 Its refcount is increased, and will be decreased when
+///                 this zone iterator is destroyed, making sure that the
+///                 base object is never destroyed before this zone iterator.
+PyObject* createZoneIteratorObject(isc::datasrc::ZoneIteratorPtr source,
+                                   PyObject* base_obj = NULL);
 
 
 } // namespace python

+ 6 - 1
src/lib/python/isc/datasrc/tests/Makefile.am

@@ -10,9 +10,14 @@ CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
+# We always add one, the location of the data source modules
+# We may want to add an API method for this to the ds factory, but that is out
+# of scope for this ticket
+LIBRARY_PATH_PLACEHOLDER = $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:
 if SET_ENV_LIBRARY_PATH
 LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+else
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
 endif
 
 # test using command-line arguments, so use check-local target instead of TESTS

+ 93 - 7
src/lib/python/isc/datasrc/tests/datasrc_test.py

@@ -19,14 +19,16 @@ import isc.dns
 import unittest
 import os
 import shutil
+import json
 
 TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
 TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
 
 READ_ZONE_DB_FILE = TESTDATA_PATH + "example.com.sqlite3"
-BROKEN_DB_FILE = TESTDATA_PATH + "brokendb.sqlite3"
 WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
-NEW_DB_FILE = TESTDATA_WRITE_PATH + "new_db.sqlite3"
+
+READ_ZONE_DB_CONFIG = "{ \"database_file\": \"" + READ_ZONE_DB_FILE + "\" }"
+WRITE_ZONE_DB_CONFIG = "{ \"database_file\": \"" + WRITE_ZONE_DB_FILE + "\"}"
 
 def add_rrset(rrset_list, name, rrclass, rrtype, ttl, rdatas):
     rrset_to_add = isc.dns.RRset(name, rrclass, rrtype, ttl)
@@ -59,13 +61,27 @@ def check_for_rrset(expected_rrsets, rrset):
 
 class DataSrcClient(unittest.TestCase):
 
-    def test_construct(self):
+    def test_constructors(self):
         # can't construct directly
         self.assertRaises(TypeError, isc.datasrc.ZoneIterator)
 
+        self.assertRaises(TypeError, isc.datasrc.DataSourceClient, 1, "{}")
+        self.assertRaises(TypeError, isc.datasrc.DataSourceClient, "sqlite3", 1)
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "foo", "{}")
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "sqlite3", "")
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "sqlite3", "{}")
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "sqlite3",
+                          "{ \"foo\": 1 }")
+        self.assertRaises(isc.datasrc.Error,
+                          isc.datasrc.DataSourceClient, "memory",
+                          "{ \"foo\": 1 }")
 
     def test_iterate(self):
-        dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+        dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
 
         # for RRSIGS, the TTL's are currently modified. This test should
         # start failing when we fix that.
@@ -176,7 +192,7 @@ class DataSrcClient(unittest.TestCase):
         self.assertRaises(TypeError, isc.datasrc.ZoneFinder)
 
     def test_find(self):
-        dsc = isc.datasrc.DataSourceClient(READ_ZONE_DB_FILE)
+        dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
 
         result, finder = dsc.find_zone(isc.dns.Name("example.com"))
         self.assertEqual(finder.SUCCESS, result)
@@ -231,6 +247,21 @@ class DataSrcClient(unittest.TestCase):
             "cname-ext.example.com. 3600 IN CNAME www.sql1.example.com.\n",
             rrset.to_text())
 
+        result, rrset = finder.find(isc.dns.Name("foo.wild.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.WILDCARD, result)
+        self.assertEqual("foo.wild.example.com. 3600 IN A 192.0.2.255\n",
+                         rrset.to_text())
+
+        result, rrset = finder.find(isc.dns.Name("foo.wild.example.com"),
+                                    isc.dns.RRType.TXT(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.WILDCARD_NXRRSET, result)
+        self.assertEqual(None, rrset)
+
         self.assertRaises(TypeError, finder.find,
                           "foo",
                           isc.dns.RRType.A(),
@@ -247,6 +278,24 @@ class DataSrcClient(unittest.TestCase):
                           None,
                           "foo")
 
+    def test_find_previous(self):
+        dsc = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
+
+        result, finder = dsc.find_zone(isc.dns.Name("example.com"))
+        self.assertEqual(finder.SUCCESS, result)
+
+        prev = finder.find_previous_name(isc.dns.Name("bbb.example.com"))
+        self.assertEqual("example.com.", prev.to_text())
+
+        prev = finder.find_previous_name(isc.dns.Name("zzz.example.com"))
+        self.assertEqual("www.example.com.", prev.to_text())
+
+        prev = finder.find_previous_name(prev)
+        self.assertEqual("*.wild.example.com.", prev.to_text())
+
+        self.assertRaises(isc.datasrc.NotImplemented,
+                          finder.find_previous_name,
+                          isc.dns.Name("com"))
 
 class DataSrcUpdater(unittest.TestCase):
 
@@ -260,7 +309,7 @@ class DataSrcUpdater(unittest.TestCase):
 
     def test_update_delete_commit(self):
 
-        dsc = isc.datasrc.DataSourceClient(WRITE_ZONE_DB_FILE)
+        dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
 
         # first make sure, through a separate finder, that some record exists
         result, finder = dsc.find_zone(isc.dns.Name("example.com"))
@@ -333,8 +382,40 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
 
+    def test_two_modules(self):
+        # load two modules, and check if they don't interfere
+        mem_cfg = { "type": "memory", "class": "IN", "zones": [] };
+        dsc_mem = isc.datasrc.DataSourceClient("memory", json.dumps(mem_cfg))
+        dsc_sql = isc.datasrc.DataSourceClient("sqlite3", READ_ZONE_DB_CONFIG)
+
+        # check if exceptions are working
+        self.assertRaises(isc.datasrc.Error, isc.datasrc.DataSourceClient,
+                          "memory", "{}")
+        self.assertRaises(isc.datasrc.Error, isc.datasrc.DataSourceClient,
+                          "sqlite3", "{}")
+
+        # see if a lookup succeeds in sqlite3 ds
+        result, finder = dsc_sql.find_zone(isc.dns.Name("example.com"))
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual(isc.dns.RRClass.IN(), finder.get_class())
+        self.assertEqual("example.com.", finder.get_origin().to_text())
+        result, rrset = finder.find(isc.dns.Name("www.example.com"),
+                                    isc.dns.RRType.A(),
+                                    None,
+                                    finder.FIND_DEFAULT)
+        self.assertEqual(finder.SUCCESS, result)
+        self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+                         rrset.to_text())
+
+        # see if a lookup fails in mem ds
+        result, finder = dsc_mem.find_zone(isc.dns.Name("example.com"))
+        self.assertEqual(finder.NXDOMAIN, result)
+
+
     def test_update_delete_abort(self):
-        dsc = isc.datasrc.DataSourceClient(WRITE_ZONE_DB_FILE)
+        # we don't do enything with this one, just making sure loading two
+        # datasources
+        dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
 
         # first make sure, through a separate finder, that some record exists
         result, finder = dsc.find_zone(isc.dns.Name("example.com"))
@@ -383,6 +464,11 @@ class DataSrcUpdater(unittest.TestCase):
         self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
                          rrset.to_text())
 
+    def test_update_for_no_zone(self):
+        dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+        self.assertEqual(None,
+                         dsc.get_updater(isc.dns.Name("notexistent.example"),
+                                         True))
 
 if __name__ == "__main__":
     isc.log.init("bind10")

+ 0 - 0
src/lib/python/isc/datasrc/updater_python.cc


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