Parcourir la source

sync with trunk
also fixed a bug in recursor which caused a segfault (wrong iterator use in a foreach)


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac327@3783 e5f2f494-b856-4b98-b285-d166d9295462

Jelte Jansen il y a 14 ans
Parent
commit
8e9c9912d7
98 fichiers modifiés avec 4646 ajouts et 640 suppressions
  1. 71 17
      ChangeLog
  2. 1 1
      Makefile.am
  3. 3 2
      README
  4. 5 3
      configure.ac
  5. 2 2
      src/bin/Makefile.am
  6. 1 0
      src/bin/auth/Makefile.am
  7. 2 1
      src/bin/auth/auth_srv.h
  8. 23 22
      src/lib/dns/tests/tsig_unittest.cc
  9. 119 0
      src/bin/auth/query.h
  10. 2 0
      src/bin/auth/tests/Makefile.am
  11. 70 0
      src/bin/auth/tests/query_unittest.cc
  12. 266 198
      src/bin/bind10/bind10.py.in
  13. 12 0
      src/bin/bind10/bob.spec
  14. 1 1
      src/bin/bind10/run_bind10.sh.in
  15. 200 6
      src/bin/bind10/tests/bind10_test.py
  16. 5 2
      src/bin/bindctl/bindcmd.py
  17. 6 3
      src/bin/bindctl/bindctl-source.py.in
  18. 1 1
      src/bin/host/host.cc
  19. 5 3
      src/bin/msgq/msgq.py.in
  20. 1 0
      src/bin/recurse/Makefile.am
  21. 5 6
      src/bin/recurse/recursor.cc
  22. 1 0
      src/bin/recurse/tests/Makefile.am
  23. 0 1
      src/bin/xfrin/b10-xfrin.xml
  24. 2 7
      src/bin/xfrout/b10-xfrout.8
  25. 1 8
      src/bin/xfrout/b10-xfrout.xml
  26. 0 6
      src/bin/xfrout/xfrout.spec.pre.in
  27. 2 2
      src/lib/bench/benchmark.h
  28. 0 5
      src/lib/config/config_data.cc
  29. 5 5
      src/lib/config/tests/config_data_unittests.cc
  30. 1 0
      src/lib/datasrc/Makefile.am
  31. 3 3
      src/lib/datasrc/cache.h
  32. 1 4
      src/lib/datasrc/data_source.cc
  33. 1 0
      src/lib/datasrc/static_datasrc.cc
  34. 1 0
      src/lib/datasrc/tests/Makefile.am
  35. 0 34
      src/lib/datasrc/tests/datasrc_unittest.cc
  36. 0 1
      src/lib/datasrc/tests/sqlite3_unittest.cc
  37. 1 1
      src/lib/datasrc/tests/static_unittest.cc
  38. 1 1
      src/lib/datasrc/tests/test_datasrc.cc
  39. 113 0
      src/lib/datasrc/tests/zonetable_unittest.cc
  40. 117 0
      src/lib/datasrc/zonetable.cc
  41. 383 0
      src/lib/datasrc/zonetable.h
  42. 6 2
      src/lib/dns/Makefile.am
  43. 1 0
      src/lib/dns/python/Makefile.am
  44. 9 0
      src/lib/dns/python/pydnspp.cc
  45. 1 1
      src/lib/dns/python/rrset_python.cc
  46. 1 0
      src/lib/dns/python/tests/Makefile.am
  47. 174 0
      src/lib/dns/python/tests/tsigkey_python_test.py
  48. 455 0
      src/lib/dns/python/tsigkey_python.cc
  49. 501 0
      src/lib/dns/rdata/any_255/tsig_250.cc
  50. 160 0
      src/lib/dns/rdata/any_255/tsig_250.h
  51. 1 10
      src/lib/dns/rrclass-placeholder.h
  52. 2 3
      src/lib/dns/rrset.cc
  53. 17 13
      src/lib/dns/rrset.h
  54. 2 1
      src/lib/dns/tests/Makefile.am
  55. 0 1
      src/lib/dns/tests/message_unittest.cc
  56. 368 0
      src/lib/dns/tests/rdata_tsig_unittest.cc
  57. 1 3
      src/lib/dns/tests/rrset_unittest.cc
  58. 0 1
      src/lib/dns/tests/rrsetlist_unittest.cc
  59. 16 0
      src/lib/dns/tests/testdata/Makefile.am
  60. 2 1
      src/lib/dns/tests/testdata/edns_toWire4.spec
  61. 80 16
      src/lib/dns/tests/testdata/gen-wiredata.py.in
  62. 6 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec
  63. 8 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec
  64. 8 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec
  65. 11 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec
  66. 7 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec
  67. 7 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec
  68. 8 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec
  69. 8 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec
  70. 8 0
      src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec
  71. 11 0
      src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec
  72. 13 0
      src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec
  73. 15 0
      src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec
  74. 13 0
      src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec
  75. 13 0
      src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec
  76. 230 0
      src/lib/dns/tests/tsigkey_unittest.cc
  77. 0 33
      src/lib/dns/tsig.cc
  78. 0 72
      src/lib/dns/tsig.h
  79. 167 0
      src/lib/dns/tsigkey.cc
  80. 300 0
      src/lib/dns/tsigkey.h
  81. 2 2
      src/lib/exceptions/exceptions.h
  82. 145 36
      src/lib/python/isc/cc/data.py
  83. 91 2
      src/lib/python/isc/cc/tests/data_test.py
  84. 19 10
      src/lib/python/isc/config/ccsession.py
  85. 103 60
      src/lib/python/isc/config/config_data.py
  86. 2 0
      src/lib/python/isc/config/tests/ccsession_test.py
  87. 75 25
      src/lib/python/isc/config/tests/config_data_test.py
  88. 2 0
      src/lib/python/isc/datasrc/Makefile.am
  89. 1 1
      src/lib/python/isc/datasrc/master.py
  90. 12 0
      src/lib/python/isc/datasrc/tests/Makefile.am
  91. 35 0
      src/lib/python/isc/datasrc/tests/master_test.py
  92. 1 0
      src/lib/python/isc/notify/notify_out.py
  93. 1 1
      src/lib/python/isc/notify/tests/notify_out_test.py
  94. 5 0
      src/lib/python/isc/utils/Makefile.am
  95. 0 0
      src/lib/python/isc/utils/__init__.py
  96. 37 0
      src/lib/python/isc/utils/process.py
  97. 12 0
      src/lib/python/isc/utils/tests/Makefile.am
  98. 39 0
      src/lib/python/isc/utils/tests/process_test.py

+ 71 - 17
ChangeLog

@@ -12,6 +12,62 @@
 	It has "listen_on" and "forward_addresses" options.
 	It has "listen_on" and "forward_addresses" options.
 	(Trac #389, r3448)
 	(Trac #389, r3448)
 
 
+bind10-devel-20101201 released on December 01, 2010
+
+  125.  [func]		jelte
+	Added support for addressing individual list items in bindctl
+	configuration commands; If you have an element that is a list, you
+	can use foo[X] to address a specific item, where X is an integer
+	(starting at 0)
+	(Trac #405, svn r3739)
+
+  124.  [bug]		jreed
+	Fix some wrong version reporting. Now also show the version
+	for the component and BIND 10 suite. (Trac #302, svn r3696)
+
+  123.  [bug]		jelte
+	src/bin/bindctl printed values had the form of python literals
+	(e.g. 'True'), while the input requires valid JSON (e.g. 'true').
+	Output changed to JSON format for consistency. (svn r3694)
+
+  122.  [func]		stephen
+	src/bin/bind10: Added configuration options to Boss to determine
+	whether to start the authoritative server, recursive server (or
+	both). A dummy recursor has been provided for test purposes.
+	(Trac #412, svn r3676)
+
+  121.  [func]		jinmei
+	src/lib/dns: Added support for TSIG RDATA.  At this moment this is
+	not much of real use, however, because no protocol support was
+	added yet.  It will soon be added. (Trac #372, svn r3649)
+
+  120.  [func]		jinmei
+	src/lib/dns: introduced two new classes, TSIGKey and TSIGKeyRing,
+	to manage TSIG keys. (Trac #381, svn r3622)
+
+  119.	[bug]		jinmei
+	The master file parser of the python datasrc module incorrectly
+	regarded a domain name beginning with a decimal number as a TTL
+	specification.  This confused b10-loadzone and had it reject to
+	load a zone file that contains such a name.
+	Note: this fix is incomplete and the loadzone would still be
+	confused if the owner name is a syntactically indistinguishable
+	from a TTL specification.  This is part of a more general issue
+	and will be addressed in Trac #413. (Trac #411, svn r3599)
+
+  118.	[func]		jinmei
+	src/lib/dns: changed the interface of
+	AbstractRRset::getRdataIterator() so that the internal
+	cursor would point to the first RDATA automatically.  This
+	will be a more intuitive and less error prone behavior.
+	This is a backward compatible change. (Trac #410, r3595)
+
+  117.  [func]		jinmei
+	src/lib/datasrc: added new zone and zone table classes for the
+	support of in memory data source.  This is an intermediate step to
+	the bigger feature, and is not yet actually usable in practice.
+	(Trac #399, svn r3590)
+
   116.	[bug]		jerry
   116.	[bug]		jerry
 	src/bin/xfrout: Xfrout and Auth will communicate by long tcp
 	src/bin/xfrout: Xfrout and Auth will communicate by long tcp
 	connection, Auth needs to make a new connection only on the first
 	connection, Auth needs to make a new connection only on the first
@@ -40,15 +96,13 @@
 	Add one mixin class to override the naive serve_forever() provided
 	Add one mixin class to override the naive serve_forever() provided
 	in python library socketserver. Instead of polling for shutdwon
 	in python library socketserver. Instead of polling for shutdwon
 	every poll_interval seconds, one socketpair is used to wake up
 	every poll_interval seconds, one socketpair is used to wake up
-	the waiting server.(Trac #352, svn r3366)
+	the waiting server. (Trac #352, svn r3366)
 
 
   111.	[bug]*   zhanglikun, Michal Vaner
   111.	[bug]*   zhanglikun, Michal Vaner
-	Make sure process xfrin/xfrout/zonemgr/cmdctl can be stoped
+	Make sure process xfrin/xfrout/zonemgr/cmdctl can be stopped
 	properly when user enter "ctrl+c" or 'Boss shutdown' command
 	properly when user enter "ctrl+c" or 'Boss shutdown' command
-	through	bindctl.
-
-	The ZonemgrRefresh.run_timer and NotifyOut.dispatcher spawn
-	a thread themselves.
+	through bindctl.  The ZonemgrRefresh.run_timer and
+	NotifyOut.dispatcher spawn a thread themselves.
 	(Trac #335, svn r3273)
 	(Trac #335, svn r3273)
 
 
   110.  [func]      Michal Vaner
   110.  [func]      Michal Vaner
@@ -60,7 +114,7 @@
   109.  [func]		naokikambe
   109.  [func]		naokikambe
 	Added the initial version of the stats module for the statistics
 	Added the initial version of the stats module for the statistics
 	feature of BIND 10, which supports the restricted features and
 	feature of BIND 10, which supports the restricted features and
-	items and reports via bindctl command (Trac #191, r3218)
+	items and reports via bindctl command. (Trac #191, r3218)
 	Added the document of the stats module, which is about how stats
 	Added the document of the stats module, which is about how stats
 	module collects the data (Trac #170, [wiki:StatsModule])
 	module collects the data (Trac #170, [wiki:StatsModule])
 
 
@@ -87,11 +141,11 @@
   104.	[bug]		jerry
   104.	[bug]		jerry
 	bin/zonemgr: zonemgr should be attempting to refresh expired zones.
 	bin/zonemgr: zonemgr should be attempting to refresh expired zones.
 	(Trac #336, r3139)
 	(Trac #336, r3139)
-				   
+ 
   103.	[bug]		jerry
   103.	[bug]		jerry
 	lib/python/isc/log: Fixed an issue with python logging,
 	lib/python/isc/log: Fixed an issue with python logging,
-	python log shouldn't die with OSError.(Trac #267, r3137)
-				   
+	python log shouldn't die with OSError. (Trac #267, r3137)
+ 
   102.	[build]		jinmei
   102.	[build]		jinmei
 	Disable threads in ASIO to minimize build time dependency.
 	Disable threads in ASIO to minimize build time dependency.
 	(Trac #345, r3100)
 	(Trac #345, r3100)
@@ -144,7 +198,7 @@ bind10-devel-20100917 released on September 17, 2010
   93.	[bug]		jinmei
   93.	[bug]		jinmei
 	lib/datasrc: A DS query could crash the library (and therefore,
 	lib/datasrc: A DS query could crash the library (and therefore,
 	e.g. the authoritative server) if some RR of the same apex name
 	e.g. the authoritative server) if some RR of the same apex name
-	is stored in the hot spot cache.  (Trac #307, svn r2923)
+	is stored in the hot spot cache. (Trac #307, svn r2923)
 
 
   92.	[func]*		jelte
   92.	[func]*		jelte
 	libdns_python (the python wrappers for libdns++) has been renamed
 	libdns_python (the python wrappers for libdns++) has been renamed
@@ -324,7 +378,7 @@ bind10-devel-20100701 released on July 1, 2010
   66.  [bug]		each
   66.  [bug]		each
 	Check for duplicate RRsets before inserting data into a message
 	Check for duplicate RRsets before inserting data into a message
 	section; this, among other things, will prevent multiple copies
 	section; this, among other things, will prevent multiple copies
-	of the same CNAME from showing up when there's a loop.  (Trac #69,
+	of the same CNAME from showing up when there's a loop. (Trac #69,
 	svn r2350)
 	svn r2350)
     
     
   65.  [func]		shentingting
   65.  [func]		shentingting
@@ -446,7 +500,7 @@ bind10-devel-20100602 released on June 2, 2010
 	#205, svn r1957)
 	#205, svn r1957)
 
 
   44.   [build]         jreed
   44.   [build]         jreed
-	Install headers for libdns and libexception.  (Trac #68,
+	Install headers for libdns and libexception. (Trac #68,
 	svn r1941)
 	svn r1941)
 
 
   43.   [func]          jelte
   43.   [func]          jelte
@@ -454,7 +508,7 @@ bind10-devel-20100602 released on June 2, 2010
 
 
   42.   [func]          jelte
   42.   [func]          jelte
 	lib/python/isc/config:      Make temporary file with python
 	lib/python/isc/config:      Make temporary file with python
-	tempfile module instead of manual with fixed name.  (Trac
+	tempfile module instead of manual with fixed name. (Trac
 	#184, svn r1859)
 	#184, svn r1859)
 
 
   41.   [func]          jelte
   41.   [func]          jelte
@@ -462,7 +516,7 @@ bind10-devel-20100602 released on June 2, 2010
 
 
   40.   [build]         jreed
   40.   [build]         jreed
 	Report detected features and configure settings at end of
 	Report detected features and configure settings at end of
-	configure output.  (svn r1836)
+	configure output. (svn r1836)
 
 
   39.   [func]*         each
   39.   [func]*         each
 	Renamed libauth to libdatasrc.
 	Renamed libauth to libdatasrc.
@@ -475,7 +529,7 @@ bind10-devel-20100602 released on June 2, 2010
 	(Trac #135, #151, #134, svn r1797)
 	(Trac #135, #151, #134, svn r1797)
 
 
   37.   [build]         jinmei
   37.   [build]         jinmei
-	Check for the availability of python-config.  (Trac #159,
+	Check for the availability of python-config. (Trac #159,
 	svn r1794)
 	svn r1794)
 
 
   36.	[func]		shane
   36.	[func]		shane
@@ -520,7 +574,7 @@ bind10-devel-20100421 released on April 21, 2010
 
 
   27.	[build]
   27.	[build]
 	Add missing copyright license statements to various source
 	Add missing copyright license statements to various source
-	files.  (svn r1750)
+	files. (svn r1750)
 
 
   26.	[func]
   26.	[func]
 	Use PACKAGE_STRING (name + version) from config.h instead
 	Use PACKAGE_STRING (name + version) from config.h instead

+ 1 - 1
Makefile.am

@@ -37,7 +37,7 @@ report-coverage:
 			\*_unittest.cc \
 			\*_unittest.cc \
 			\*_unittests.h \
 			\*_unittests.h \
 		--output report.info
 		--output report.info
-	$(GENHTML) -o coverage report.info 
+	$(GENHTML) --legend -o coverage report.info 
 
 
 coverage: clean-coverage perform-coverage report-coverage
 coverage: clean-coverage perform-coverage report-coverage
 
 

+ 3 - 2
README

@@ -17,8 +17,9 @@ This release includes the bind10 master process, b10-msgq message
 bus, b10-auth authoritative DNS server (with SQLite3 backend),
 bus, b10-auth authoritative DNS server (with SQLite3 backend),
 b10-cmdctl remote control daemon, b10-cfgmgr configuration manager,
 b10-cmdctl remote control daemon, b10-cfgmgr configuration manager,
 b10-xfrin AXFR inbound service, b10-xfrout outgoing AXFR service,
 b10-xfrin AXFR inbound service, b10-xfrout outgoing AXFR service,
-b10-zonemgr secondary manager, and a new libdns++ library for C++
-with a python wrapper.
+b10-zonemgr secondary manager, b10-stats statistics collection and
+reporting daemon, and a new libdns++ library for C++ with a python
+wrapper.
 
 
 Documentation is included and also available via the BIND 10
 Documentation is included and also available via the BIND 10
 website at http://bind10.isc.org/
 website at http://bind10.isc.org/

+ 5 - 3
configure.ac

@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 # Process this file with autoconf to produce a configure script.
 
 
 AC_PREREQ([2.59])
 AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20101013, bind10-dev@isc.org)
+AC_INIT(bind10-devel, 20101201, bind10-dev@isc.org)
 AC_CONFIG_SRCDIR(README)
 AC_CONFIG_SRCDIR(README)
 AM_INIT_AUTOMAKE
 AM_INIT_AUTOMAKE
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_HEADERS([config.h])
@@ -496,6 +496,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/util/Makefile
                  src/lib/python/isc/util/Makefile
                  src/lib/python/isc/util/tests/Makefile
                  src/lib/python/isc/util/tests/Makefile
                  src/lib/python/isc/datasrc/Makefile
                  src/lib/python/isc/datasrc/Makefile
+                 src/lib/python/isc/datasrc/tests/Makefile
                  src/lib/python/isc/cc/Makefile
                  src/lib/python/isc/cc/Makefile
                  src/lib/python/isc/cc/tests/Makefile
                  src/lib/python/isc/cc/tests/Makefile
                  src/lib/python/isc/config/Makefile
                  src/lib/python/isc/config/Makefile
@@ -536,6 +537,8 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/xfrout/xfrout.spec.pre
            src/bin/xfrout/xfrout.spec.pre
            src/bin/xfrout/tests/xfrout_test
            src/bin/xfrout/tests/xfrout_test
            src/bin/xfrout/run_b10-xfrout.sh
            src/bin/xfrout/run_b10-xfrout.sh
+           src/bin/recurse/recurse.spec.pre
+           src/bin/recurse/spec_config.h.pre
            src/bin/zonemgr/zonemgr.py
            src/bin/zonemgr/zonemgr.py
            src/bin/zonemgr/zonemgr.spec.pre
            src/bin/zonemgr/zonemgr.spec.pre
            src/bin/zonemgr/tests/zonemgr_test
            src/bin/zonemgr/tests/zonemgr_test
@@ -563,8 +566,6 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            src/bin/msgq/run_msgq.sh
            src/bin/msgq/run_msgq.sh
            src/bin/auth/auth.spec.pre
            src/bin/auth/auth.spec.pre
            src/bin/auth/spec_config.h.pre
            src/bin/auth/spec_config.h.pre
-           src/bin/recurse/recurse.spec.pre
-           src/bin/recurse/spec_config.h.pre
            src/bin/tests/process_rename_test.py
            src/bin/tests/process_rename_test.py
            src/lib/config/tests/data_def_unittests_config.h
            src/lib/config/tests/data_def_unittests_config.h
            src/lib/python/isc/config/tests/config_test
            src/lib/python/isc/config/tests/config_test
@@ -580,6 +581,7 @@ AC_OUTPUT([src/bin/cfgmgr/b10-cfgmgr.py
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
            chmod +x src/bin/xfrin/run_b10-xfrin.sh
            chmod +x src/bin/xfrout/run_b10-xfrout.sh
            chmod +x src/bin/xfrout/run_b10-xfrout.sh
+           chmod +x src/bin/recurse/run_b10-recurse.sh
            chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
            chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
            chmod +x src/bin/stats/tests/stats_test
            chmod +x src/bin/stats/tests/stats_test
            chmod +x src/bin/stats/run_b10-stats.sh
            chmod +x src/bin/stats/run_b10-stats.sh

+ 2 - 2
src/bin/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth recurse xfrin \
-	xfrout usermgr zonemgr stats tests
+SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth xfrin xfrout \
+	usermgr zonemgr stats tests recurse
 
 
 check-recursive: all-recursive
 check-recursive: all-recursive

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

@@ -37,6 +37,7 @@ spec_config.h: spec_config.h.pre
 BUILT_SOURCES = spec_config.h 
 BUILT_SOURCES = spec_config.h 
 pkglibexec_PROGRAMS = b10-auth
 pkglibexec_PROGRAMS = b10-auth
 b10_auth_SOURCES = auth_srv.cc auth_srv.h
 b10_auth_SOURCES = auth_srv.cc auth_srv.h
+b10_auth_SOURCES += query.cc query.h
 b10_auth_SOURCES += change_user.cc change_user.h
 b10_auth_SOURCES += change_user.cc change_user.h
 b10_auth_SOURCES += common.h
 b10_auth_SOURCES += common.h
 b10_auth_SOURCES += main.cc
 b10_auth_SOURCES += main.cc

+ 2 - 1
src/bin/auth/auth_srv.h

@@ -147,7 +147,7 @@ public:
     /// containing the result of the update operation.
     /// containing the result of the update operation.
     isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
     isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr config);
 
 
-    /// \param Returns the command and configuration session for the
+    /// \brief Returns the command and configuration session for the
     /// \c AuthSrv.
     /// \c AuthSrv.
     ///
     ///
     /// This method never throws an exception.
     /// This method never throws an exception.
@@ -223,6 +223,7 @@ public:
     /// is shutdown.
     /// is shutdown.
     ///
     ///
     void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
     void setXfrinSession(isc::cc::AbstractSession* xfrin_session);
+
 private:
 private:
     AuthSrvImpl* impl_;
     AuthSrvImpl* impl_;
     asiolink::IOService* io_service_;
     asiolink::IOService* io_service_;

+ 23 - 22
src/lib/dns/tests/tsig_unittest.cc

@@ -12,30 +12,31 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
-// $Id: rrtype_unittest.cc 476 2010-01-19 00:29:28Z jinmei $
+#include <dns/message.h>
+#include <dns/rcode.h>
 
 
-#include <gtest/gtest.h>
+#include <datasrc/zonetable.h>
 
 
-#include <dns/tsig.h>
+#include <auth/query.h>
 
 
-#include <dns/tests/unittest_util.h>
-
-using isc::UnitTestUtil;
-using namespace std;
 using namespace isc::dns;
 using namespace isc::dns;
-
-namespace {
-class TsigTest : public ::testing::Test {
-protected:
-    TsigTest() {}
-};
-
-// simple creation test to get the testing ball rolling
-TEST_F(TsigTest, creates) {
-    Tsig tsig(Name("example.com"), Tsig::HMACMD5, "someRandomData");
-    EXPECT_TRUE(1);
+using namespace isc::datasrc;
+
+namespace isc {
+namespace auth {
+void
+Query::process() const {
+    const ZoneTable::FindResult result = zone_table_.find(qname_);
+
+    if (result.code != ZoneTable::SUCCESS &&
+        result.code != ZoneTable::PARTIALMATCH) {
+        response_.setRcode(Rcode::SERVFAIL());
+        return;
+    }
+
+    // Right now we have no code to search the zone, so we simply return
+    // NXDOMAIN for tests.
+    response_.setRcode(Rcode::NXDOMAIN());
+}
+}
 }
 }
-
-} // end namespace
-
-

+ 119 - 0
src/bin/auth/query.h

@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+namespace isc {
+namespace dns {
+class Message;
+class Name;
+class RRType;
+}
+
+namespace datasrc {
+class ZoneTable;
+}
+
+namespace auth {
+
+/// The \c Query class represents a standard DNS query that encapsulates
+/// processing logic to answer the query.
+///
+/// Many of the design details for this class are still in flux.
+/// We'll revisit and update them as we add more functionality, for example:
+/// - zone_table parameter of the constructor.  This will eventually be
+///   replaced with a generic DataSrc object, or perhaps a notion of "view".
+/// - as a related point, we may have to pass the RR class of the query.
+///   in the initial implementation the RR class is an attribute of zone
+///   table and omitted.  It's not clear if this assumption holds with
+///   generic data sources.  On the other hand, it will help keep
+///   implementation simpler, and we might rather want to modify the design
+///   of the data source on this point.
+/// - return value of process().  rather than or in addition to setting the
+///   Rcode, we might use it as a return value of \c process().
+/// - we'll have to be able to specify whether DNSSEC is requested.
+///   It's an open question whether it should be in the constructor or via a
+///   separate attribute setter.
+/// - likewise, we'll eventually need to do per zone access control, for which
+///   we need querier's information such as its IP address.
+/// - zone_table (or DataSrc eventually) and response may better be parameters
+///   to process() instead of the constructor.
+///
+/// <b>Note:</b> The class name is intentionally the same as the one used in
+/// the datasrc library.  This is because the plan is to eventually merge
+/// the two classes.  We could give it a different name such as "AuthQuery"
+/// to avoid possible ambiguity, but it may sound redundant in that it's
+/// obvious that this class is for authoritative queries.
+/// Since the interfaces are very different for now and it's less
+/// likely to misuse one of the classes instead of the other
+/// accidentally, and since it's considered a temporary development state,
+/// we keep this name at the moment.
+class Query {
+public:
+    /// Constructor from query parameters.
+    ///
+    /// This constructor never throws an exception.
+    ///
+    /// \param zone_table The zone table wherein the answer to the query is
+    /// to be found.
+    /// \param qname The query name
+    /// \param qtype The RR type of the query
+    /// \param response The response message to store the answer to the query.
+    Query(const isc::datasrc::ZoneTable& zone_table,
+          const isc::dns::Name& qname, const isc::dns::RRType& qtype,
+          isc::dns::Message& response) :
+        zone_table_(zone_table), qname_(qname), qtype_(qtype),
+        response_(response)
+    {}
+
+    /// Process the query.
+    ///
+    /// This method first identifies the zone that best matches the query
+    /// name (and in some cases RR type when the search is dependent on the
+    /// type) and then searches the zone for an entry that best matches the
+    /// query name.
+    /// It then updates the response message accordingly; for example, a
+    /// successful search would result in adding a corresponding RRset to
+    /// the answer section of the response.
+    ///
+    /// If no matching zone is found in the zone table, the RCODE of
+    /// SERVFAIL will be set in the response.
+    /// <b>Note:</b> this is different from the error code that BIND 9 returns
+    /// by default when it's configured as an authoritative-only server (and
+    /// from the behavior of the BIND 10 datasrc library, which was implemented
+    /// to be compatible with BIND 9).
+    /// The difference comes from the fact that BIND 9 returns REFUSED as a
+    /// result of access control check on the use of its cache.
+    /// Since BIND 10's authoritative server doesn't have the notion of cache
+    /// by design, it doesn't make sense to return REFUSED.  On the other hand,
+    /// providing compatible behavior may have its own benefit, so this point
+    /// should be revisited later.
+    ///
+    /// Right now this method never throws an exception, but it may in a
+    /// future version.
+    void process() const;
+
+private:
+    const isc::datasrc::ZoneTable& zone_table_;
+    const isc::dns::Name& qname_;
+    const isc::dns::RRType& qtype_;
+    isc::dns::Message& response_;
+};
+
+}
+}
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -19,8 +19,10 @@ TESTS += run_unittests
 run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
 run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
+run_unittests_SOURCES += ../query.h ../query.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += ../change_user.h ../change_user.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
+run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += change_user_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 70 - 0
src/bin/auth/tests/query_unittest.cc

@@ -0,0 +1,70 @@
+// 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 <dns/message.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrtype.h>
+
+#include <datasrc/zonetable.h>
+
+#include <auth/query.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+using namespace isc::auth;
+
+namespace {
+class QueryTest : public ::testing::Test {
+protected:
+    QueryTest() :
+        qname(Name("www.example.com")), qclass(RRClass::IN()),
+        qtype(RRType::A()), response(Message::RENDER),
+        query(zone_table, qname, qtype, response)
+    {
+        response.setRcode(Rcode::NOERROR());
+    }
+    ZoneTable zone_table;
+    const Name qname;
+    const RRClass qclass;
+    const RRType qtype;
+    Message response;
+    Query query;
+};
+
+TEST_F(QueryTest, noZone) {
+    // There's no zone in the zone table.  So the response should have
+    // SERVFAIL.
+    query.process();
+    EXPECT_EQ(Rcode::SERVFAIL(), response.getRcode());
+}
+
+TEST_F(QueryTest, matchZone) {
+    // add a matching zone.  since the zone is empty right now, the response
+    // should have NXDOMAIN.
+    zone_table.add(ZonePtr(new MemoryZone(qclass, Name("example.com"))));
+    query.process();
+    EXPECT_EQ(Rcode::NXDOMAIN(), response.getRcode());
+}
+
+TEST_F(QueryTest, noMatchZone) {
+    // there's a zone in the table but it doesn't match the qname.  should
+    // result in SERVFAIL.
+    zone_table.add(ZonePtr(new MemoryZone(qclass, Name("example.org"))));
+    query.process();
+    EXPECT_EQ(Rcode::SERVFAIL(), response.getRcode());
+}
+}

+ 266 - 198
src/bin/bind10/bind10.py.in

@@ -15,7 +15,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 
-"""\
+"""
 This file implements the Boss of Bind (BoB, or bob) program.
 This file implements the Boss of Bind (BoB, or bob) program.
 
 
 Its purpose is to start up the BIND 10 system, and then manage the
 Its purpose is to start up the BIND 10 system, and then manage the
@@ -72,7 +72,7 @@ isc.util.process.rename(sys.argv[0])
 # This is the version that gets displayed to the user.
 # This is the version that gets displayed to the user.
 # The VERSION string consists of the module name, the module version
 # The VERSION string consists of the module name, the module version
 # number, and the overall BIND 10 version number (set in configure.ac).
 # number, and the overall BIND 10 version number (set in configure.ac).
-VERSION = "bind10 20100916 (BIND 10 @PACKAGE_VERSION@)"
+VERSION = "bind10 20101129 (BIND 10 @PACKAGE_VERSION@)"
 
 
 # This is for bind10.boottime of stats module
 # This is for bind10.boottime of stats module
 _BASETIME = time.gmtime()
 _BASETIME = time.gmtime()
@@ -189,26 +189,23 @@ class ProcessInfo:
     def respawn(self):
     def respawn(self):
         self._spawn()
         self._spawn()
 
 
+class CChannelConnectError(Exception): pass
+
 class BoB:
 class BoB:
     """Boss of BIND class."""
     """Boss of BIND class."""
     
     
     def __init__(self, msgq_socket_file=None, dns_port=5300, address=None,
     def __init__(self, msgq_socket_file=None, dns_port=5300, address=None,
                  forward=None, nocache=False, verbose=False, setuid=None,
                  forward=None, nocache=False, verbose=False, setuid=None,
                  username=None):
                  username=None):
-        """Initialize the Boss of BIND. This is a singleton (only one
-        can run).
+        """
+            Initialize the Boss of BIND. This is a singleton (only one can run).
         
         
-        The msgq_socket_file specifies the UNIX domain socket file
-        that the msgq process listens on.
-        If verbose is True, then the boss reports what it is doing.
+            The msgq_socket_file specifies the UNIX domain socket file that the
+            msgq process listens on.  If verbose is True, then the boss reports
+            what it is doing.
         """
         """
-        self.verbose = verbose
-        self.msgq_socket_file = msgq_socket_file
+        self.address = address
         self.dns_port = dns_port
         self.dns_port = dns_port
-        self.address = None
-        self.nocache = nocache
-        if address:
-            self.address = address
         self.forward = None
         self.forward = None
         self.recursive = False
         self.recursive = False
         if forward:
         if forward:
@@ -217,137 +214,215 @@ class BoB:
             self.nocache = False
             self.nocache = False
         self.cc_session = None
         self.cc_session = None
         self.ccs = None
         self.ccs = None
-        self.processes = {}
+        self.cfg_start_auth = True
+        self.cfg_start_recurse = False
+        self.curproc = None
         self.dead_processes = {}
         self.dead_processes = {}
+        self.msgq_socket_file = msgq_socket_file
+        self.nocache = nocache
+        self.processes = {}
         self.runnable = False
         self.runnable = False
         self.uid = setuid
         self.uid = setuid
         self.username = username
         self.username = username
+        self.verbose = verbose
 
 
     def config_handler(self, new_config):
     def config_handler(self, new_config):
         if self.verbose:
         if self.verbose:
-            sys.stdout.write("[bind10] handling new config:\n")
-            sys.stdout.write(new_config + "\n")
+            sys.stdout.write("[bind10] Handling new configuration: " +
+                str(new_config) + "\n")
         answer = isc.config.ccsession.create_answer(0)
         answer = isc.config.ccsession.create_answer(0)
         return answer
         return answer
         # TODO
         # TODO
 
 
     def command_handler(self, command, args):
     def command_handler(self, command, args):
         if self.verbose:
         if self.verbose:
-            sys.stdout.write("[bind10] Boss got command:\n")
-            sys.stdout.write(command + "\n")
+            sys.stdout.write("[bind10] Boss got command: " + command + "\n")
         answer = isc.config.ccsession.create_answer(1, "command not implemented")
         answer = isc.config.ccsession.create_answer(1, "command not implemented")
         if type(command) != str:
         if type(command) != str:
             answer = isc.config.ccsession.create_answer(1, "bad command")
             answer = isc.config.ccsession.create_answer(1, "bad command")
         else:
         else:
-            cmd = command
-            if cmd == "shutdown":
-                sys.stdout.write("[bind10] got shutdown command\n")
+            if command == "shutdown":
                 self.runnable = False
                 self.runnable = False
                 answer = isc.config.ccsession.create_answer(0)
                 answer = isc.config.ccsession.create_answer(0)
             else:
             else:
                 answer = isc.config.ccsession.create_answer(1, 
                 answer = isc.config.ccsession.create_answer(1, 
                                                             "Unknown command")
                                                             "Unknown command")
         return answer
         return answer
-    
-    def startup(self):
-        """Start the BoB instance.
- 
-        Returns None if successful, otherwise an string describing the
-        problem.
+
+    def kill_started_processes(self):
+        """
+            Called as part of the exception handling when a process fails to
+            start, this runs through the list of started processes, killing
+            each one.  It then clears that list.
         """
         """
-        # try to connect to the c-channel daemon, 
-        # to see if it is already running
-        c_channel_env = {}
-        if self.msgq_socket_file is not None:
-             c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file 
         if self.verbose:
         if self.verbose:
-            sys.stdout.write("[bind10] Checking for already running b10-msgq\n")
-        # try to connect, and if we can't wait a short while
-        try:
-            self.cc_session = isc.cc.Session(self.msgq_socket_file)
-            return "b10-msgq already running, or socket file not cleaned , cannot start"
-        except isc.cc.session.SessionError:
-            # this is the case we want, where the msgq is not running
-            pass
+            sys.stdout.write("[bind10] killing started processes:\n")
+
+        for pid in self.processes:
+            if self.verbose:
+                sys.stdout.write("[bind10] - %s\n" % self.processes[pid].name)
+            self.processes[pid].process.kill()
+        self.processes = {}
 
 
-        # start the c-channel daemon
+    def read_bind10_config(self):
+        """
+            Reads the parameters associated with the BoB module itself.
+
+            At present these are the components to start although arguably this
+            information should be in the configuration for the appropriate
+            module itself. (However, this would cause difficulty in the case of
+            xfrin/xfrout and zone manager as we don't need to start those if we
+            are not running the authoritative server.)
+        """
         if self.verbose:
         if self.verbose:
-            if self.msgq_socket_file:
-                sys.stdout.write("[bind10] Starting b10-msgq\n")
-        try:
-            c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
-                                    True, not self.verbose, uid=self.uid,
-                                    username=self.username)
-        except Exception as e:
-            return "Unable to start b10-msgq; " + str(e)
-        self.processes[c_channel.pid] = c_channel
+            sys.stdout.write("[bind10] Reading Boss configuration:\n")
+
+        config_data = self.ccs.get_full_config()
+        self.cfg_start_auth = config_data.get("start_auth")
+        self.cfg_start_recurse = config_data.get("start_recurse")
+
         if self.verbose:
         if self.verbose:
-            sys.stdout.write("[bind10] Started b10-msgq (PID %d)\n" % 
-                             c_channel.pid)
+            sys.stdout.write("[bind10] - start_auth: %s\n" %
+                str(self.cfg_start_auth))
+            sys.stdout.write("[bind10] - start_recurse: %s\n" %
+                str(self.cfg_start_recurse))
+
+    def log_starting(self, process, port = None, address = None):
+        """
+            A convenience function to output a "Starting xxx" message if the
+            verbose option is set.  Putting this into a separate method ensures
+            that the output form is consistent across all processes.
+
+            The process name (passed as the first argument) is put into
+            self.curproc, and is used to indicate which process failed to
+            start if there is an error (and is used in the "Started" message
+            on success).  The optional port and address information are
+            appended to the message (if present).
+        """
+        self.curproc = process
+        if self.verbose:
+            sys.stdout.write("[bind10] Starting %s" % self.curproc)
+            if port is not None:
+                sys.stdout.write(" on port %d" % port)
+                if address is not None:
+                    sys.stdout.write(" (address %s)" % str(address))
+            sys.stdout.write("\n")
 
 
-        # now connect to the c-channel
+    def log_started(self, pid = None):
+        """
+            A convenience function to output a 'Started xxxx (PID yyyy)'
+            message.  As with starting_message(), this ensures a consistent
+            format.
+        """
+        if self.verbose:
+            sys.stdout.write("[bind10] Started %s" % self.curproc)
+            if pid is not None:
+                sys.stdout.write(" (PID %d)" % pid)
+            sys.stdout.write("\n")
+
+    # The next few methods start the individual processes of BIND-10.  They
+    # are called via start_all_process().  If any fail, an exception is raised
+    # which is caught by the caller of start_all_processes(); this kills
+    # processes started up to that point before terminating the program.
+
+    def start_msgq(self, c_channel_env):
+        """
+            Start the message queue and connect to the command channel.
+        """
+        self.log_starting("b10-msgq")
+        c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
+                                True, not self.verbose, uid=self.uid,
+                                username=self.username)
+        self.processes[c_channel.pid] = c_channel
+        self.log_started(c_channel.pid)
+
+        # Now connect to the c-channel
         cc_connect_start = time.time()
         cc_connect_start = time.time()
         while self.cc_session is None:
         while self.cc_session is None:
             # if we have been trying for "a while" give up
             # if we have been trying for "a while" give up
             if (time.time() - cc_connect_start) > 5:
             if (time.time() - cc_connect_start) > 5:
-                c_channel.process.kill()
-                return "Unable to connect to c-channel after 5 seconds"
+                raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
+
             # try to connect, and if we can't wait a short while
             # try to connect, and if we can't wait a short while
             try:
             try:
                 self.cc_session = isc.cc.Session(self.msgq_socket_file)
                 self.cc_session = isc.cc.Session(self.msgq_socket_file)
             except isc.cc.session.SessionError:
             except isc.cc.session.SessionError:
                 time.sleep(0.1)
                 time.sleep(0.1)
 
 
-        # start the configuration manager
-        if self.verbose:
-            sys.stdout.write("[bind10] Starting b10-cfgmgr\n")
-        try:
-            bind_cfgd = ProcessInfo("b10-cfgmgr", ["b10-cfgmgr"],
-                                    c_channel_env, uid=self.uid,
-                                    username=self.username)
-        except Exception as e:
-            c_channel.process.kill()
-            return "Unable to start b10-cfgmgr; " + str(e)
+    def start_cfgmgr(self, c_channel_env):
+        """
+            Starts the configuration manager process
+        """
+        self.log_starting("b10-cfgmgr")
+        bind_cfgd = ProcessInfo("b10-cfgmgr", ["b10-cfgmgr"],
+                                c_channel_env, uid=self.uid,
+                                username=self.username)
         self.processes[bind_cfgd.pid] = bind_cfgd
         self.processes[bind_cfgd.pid] = bind_cfgd
-        if self.verbose:
-            sys.stdout.write("[bind10] Started b10-cfgmgr (PID %d)\n" % 
-                             bind_cfgd.pid)
+        self.log_started(bind_cfgd.pid)
 
 
         # sleep until b10-cfgmgr is fully up and running, this is a good place
         # sleep until b10-cfgmgr is fully up and running, this is a good place
         # to have a (short) timeout on synchronized groupsend/receive
         # to have a (short) timeout on synchronized groupsend/receive
         # TODO: replace the sleep by a listen for ConfigManager started
         # TODO: replace the sleep by a listen for ConfigManager started
         # message
         # message
         time.sleep(1)
         time.sleep(1)
-        if self.verbose:
-            sys.stdout.write("[bind10] starting ccsession\n")
+
+    def start_ccsession(self, c_channel_env):
+        """
+            Start the CC Session
+
+            The argument c_channel_env is unused but is supplied to keep the
+            argument list the same for all start_xxx methods.
+        """
+        self.log_starting("ccsession")
         self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, 
         self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, 
                                       self.config_handler, self.command_handler)
                                       self.config_handler, self.command_handler)
         self.ccs.start()
         self.ccs.start()
+        self.log_started()
+
+    # A couple of utility methods for starting processes...
+
+    def start_process(self, name, args, c_channel_env, port=None, address=None):
+        """
+            Given a set of command arguments, start the process and output
+            appropriate log messages.  If the start is successful, the process
+            is added to the list of started processes.
+
+            The port and address arguments are for log messages only.
+        """
+        self.log_starting(name, port, address)
+        newproc = ProcessInfo(name, args, c_channel_env)
+        self.processes[newproc.pid] = newproc
+        self.log_started(newproc.pid)
+
+    def start_simple(self, name, c_channel_env, port=None, address=None):
+        """
+            Most of the BIND-10 processes are started with the command:
+
+                <process-name> [-v]
+
+            ... where -v is appended if verbose is enabled.  This method
+            generates the arguments from the name and starts the process.
+
+            The port and address arguments are for log messages only.
+        """
+        # Set up the command arguments.
+        args = [name]
         if self.verbose:
         if self.verbose:
-            sys.stdout.write("[bind10] ccsession started\n")
+            args += ['-v']
 
 
-        # if we're running a recursive-only server, we skip the xfrout
-        # modules. otherwise, start xfrout before the DNS server, to make
-        # sure every xfr-query can be processed properly.
-        xfrout=None
-        if not self.recursive:
-            xfrout_args = ['b10-xfrout']
-            if self.verbose:
-                sys.stdout.write("[bind10] Starting b10-xfrout\n")
-                xfrout_args += ['-v']
-            try:
-                xfrout = ProcessInfo("b10-xfrout", xfrout_args, 
-                                     c_channel_env )
-            except Exception as e:
-                c_channel.process.kill()
-                bind_cfgd.process.kill()
-                return "Unable to start b10-xfrout; " + str(e)
-            self.processes[xfrout.pid] = xfrout
-            if self.verbose:
-                sys.stdout.write("[bind10] Started b10-xfrout (PID %d)\n" % 
-                                 xfrout.pid)
+        # ... and start the process
+        self.start_process(name, args, c_channel_env, port, address)
 
 
-        # start DNS server
+    # The next few methods start up the rest of the BIND-10 processes.
+    # Although many of these methods are little more than a call to
+    # start_simple, they are retained (a) for testing reasons and (b) as a place
+    # where modifications can be made if the process start-up sequence changes
+    # for a given process.
+
+    def start_auth(self, c_channel_env):
+        """
+            Start the Authoritative server
+        """
         # XXX: this must be read from the configuration manager in the future
         # XXX: this must be read from the configuration manager in the future
         if self.recursive:
         if self.recursive:
             dns_prog = 'b10-recurse'
             dns_prog = 'b10-recurse'
@@ -365,123 +440,115 @@ class BoB:
             dnsargs += ['-u', str(self.uid)]
             dnsargs += ['-u', str(self.uid)]
         if self.verbose:
         if self.verbose:
             dnsargs += ['-v']
             dnsargs += ['-v']
-            sys.stdout.write("Starting %s using port %d" %
-                             (dns_prog, self.dns_port))
-            if self.address:
-                sys.stdout.write(" on %s" % str(self.address))
-            sys.stdout.write("\n")
-        try:
-            dns = ProcessInfo(dns_prog, dnsargs, c_channel_env)
-        except Exception as e:
-            c_channel.process.kill()
-            bind_cfgd.process.kill()
-            if xfrout:
-                xfrout.process.kill()
-            return "Unable to start " + dns_prog + ": " + str(e)
-        self.processes[dns.pid] = dns
+
+        # ... and start
+        self.start_process("b10-auth", dnsargs, c_channel_env,
+            self.dns_port, self.address)
+
+    def start_recurse(self, c_channel_env):
+        """
+            Start the Resolver.  At present, all these arguments and switches
+            are pure speculation.  As with the auth daemon, they should be
+            read from the configuration database.
+        """
+        self.curproc = "b10-recurse"
+        # XXX: this must be read from the configuration manager in the future
+        resargs = ['b10-recurse']
+        if self.uid:
+            resargs += ['-u', str(self.uid)]
         if self.verbose:
         if self.verbose:
-            sys.stdout.write("[bind10] Started %s (PID %d)\n" %
-                             (dns_prog, dns.pid))
+            resargs += ['-v']
+
+        # ... and start
+        self.start_process("b10-recurse", resargs, c_channel_env)
+
+    def start_xfrout(self, c_channel_env):
+        self.start_simple("b10-xfrout", c_channel_env)
+
+    def start_xfrin(self, c_channel_env):
+        self.start_simple("b10-xfrin", c_channel_env)
+
+    def start_zonemgr(self, c_channel_env):
+        self.start_simple("b10-zonemgr", c_channel_env)
+
+    def start_stats(self, c_channel_env):
+        self.start_simple("b10-stats", c_channel_env)
+
+    def start_cmdctl(self, c_channel_env):
+        # XXX: we hardcode port 8080
+        self.start_simple("b10-cmdctl", c_channel_env, 8080)
 
 
-        # everything after the DNS server can run as non-root
+    def start_all_processes(self, c_channel_env):
+        """
+            Starts up all the processes.  Any exception generated during the
+            starting of the processes is handled by the caller.
+        """
+        self.start_msgq(c_channel_env)
+        self.start_cfgmgr(c_channel_env)
+        self.start_ccsession(c_channel_env)
+
+        # Extract the parameters associated with Bob.  This can only be
+        # done after the CC Session is started.
+        self.read_bind10_config()
+
+        # Continue starting the processes.  The authoritative server (if
+        # selected):
+        if self.cfg_start_auth:
+            self.start_auth(c_channel_env)
+
+        # ... and resolver (if selected):
+        if self.cfg_start_recurse:
+            self.start_recurse(c_channel_env)
+
+        # Everything after the main components can run as non-root.
+        # TODO: this is only temporary - once the privileged socket creator is
+        # fully working, nothing else will run as root.
         if self.uid is not None:
         if self.uid is not None:
             posix.setuid(self.uid)
             posix.setuid(self.uid)
 
 
-        xfrind=None
-        if not self.recursive:
-            # If we're running an authoritative server, start b10-xfrin
-            xfrin_args = ['b10-xfrin']
-            if self.verbose:
-                sys.stdout.write("[bind10] Starting b10-xfrin\n")
-                xfrin_args += ['-v']
-            try:
-                xfrind = ProcessInfo("b10-xfrin", xfrin_args,
-                                     c_channel_env)
-            except Exception as e:
-                c_channel.process.kill()
-                bind_cfgd.process.kill()
-                if xfrout:
-                    xfrout.process.kill()
-                dns.process.kill()
-                return "Unable to start b10-xfrin; " + str(e)
-            self.processes[xfrind.pid] = xfrind
-            if self.verbose:
-                sys.stdout.write("[bind10] Started b10-xfrin (PID %d)\n" % 
-                                 xfrind.pid)
-
-        zonemgr=None
-        if not self.recursive:
-            # If we're running an authoritative server, start b10-zonemgr
-            zonemgr_args = ['b10-zonemgr']
-            if self.verbose:
-                sys.stdout.write("[bind10] Starting b10-zonemgr\n")
-                zonemgr_args += ['-v']
-            try:
-                zonemgr = ProcessInfo("b10-zonemgr", zonemgr_args,
-                                     c_channel_env)
-            except Exception as e:
-                c_channel.process.kill()
-                bind_cfgd.process.kill()
-                dns.process.kill()
-                if xfrout:
-                    xfrout.process.kill()
-                if xfrind:
-                    xfrind.process.kill()
-                return "Unable to start b10-zonemgr; " + str(e)
-            self.processes[zonemgr.pid] = zonemgr 
-            if self.verbose:
-                sys.stdout.write("[bind10] Started b10-zonemgr(PID %d)\n" % 
-                                 zonemgr.pid)
+        # xfrin/xfrout and the zone manager are only meaningful if the
+        # authoritative server has been started.
+        if self.cfg_start_auth:
+            self.start_xfrout(c_channel_env)
+            self.start_xfrin(c_channel_env)
+            self.start_zonemgr(c_channel_env)
 
 
-        # start b10-stats
-        stats_args = ['b10-stats']
+        # ... and finally start the remaining processes
+        self.start_stats(c_channel_env)
+        self.start_cmdctl(c_channel_env)
+    
+    def startup(self):
+        """
+            Start the BoB instance.
+ 
+            Returns None if successful, otherwise an string describing the
+            problem.
+        """
+        # Try to connect to the c-channel daemon, to see if it is already
+        # running
+        c_channel_env = {}
+        if self.msgq_socket_file is not None:
+             c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file 
         if self.verbose:
         if self.verbose:
-            sys.stdout.write("[bind10] Starting b10-stats\n")
-            stats_args += ['-v']
+           sys.stdout.write("[bind10] Checking for already running b10-msgq\n")
+        # try to connect, and if we can't wait a short while
         try:
         try:
-            statsd = ProcessInfo("b10-stats", stats_args,
-                                 c_channel_env)
-        except Exception as e:
-            c_channel.process.kill()
-            bind_cfgd.process.kill()
-            xfrout.process.kill()
-            auth.process.kill()
-            xfrind.process.kill()
-            zonemgr.process.kill()
-            return "Unable to start b10-stats; " + str(e)
-
-        self.processes[statsd.pid] = statsd
-        if self.verbose:
-            sys.stdout.write("[bind10] Started b10-stats (PID %d)\n" % statsd.pid)
+            self.cc_session = isc.cc.Session(self.msgq_socket_file)
+            return "b10-msgq already running, or socket file not cleaned , cannot start"
+        except isc.cc.session.SessionError:
+            # this is the case we want, where the msgq is not running
+            pass
 
 
-        # start the b10-cmdctl
-        # XXX: we hardcode port 8080
-        cmdctl_args = ['b10-cmdctl']
-        if self.verbose:
-            sys.stdout.write("[bind10] Starting b10-cmdctl on port 8080\n")
-            cmdctl_args += ['-v']
+        # Start all processes.  If any one fails to start, kill all started
+        # processes and exit with an error indication.
         try:
         try:
-            cmd_ctrld = ProcessInfo("b10-cmdctl", cmdctl_args,
-                                    c_channel_env)
+            self.start_all_processes(c_channel_env)
         except Exception as e:
         except Exception as e:
-            c_channel.process.kill()
-            bind_cfgd.process.kill()
-            dns.process.kill()
-            if xfrout:
-                xfrout.process.kill()
-            if xfrind:
-                xfrind.process.kill()
-            if zonemgr:
-                zonemgr.process.kill()
-            statsd.process.kill()
-            return "Unable to start b10-cmdctl; " + str(e)
-        self.processes[cmd_ctrld.pid] = cmd_ctrld
-        if self.verbose:
-            sys.stdout.write("[bind10] Started b10-cmdctl (PID %d)\n" % 
-                             cmd_ctrld.pid)
+            self.kill_started_processes()
+            return "Unable to start " + self.curproc + ": " + str(e)
 
 
+        # Started successfully
         self.runnable = True
         self.runnable = True
-
         return None
         return None
 
 
     def stop_all_processes(self):
     def stop_all_processes(self):
@@ -645,7 +712,7 @@ def check_port(option, opt_str, value, parser):
     a valid port number. Used by OptionParser() on startup."""
     a valid port number. Used by OptionParser() on startup."""
     try:
     try:
         if opt_str in ['-p', '--port']:
         if opt_str in ['-p', '--port']:
-            parser.values.auth_port = isc.net.parse.port_parse(value)
+            parser.values.dns_port = isc.net.parse.port_parse(value)
         else:
         else:
             raise OptionValueError("Unknown option " + opt_str)
             raise OptionValueError("Unknown option " + opt_str)
     except ValueError as e:
     except ValueError as e:
@@ -677,7 +744,7 @@ def main():
     # Parse any command-line options.
     # Parse any command-line options.
     parser = OptionParser(version=VERSION)
     parser = OptionParser(version=VERSION)
     parser.add_option("-a", "--address", dest="address", type="string",
     parser.add_option("-a", "--address", dest="address", type="string",
-                      action="callback", callback=check_addr, default='',
+                      action="callback", callback=check_addr, default=None,
                       help="address the DNS server will use (default: listen on all addresses)")
                       help="address the DNS server will use (default: listen on all addresses)")
     parser.add_option("-f", "--forward", dest="forward", type="string",
     parser.add_option("-f", "--forward", dest="forward", type="string",
                       action="callback", callback=check_addr, default='',
                       action="callback", callback=check_addr, default='',
@@ -810,6 +877,7 @@ def main():
     # shutdown
     # shutdown
     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
     boss_of_bind.shutdown()
     boss_of_bind.shutdown()
+    sys.stdout.write("[bind10] BIND 10 exiting\n");
     sys.exit(0)
     sys.exit(0)
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":

+ 12 - 0
src/bin/bind10/bob.spec

@@ -3,6 +3,18 @@
     "module_name": "Boss",
     "module_name": "Boss",
     "module_description": "Master process",
     "module_description": "Master process",
     "config_data": [
     "config_data": [
+      {
+        "item_name": "start_auth",
+        "item_type": "boolean",
+        "item_optional": false,
+        "item_default": true
+      },
+      {
+        "item_name": "start_recurse",
+        "item_type": "boolean",
+        "item_optional": false,
+        "item_default": false
+      }
     ],
     ],
     "commands": [
     "commands": [
       {
       {

+ 1 - 1
src/bin/bind10/run_bind10.sh.in

@@ -23,7 +23,7 @@ BIND10_PATH=@abs_top_builddir@/src/bin/bind10
 PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/recurse:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
 PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/recurse:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:$PATH
 export PATH
 export PATH
 
 
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs
 export PYTHONPATH
 export PYTHONPATH
 
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 200 - 6
src/bin/bind10/tests/bind10_test.py

@@ -79,43 +79,237 @@ class TestBoB(unittest.TestCase):
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, None)
         self.assertEqual(bob.msgq_socket_file, None)
         self.assertEqual(bob.dns_port, 5300)
         self.assertEqual(bob.dns_port, 5300)
-        self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.address, None)
         self.assertEqual(bob.address, None)
+        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.runnable, False)
         self.assertEqual(bob.runnable, False)
+        self.assertEqual(bob.uid, None)
+        self.assertEqual(bob.username, None)
+        self.assertEqual(bob.nocache, False)
+        self.assertEqual(bob.cfg_start_auth, True)
+        self.assertEqual(bob.cfg_start_recurse, False)
 
 
     def test_init_alternate_socket(self):
     def test_init_alternate_socket(self):
         bob = BoB("alt_socket_file")
         bob = BoB("alt_socket_file")
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
         self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
+        self.assertEqual(bob.address, None)
+        self.assertEqual(bob.dns_port, 5300)
         self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.runnable, False)
         self.assertEqual(bob.runnable, False)
+        self.assertEqual(bob.uid, None)
+        self.assertEqual(bob.username, None)
+        self.assertEqual(bob.nocache, False)
+        self.assertEqual(bob.cfg_start_auth, True)
+        self.assertEqual(bob.cfg_start_recurse, False)
 
 
     def test_init_alternate_dns_port(self):
     def test_init_alternate_dns_port(self):
         bob = BoB(None, 9999)
         bob = BoB(None, 9999)
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.msgq_socket_file, None)
         self.assertEqual(bob.msgq_socket_file, None)
         self.assertEqual(bob.dns_port, 9999)
         self.assertEqual(bob.dns_port, 9999)
-        self.assertEqual(bob.cc_session, None)
         self.assertEqual(bob.address, None)
         self.assertEqual(bob.address, None)
+        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.runnable, False)
         self.assertEqual(bob.runnable, False)
+        self.assertEqual(bob.uid, None)
+        self.assertEqual(bob.username, None)
+        self.assertEqual(bob.nocache, False)
+        self.assertEqual(bob.cfg_start_auth, True)
+        self.assertEqual(bob.cfg_start_recurse, False)
 
 
     def test_init_alternate_address(self):
     def test_init_alternate_address(self):
-        bob = BoB(None, 5300, IPAddr('127.127.127.127'))
+        bob = BoB(None, 1234, IPAddr('127.127.127.127'))
         self.assertEqual(bob.verbose, False)
         self.assertEqual(bob.verbose, False)
-        self.assertEqual(bob.dns_port, 5300)
         self.assertEqual(bob.msgq_socket_file, None)
         self.assertEqual(bob.msgq_socket_file, None)
-        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.dns_port, 1234)
         self.assertEqual(bob.address.addr, socket.inet_aton('127.127.127.127'))
         self.assertEqual(bob.address.addr, socket.inet_aton('127.127.127.127'))
+        self.assertEqual(bob.cc_session, None)
+        self.assertEqual(bob.ccs, None)
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.dead_processes, {})
         self.assertEqual(bob.runnable, False)
         self.assertEqual(bob.runnable, False)
-    # verbose testing...
+        self.assertEqual(bob.uid, None)
+        self.assertEqual(bob.username, None)
+        self.assertEqual(bob.nocache, False)
+        self.assertEqual(bob.cfg_start_auth, True)
+        self.assertEqual(bob.cfg_start_recurse, False)
+
+# Class for testing the Bob.start_all_processes() method call.
+#
+# Although testing that external processes start is outside the scope
+# of the unit test, by overriding the process start methods we can check
+# that the right processes are started depending on the configuration
+# options.
+class StartAllProcessesBob(BoB):
+    def __init__(self):
+        BoB.__init__(self)
+
+# Set flags as to which of the overridden methods has been run.
+        self.msgq = False
+        self.cfgmgr = False
+        self.ccsession = False
+        self.auth = False
+        self.recurse = False
+        self.xfrout = False
+        self.xfrin = False
+        self.zonemgr = False
+        self.stats = False
+        self.cmdctl = False
+
+    def read_bind10_config(self):
+        # Configuration options are set directly
+        pass
+
+    def start_msgq(self, c_channel_env):
+        self.msgq = True
+
+    def start_cfgmgr(self, c_channel_env):
+        self.cfgmgr = True
+
+    def start_ccsession(self, c_channel_env):
+        self.ccsession = True
+
+    def start_auth(self, c_channel_env):
+        self.auth = True
+
+    def start_recurse(self, c_channel_env):
+        self.recurse = True
+
+    def start_xfrout(self, c_channel_env):
+        self.xfrout = True
+
+    def start_xfrin(self, c_channel_env):
+        self.xfrin = True
+
+    def start_zonemgr(self, c_channel_env):
+        self.zonemgr = True
+
+    def start_stats(self, c_channel_env):
+        self.stats = True
+
+    def start_cmdctl(self, c_channel_env):
+        self.cmdctl = True
+
+# Check that the start_all_processes method starts the right combination
+# of processes.
+class TestStartAllProcessesBob(unittest.TestCase):
+    def check_preconditions(self, bob):
+        self.assertEqual(bob.msgq, False)
+        self.assertEqual(bob.cfgmgr, False)
+        self.assertEqual(bob.ccsession, False)
+        self.assertEqual(bob.auth, False)
+        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.xfrout, False)
+        self.assertEqual(bob.xfrin, False)
+        self.assertEqual(bob.zonemgr, False)
+        self.assertEqual(bob.stats, False)
+        self.assertEqual(bob.cmdctl, False)
+
+    # Checks the processes started when starting neither auth nor recurse
+    # is specified.
+    def test_start_none(self):
+        # Created Bob and ensure initialization correct
+        bob = StartAllProcessesBob()
+        self.check_preconditions(bob)
+
+        # Start processes and check what was started
+        c_channel_env = {}
+        bob.cfg_start_auth = False
+        bob.cfg_start_recurse = False
+
+        bob.start_all_processes(c_channel_env)
+
+        self.assertEqual(bob.msgq, True)
+        self.assertEqual(bob.cfgmgr, True)
+        self.assertEqual(bob.ccsession, True)
+        self.assertEqual(bob.auth, False)
+        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.xfrout, False)
+        self.assertEqual(bob.xfrin, False)
+        self.assertEqual(bob.zonemgr, False)
+        self.assertEqual(bob.stats, True)
+        self.assertEqual(bob.cmdctl, True)
+
+    # Checks the processes started when starting only the auth process
+    def test_start_auth(self):
+        # Created Bob and ensure initialization correct
+        bob = StartAllProcessesBob()
+        self.check_preconditions(bob)
+
+        # Start processes and check what was started
+        c_channel_env = {}
+        bob.cfg_start_auth = True
+        bob.cfg_start_recurse = False
+
+        bob.start_all_processes(c_channel_env)
+
+        self.assertEqual(bob.msgq, True)
+        self.assertEqual(bob.cfgmgr, True)
+        self.assertEqual(bob.ccsession, True)
+        self.assertEqual(bob.auth, True)
+        self.assertEqual(bob.recurse, False)
+        self.assertEqual(bob.xfrout, True)
+        self.assertEqual(bob.xfrin, True)
+        self.assertEqual(bob.zonemgr, True)
+        self.assertEqual(bob.stats, True)
+        self.assertEqual(bob.cmdctl, True)
+
+    # Checks the processes started when starting only the recurse process
+    def test_start_recurse(self):
+        # Created Bob and ensure initialization correct
+        bob = StartAllProcessesBob()
+        self.check_preconditions(bob)
+
+        # Start processes and check what was started
+        c_channel_env = {}
+        bob.cfg_start_auth = False
+        bob.cfg_start_recurse = True
+
+        bob.start_all_processes(c_channel_env)
+
+        self.assertEqual(bob.msgq, True)
+        self.assertEqual(bob.cfgmgr, True)
+        self.assertEqual(bob.ccsession, True)
+        self.assertEqual(bob.auth, False)
+        self.assertEqual(bob.recurse, True)
+        self.assertEqual(bob.xfrout, False)
+        self.assertEqual(bob.xfrin, False)
+        self.assertEqual(bob.zonemgr, False)
+        self.assertEqual(bob.stats, True)
+        self.assertEqual(bob.cmdctl, True)
+
+    # Checks the processes started when starting both auth and recurse process
+    def test_start_both(self):
+        # Created Bob and ensure initialization correct
+        bob = StartAllProcessesBob()
+        self.check_preconditions(bob)
+
+        # Start processes and check what was started
+        c_channel_env = {}
+        bob.cfg_start_auth = True
+        bob.cfg_start_recurse = True
+
+        bob.start_all_processes(c_channel_env)
+
+        self.assertEqual(bob.msgq, True)
+        self.assertEqual(bob.cfgmgr, True)
+        self.assertEqual(bob.ccsession, True)
+        self.assertEqual(bob.auth, True)
+        self.assertEqual(bob.recurse, True)
+        self.assertEqual(bob.xfrout, True)
+        self.assertEqual(bob.xfrin, True)
+        self.assertEqual(bob.zonemgr, True)
+        self.assertEqual(bob.stats, True)
+        self.assertEqual(bob.cmdctl, True)
+
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

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

@@ -558,7 +558,7 @@ class BindCmdInterpreter(Cmd):
                     if value_map['type'] in [ 'module', 'map', 'list' ]:
                     if value_map['type'] in [ 'module', 'map', 'list' ]:
                         line += "/"
                         line += "/"
                     else:
                     else:
-                        line += ":\t" + str(value_map['value'])
+                        line += ":\t" + json.dumps(value_map['value'])
                     line += "\t" + value_map['type']
                     line += "\t" + value_map['type']
                     line += "\t"
                     line += "\t"
                     if value_map['default']:
                     if value_map['default']:
@@ -569,7 +569,10 @@ class BindCmdInterpreter(Cmd):
             elif cmd.command == "add":
             elif cmd.command == "add":
                 self.config_data.add_value(identifier, cmd.params['value'])
                 self.config_data.add_value(identifier, cmd.params['value'])
             elif cmd.command == "remove":
             elif cmd.command == "remove":
-                self.config_data.remove_value(identifier, cmd.params['value'])
+                if 'value' in cmd.params:
+                    self.config_data.remove_value(identifier, cmd.params['value'])
+                else:
+                    self.config_data.remove_value(identifier, None)
             elif cmd.command == "set":
             elif cmd.command == "set":
                 if 'identifier' not in cmd.params:
                 if 'identifier' not in cmd.params:
                     print("Error: missing identifier or value")
                     print("Error: missing identifier or value")

+ 6 - 3
src/bin/bindctl/bindctl-source.py.in

@@ -28,7 +28,10 @@ import isc.util.process
 
 
 isc.util.process.rename()
 isc.util.process.rename()
 
 
-__version__ = 'Bindctl'
+# This is the version that gets displayed to the user.
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "bindctl 20101201 (BIND 10 @PACKAGE_VERSION@)"
 
 
 def prepare_config_commands(tool):
 def prepare_config_commands(tool):
     '''Prepare fixed commands for local configuration editing'''
     '''Prepare fixed commands for local configuration editing'''
@@ -48,7 +51,7 @@ def prepare_config_commands(tool):
     cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list")
     cmd = CommandInfo(name = "remove", desc = "Remove entry from configuration list")
     param = ParamInfo(name = "identifier", type = "string", optional=True)
     param = ParamInfo(name = "identifier", type = "string", optional=True)
     cmd.add_param(param)
     cmd.add_param(param)
-    param = ParamInfo(name = "value", type = "string", optional=False)
+    param = ParamInfo(name = "value", type = "string", optional=True)
     cmd.add_param(param)
     cmd.add_param(param)
     module.add_command(cmd)
     module.add_command(cmd)
 
 
@@ -113,7 +116,7 @@ def set_bindctl_options(parser):
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     try:
     try:
-        parser = OptionParser(version = __version__)
+        parser = OptionParser(version = VERSION)
         set_bindctl_options(parser)
         set_bindctl_options(parser)
         (options, args) = parser.parse_args()
         (options, args) = parser.parse_args()
         server_addr = options.addr + ':' + str(options.port)
         server_addr = options.addr + ':' + str(options.port)

+ 1 - 1
src/bin/host/host.cc

@@ -131,7 +131,7 @@ host_lookup(const char* const name, const char* const type) {
                       }
                       }
 
 
                       RdataIteratorPtr rit = (*it)->getRdataIterator();
                       RdataIteratorPtr rit = (*it)->getRdataIterator();
-                      for (rit->first(); !rit->isLast(); rit->next()) {
+                      for (; !rit->isLast(); rit->next()) {
                           // instead of using my name, maybe use returned label?
                           // instead of using my name, maybe use returned label?
                           cout << name << " has address " <<
                           cout << name << " has address " <<
                               (*rit).getCurrent().toText() << endl;
                               (*rit).getCurrent().toText() << endl;

+ 5 - 3
src/bin/msgq/msgq.py.in

@@ -38,7 +38,9 @@ import isc.cc
 isc.util.process.rename()
 isc.util.process.rename()
 
 
 # This is the version that gets displayed to the user.
 # This is the version that gets displayed to the user.
-__version__ = "v20091030 (Paving the DNS Parking Lot)"
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "b10-msgq 20100818 (BIND 10 @PACKAGE_VERSION@)"
 
 
 class MsgQReceiveError(Exception): pass
 class MsgQReceiveError(Exception): pass
 
 
@@ -421,7 +423,7 @@ if __name__ == "__main__":
         parser.values.msgq_port = intval
         parser.values.msgq_port = intval
 
 
     # Parse any command-line options.
     # Parse any command-line options.
-    parser = OptionParser(version=__version__)
+    parser = OptionParser(version=VERSION)
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
     parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
                       help="display more about what is going on")
                       help="display more about what is going on")
     parser.add_option("-s", "--socket-file", dest="msgq_socket_file",
     parser.add_option("-s", "--socket-file", dest="msgq_socket_file",
@@ -433,7 +435,7 @@ if __name__ == "__main__":
 
 
     # Announce startup.
     # Announce startup.
     if options.verbose:
     if options.verbose:
-        sys.stdout.write("[b10-msgq] MsgQ %s\n" % __version__)
+        sys.stdout.write("[b10-msgq] %s\n" % VERSION)
 
 
     msgq = MsgQ(options.msgq_socket_file, options.verbose)
     msgq = MsgQ(options.msgq_socket_file, options.verbose)
 
 

+ 1 - 0
src/bin/recurse/Makefile.am

@@ -46,6 +46,7 @@ b10_recurse_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 b10_recurse_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 b10_recurse_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 b10_recurse_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 b10_recurse_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 b10_recurse_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
 b10_recurse_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+b10_recurse_LDADD += $(top_builddir)/src/lib/log/liblog.la
 b10_recurse_LDADD += $(top_builddir)/src/bin/auth/change_user.o
 b10_recurse_LDADD += $(top_builddir)/src/bin/auth/change_user.o
 b10_recurse_LDFLAGS = -pthread
 b10_recurse_LDFLAGS = -pthread
 
 

+ 5 - 6
src/bin/recurse/recursor.cc

@@ -152,8 +152,8 @@ public:
         message_(message), section_(sect)
         message_(message), section_(sect)
     {}
     {}
     void operator()(const RRsetPtr rrset) {
     void operator()(const RRsetPtr rrset) {
-        dlog("Adding RRSet to message section " +
-            boost::lexical_cast<string>(section_));
+        //dlog("Adding RRSet to message section " +
+        //    boost::lexical_cast<string>(section_));
         message_->addRRset(section_, rrset, true);
         message_->addRRset(section_, rrset, true);
     }
     }
     MessagePtr message_;
     MessagePtr message_;
@@ -263,17 +263,16 @@ public:
                 for_each(incoming.beginSection(Message::SECTION_ANSWER),
                 for_each(incoming.beginSection(Message::SECTION_ANSWER),
                          incoming.endSection(Message::SECTION_ANSWER),
                          incoming.endSection(Message::SECTION_ANSWER),
                          SectionInserter(message, Message::SECTION_ANSWER));
                          SectionInserter(message, Message::SECTION_ANSWER));
+                for_each(incoming.beginSection(Message::SECTION_AUTHORITY),
+                         incoming.endSection(Message::SECTION_AUTHORITY),
+                         SectionInserter(message, Message::SECTION_AUTHORITY));
                 for_each(incoming.beginSection(Message::SECTION_ADDITIONAL),
                 for_each(incoming.beginSection(Message::SECTION_ADDITIONAL),
                          incoming.endSection(Message::SECTION_ADDITIONAL),
                          incoming.endSection(Message::SECTION_ADDITIONAL),
                          SectionInserter(message, Message::SECTION_ADDITIONAL));
                          SectionInserter(message, Message::SECTION_ADDITIONAL));
-                for_each(incoming.beginSection(Message::SECTION_AUTHORITY),
-                         incoming.endSection(Message::SECTION_ADDITIONAL),
-                         SectionInserter(message, Message::SECTION_AUTHORITY));
             } catch (const Exception& ex) {
             } catch (const Exception& ex) {
                 // Incoming message couldn't be read, we just SERVFAIL
                 // Incoming message couldn't be read, we just SERVFAIL
                 message->setRcode(Rcode::SERVFAIL());
                 message->setRcode(Rcode::SERVFAIL());
             }
             }
-
         }
         }
 
 
         // Now we can clear the buffer and render the new message into it
         // Now we can clear the buffer and render the new message into it

+ 1 - 0
src/bin/recurse/tests/Makefile.am

@@ -33,6 +33,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/config/libcfgclient.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
 run_unittests_LDADD += $(top_builddir)/src/lib/xfr/libxfr.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
 endif
 endif
 
 
 noinst_PROGRAMS = $(TESTS)
 noinst_PROGRAMS = $(TESTS)

+ 0 - 1
src/bin/xfrin/b10-xfrin.xml

@@ -149,7 +149,6 @@
       the authoritative server to transfer from,
       the authoritative server to transfer from,
       and <varname>port</varname> to define the port number on the
       and <varname>port</varname> to define the port number on the
       authoritative server (defaults to 53).
       authoritative server (defaults to 53).
-<!-- TODO: note: not documenting db_file since that will be removed. -->
      </para>
      </para>
 <!-- TODO: later hostname for master? -->
 <!-- TODO: later hostname for master? -->
 
 

+ 2 - 7
src/bin/xfrout/b10-xfrout.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-xfrout
 .\"     Title: b10-xfrout
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: September 8, 2010
+.\"      Date: December 1, 2010
 .\"    Manual: BIND10
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"  Language: English
 .\"
 .\"
-.TH "B10\-XFROUT" "8" "September 8, 2010" "BIND10" "BIND10"
+.TH "B10\-XFROUT" "8" "December 1, 2010" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" * set default formatting
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
@@ -67,11 +67,6 @@ receives its configurations from
 The configurable settings are:
 The configurable settings are:
 .PP
 .PP
 
 
-\fIdb_file\fR
-defines the path to the SQLite3 data store file\&. The default is
-/usr/local/var/bind10\-devel/zone\&.sqlite3\&.
-.PP
-
 \fItransfers_out\fR
 \fItransfers_out\fR
 defines the maximum number of outgoing zone transfers that can run concurrently\&. The default is 10\&.
 defines the maximum number of outgoing zone transfers that can run concurrently\&. The default is 10\&.
 .if n \{\
 .if n \{\

+ 1 - 8
src/bin/xfrout/b10-xfrout.xml

@@ -21,7 +21,7 @@
 <refentry>
 <refentry>
 
 
   <refentryinfo>
   <refentryinfo>
-    <date>September 8, 2010</date>
+    <date>December 1, 2010</date>
   </refentryinfo>
   </refentryinfo>
 
 
   <refmeta>
   <refmeta>
@@ -94,13 +94,6 @@
       The configurable settings are:
       The configurable settings are:
     </para>
     </para>
     <para>
     <para>
-      <varname>db_file</varname>
-      defines the path to the SQLite3 data store file.
-      The default is
-      <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
-<!-- TODO: db_file will be removed -->
-    </para>
-    <para>
       <varname>transfers_out</varname>
       <varname>transfers_out</varname>
       defines the maximum number of outgoing zone transfers
       defines the maximum number of outgoing zone transfers
       that can run concurrently. The default is 10.
       that can run concurrently. The default is 10.

+ 0 - 6
src/bin/xfrout/xfrout.spec.pre.in

@@ -9,12 +9,6 @@
          "item_default": 10
          "item_default": 10
        },
        },
        {
        {
-         "item_name": "db_file",
-         "item_type": "string",
-         "item_optional": false,
-         "item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
-       },
-       {
          "item_name": "log_name",
          "item_name": "log_name",
          "item_type": "string",
          "item_type": "string",
          "item_optional": false,
          "item_optional": false,

+ 2 - 2
src/lib/bench/benchmark.h

@@ -200,7 +200,7 @@ private:
     BenchMark(const BenchMark& source);
     BenchMark(const BenchMark& source);
     BenchMark& operator=(const BenchMark& source);
     BenchMark& operator=(const BenchMark& source);
 public:
 public:
-    /// \bench Constructor for immediate run.
+    /// \brief Constructor for immediate run.
     ///
     ///
     /// This is the constructor that is expected to be used normally.
     /// This is the constructor that is expected to be used normally.
     /// It runs the benchmark within the constructor and prints the result,
     /// It runs the benchmark within the constructor and prints the result,
@@ -217,7 +217,7 @@ public:
         initialize(true);
         initialize(true);
     }
     }
 
 
-    /// \bench Constructor for finer-grained control.
+    /// \brief Constructor for finer-grained control.
     ///
     ///
     /// This constructor takes the third parameter, \c immediate, to control
     /// This constructor takes the third parameter, \c immediate, to control
     /// whether to run the benchmark within the constructor.
     /// whether to run the benchmark within the constructor.

+ 0 - 5
src/lib/config/config_data.cc

@@ -128,11 +128,6 @@ spec_name_list(ElementPtr result, ConstElementPtr spec_part,
                 if (recurse && list_el->get("item_type")->stringValue() == "map") {
                 if (recurse && list_el->get("item_type")->stringValue() == "map") {
                     spec_name_list(result, list_el->get("map_item_spec"), new_prefix, recurse);
                     spec_name_list(result, list_el->get("map_item_spec"), new_prefix, recurse);
                 } else {
                 } else {
-                    if (list_el->get("item_type")->stringValue() == "map" ||
-                        list_el->get("item_type")->stringValue() == "list"
-                    ) {
-                        new_prefix += "/";
-                    }
                     result->add(Element::create(new_prefix));
                     result->add(Element::create(new_prefix));
                 }
                 }
             }
             }

+ 5 - 5
src/lib/config/tests/config_data_unittests.cc

@@ -120,8 +120,8 @@ TEST(ConfigData, getItemList) {
     ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
     ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
     ConfigData cd = ConfigData(spec2);
     ConfigData cd = ConfigData(spec2);
 
 
-    EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/\" ]", cd.getItemList()->str());
-    EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5/\", \"item6/value1\", \"item6/value2\" ]", cd.getItemList("", true)->str());
+    EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5\", \"item6\" ]", cd.getItemList()->str());
+    EXPECT_EQ("[ \"item1\", \"item2\", \"item3\", \"item4\", \"item5\", \"item6/value1\", \"item6/value2\" ]", cd.getItemList("", true)->str());
     EXPECT_EQ("[ \"item6/value1\", \"item6/value2\" ]", cd.getItemList("item6")->str());
     EXPECT_EQ("[ \"item6/value1\", \"item6/value2\" ]", cd.getItemList("item6")->str());
 }
 }
 
 
@@ -129,12 +129,12 @@ TEST(ConfigData, getFullConfig) {
     ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
     ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
     ConfigData cd = ConfigData(spec2);
     ConfigData cd = ConfigData(spec2);
 
 
-    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
     ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
     ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
     cd.setLocalConfig(my_config);
     cd.setLocalConfig(my_config);
-    EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+    EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
     ElementPtr my_config2 = Element::fromJSON("{ \"item6\": { \"value1\": \"a\" } }");
     ElementPtr my_config2 = Element::fromJSON("{ \"item6\": { \"value1\": \"a\" } }");
     cd.setLocalConfig(my_config2);
     cd.setLocalConfig(my_config2);
-    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5/\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None }", cd.getFullConfig()->str());
+    EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None }", cd.getFullConfig()->str());
 }
 }
 
 

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

@@ -15,3 +15,4 @@ libdatasrc_la_SOURCES += static_datasrc.h static_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
 libdatasrc_la_SOURCES += sqlite3_datasrc.h sqlite3_datasrc.cc
 libdatasrc_la_SOURCES += query.h query.cc
 libdatasrc_la_SOURCES += query.h query.cc
 libdatasrc_la_SOURCES += cache.h cache.cc
 libdatasrc_la_SOURCES += cache.h cache.cc
+libdatasrc_la_SOURCES += zonetable.h zonetable.cc

+ 3 - 3
src/lib/datasrc/cache.h

@@ -170,9 +170,9 @@ public:
     /// then promoted to the head of the LRU queue.  (NOTE: Because
     /// then promoted to the head of the LRU queue.  (NOTE: Because
     /// of this, "retrieve" cannot be implemented as a const method.)
     /// of this, "retrieve" cannot be implemented as a const method.)
     ///
     ///
-    /// \param name The query name
-    /// \param rrclass The query class
-    /// \param rrtype The query type
+    /// \param qname The query name
+    /// \param qclass The query class
+    /// \param qtype The query type
     /// \param rrset Returns the RRset found, if any, to the caller
     /// \param rrset Returns the RRset found, if any, to the caller
     /// \param flags Returns the flags, if any, to the caller
     /// \param flags Returns the flags, if any, to the caller
     ///
     ///

+ 1 - 4
src/lib/datasrc/data_source.cc

@@ -95,7 +95,7 @@ getAdditional(Query& q, ConstRRsetPtr rrset) {
     }
     }
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    for (it->first(); !it->isLast(); it->next()) {
+    for (; !it->isLast(); it->next()) {
         const Rdata& rd(it->getCurrent());
         const Rdata& rd(it->getCurrent());
         if (rrset->getType() == RRType::NS()) {
         if (rrset->getType() == RRType::NS()) {
             const generic::NS& ns = dynamic_cast<const generic::NS&>(rd);
             const generic::NS& ns = dynamic_cast<const generic::NS&>(rd);
@@ -123,7 +123,6 @@ synthesizeCname(QueryTaskPtr task, RRsetPtr rrset, RRsetList& target) {
 
 
     // More than one DNAME RR in the RRset is illegal, so we only have
     // More than one DNAME RR in the RRset is illegal, so we only have
     // to process the first one.
     // to process the first one.
-    it->first();
     if (it->isLast()) {
     if (it->isLast()) {
         return;
         return;
     }
     }
@@ -152,7 +151,6 @@ chaseCname(Query& q, QueryTaskPtr task, RRsetPtr rrset) {
 
 
     // More than one CNAME RR in the RRset is illegal, so we only have
     // More than one CNAME RR in the RRset is illegal, so we only have
     // to process the first one.
     // to process the first one.
-    it->first();
     if (it->isLast()) {
     if (it->isLast()) {
         return;
         return;
     }
     }
@@ -660,7 +658,6 @@ getNsec3Param(Query& q, ZoneInfo& zoneinfo) {
     // XXX: currently only one NSEC3 chain per zone is supported;
     // XXX: currently only one NSEC3 chain per zone is supported;
     // we will need to revisit this.
     // we will need to revisit this.
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     if (it->isLast()) {
     if (it->isLast()) {
         return (ConstNsec3ParamPtr());
         return (ConstNsec3ParamPtr());
     }
     }

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

@@ -79,6 +79,7 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
     authors->addRdata(generic::TXT("JINMEI Tatuya"));
     authors->addRdata(generic::TXT("JINMEI Tatuya"));
     authors->addRdata(generic::TXT("Kazunori Fujiwara"));
     authors->addRdata(generic::TXT("Kazunori Fujiwara"));
     authors->addRdata(generic::TXT("Michael Graff"));
     authors->addRdata(generic::TXT("Michael Graff"));
+    authors->addRdata(generic::TXT("Michal Vaner"));
     authors->addRdata(generic::TXT("Naoki Kambe"));
     authors->addRdata(generic::TXT("Naoki Kambe"));
     authors->addRdata(generic::TXT("Shane Kerr"));
     authors->addRdata(generic::TXT("Shane Kerr"));
     authors->addRdata(generic::TXT("Shen Tingting"));
     authors->addRdata(generic::TXT("Shen Tingting"));

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

@@ -24,6 +24,7 @@ run_unittests_SOURCES += static_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
 run_unittests_SOURCES += cache_unittest.cc
 run_unittests_SOURCES += cache_unittest.cc
 run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
 run_unittests_SOURCES += test_datasrc.h test_datasrc.cc
+run_unittests_SOURCES += zonetable_unittest.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD = $(GTEST_LDADD)
 run_unittests_LDADD = $(GTEST_LDADD)

+ 0 - 34
src/lib/datasrc/tests/datasrc_unittest.cc

@@ -124,7 +124,6 @@ DataSrcTest::QueryCommon(const RRClass& qclass) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -138,7 +137,6 @@ DataSrcTest::QueryCommon(const RRClass& qclass) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -154,7 +152,6 @@ DataSrcTest::QueryCommon(const RRClass& qclass) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -193,7 +190,6 @@ TEST_F(DataSrcTest, NSQuery) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -216,7 +212,6 @@ TEST_F(DataSrcTest, DuplicateQuery) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -237,7 +232,6 @@ TEST_F(DataSrcTest, DuplicateQuery) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -334,7 +328,6 @@ TEST_F(DataSrcTest, Wildcard) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -353,7 +346,6 @@ TEST_F(DataSrcTest, Wildcard) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -369,7 +361,6 @@ TEST_F(DataSrcTest, Wildcard) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -406,7 +397,6 @@ TEST_F(DataSrcTest, WildcardCname) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("www.example.com.", it->getCurrent().toText());
     EXPECT_EQ("www.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -419,7 +409,6 @@ TEST_F(DataSrcTest, WildcardCname) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -438,7 +427,6 @@ TEST_F(DataSrcTest, WildcardCname) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -454,7 +442,6 @@ TEST_F(DataSrcTest, WildcardCname) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -474,7 +461,6 @@ TEST_F(DataSrcTest, WildcardCnameNodata) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("www.example.com.", it->getCurrent().toText());
     EXPECT_EQ("www.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -506,7 +492,6 @@ TEST_F(DataSrcTest, WildcardCnameNxdomain) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("spork.example.com.", it->getCurrent().toText());
     EXPECT_EQ("spork.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -544,7 +529,6 @@ TEST_F(DataSrcTest, AuthDelegation) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -556,7 +540,6 @@ TEST_F(DataSrcTest, AuthDelegation) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -572,7 +555,6 @@ TEST_F(DataSrcTest, AuthDelegation) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -591,7 +573,6 @@ TEST_F(DataSrcTest, Dname) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("sql1.example.com.", it->getCurrent().toText());
     EXPECT_EQ("sql1.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -605,7 +586,6 @@ TEST_F(DataSrcTest, Dname) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -621,7 +601,6 @@ TEST_F(DataSrcTest, Dname) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -649,7 +628,6 @@ TEST_F(DataSrcTest, Cname) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("cnametest.flame.org.", it->getCurrent().toText());
     EXPECT_EQ("cnametest.flame.org.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -668,7 +646,6 @@ TEST_F(DataSrcTest, CnameInt) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("www.example.com.", it->getCurrent().toText());
     EXPECT_EQ("www.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -695,7 +672,6 @@ TEST_F(DataSrcTest, CnameExt) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("www.sql1.example.com.", it->getCurrent().toText());
     EXPECT_EQ("www.sql1.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -720,7 +696,6 @@ TEST_F(DataSrcTest, Delegation) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
     EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_FALSE(it->isLast());
     EXPECT_FALSE(it->isLast());
@@ -732,7 +707,6 @@ TEST_F(DataSrcTest, Delegation) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -751,7 +725,6 @@ TEST_F(DataSrcTest, NSDelegation) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
     EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_FALSE(it->isLast());
     EXPECT_FALSE(it->isLast());
@@ -763,7 +736,6 @@ TEST_F(DataSrcTest, NSDelegation) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -795,7 +767,6 @@ TEST_F(DataSrcTest, NSECZonecut) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -817,7 +788,6 @@ TEST_F(DataSrcTest, DNAMEZonecut) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
     EXPECT_EQ("ns1.subzone.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_FALSE(it->isLast());
     EXPECT_FALSE(it->isLast());
@@ -829,7 +799,6 @@ TEST_F(DataSrcTest, DNAMEZonecut) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
@@ -854,7 +823,6 @@ TEST_F(DataSrcTest, DS) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
     EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
@@ -890,7 +858,6 @@ TEST_F(DataSrcTest, NSECZonecutOfNonsecureZone) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ(createRdata(RRType::NS(), RRClass::IN(),
     EXPECT_EQ(createRdata(RRType::NS(), RRClass::IN(),
                           "ns.sub.example.org.")->toText(),
                           "ns.sub.example.org.")->toText(),
               it->getCurrent().toText());
               it->getCurrent().toText());
@@ -904,7 +871,6 @@ TEST_F(DataSrcTest, NSECZonecutOfNonsecureZone) {
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
     EXPECT_EQ(RRClass::IN(), rrset->getClass());
 
 
     it = rrset->getRdataIterator();
     it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ(createRdata(RRType::A(), RRClass::IN(), "192.0.2.101")->toText(),
     EXPECT_EQ(createRdata(RRType::A(), RRClass::IN(), "192.0.2.101")->toText(),
               it->getCurrent().toText());
               it->getCurrent().toText());
     it->next();
     it->next();

+ 0 - 1
src/lib/datasrc/tests/sqlite3_unittest.cc

@@ -244,7 +244,6 @@ checkRRset(RRsetPtr rrset, const Name& expected_name,
     EXPECT_EQ(expected_rrttl, rrset->getTTL());
     EXPECT_EQ(expected_rrttl, rrset->getTTL());
 
 
     RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
     RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
-    rdata_iterator->first();
     vector<string>::const_iterator data_it = expected_data.begin();
     vector<string>::const_iterator data_it = expected_data.begin();
     for (; data_it != expected_data.end(); ++data_it) {
     for (; data_it != expected_data.end(); ++data_it) {
         EXPECT_FALSE(rdata_iterator->isLast());
         EXPECT_FALSE(rdata_iterator->isLast());

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

@@ -63,6 +63,7 @@ protected:
         authors_data.push_back("JINMEI Tatuya");
         authors_data.push_back("JINMEI Tatuya");
         authors_data.push_back("Kazunori Fujiwara");
         authors_data.push_back("Kazunori Fujiwara");
         authors_data.push_back("Michael Graff");
         authors_data.push_back("Michael Graff");
+        authors_data.push_back("Michal Vaner");
         authors_data.push_back("Naoki Kambe");
         authors_data.push_back("Naoki Kambe");
         authors_data.push_back("Shane Kerr");
         authors_data.push_back("Shane Kerr");
         authors_data.push_back("Shen Tingting");
         authors_data.push_back("Shen Tingting");
@@ -109,7 +110,6 @@ checkRRset(ConstRRsetPtr rrset, const Name& expected_name,
     EXPECT_EQ(rrttl, rrset->getTTL());
     EXPECT_EQ(rrttl, rrset->getTTL());
 
 
     RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
     RdataIteratorPtr rdata_iterator = rrset->getRdataIterator();
-    rdata_iterator->first();
     vector<string>::const_iterator data_it = expected_data.begin();
     vector<string>::const_iterator data_it = expected_data.begin();
     for (; data_it != expected_data.end(); ++data_it) {
     for (; data_it != expected_data.end(); ++data_it) {
         EXPECT_FALSE(rdata_iterator->isLast());
         EXPECT_FALSE(rdata_iterator->isLast());

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

@@ -408,7 +408,7 @@ copyRRset(RRsetPtr const source) {
     RRsetPtr rrset = RRsetPtr(new RRset(source->getName(), source->getClass(),
     RRsetPtr rrset = RRsetPtr(new RRset(source->getName(), source->getClass(),
                                         source->getType(), source->getTTL()));
                                         source->getType(), source->getTTL()));
     RdataIteratorPtr it = source->getRdataIterator();
     RdataIteratorPtr it = source->getRdataIterator();
-    for (it->first(); !it->isLast(); it->next()) {
+    for (; !it->isLast(); it->next()) {
         rrset->addRdata(it->getCurrent());
         rrset->addRdata(it->getCurrent());
     }
     }
     if (source->getRRsig()) {
     if (source->getRRsig()) {

+ 113 - 0
src/lib/datasrc/tests/zonetable_unittest.cc

@@ -0,0 +1,113 @@
+// 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 <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/zonetable.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc;
+
+namespace {
+TEST(ZoneTest, init) {
+    MemoryZone zone(RRClass::IN(), Name("example.com"));
+    EXPECT_EQ(Name("example.com"), zone.getOrigin());
+    EXPECT_EQ(RRClass::IN(), zone.getClass());
+
+    MemoryZone ch_zone(RRClass::CH(), Name("example"));
+    EXPECT_EQ(Name("example"), ch_zone.getOrigin());
+    EXPECT_EQ(RRClass::CH(), ch_zone.getClass());
+}
+
+TEST(ZoneTest, find) {
+    MemoryZone zone(RRClass::IN(), Name("example.com"));
+    EXPECT_EQ(Zone::NXDOMAIN,
+              zone.find(Name("www.example.com"), RRType::A()).code);
+}
+
+class ZoneTableTest : public ::testing::Test {
+protected:
+    ZoneTableTest() : zone1(new MemoryZone(RRClass::IN(),
+                                           Name("example.com"))),
+                      zone2(new MemoryZone(RRClass::IN(),
+                                           Name("example.net"))),
+                      zone3(new MemoryZone(RRClass::IN(), Name("example")))
+    {}
+    ZoneTable zone_table;
+    ZonePtr zone1, zone2, zone3;
+};
+
+TEST_F(ZoneTableTest, add) {
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1));
+    EXPECT_EQ(ZoneTable::EXIST, zone_table.add(zone1));
+    // names are compared in a case insensitive manner.
+    EXPECT_EQ(ZoneTable::EXIST, zone_table.add(
+                  ZonePtr(new MemoryZone(RRClass::IN(), Name("EXAMPLE.COM")))));
+
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2));
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3));
+
+    // Zone table is indexed only by name.  Duplicate origin name with
+    // different zone class isn't allowed.
+    EXPECT_EQ(ZoneTable::EXIST, zone_table.add(
+                  ZonePtr(new MemoryZone(RRClass::CH(),
+                                         Name("example.com")))));
+
+    /// Bogus zone (NULL)
+    EXPECT_THROW(zone_table.add(ZonePtr()), isc::InvalidParameter);
+}
+
+TEST_F(ZoneTableTest, remove) {
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1));
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2));
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3));
+
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.remove(Name("example.net")));
+    EXPECT_EQ(ZoneTable::NOTFOUND, zone_table.remove(Name("example.net")));
+}
+
+TEST_F(ZoneTableTest, find) {
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone1));
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone2));
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone3));
+
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.find(Name("example.com")).code);
+    EXPECT_EQ(Name("example.com"),
+              zone_table.find(Name("example.com")).zone->getOrigin());
+
+    EXPECT_EQ(ZoneTable::NOTFOUND,
+              zone_table.find(Name("example.org")).code);
+    EXPECT_EQ(static_cast<const Zone*>(NULL),
+              zone_table.find(Name("example.org")).zone);
+
+    // there's no exact match.  the result should be the longest match,
+    // and the code should be PARTIALMATCH.
+    EXPECT_EQ(ZoneTable::PARTIALMATCH,
+              zone_table.find(Name("www.example.com")).code);
+    EXPECT_EQ(Name("example.com"),
+              zone_table.find(Name("www.example.com")).zone->getOrigin());
+
+    // make sure the partial match is indeed the longest match by adding
+    // a zone with a shorter origin and query again.
+    ZonePtr zone_com(new MemoryZone(RRClass::IN(), Name("com")));
+    EXPECT_EQ(ZoneTable::SUCCESS, zone_table.add(zone_com));
+    EXPECT_EQ(Name("example.com"),
+              zone_table.find(Name("www.example.com")).zone->getOrigin());
+}
+}

+ 117 - 0
src/lib/datasrc/zonetable.cc

@@ -0,0 +1,117 @@
+// 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.
+
+// Note: map and utility (for 'pair') are for temporary workaround.
+// we'll soon replace them with built-in intelligent backend structure. 
+#include <map>
+#include <utility>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/zonetable.h>
+
+using namespace std;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+struct MemoryZone::MemoryZoneImpl {
+    MemoryZoneImpl(const RRClass& zone_class, const Name& origin) :
+        zone_class_(zone_class), origin_(origin)
+    {}
+    RRClass zone_class_;
+    Name origin_;
+};
+
+MemoryZone::MemoryZone(const RRClass& zone_class, const Name& origin) :
+    impl_(new MemoryZoneImpl(zone_class, origin))
+{
+}
+
+MemoryZone::~MemoryZone() {
+    delete impl_;
+}
+
+const Name&
+MemoryZone::getOrigin() const {
+    return (impl_->origin_);
+}
+
+const RRClass&
+MemoryZone::getClass() const {
+    return (impl_->zone_class_);
+}
+
+Zone::FindResult
+MemoryZone::find(const Name&, const RRType&) const {
+    // This is a tentative implementation that always returns NXDOMAIN.
+    return (FindResult(NXDOMAIN, RRsetPtr()));
+}
+
+// This is a temporary, inefficient implementation using std::map and handmade
+// iteration to realize longest match.
+
+struct ZoneTable::ZoneTableImpl {
+    typedef map<Name, ZonePtr> ZoneMap;
+    typedef pair<Name, ZonePtr> NameAndZone;
+    ZoneMap zones;
+};
+
+ZoneTable::ZoneTable() : impl_(new ZoneTableImpl)
+{}
+
+ZoneTable::~ZoneTable() {
+    delete impl_;
+}
+
+ZoneTable::Result
+ZoneTable::add(ZonePtr zone) {
+    if (!zone) {
+        isc_throw(InvalidParameter,
+                  "Null pointer is passed to ZoneTable::add()");
+    }
+
+    if (impl_->zones.insert(
+            ZoneTableImpl::NameAndZone(zone->getOrigin(), zone)).second
+        == true) {
+        return (SUCCESS);
+    } else {
+        return (EXIST);
+    }
+}
+
+ZoneTable::Result
+ZoneTable::remove(const Name& origin) {
+    return (impl_->zones.erase(origin) == 1 ? SUCCESS : NOTFOUND);
+}
+
+ZoneTable::FindResult
+ZoneTable::find(const Name& name) const {
+    // Inefficient internal loop to find a longest match.
+    // This will be replaced with a single call to more intelligent backend.
+    for (int i = 0; i < name.getLabelCount(); ++i) {
+        Name matchname(name.split(i));
+        ZoneTableImpl::ZoneMap::const_iterator found =
+            impl_->zones.find(matchname);
+        if (found != impl_->zones.end()) {
+            return (FindResult(i == 0 ? SUCCESS : PARTIALMATCH,
+                               (*found).second.get()));
+        }
+    }
+    return (FindResult(NOTFOUND, NULL));
+}
+} // end of namespace datasrc
+} // end of namespace isc

+ 383 - 0
src/lib/datasrc/zonetable.h

@@ -0,0 +1,383 @@
+// 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.
+
+#ifndef __ZONETABLE_H
+#define __ZONETABLE_H 1
+
+#include <boost/shared_ptr.hpp>
+
+#include <dns/rrset.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+};
+
+namespace datasrc {
+
+/// \brief The base class for a single authoritative zone
+///
+/// The \c Zone class is an abstract base class for representing
+/// a DNS zone as part of data source.
+///
+/// At the moment this is provided mainly for making the \c ZoneTable class
+/// and the authoritative query logic  testable, and only provides a minimal
+/// set of features.
+/// This is why this class is defined in the same header file, but it may
+/// have to move to a separate header file when we understand what is
+/// necessary for this class for actual operation.
+///
+/// The idea is to provide a specific derived zone class for each data
+/// source, beginning with in memory one.  At that point the derived classes
+/// will have more specific features.  For example, they will maintain
+/// information about the location of a zone file, whether it's loaded in
+/// memory, etc.
+///
+/// It's not yet clear how the derived zone classes work with various other
+/// data sources when we integrate these components, but one possibility is
+/// something like this:
+/// - If the underlying database such as some variant of SQL doesn't have an
+///   explicit representation of zones (as part of public interface), we can
+///   probably use a "default" zone class that simply encapsulates the
+///   corresponding data source and calls a common "find" like method.
+/// - Some data source may want to specialize it by inheritance as an
+///   optimization.  For example, in the current schema design of the sqlite3
+///   data source, its (derived) zone class would contain the information of
+///   the "zone ID".
+///
+/// <b>Note:</b> Unlike some other abstract base classes we don't name the
+/// class beginning with "Abstract".  This is because we want to have
+/// commonly used definitions such as \c Result and \c ZonePtr, and we want
+/// to make them look more intuitive.
+class Zone {
+public:
+    /// Result codes of the \c find() method.
+    ///
+    /// Note: the codes are tentative.  We may need more, or we may find
+    /// some of them unnecessary as we implement more details.
+    enum Result {
+        SUCCESS,                ///< An exact match is found.
+        DELEGATION,             ///< The search encounters a zone cut.
+        NXDOMAIN, ///< There is no domain name that matches the search name
+        NXRRSET,  ///< There is a matching name but no RRset of the search type
+        CNAME,    ///< The search encounters and returns a CNAME RR
+        DNAME     ///< The search encounters and returns a DNAME RR
+    };
+
+    /// A helper structure to represent the search result of \c find().
+    ///
+    /// This is a straightforward tuple of the result code and a pointer
+    /// to the found RRset to represent the result of \c find()
+    /// (there will be more members in the future - see the class
+    /// description).
+    /// We use this in order to avoid overloading the return value for both
+    /// the result code ("success" or "not found") and the found object,
+    /// i.e., avoid using \c NULL to mean "not found", etc.
+    ///
+    /// This is a simple value class whose internal state never changes,
+    /// so for convenience we allow the applications to refer to the members
+    /// directly.
+    ///
+    /// Note: we should eventually include a notion of "zone node", which
+    /// corresponds to a particular domain name of the zone, so that we can
+    /// find RRsets of a different RR type for that name (e.g. for type ANY
+    /// query or to include DS RRs with delegation).
+    ///
+    /// Note: we may also want to include the closest enclosure "node" to
+    /// optimize including the NSEC for no-wildcard proof (FWIW NSD does that).
+    struct FindResult {
+        FindResult(Result param_code,
+                   const isc::dns::ConstRRsetPtr param_rrset) :
+            code(param_code), rrset(param_rrset)
+        {}
+        const Result code;
+        const isc::dns::ConstRRsetPtr rrset;
+    };
+
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    //@{
+protected:
+    /// The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class should
+    /// never be instantiated (except as part of a derived class).
+    Zone() {}
+public:
+    /// The destructor.
+    virtual ~Zone() {}
+    //@}
+
+    ///
+    /// \name Getter Methods
+    ///
+    /// These methods should never throw an exception.
+    //@{
+    /// Return the origin name of the zone.
+    virtual const isc::dns::Name& getOrigin() const = 0;
+
+    /// Return the RR class of the zone.
+    virtual const isc::dns::RRClass& getClass() const = 0;
+    //@}
+
+    ///
+    /// \name Search Method
+    ///
+    //@{
+    /// Search the zone for a given pair of domain name and RR type.
+    ///
+    /// Each derived version of this method searches the underlying backend
+    /// for the data that best matches the given name and type.
+    /// This method is expected to be "intelligent", and identifies the
+    /// best possible answer for the search key.  Specifically,
+    /// - If the search name belongs under a zone cut, it returns the code
+    ///   of \c DELEGATION and the NS RRset at the zone cut.
+    /// - If there is no matching name, it returns the code of \c NXDOMAIN,
+    ///   and, if DNSSEC is requested, the NSEC RRset that proves the
+    ///   non-existence.
+    /// - If there is a matching name but no RRset of the search type, it
+    ///   returns the code of \c NXRRSET, and, if DNSSEC is required,
+    ///   the NSEC RRset for that name.
+    /// - If there is a matching name with CNAME, it returns the code of
+    ///   \c CNAME and that CNAME RR.
+    /// - If the search name matches a delegation point of DNAME, it returns
+    ///   the code of \c DNAME and that DNAME RR.
+    ///
+    /// A derived version of this method may involve internal resource
+    /// allocation, especially for constructing the resulting RRset, and may
+    /// throw an exception if it fails.
+    /// It should not throw other types of exceptions.
+    ///
+    /// Note: It's quite likely that we'll need to specify search options.
+    /// For example, we should be able to specify whether to allow returning
+    /// glue records at or under a zone cut.  We leave this interface open
+    /// at this moment.
+    ///
+    /// \param name The domain name to be searched for.
+    /// \param type The RR type to be searched for.
+    /// \return A \c FindResult object enclosing the search result (see above).
+    virtual FindResult find(const isc::dns::Name& name,
+                            const isc::dns::RRType& type) const = 0;
+    //@}
+};
+
+/// \brief A pointer-like type pointing to a \c Zone object.
+typedef boost::shared_ptr<Zone> ZonePtr;
+
+/// \brief A pointer-like type pointing to a \c Zone object.
+typedef boost::shared_ptr<const Zone> ConstZonePtr;
+
+/// A derived zone class intended to be used with the memory data source.
+///
+/// Currently this is almost empty and is only used for testing the
+/// \c ZoneTable class.  It will be substantially expanded, and will probably
+/// moved to a separate header file.
+class MemoryZone : public Zone {
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    /// \b Note:
+    /// The copy constructor and the assignment operator are intentionally
+    /// defined as private, making this class non copyable.
+    //@{
+private:
+    MemoryZone(const MemoryZone& source);
+    MemoryZone& operator=(const MemoryZone& source);
+public:
+    /// \brief Constructor from zone parameters.
+    ///
+    /// This constructor internally involves resource allocation, and if
+    /// it fails, a corresponding standard exception will be thrown.
+    /// It never throws an exception otherwise.
+    ///
+    /// \param rrclass The RR class of the zone.
+    /// \param origin The origin name of the zone.
+    MemoryZone(const isc::dns::RRClass& rrclass, const isc::dns::Name& origin);
+
+    /// The destructor.
+    virtual ~MemoryZone();
+    //@}
+
+    virtual const isc::dns::Name& getOrigin() const;
+    virtual const isc::dns::RRClass& getClass() const;
+    virtual FindResult find(const isc::dns::Name& name,
+                            const isc::dns::RRType& type) const;
+
+private:
+    struct MemoryZoneImpl;
+    MemoryZoneImpl* impl_;
+};
+
+/// \brief A set of authoritative zones.
+///
+/// The \c ZoneTable class represents a set of zones of the same RR class
+/// and provides a basic interface to help DNS lookup processing.
+/// For a given domain name, its \c find() method searches the set for a zone
+/// that gives a longest match against that name.
+///
+/// The set of zones are assumed to be of the same RR class, but the
+/// \c ZoneTable class does not enforce the assumption through its interface.
+/// For example, the \c add() method does not check if the new zone
+/// is of the same RR class as that of the others already in the table.
+/// It is caller's responsibility to ensure this assumption.
+///
+/// <b>Notes to developer:</b>
+///
+/// The add() method takes a (Boost) shared pointer because it would be
+/// inconvenient to require the caller to maintain the ownership of zones,
+/// while it wouldn't be safe to delete unnecessary zones inside the zone
+/// table.
+///
+/// On the other hand, the find() method returns a bare pointer, rather than
+/// the shared pointer, in order to minimize the dependency on Boost
+/// definitions in our public interfaces.  This means the caller can only
+/// refer to the returned object (via the pointer) for a short period.
+///  It should be okay for simple lookup purposes, but if we see the need
+/// for keeping a \c Zone object for a longer period of context, we may
+/// have to revisit this decision.
+///
+/// Currently, \c FindResult::zone is immutable for safety.
+/// In future versions we may want to make it changeable.  For example,
+/// we may want to allow configuration update on an existing zone.
+///
+/// In BIND 9's "zt" module, the equivalent of \c find() has an "option"
+/// parameter.  The only defined option is the one to specify the "no exact"
+/// mode, and the only purpose of that mode is to prefer a second longest match
+/// even if there is an exact match in order to deal with type DS query.
+/// This trick may help enhance performance, but it also seems to make the
+/// implementation complicated for a very limited, minor case.  So, for now,
+/// we don't introduce the special mode, and, since it was the only reason to
+/// have search options in BIND 9, our initial implementation doesn't provide
+/// a switch for options.
+class ZoneTable {
+public:
+    /// Result codes of various public methods of \c ZoneTable.
+    ///
+    /// The detailed semantics may differ in different methods.
+    /// See the description of specific methods for more details.
+    enum Result {
+        SUCCESS,  ///< The operation is successful.
+        EXIST,    ///< A zone is already stored in \c ZoneTable.
+        NOTFOUND, ///< The specified zone is not found in \c ZoneTable.
+        PARTIALMATCH ///< \c Only a partial match is found in \c find(). 
+    };
+
+    /// \brief A helper structure to represent the search result of
+    /// <code>ZoneTable::find()</code>.
+    ///
+    /// This is a straightforward pair of the result code and a pointer
+    /// to the found zone to represent the result of \c find().
+    /// We use this in order to avoid overloading the return value for both
+    /// the result code ("success" or "not found") and the found object,
+    /// i.e., avoid using \c NULL to mean "not found", etc.
+    ///
+    /// This is a simple value class with no internal state, so for
+    /// convenience we allow the applications to refer to the members
+    /// directly.
+    ///
+    /// See the description of \c find() for the semantics of the member
+    /// variables.
+    struct FindResult {
+        FindResult(Result param_code, const Zone* param_zone) :
+            code(param_code), zone(param_zone)
+        {}
+        const Result code;
+        const Zone* const zone;
+    };
+
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    /// \b Note:
+    /// The copy constructor and the assignment operator are intentionally
+    /// defined as private, making this class non copyable.
+    //@{
+private:
+    ZoneTable(const ZoneTable& source);
+    ZoneTable& operator=(const ZoneTable& source);
+
+public:
+    /// Default constructor.
+    ///
+    /// This constructor internally involves resource allocation, and if
+    /// it fails, a corresponding standard exception will be thrown.
+    /// It never throws an exception otherwise.
+    ZoneTable();
+
+    /// The destructor.
+    ~ZoneTable();
+    //@}
+
+    /// Add a \c Zone to the \c ZoneTable.
+    ///
+    /// \c zone must not be associated with a NULL pointer; otherwise
+    /// an exception of class \c InvalidParameter will be thrown.
+    /// If internal resource allocation fails, a corresponding standard
+    /// exception will be thrown.
+    /// This method never throws an exception otherwise.
+    ///
+    /// \param zone A \c Zone object to be added.
+    /// \return \c SUCCESS If the zone is successfully added to the zone table.
+    /// \return \c EXIST The zone table already stores a zone that has the
+    /// same origin.
+    Result add(ZonePtr zone);
+
+    /// Remove a \c Zone of the given origin name from the \c ZoneTable.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param origin The origin name of the zone to be removed.
+    /// \return \c SUCCESS If the zone is successfully removed from the
+    /// zone table.
+    /// \return \c NOTFOUND The zone table does not store the zone that matches
+    /// \c origin.
+    Result remove(const isc::dns::Name& origin);
+
+    /// Find a \c Zone that best matches the given name in the \c ZoneTable.
+    ///
+    /// It searches the internal storage for a \c Zone that gives the
+    /// longest match against \c name, and returns the result in the
+    /// form of a \c FindResult object as follows:
+    /// - \c code: The result code of the operation.
+    ///   - \c SUCCESS: A zone that gives an exact match is found
+    ///   - \c PARTIALMATCH: A zone whose origin is a super domain of
+    ///     \c name is found (but there is no exact match)
+    ///   - \c NOTFOUND: For all other cases.
+    /// - \c zone: A pointer to the found \c Zone object if one is found;
+    /// otherwise \c NULL.
+    ///
+    /// The pointer returned in the \c FindResult object is only valid until
+    /// the corresponding zone is removed from the zone table.
+    /// The caller must ensure that the zone is held in the zone table while
+    /// it needs to refer to it.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param name A domain name for which the search is performed.
+    /// \return A \c FindResult object enclosing the search result (see above).
+    FindResult find(const isc::dns::Name& name) const;
+
+private:
+    struct ZoneTableImpl;
+    ZoneTableImpl* impl_;
+};
+}
+}
+#endif  // __ZONETABLE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 6 - 2
src/lib/dns/Makefile.am

@@ -13,6 +13,8 @@ EXTRA_DIST += rrtype-placeholder.h
 
 
 # TODO: double-check that this is the only way
 # TODO: double-check that this is the only way
 # NOTE: when an rdata file is added, please also add to this list:
 # NOTE: when an rdata file is added, please also add to this list:
+EXTRA_DIST += rdata/any_255/tsig_250.cc
+EXTRA_DIST += rdata/any_255/tsig_250.h
 EXTRA_DIST += rdata/in_1/aaaa_28.cc
 EXTRA_DIST += rdata/in_1/aaaa_28.cc
 EXTRA_DIST += rdata/in_1/aaaa_28.h
 EXTRA_DIST += rdata/in_1/aaaa_28.h
 EXTRA_DIST += rdata/in_1/a_1.cc
 EXTRA_DIST += rdata/in_1/a_1.cc
@@ -81,7 +83,7 @@ libdns___la_SOURCES += rrttl.h rrttl.cc
 libdns___la_SOURCES += rrtype.cc
 libdns___la_SOURCES += rrtype.cc
 libdns___la_SOURCES += question.h question.cc
 libdns___la_SOURCES += question.h question.cc
 libdns___la_SOURCES += util/sha1.h util/sha1.cc
 libdns___la_SOURCES += util/sha1.h util/sha1.cc
-libdns___la_SOURCES += tsig.h tsig.cc
+libdns___la_SOURCES += tsigkey.h tsigkey.cc
 
 
 nodist_libdns___la_SOURCES = rdataclass.cc rrclass.h rrtype.h
 nodist_libdns___la_SOURCES = rdataclass.cc rrclass.h rrtype.h
 nodist_libdns___la_SOURCES += rrparamregistry.cc
 nodist_libdns___la_SOURCES += rrparamregistry.cc
@@ -96,11 +98,13 @@ libdns___includedir = $(includedir)/dns
 libdns___include_HEADERS = \
 libdns___include_HEADERS = \
 	buffer.h \
 	buffer.h \
 	dnssectime.h \
 	dnssectime.h \
+	edns.h \
 	exceptions.h \
 	exceptions.h \
 	message.h \
 	message.h \
 	messagerenderer.h \
 	messagerenderer.h \
 	name.h \
 	name.h \
 	question.h \
 	question.h \
+	rcode.h \
 	rdata.h \
 	rdata.h \
 	rdataclass.h \
 	rdataclass.h \
 	rrclass.h \
 	rrclass.h \
@@ -109,7 +113,7 @@ libdns___include_HEADERS = \
 	rrsetlist.h \
 	rrsetlist.h \
 	rrttl.h \
 	rrttl.h \
 	rrtype.h \
 	rrtype.h \
-	tsig.h
+	tsigkey.h
 # Purposely not installing these headers:
 # Purposely not installing these headers:
 # util/*.h: used only internally, and not actually DNS specific
 # util/*.h: used only internally, and not actually DNS specific
 # rrclass-placeholder.h
 # rrclass-placeholder.h

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

@@ -24,6 +24,7 @@ EXTRA_DIST += question_python.cc
 EXTRA_DIST += rrttl_python.cc
 EXTRA_DIST += rrttl_python.cc
 EXTRA_DIST += rdata_python.cc
 EXTRA_DIST += rdata_python.cc
 EXTRA_DIST += rrtype_python.cc
 EXTRA_DIST += rrtype_python.cc
+EXTRA_DIST += tsigkey_python.cc
 
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.
 # suffix for dynamic objects.  -module is necessary to work this around.

+ 9 - 0
src/lib/dns/python/pydnspp.cc

@@ -57,6 +57,7 @@ static PyObject* po_DNSMessageBADVERS;
 #include <dns/python/rrset_python.cc>          // needs Rdata, RRTTL
 #include <dns/python/rrset_python.cc>          // needs Rdata, RRTTL
 #include <dns/python/question_python.cc>       // needs RRClass, RRType, RRTTL,
 #include <dns/python/question_python.cc>       // needs RRClass, RRType, RRTTL,
                                                // Name
                                                // Name
+#include <dns/python/tsigkey_python.cc>        // needs Name
 #include <dns/python/opcode_python.cc>
 #include <dns/python/opcode_python.cc>
 #include <dns/python/rcode_python.cc>
 #include <dns/python/rcode_python.cc>
 #include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
 #include <dns/python/edns_python.cc>           // needs Messagerenderer, Rcode
@@ -146,6 +147,14 @@ PyInit_pydnspp(void) {
         return (NULL);
         return (NULL);
     }
     }
 
 
+    if (!initModulePart_TSIGKey(mod)) {
+        return (NULL);
+    }
+
+    if (!initModulePart_TSIGKeyRing(mod)) {
+        return (NULL);
+    }
+
     return (mod);
     return (mod);
 }
 }
 
 

+ 1 - 1
src/lib/dns/python/rrset_python.cc

@@ -355,7 +355,7 @@ RRset_getRdata(s_RRset* self) {
 
 
     RdataIteratorPtr it = self->rrset->getRdataIterator();
     RdataIteratorPtr it = self->rrset->getRdataIterator();
 
 
-    for (it->first(); !it->isLast(); it->next()) {
+    for (; !it->isLast(); it->next()) {
         s_Rdata *rds = static_cast<s_Rdata*>(rdata_type.tp_alloc(&rdata_type, 0));
         s_Rdata *rds = static_cast<s_Rdata*>(rdata_type.tp_alloc(&rdata_type, 0));
         if (rds != NULL) {
         if (rds != NULL) {
             // hmz them iterators/shared_ptrs and private constructors
             // hmz them iterators/shared_ptrs and private constructors

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

@@ -10,6 +10,7 @@ PYTESTS += rrclass_python_test.py
 PYTESTS += rrset_python_test.py
 PYTESTS += rrset_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrtype_python_test.py
 PYTESTS += rrtype_python_test.py
+PYTESTS += tsigkey_python_test.py
 
 
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += testutil.py
 EXTRA_DIST += testutil.py

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

@@ -0,0 +1,174 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# $Id$
+
+import unittest
+from pydnspp import *
+
+class TSIGKeyTest(unittest.TestCase):
+    key_name = Name('example.com')
+    secret = b'anotherRandomData'
+
+    def test_algorithm_names(self):
+        self.assertEqual(Name('hmac-md5.sig-alg.reg.int'),
+                         TSIGKey.HMACMD5_NAME)
+        self.assertEqual(Name('hmac-sha1'), TSIGKey.HMACSHA1_NAME)
+        self.assertEqual(Name('hmac-sha256'), TSIGKey.HMACSHA256_NAME)
+
+    def test_init(self):
+        key = TSIGKey(self.key_name, TSIGKey.HMACMD5_NAME, self.secret)
+        self.assertEqual(self.key_name, key.get_key_name())
+        self.assertEqual(Name('hmac-md5.sig-alg.reg.int'),
+                         key.get_algorithm_name())
+        self.assertEqual(self.secret, key.get_secret())
+
+        self.assertRaises(InvalidParameter, TSIGKey, self.key_name,
+                          Name('unknown-alg'), self.secret)
+
+        self.assertEqual('hmac-sha1.',
+                         TSIGKey(self.key_name, TSIGKey.HMACSHA1_NAME,
+                                 self.secret).get_algorithm_name().to_text())
+
+        self.assertRaises(TypeError, TSIGKey, self.key_name,
+                          TSIGKey.HMACMD5_NAME,
+                          'should be binary') # signature mismatch
+
+class TSIGKeyRingTest(unittest.TestCase):
+    key_name = Name('example.com')
+    secret = b'someRandomData'
+
+    def setUp(self):
+        self.keyring = TSIGKeyRing()
+
+    def test_init(self):
+        self.assertEqual(0, self.keyring.size())
+        self.assertRaises(TypeError, TSIGKeyRing, 1)
+        self.assertRaises(TypeError, TSIGKeyRing, 'there should not be arg')
+
+    def test_add(self):
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(self.key_name,
+                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.secret)))
+        self.assertEqual(1, self.keyring.size())
+        self.assertEqual(TSIGKeyRing.EXIST,
+                         self.keyring.add(TSIGKey(self.key_name,
+                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.EXIST,
+                         self.keyring.add(TSIGKey(self.key_name,
+                                                  TSIGKey.HMACSHA1_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.EXIST,
+                         self.keyring.add(TSIGKey(Name('EXAMPLE.COM'),
+                                                  TSIGKey.HMACSHA1_NAME,
+                                                  self.secret)))
+        self.assertEqual(1, self.keyring.size())
+
+    def test_add_more(self):
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(self.key_name,
+                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(Name('another.example'),
+                                                  TSIGKey.HMACMD5_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(Name('more.example'),
+                                                  TSIGKey.HMACSHA1_NAME,
+                                                  self.secret)))
+        self.assertEqual(3, self.keyring.size())
+
+        self.assertRaises(TypeError, self.keyring.add, 1)
+        self.assertRaises(TypeError, self.keyring.add, 'invalid arg')
+
+    def test_remove(self):
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(self.key_name,
+                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.remove(self.key_name))
+        self.assertEqual(TSIGKeyRing.NOTFOUND,
+                         self.keyring.remove(self.key_name))
+
+        self.assertRaises(TypeError, self.keyring.add, 1)
+        self.assertRaises(TypeError, self.keyring.add, 'invalid arg')
+        self.assertRaises(TypeError, self.keyring.add, self.key_name, 0)
+
+    def test_remove_from_some(self):
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(self.key_name,
+                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(Name('another.example'),
+                                                  TSIGKey.HMACMD5_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(Name('more.example'),
+                                                  TSIGKey.HMACSHA1_NAME,
+                                                  self.secret)))
+
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.remove(Name('another.example')))
+        self.assertEqual(TSIGKeyRing.NOTFOUND,
+                         self.keyring.remove(Name('noexist.example')))
+        self.assertEqual(2, self.keyring.size())
+
+    def test_find(self):
+        self.assertEqual((TSIGKeyRing.NOTFOUND, None),
+                         self.keyring.find(self.key_name))
+
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(self.key_name,
+                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.secret)))
+        (code, key) = self.keyring.find(self.key_name)
+        self.assertEqual(TSIGKeyRing.SUCCESS, code)
+        self.assertEqual(self.key_name, key.get_key_name())
+        self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
+        self.assertEqual(self.secret, key.get_secret())
+
+        self.assertRaises(TypeError, self.keyring.find, 1)
+        self.assertRaises(TypeError, self.keyring.find, 'should be a name')
+        self.assertRaises(TypeError, self.keyring.find, self.key_name, 0)
+
+    def test_find_from_some(self):
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(self.key_name,
+                                                  TSIGKey.HMACSHA256_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(Name('another.example'),
+                                                  TSIGKey.HMACMD5_NAME,
+                                                  self.secret)))
+        self.assertEqual(TSIGKeyRing.SUCCESS,
+                         self.keyring.add(TSIGKey(Name('more.example'),
+                                                  TSIGKey.HMACSHA1_NAME,
+                                                  self.secret)))
+
+        (code, key) = self.keyring.find(Name('another.example'))
+        self.assertEqual(TSIGKeyRing.SUCCESS, code)
+        self.assertEqual(Name('another.example'), key.get_key_name())
+        self.assertEqual(TSIGKey.HMACMD5_NAME, key.get_algorithm_name())
+
+        self.assertEqual((TSIGKeyRing.NOTFOUND, None),
+                         self.keyring.find(Name('noexist.example')))
+
+if __name__ == '__main__':
+    unittest.main()

+ 455 - 0
src/lib/dns/python/tsigkey_python.cc

@@ -0,0 +1,455 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <new>
+
+#include <dns/tsigkey.h>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+//
+// Definition of the classes
+//
+
+// For each class, we need a struct, a helper functions (init, destroy,
+// and static wrappers around the methods we export), a list of methods,
+// and a type description
+
+namespace {
+//
+// TSIGKey
+//
+
+// The s_* Class simply covers one instantiation of the object
+
+class s_TSIGKey : public PyObject {
+public:
+    s_TSIGKey() : tsigkey(NULL) {}
+    TSIGKey* tsigkey;
+};
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+// General creation and destruction
+int TSIGKey_init(s_TSIGKey* self, PyObject* args);
+void TSIGKey_destroy(s_TSIGKey* self);
+
+// These are the functions we export
+// This is a second version of toText, we need one where the argument
+// is a PyObject*, for the str() function in python.
+PyObject* TSIGKey_getKeyName(const s_TSIGKey* self);
+PyObject* TSIGKey_getAlgorithmName(const s_TSIGKey* self);
+PyObject* TSIGKey_getSecret(const s_TSIGKey* self);
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef TSIGKey_methods[] = {
+    { "get_key_name",
+      reinterpret_cast<PyCFunction>(TSIGKey_getKeyName), METH_NOARGS,
+      "Return the key name." },
+    { "get_algorithm_name",
+      reinterpret_cast<PyCFunction>(TSIGKey_getAlgorithmName), METH_NOARGS,
+      "Return the algorithm name." },
+    { "get_secret",
+      reinterpret_cast<PyCFunction>(TSIGKey_getSecret), METH_NOARGS,
+      "Return the value of the TSIG secret." },
+    { NULL, NULL, 0, NULL }
+};
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_EDNS
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsigkey_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python.TSIGKey",
+    sizeof(s_TSIGKey),                  // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)TSIGKey_destroy,        // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash 
+    NULL,                               // tp_call
+    NULL,                               // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The TSIGKey class holds a TSIG key along with some related attributes as "
+    "defined in RFC2845.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    TSIGKey_methods,                    // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    (initproc)TSIGKey_init,             // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+// A helper function to build a python "Name" object with error handling
+// encapsulated.
+s_Name*
+createNameObject(const Name& source) {
+    s_Name* name = PyObject_New(s_Name, &name_type);
+    if (name == NULL) {
+        return (NULL);
+    }
+    name->name = new(nothrow) Name(source);
+    if (name->name == NULL) {
+        Py_DECREF(name);
+        PyErr_SetString(po_IscException, "Allocating Name object failed");
+        return (NULL);
+    }
+    return (name);
+}
+
+int
+TSIGKey_init(s_TSIGKey* self, PyObject* args) {
+    const s_Name* key_name;
+    const s_Name* algorithm_name;
+    PyObject* bytes_obj;
+    const char* secret;
+    Py_ssize_t secret_len;
+
+    if (PyArg_ParseTuple(args, "O!O!O", &name_type, &key_name,
+                         &name_type, &algorithm_name, &bytes_obj) &&
+        PyObject_AsCharBuffer(bytes_obj, &secret, &secret_len) != -1) {
+        try {
+            self->tsigkey = new TSIGKey(*key_name->name,
+                                        *algorithm_name->name,
+                                        secret, secret_len);
+        } catch (const isc::InvalidParameter& ex) {
+            PyErr_SetString(po_InvalidParameter, ex.what());
+            return (-1);
+        } catch (...) {
+            PyErr_SetString(po_IscException, "Unexpected exception");
+            return (-1);
+        }
+        return (0);
+    }
+
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIGKey constructor");
+
+    return (-1);
+}
+
+void
+TSIGKey_destroy(s_TSIGKey* const self) {
+    delete self->tsigkey;
+    self->tsigkey = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGKey_getKeyName(const s_TSIGKey* const self) {
+    return (createNameObject(self->tsigkey->getKeyName()));
+}
+
+PyObject*
+TSIGKey_getAlgorithmName(const s_TSIGKey* const self) {
+    return (createNameObject(self->tsigkey->getAlgorithmName()));
+}
+
+PyObject*
+TSIGKey_getSecret(const s_TSIGKey* const self) {
+    return (Py_BuildValue("y#", self->tsigkey->getSecret(),
+                          self->tsigkey->getSecretLength()));
+}
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGKey(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&tsigkey_type) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsigkey_type);
+    void* p = &tsigkey_type;
+    if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
+        Py_DECREF(&tsigkey_type);
+        return (false);
+    }
+
+    s_Name* name;
+    if ((name = createNameObject(TSIGKey::HMACMD5_NAME())) == NULL) {
+        goto cleanup;
+    }
+    addClassVariable(tsigkey_type, "HMACMD5_NAME", name);
+    if ((name = createNameObject(TSIGKey::HMACSHA1_NAME())) == NULL) {
+        goto cleanup;
+    }
+    addClassVariable(tsigkey_type, "HMACSHA1_NAME", name);
+    if ((name = createNameObject(TSIGKey::HMACSHA256_NAME())) == NULL) {
+        goto cleanup;
+    }
+    addClassVariable(tsigkey_type, "HMACSHA256_NAME", name);
+
+    return (true);
+
+  cleanup:
+    Py_DECREF(&tsigkey_type);
+    return (false);
+}
+//
+// End of TSIGKey
+//
+
+//
+// TSIGKeyRing
+//
+
+// The s_* Class simply covers one instantiation of the object
+
+// The s_* Class simply covers one instantiation of the object
+
+class s_TSIGKeyRing : public PyObject {
+public:
+    s_TSIGKeyRing() : keyring(NULL) {}
+    TSIGKeyRing* keyring;
+};
+
+//
+// We declare the functions here, the definitions are below
+// the type definition of the object, since both can use the other
+//
+
+int TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args);
+void TSIGKeyRing_destroy(s_TSIGKeyRing* self);
+
+PyObject* TSIGKeyRing_size(const s_TSIGKeyRing* self);
+PyObject* TSIGKeyRing_add(const s_TSIGKeyRing* self, PyObject* args);
+PyObject* TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args);
+PyObject* TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args);
+
+PyMethodDef TSIGKeyRing_methods[] = {
+    { "size", reinterpret_cast<PyCFunction>(TSIGKeyRing_size), METH_NOARGS,
+      "Return the number of keys stored in the TSIGKeyRing." },
+    { "add", reinterpret_cast<PyCFunction>(TSIGKeyRing_add), METH_VARARGS,
+      "Add a TSIGKey to the TSIGKeyRing." },
+    { "remove", reinterpret_cast<PyCFunction>(TSIGKeyRing_remove),
+      METH_VARARGS,
+      "Remove a TSIGKey for the given name from the TSIGKeyRing." },
+    { "find", reinterpret_cast<PyCFunction>(TSIGKeyRing_find), METH_VARARGS,
+      "Find a TSIGKey for the given name in the TSIGKeyRing. "
+      "It returns a tuple of (result_code, key)." },
+    { NULL, NULL, 0, NULL }
+};
+
+PyTypeObject tsigkeyring_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python.TSIGKeyRing",
+    sizeof(s_TSIGKeyRing),              // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)TSIGKeyRing_destroy,    // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash 
+    NULL,                               // tp_call
+    NULL,                               // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "A simple repository of a set of TSIGKey objects.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    TSIGKeyRing_methods,                // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    (initproc)TSIGKeyRing_init,         // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+int
+TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
+    if (!PyArg_ParseTuple(args, "")) {
+        PyErr_Clear();
+        PyErr_SetString(PyExc_TypeError,
+                        "Invalid arguments to TSIGKeyRing constructor");
+        return (-1);
+    }
+    
+    self->keyring = new(nothrow) TSIGKeyRing();
+    if (self->keyring == NULL) {
+        PyErr_SetString(po_IscException, "Allocating TSIGKeyRing failed");
+        return (-1);
+    }
+
+    return (0);
+}
+
+void
+TSIGKeyRing_destroy(s_TSIGKeyRing* self) {
+    delete self->keyring;
+    self->keyring = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGKeyRing_size(const s_TSIGKeyRing* const self) {
+    return (Py_BuildValue("I", self->keyring->size()));
+}
+
+PyObject*
+TSIGKeyRing_add(const s_TSIGKeyRing* const self, PyObject* args) {
+    s_TSIGKey* tsigkey;
+    
+    if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey)) {
+        try {
+            const TSIGKeyRing::Result result =
+                self->keyring->add(*tsigkey->tsigkey);
+            return (Py_BuildValue("I", result));
+        } catch (...) {
+            PyErr_SetString(po_IscException, "Unexpected exception");
+            return (NULL);
+        }
+    }
+
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError, "Invalid arguments to TSIGKeyRing.add");
+
+    return (NULL);
+}
+
+PyObject*
+TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
+    s_Name* key_name;
+
+    if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
+        const TSIGKeyRing::Result result =
+            self->keyring->remove(*key_name->name);
+        return (Py_BuildValue("I", result));
+    }
+
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError, "Invalid arguments to TSIGKeyRing.add");
+
+    return (NULL);
+}
+
+PyObject*
+TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
+    s_Name* key_name;
+
+    if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
+        const TSIGKeyRing::FindResult result =
+            self->keyring->find(*key_name->name);
+        if (result.key != NULL) {
+            s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
+            if (key == NULL) {
+                return (NULL);
+            }
+            key->tsigkey = new(nothrow) TSIGKey(*result.key);
+            if (key->tsigkey == NULL) {
+                Py_DECREF(key);
+                PyErr_SetString(po_IscException,
+                                "Allocating TSIGKey object failed");
+                return (NULL);
+            }
+            return (Py_BuildValue("IN", result.code, key));
+        } else {
+            return (Py_BuildValue("Is", result.code, NULL));
+        }
+    }
+
+    return (NULL);
+}
+
+bool
+initModulePart_TSIGKeyRing(PyObject* mod) {
+    if (PyType_Ready(&tsigkeyring_type) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsigkeyring_type);
+    void* p = &tsigkeyring_type;
+    if (PyModule_AddObject(mod, "TSIGKeyRing",
+                           static_cast<PyObject*>(p)) != 0) {
+        Py_DECREF(&tsigkeyring_type);
+        return (false);
+    }
+
+    addClassVariable(tsigkeyring_type, "SUCCESS",
+                     Py_BuildValue("I", TSIGKeyRing::SUCCESS));
+    addClassVariable(tsigkeyring_type, "EXIST",
+                     Py_BuildValue("I", TSIGKeyRing::EXIST));
+    addClassVariable(tsigkeyring_type, "NOTFOUND",
+                     Py_BuildValue("I", TSIGKeyRing::NOTFOUND));
+
+    return (true);
+}
+
+} // end of unnamed namespace

+ 501 - 0
src/lib/dns/rdata/any_255/tsig_250.cc

@@ -0,0 +1,501 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <string>
+#include <sstream>
+#include <vector>
+
+#include <boost/lexical_cast.hpp>
+
+#include <dns/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+#include <dns/util/base64.h>
+
+using namespace std;
+using namespace boost;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// This is a straightforward representation of TSIG RDATA fields.
+struct TSIG::TSIGImpl {
+    TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+             vector<uint8_t>& mac, uint16_t original_id, uint16_t error,
+             vector<uint8_t>& other_data) :
+        algorithm_(algorithm), time_signed_(time_signed), fudge_(fudge),
+        mac_(mac), original_id_(original_id), error_(error),
+        other_data_(other_data)
+    {}
+    TSIGImpl(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+             size_t macsize, const void* mac, uint16_t original_id,
+             uint16_t error, size_t other_len, const void* other_data) :
+        algorithm_(algorithm), time_signed_(time_signed), fudge_(fudge),
+        mac_(static_cast<const uint8_t*>(mac),
+             static_cast<const uint8_t*>(mac) + macsize),
+        original_id_(original_id), error_(error),
+        other_data_(static_cast<const uint8_t*>(other_data),
+                    static_cast<const uint8_t*>(other_data) + other_len)
+    {}
+    template <typename Output>
+    void toWireCommon(Output& output) const;
+
+    const Name algorithm_;
+    const uint64_t time_signed_;
+    const uint16_t fudge_;
+    const vector<uint8_t> mac_;
+    const uint16_t original_id_;
+    const uint16_t error_;
+    const vector<uint8_t> other_data_;
+};
+
+namespace {
+string
+getToken(istringstream& iss, const string& full_input) {
+    string token;
+    iss >> token;
+    if (iss.bad() || iss.fail()) {
+        isc_throw(InvalidRdataText, "Invalid TSIG text: parse error" <<
+                  full_input);
+    }
+    return (token);
+}
+
+// This helper function converts a string token to an *unsigned* integer.
+// NumType is a *signed* integral type (e.g. int32_t) that is sufficiently
+// wide to store resulting integers.
+// BitSize is the maximum number of bits that the resulting integer can take.
+// This function first checks whether the given token can be converted to
+// an integer of NumType type.  It then confirms the conversion result is
+// within the valid range, i.e., [0, 2^NumType - 1].  The second check is
+// necessary because lexical_cast<T> where T is an unsigned integer type
+// doesn't correctly reject negative numbers when compiled with SunStudio.
+template <typename NumType, int BitSize>
+NumType
+tokenToNum(const string& num_token) {
+    NumType num;
+    try {
+        num = lexical_cast<NumType>(num_token);
+    } catch (const boost::bad_lexical_cast& ex) {
+        isc_throw(InvalidRdataText, "Invalid TSIG numeric parameter: " <<
+                  num_token);
+    }
+    if (num < 0 || num >= (static_cast<NumType>(1) << BitSize)) {
+        isc_throw(InvalidRdataText, "Numeric TSIG parameter out of range: " <<
+                  num);
+    }
+    return (num);
+}
+}
+
+/// \brief Constructor from string.
+///
+/// \c tsig_str must be formatted as follows:
+/// \code <Alg> <Time> <Fudge> <MACsize> [<MAC>] <OrigID> <Error> <OtherLen> [<OtherData>]
+/// \endcode
+/// where
+/// - <Alg> is a valid textual representation of domain name.
+/// - <Time> is an unsigned 48-bit decimal integer.
+/// - <MACSize>, <OrigID>, and <OtherLen> are an unsigned 16-bit decimal
+///   integer.
+/// - <Error> is an unsigned 16-bit decimal integer or a valid mnemonic for
+///   the Error field specified in RFC2845.  Currently, "BADSIG", "BADKEY",
+///   and "BADTIME" are supported (case sensitive).  In future versions
+///   other representations that are compatible with the DNS RCODE will be
+///   supported.
+/// - <MAC> and <OtherData> is a BASE-64 encoded string that does not contain
+///   space characters.
+///   When <MACSize> and <OtherLen> is 0, <MAC> and <OtherData> must not
+///   appear in \c tsgi_str, respectively.
+/// - The decoded data of <MAC> is <MACSize> bytes of binary stream.
+/// - The decoded data of <OtherData> is <OtherLen> bytes of binary stream.
+///
+/// An example of valid string is:
+/// \code "hmac-sha256. 853804800 300 3 AAAA 2845 0 0" \endcode
+/// In this example <OtherData> is missing because <OtherLen> is 0.
+///
+/// Note that RFC2845 does not define the standard presentation format
+/// of %TSIG RR, so the above syntax is implementation specific.
+/// This is, however, compatible with the format acceptable to BIND 9's
+/// RDATA parser.
+///
+/// <b>Exceptions</b>
+///
+/// If <Alg> is not a valid domain name, a corresponding exception from
+/// the \c Name class will be thrown;
+/// if <MAC> or <OtherData> is not validly encoded in BASE-64, an exception
+/// of class \c isc::BadValue will be thrown;
+/// if %any of the other bullet points above is not met, an exception of
+/// class \c InvalidRdataText will be thrown.
+/// This constructor internally involves resource allocation, and if it fails
+/// a corresponding standard exception will be thrown.
+TSIG::TSIG(const std::string& tsig_str) : impl_(NULL) {
+    istringstream iss(tsig_str);
+
+    const Name algorithm(getToken(iss, tsig_str));
+    const int64_t time_signed = tokenToNum<int64_t, 48>(getToken(iss,
+                                                                 tsig_str));
+    const int32_t fudge = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
+    const int32_t macsize = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
+
+    const string mac_txt = (macsize > 0) ? getToken(iss, tsig_str) : "";
+    vector<uint8_t> mac;
+    decodeBase64(mac_txt, mac);
+    if (mac.size() != macsize) {
+        isc_throw(InvalidRdataText, "TSIG MAC size and data are inconsistent");
+    }
+
+    const int32_t orig_id = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
+
+    const string error_txt = getToken(iss, tsig_str);
+    int32_t error = 0;
+    // XXX: In the initial implementation we hardcode the mnemonics.
+    // We'll soon generalize this.
+    if (error_txt == "BADSIG") {
+        error = 16;
+    } else if (error_txt == "BADKEY") {
+        error = 17;
+    } else if (error_txt == "BADTIME") {
+        error = 18;
+    } else {
+        error = tokenToNum<int32_t, 16>(error_txt);
+    }
+
+    const int32_t otherlen = tokenToNum<int32_t, 16>(getToken(iss, tsig_str));
+    const string otherdata_txt = (otherlen > 0) ? getToken(iss, tsig_str) : "";
+    vector<uint8_t> other_data;
+    decodeBase64(otherdata_txt, other_data);
+
+    if (!iss.eof()) {
+        isc_throw(InvalidRdataText, "Unexpected input for TSIG RDATA: " <<
+                  tsig_str);
+    }
+
+    impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac, orig_id,
+                         error, other_data);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// When a read operation on \c buffer fails (e.g., due to a corrupted
+/// message) a corresponding exception from the \c InputBuffer class will
+/// be thrown.
+/// If the wire-format data does not begin with a valid domain name,
+/// a corresponding exception from the \c Name class will be thrown.
+/// In addition, this constructor internally involves resource allocation,
+/// and if it fails a corresponding standard exception will be thrown.
+///
+/// According to RFC3597, the Algorithm field must be a non compressed form
+/// of domain name.  But this implementation accepts a %TSIG RR even if that
+/// field is compressed.
+///
+/// \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.
+/// But this constructor does not use this parameter; if necessary, the caller
+/// must check consistency between the length parameter and the actual
+/// RDATA length.
+TSIG::TSIG(InputBuffer& buffer, size_t) : impl_(NULL) {
+    Name algorithm(buffer);
+
+    uint8_t time_signed_buf[6];
+    buffer.readData(time_signed_buf, sizeof(time_signed_buf));
+    const uint64_t time_signed =
+        (static_cast<uint64_t>(time_signed_buf[0]) << 40 |
+         static_cast<uint64_t>(time_signed_buf[1]) << 32 |
+         static_cast<uint64_t>(time_signed_buf[2]) << 24 |
+         static_cast<uint64_t>(time_signed_buf[3]) << 16 |
+         static_cast<uint64_t>(time_signed_buf[4]) << 8 |
+         static_cast<uint64_t>(time_signed_buf[5]));
+
+    const uint16_t fudge = buffer.readUint16();
+
+    const uint16_t mac_size = buffer.readUint16();
+    vector<uint8_t> mac(mac_size);
+    if (mac_size > 0) {
+        buffer.readData(&mac[0], mac_size);
+    }
+
+    const uint16_t original_id = buffer.readUint16();
+    const uint16_t error = buffer.readUint16();
+
+    const uint16_t other_len = buffer.readUint16();
+    vector<uint8_t> other_data(other_len);
+    if (other_len > 0) {
+        buffer.readData(&other_data[0], other_len);
+    }
+
+    impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac, original_id,
+                         error, other_data);
+}
+
+TSIG::TSIG(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+           uint16_t mac_size, const void* mac, uint16_t original_id,
+           uint16_t error, uint16_t other_len, const void* other_data) :
+    impl_(NULL)
+{
+    // Time Signed is a 48-bit value.
+    if ((time_signed >> 48) != 0) {
+        isc_throw(OutOfRange, "TSIG Time Signed is too large: " <<
+                  time_signed);
+    }
+    if ((mac_size == 0 && mac != NULL) || (mac_size > 0 && mac == NULL)) {
+        isc_throw(InvalidParameter, "TSIG MAC size and data inconsistent");
+    }
+    if ((other_len == 0 && other_data != NULL) ||
+        (other_len > 0 && other_data == NULL)) {
+        isc_throw(InvalidParameter,
+                  "TSIG Other data length and data inconsistent");
+    }
+    impl_ = new TSIGImpl(algorithm, time_signed, fudge, mac_size, mac,
+                         original_id, error, other_len, other_data);
+}
+
+/// \brief The copy constructor.
+///
+/// It internally allocates a resource, and if it fails a corresponding
+/// standard exception will be thrown.
+/// This constructor never throws an exception otherwise.
+TSIG::TSIG(const TSIG& source) : Rdata(), impl_(new TSIGImpl(*source.impl_))
+{}
+
+TSIG&
+TSIG::operator=(const TSIG& source) {
+    if (impl_ == source.impl_) {
+        return (*this);
+    }
+
+    TSIGImpl* newimpl = new TSIGImpl(*source.impl_);
+    delete impl_;
+    impl_ = newimpl;
+
+    return (*this);
+}
+
+TSIG::~TSIG() {
+    delete impl_;
+}
+
+/// \brief Convert the \c TSIG to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c TSIG(const std::string&))).
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+///
+/// \return A \c string object that represents the \c TSIG object.
+std::string
+TSIG::toText() const {
+    string result;
+
+    result += impl_->algorithm_.toText() + " " +
+        lexical_cast<string>(impl_->time_signed_) + " " +
+        lexical_cast<string>(impl_->fudge_) + " " +
+        lexical_cast<string>(impl_->mac_.size()) + " ";
+    if (impl_->mac_.size() > 0) {
+        result += encodeBase64(impl_->mac_) + " ";
+    }
+    result += lexical_cast<string>(impl_->original_id_) + " ";
+    if (impl_->error_ == 16) {  // XXX: we'll soon introduce generic converter.
+        result += "BADSIG ";
+    } else if (impl_->error_ == 17) {
+        result += "BADKEY ";
+    } else if (impl_->error_ == 18) {
+        result += "BADTIME ";
+    } else {
+        result += lexical_cast<string>(impl_->error_) + " ";
+    }
+    result += lexical_cast<string>(impl_->other_data_.size());
+    if (impl_->other_data_.size() > 0) {
+        result += " " + encodeBase64(impl_->other_data_);
+    }
+
+    return (result);
+}
+
+// Common sequence of toWire() operations used for the two versions of
+// toWire().
+template <typename Output>
+void
+TSIG::TSIGImpl::toWireCommon(Output& output) const {
+    output.writeUint16(time_signed_ >> 32);
+    output.writeUint32(time_signed_ & 0xffffffff);
+    output.writeUint16(fudge_);
+    const uint16_t mac_size = mac_.size();
+    output.writeUint16(mac_size);
+    if (mac_size > 0) {
+        output.writeData(&mac_[0], mac_size);
+    }
+    output.writeUint16(original_id_);
+    output.writeUint16(error_);
+    const uint16_t other_len = other_data_.size();
+    output.writeUint16(other_len);
+    if (other_len > 0) {
+        output.writeData(&other_data_[0], other_len);
+    }
+}
+
+/// \brief Render the \c TSIG in the wire format without name compression.
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param buffer An output buffer to store the wire data.
+void
+TSIG::toWire(OutputBuffer& buffer) const {
+    impl_->algorithm_.toWire(buffer);
+    impl_->toWireCommon<OutputBuffer>(buffer);
+}
+
+/// \brief Render the \c TSIG in the wire format with taking into account
+/// compression.
+///
+/// As specified in RFC3597, the Algorithm field (a domain name) will not
+/// be compressed.  However, the domain name could be a target of compression
+/// of other compressible names (though pretty unlikely), the offset
+/// information of the algorithm name may be recorded in \c renderer.
+///
+/// If internal resource allocation fails, a corresponding
+/// standard exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \param renderer DNS message rendering context that encapsulates the
+/// output buffer and name compression information.
+void
+TSIG::toWire(MessageRenderer& renderer) const {
+    renderer.writeName(impl_->algorithm_, false);
+    impl_->toWireCommon<MessageRenderer>(renderer);
+}
+
+// A helper function commonly used for TSIG::compare().
+int
+vectorComp(const vector<uint8_t>& v1, const vector<uint8_t>& v2) {
+    const size_t this_size = v1.size();
+    const size_t other_size = v2.size();
+    if (this_size != other_size) {
+        return (this_size < other_size ? -1 : 1);
+    }
+    if (this_size > 0) {
+        return (memcmp(&v1[0], &v2[0], this_size));
+    }
+    return (0);
+}
+
+/// \brief Compare two instances of \c TSIG RDATA.
+///
+/// This method compares \c this and the \c other \c TSIG objects
+/// in terms of the DNSSEC sorting order as defined in RFC4034, and returns
+/// the result as an integer.
+///
+/// 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 TSIG object;
+/// otherwise, the standard \c bad_cast exception will be thrown.
+/// This method never throws an exception otherwise.
+///
+/// \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
+TSIG::compare(const Rdata& other) const {
+    const TSIG& other_tsig = dynamic_cast<const TSIG&>(other);
+
+    const int ncmp = compareNames(impl_->algorithm_,
+                                  other_tsig.impl_->algorithm_);
+    if (ncmp != 0) {
+        return (ncmp);
+    }
+
+    if (impl_->time_signed_ != other_tsig.impl_->time_signed_) {
+        return (impl_->time_signed_ < other_tsig.impl_->time_signed_ ? -1 : 1);
+    }
+    if (impl_->fudge_ != other_tsig.impl_->fudge_) {
+        return (impl_->fudge_ < other_tsig.impl_->fudge_ ? -1 : 1);
+    }
+    const int vcmp = vectorComp(impl_->mac_, other_tsig.impl_->mac_);
+    if (vcmp != 0) {
+        return (vcmp);
+    }
+    if (impl_->original_id_ != other_tsig.impl_->original_id_) {
+        return (impl_->original_id_ < other_tsig.impl_->original_id_ ? -1 : 1);
+    }
+    if (impl_->error_ != other_tsig.impl_->error_) {
+        return (impl_->error_ < other_tsig.impl_->error_ ? -1 : 1);
+    }
+    return (vectorComp(impl_->other_data_, other_tsig.impl_->other_data_));
+}
+
+const Name&
+TSIG::getAlgorithm() const {
+    return (impl_->algorithm_);
+}
+
+uint64_t
+TSIG::getTimeSigned() const {
+    return (impl_->time_signed_);
+}
+
+uint16_t
+TSIG::getFudge() const {
+    return (impl_->fudge_);
+}
+
+uint16_t
+TSIG::getMACSize() const {
+    return (impl_->mac_.size());
+}
+
+const void*
+TSIG::getMAC() const {
+    if (impl_->mac_.size() > 0) {
+        return (&impl_->mac_[0]);
+    } else {
+        return (NULL);
+    }
+}
+
+uint16_t
+TSIG::getOriginalID() const {
+    return (impl_->original_id_);
+}
+
+uint16_t
+TSIG::getError() const {
+    return (impl_->error_);
+}
+
+uint16_t
+TSIG::getOtherLen() const {
+    return (impl_->other_data_.size());
+}
+
+const void*
+TSIG::getOtherData() const {
+    if (impl_->other_data_.size() > 0) {
+        return (&impl_->other_data_[0]);
+    } else {
+        return (NULL);
+    }
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE

+ 160 - 0
src/lib/dns/rdata/any_255/tsig_250.h

@@ -0,0 +1,160 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+// BEGIN_HEADER_GUARD
+
+#include <stdint.h>
+
+#include <string>
+
+#include <dns/rdata.h>
+
+namespace isc {
+namespace dns {
+class Name;
+}
+}
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::TSIG class represents the TSIG RDATA as defined %in
+/// RFC2845.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// TSIG RDATA.
+class TSIG : public Rdata {
+public:
+    // BEGIN_COMMON_MEMBERS
+    // END_COMMON_MEMBERS
+
+    /// \brief Constructor from RDATA field parameters.
+    ///
+    /// The parameters are a straightforward mapping of %TSIG RDATA
+    /// fields as defined %in RFC2845, but there are some implementation
+    /// specific notes as follows.
+    ///
+    /// \c algorithm is a \c Name object that specifies the algorithm.
+    /// For example, if the algorithm is HMAC-SHA256, \c algorithm would be
+    /// \c Name("hmac-sha256").
+    ///
+    /// \c time_signed corresponds to the Time Signed field, which is of
+    /// 48-bit unsigned integer type, and therefore cannot exceed 2^48-1;
+    /// otherwise, an exception of type \c OutOfRange will be thrown.
+    ///
+    /// \c mac_size and \c mac correspond to the MAC Size and MAC fields,
+    /// respectively.  When the MAC field is empty, \c mac must be NULL.
+    /// \c mac_size and \c mac must be consistent %in that \c mac_size is 0 if
+    /// and only if \c mac is NULL; otherwise an exception of type
+    /// InvalidParameter will be thrown.
+    ///
+    /// The same restriction applies to \c other_len and \c other_data,
+    /// which correspond to the Other Len and Other Data fields, respectively.
+    ///
+    /// This constructor internally involves resource allocation, and if
+    /// it fails, a corresponding standard exception will be thrown.
+    TSIG(const Name& algorithm, uint64_t time_signed, uint16_t fudge,
+         uint16_t mac_size, const void* mac, uint16_t original_id,
+         uint16_t error, uint16_t other_len, const void* other_data);
+
+    /// \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.
+    TSIG& operator=(const TSIG& source);
+
+    /// \brief The destructor.
+    ~TSIG();
+
+    /// \brief Return the algorithm name.
+    ///
+    /// This method never throws an exception.
+    const Name& getAlgorithm() const;
+
+    /// \brief Return the value of the Time Signed field.
+    ///
+    /// The returned value does not exceed 2^48-1.
+    ///
+    /// This method never throws an exception.
+    uint64_t getTimeSigned() const;
+
+    /// \brief Return the value of the Fudge field.
+    ///
+    /// This method never throws an exception.
+    uint16_t getFudge() const;
+
+    /// \brief Return the value of the MAC Size field.
+    ///
+    /// This method never throws an exception.
+    uint16_t getMACSize() const;
+
+    /// \brief Return the value of the MAC field.
+    ///
+    /// If the MAC field is empty, it returns NULL.
+    /// Otherwise, the memory region beginning at the address returned by
+    /// this method is valid up to the bytes specified by the return value
+    /// of \c getMACSize().
+    /// The memory region is only valid while the corresponding \c TSIG
+    /// object is valid.  The caller must hold the \c TSIG object while
+    /// it needs to refer to the region or it must make a local copy of the
+    /// region.
+    ///
+    /// This method never throws an exception.
+    const void* getMAC() const;
+
+    /// \brief Return the value of the Original ID field.
+    ///
+    /// This method never throws an exception.
+    uint16_t getOriginalID() const;
+
+    /// \brief Return the value of the Error field.
+    ///
+    /// This method never throws an exception.
+    uint16_t getError() const;
+
+    /// \brief Return the value of the Other Len field.
+    ///
+    /// This method never throws an exception.
+    uint16_t getOtherLen() const;
+
+    /// \brief Return the value of the Other Data field.
+    ///
+    /// The same note as \c getMAC() applies.
+    ///
+    /// This method never throws an exception.
+    const void* getOtherData() const;
+private:
+    struct TSIGImpl;
+    TSIGImpl* impl_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 10
src/lib/dns/rrclass-placeholder.h

@@ -244,14 +244,12 @@ public:
     // END_WELL_KNOWN_CLASS_DECLARATIONS
     // END_WELL_KNOWN_CLASS_DECLARATIONS
     
     
     static const RRClass& NONE();
     static const RRClass& NONE();
-    static const RRClass& ANY();
 
 
 private:
 private:
     // \brief Meta-classes
     // \brief Meta-classes
     enum {
     enum {
         RRCLASS_RESERVED0 = 0,
         RRCLASS_RESERVED0 = 0,
-        RRCLASS_NONE = 254,
-        RRCLASS_ANY = 255
+        RRCLASS_NONE = 254
     };
     };
     uint16_t classcode_;
     uint16_t classcode_;
 };
 };
@@ -266,13 +264,6 @@ RRClass::NONE() {
     return (rrclass);
     return (rrclass);
 }
 }
 
 
-inline const RRClass&
-RRClass::ANY() {
-    static RRClass rrclass(RRCLASS_ANY);
-
-    return (rrclass);
-}
-
 ///
 ///
 /// \brief Insert the \c RRClass as a string into stream.
 /// \brief Insert the \c RRClass as a string into stream.
 ///
 ///

+ 2 - 3
src/lib/dns/rrset.cc

@@ -44,7 +44,6 @@ AbstractRRset::toText() const {
     string s;
     string s;
     RdataIteratorPtr it = getRdataIterator();
     RdataIteratorPtr it = getRdataIterator();
 
 
-    it->first();
     if (it->isLast()) {
     if (it->isLast()) {
         isc_throw(EmptyRRset, "ToText() is attempted for an empty RRset");
         isc_throw(EmptyRRset, "ToText() is attempted for an empty RRset");
     }
     }
@@ -66,7 +65,6 @@ rrsetToWire(const AbstractRRset& rrset, T& output, const size_t limit) {
     unsigned int n = 0;
     unsigned int n = 0;
     RdataIteratorPtr it = rrset.getRdataIterator();
     RdataIteratorPtr it = rrset.getRdataIterator();
 
 
-    it->first();
     if (it->isLast()) {
     if (it->isLast()) {
         isc_throw(EmptyRRset, "ToWire() is attempted for an empty RRset");
         isc_throw(EmptyRRset, "ToWire() is attempted for an empty RRset");
     }
     }
@@ -224,7 +222,8 @@ private:
     BasicRdataIterator() {}
     BasicRdataIterator() {}
 public:
 public:
     BasicRdataIterator(const std::vector<rdata::ConstRdataPtr>& datavector) :
     BasicRdataIterator(const std::vector<rdata::ConstRdataPtr>& datavector) :
-        datavector_(&datavector) {}
+        datavector_(&datavector), it_(datavector_->begin())
+    {}
     ~BasicRdataIterator() {}
     ~BasicRdataIterator() {}
     virtual void first() { it_ = datavector_->begin(); }
     virtual void first() { it_ = datavector_->begin(); }
     virtual void next() { ++it_; }
     virtual void next() { ++it_; }

+ 17 - 13
src/lib/dns/rrset.h

@@ -148,7 +148,7 @@ typedef boost::shared_ptr<RdataIterator> RdataIteratorPtr;
 ///      "sort" and "search(find)" method?
 ///      "sort" and "search(find)" method?
 ///   - what about comparing two RRsets of the same type?  If we need this,
 ///   - what about comparing two RRsets of the same type?  If we need this,
 ///     should it compare rdata's as a set or as a list (i.e. compare
 ///     should it compare rdata's as a set or as a list (i.e. compare
-///     each %rdata one by one or as a whole)?  c.f. NLnet Labs' ldns
+///     each rdata one by one or as a whole)?  c.f. NLnet Labs' ldns
 ///     (http://www.nlnetlabs.nl/projects/ldns/doc/index.html)
 ///     (http://www.nlnetlabs.nl/projects/ldns/doc/index.html)
 ///     has \c ldns_rr_list_compare(), which takes the latter approach
 ///     has \c ldns_rr_list_compare(), which takes the latter approach
 ///     (seemingly assuming the caller sorts the lists beforehand).
 ///     (seemingly assuming the caller sorts the lists beforehand).
@@ -357,7 +357,7 @@ public:
     /// \endcode
     /// \endcode
     ///
     ///
     /// This method is more strictly typed than the pointer version:
     /// This method is more strictly typed than the pointer version:
-    /// If \c %rdata does not refer to the appropriate derived
+    /// If \c rdata does not refer to the appropriate derived
     /// \c Rdata class
     /// \c Rdata class
     /// for the \c RRType for this \c RRset, it throws an exception of class
     /// for the \c RRType for this \c RRset, it throws an exception of class
     /// \c std::bad_cast.
     /// \c std::bad_cast.
@@ -385,6 +385,10 @@ public:
     /// \brief Return an iterator to go through all RDATA stored in the
     /// \brief Return an iterator to go through all RDATA stored in the
     /// \c RRset.
     /// \c RRset.
     ///
     ///
+    /// The rdata cursor of the returned iterator will point to the first
+    /// RDATA, that is, it effectively calls \c RdataIterator::first()
+    /// internally.
+    ///
     /// Using the design pattern terminology, \c getRdataIterator()
     /// Using the design pattern terminology, \c getRdataIterator()
     /// is an example of a <em>factory method</em>.
     /// is an example of a <em>factory method</em>.
     ///
     ///
@@ -417,10 +421,10 @@ public:
 /// The RDATA objects stored in the \c RRset are considered to form
 /// The RDATA objects stored in the \c RRset are considered to form
 /// a unidirectional list from the \c RdataIterator point of view (while
 /// a unidirectional list from the \c RdataIterator point of view (while
 /// the actual implementation in the derived \c RRset may not use a list).
 /// the actual implementation in the derived \c RRset may not use a list).
-/// We call this unidirectional list the <em>%rdata list</em>.
+/// We call this unidirectional list the <em>rdata list</em>.
 ///
 ///
 /// An \c RdataIterator object internally (and conceptually) holds a
 /// An \c RdataIterator object internally (and conceptually) holds a
-/// <em>%rdata cursor</em>, which points to a specific item of the %rdata list.
+/// <em>rdata cursor</em>, which points to a specific item of the rdata list.
 ///
 ///
 /// Note about design choice: as is clear from the interface, \c RdataIterator
 /// Note about design choice: as is clear from the interface, \c RdataIterator
 /// is not compatible with the standard iterator classes.
 /// is not compatible with the standard iterator classes.
@@ -458,29 +462,29 @@ private:
     //@}
     //@}
 
 
 public:
 public:
-    /// \brief Move the %rdata cursor to the first RDATA in the %rdata list
+    /// \brief Move the rdata cursor to the first RDATA in the rdata list
     /// (if any).
     /// (if any).
     ///
     ///
     /// This method can safely be called multiple times, even after moving
     /// This method can safely be called multiple times, even after moving
-    /// the %rdata cursor forward by the \c next() method.
+    /// the rdata cursor forward by the \c next() method.
     ///
     ///
     /// This method should never throw an exception.
     /// This method should never throw an exception.
     virtual void first() = 0;
     virtual void first() = 0;
 
 
-    /// \brief Move the %rdata cursor to the next RDATA in the %rdata list
+    /// \brief Move the rdata cursor to the next RDATA in the rdata list
     /// (if any).
     /// (if any).
     ///
     ///
     /// This method should never throw an exception.
     /// This method should never throw an exception.
     virtual void next() = 0;
     virtual void next() = 0;
 
 
-    /// \brief Return the current \c Rdata corresponding to the %rdata cursor.
+    /// \brief Return the current \c Rdata corresponding to the rdata cursor.
     ///
     ///
     /// \return A reference to an \c rdata::::Rdata object corresponding
     /// \return A reference to an \c rdata::::Rdata object corresponding
-    /// to the %rdata cursor.
+    /// to the rdata cursor.
     virtual const rdata::Rdata& getCurrent() const = 0;
     virtual const rdata::Rdata& getCurrent() const = 0;
 
 
-    /// \brief Return true iff the %rdata cursor has reached the end of the
-    /// %rdata list.
+    /// \brief Return true iff the rdata cursor has reached the end of the
+    /// rdata list.
     ///
     ///
     /// Once this method returns \c true, the behavior of any subsequent
     /// Once this method returns \c true, the behavior of any subsequent
     /// call to \c next() or \c getCurrent() is undefined.
     /// call to \c next() or \c getCurrent() is undefined.
@@ -489,8 +493,8 @@ public:
     ///
     ///
     /// This method should never throw an exception.
     /// This method should never throw an exception.
     ///
     ///
-    /// \return \c true if the %rdata cursor has reached the end of the
-    /// %rdata list; otherwise \c false.
+    /// \return \c true if the rdata cursor has reached the end of the
+    /// rdata list; otherwise \c false.
     virtual bool isLast() const = 0;
     virtual bool isLast() const = 0;
 };
 };
 
 

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

@@ -38,6 +38,7 @@ run_unittests_SOURCES += rdata_nsec_unittest.cc
 run_unittests_SOURCES += rdata_nsec3_unittest.cc
 run_unittests_SOURCES += rdata_nsec3_unittest.cc
 run_unittests_SOURCES += rdata_nsec3param_unittest.cc
 run_unittests_SOURCES += rdata_nsec3param_unittest.cc
 run_unittests_SOURCES += rdata_rrsig_unittest.cc
 run_unittests_SOURCES += rdata_rrsig_unittest.cc
+run_unittests_SOURCES += rdata_tsig_unittest.cc
 run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
 run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += question_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
 run_unittests_SOURCES += rrparamregistry_unittest.cc
@@ -46,7 +47,7 @@ run_unittests_SOURCES += base32hex_unittest.cc
 run_unittests_SOURCES += base64_unittest.cc
 run_unittests_SOURCES += base64_unittest.cc
 run_unittests_SOURCES += hex_unittest.cc
 run_unittests_SOURCES += hex_unittest.cc
 run_unittests_SOURCES += sha1_unittest.cc
 run_unittests_SOURCES += sha1_unittest.cc
-run_unittests_SOURCES += tsig_unittest.cc
+run_unittests_SOURCES += tsigkey_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_SOURCES += run_unittests.cc
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)

+ 0 - 1
src/lib/dns/tests/message_unittest.cc

@@ -293,7 +293,6 @@ TEST_F(MessageTest, fromWire) {
     // TTL should be 3600, even though that of the 2nd RR is 7200
     // TTL should be 3600, even though that of the 2nd RR is 7200
     EXPECT_EQ(RRTTL(3600), rrset->getTTL());
     EXPECT_EQ(RRTTL(3600), rrset->getTTL());
     RdataIteratorPtr it = rrset->getRdataIterator();
     RdataIteratorPtr it = rrset->getRdataIterator();
-    it->first();
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     it->next();
     it->next();
     EXPECT_EQ("192.0.2.2", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.2", it->getCurrent().toText());

+ 368 - 0
src/lib/dns/tests/rdata_tsig_unittest.cc

@@ -0,0 +1,368 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/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::dns::rdata;
+
+namespace {
+class Rdata_TSIG_Test : public RdataTest {
+protected:
+    vector<uint8_t> expect_data;
+};
+
+const char* const valid_text1 = "hmac-md5.sig-alg.reg.int. 1286779327 300 "
+    "0 16020 BADKEY 0";
+const char* const valid_text2 = "hmac-sha256. 1286779327 300 12 "
+    "FAKEFAKEFAKEFAKE 16020 BADSIG 0";
+
+const char* const valid_text3 = "hmac-sha1. 1286779327 300 12 "
+    "FAKEFAKEFAKEFAKE 16020 BADTIME 6 FAKEFAKE";
+const char* const valid_text4 = "hmac-sha1. 1286779327 300 12 "
+    "FAKEFAKEFAKEFAKE 16020 BADSIG 6 FAKEFAKE";
+const char* const valid_text5 = "hmac-sha256. 1286779327 300 12 "
+    "FAKEFAKEFAKEFAKE 16020 2845 0"; // using numeric error code
+const char* const too_long_label = "012345678901234567890123456789"
+    "0123456789012345678901234567890123";
+
+// commonly used test RDATA
+const any::TSIG rdata_tsig((string(valid_text1)));
+
+TEST_F(Rdata_TSIG_Test, createFromText) {
+    // normal case.  it also tests getter methods.
+    EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), rdata_tsig.getAlgorithm());
+    EXPECT_EQ(1286779327, rdata_tsig.getTimeSigned());
+    EXPECT_EQ(300, rdata_tsig.getFudge());
+    EXPECT_EQ(0, rdata_tsig.getMACSize());
+    EXPECT_EQ(static_cast<void*>(NULL), rdata_tsig.getMAC());
+    EXPECT_EQ(16020, rdata_tsig.getOriginalID());
+    EXPECT_EQ(17, rdata_tsig.getError()); // TODO: use constant
+    EXPECT_EQ(0, rdata_tsig.getOtherLen());
+    EXPECT_EQ(static_cast<void*>(NULL), rdata_tsig.getOtherData());
+
+    any::TSIG tsig2((string(valid_text2)));
+    EXPECT_EQ(12, tsig2.getMACSize());
+    EXPECT_EQ(16, tsig2.getError()); // TODO: use constant
+
+    any::TSIG tsig3((string(valid_text3)));
+    EXPECT_EQ(6, tsig3.getOtherLen());
+
+    // The other data is unusual, but we don't reject it.
+    EXPECT_NO_THROW(any::TSIG(string(valid_text4)));
+
+    // numeric representation of TSIG error
+    any::TSIG tsig5((string(valid_text5)));
+    EXPECT_EQ(2845, tsig5.getError());
+
+    //
+    // invalid cases
+    //
+    // there's a garbage parameter at the end
+    EXPECT_THROW(any::TSIG("foo 0 0 0 0 BADKEY 0 0"), InvalidRdataText);
+    // input is too short
+    EXPECT_THROW(any::TSIG("foo 0 0 0 0 BADKEY"), InvalidRdataText);
+    // bad domain name
+    EXPECT_THROW(any::TSIG(string(too_long_label) + "0 0 0 0 BADKEY 0"),
+                 TooLongLabel);
+    // time is too large (2814...6 is 2^48)
+    EXPECT_THROW(any::TSIG("foo 281474976710656 0 0 0 BADKEY 0"),
+                 InvalidRdataText);
+    // invalid time (negative)
+    EXPECT_THROW(any::TSIG("foo -1 0 0 0 BADKEY 0"), InvalidRdataText);
+    // fudge is too large
+    EXPECT_THROW(any::TSIG("foo 0 65536 0 0 BADKEY 0"), InvalidRdataText);
+    // invalid fudge (negative)
+    EXPECT_THROW(any::TSIG("foo 0 -1 0 0 BADKEY 0"), InvalidRdataText);
+    // MAC size is too large
+    EXPECT_THROW(any::TSIG("foo 0 0 65536 0 BADKEY 0"), InvalidRdataText);
+    // MAC size and MAC mismatch
+    EXPECT_THROW(any::TSIG("foo 0 0 9 FAKE 0 BADKEY 0"), InvalidRdataText);
+    EXPECT_THROW(any::TSIG("foo 0 0 0 FAKE 0 BADKEY 0"), InvalidRdataText);
+    // MAC is bad base64
+    EXPECT_THROW(any::TSIG("foo 0 0 3 FAK= 0 BADKEY 0"), isc::BadValue);
+    // Unknown error code
+    EXPECT_THROW(any::TSIG("foo 0 0 0 0 TEST 0"), InvalidRdataText);
+    // Numeric error code is too large
+    EXPECT_THROW(any::TSIG("foo 0 0 0 0 65536 0"), InvalidRdataText);
+    // Other len is too large
+    EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 65536 FAKE"), InvalidRdataText);
+    // Other len and data mismatch
+    EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 9 FAKE"), InvalidRdataText);
+    EXPECT_THROW(any::TSIG("foo 0 0 0 0 NOERROR 0 FAKE"), InvalidRdataText);
+}
+
+void
+fromWireCommonChecks(const any::TSIG& tsig) {
+    EXPECT_EQ(Name("hmac-sha256"), tsig.getAlgorithm());
+    EXPECT_EQ(1286978795, tsig.getTimeSigned());
+    EXPECT_EQ(300, tsig.getFudge());
+
+    vector<uint8_t> expect_mac(32, 'x');
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        &expect_mac[0], expect_mac.size(),
+                        tsig.getMAC(), tsig.getMACSize());
+
+    EXPECT_EQ(2845, tsig.getOriginalID());
+
+    EXPECT_EQ(0, tsig.getOtherLen());
+    EXPECT_EQ(static_cast<const void*>(NULL), tsig.getOtherData());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWire) {
+    RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                        "rdata_tsig_fromWire1.wire"));
+    fromWireCommonChecks(dynamic_cast<any::TSIG&>(*rdata));
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithOtherData) {
+    RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                        "rdata_tsig_fromWire2.wire"));
+    const any::TSIG& tsig(dynamic_cast<any::TSIG&>(*rdata));
+
+    EXPECT_EQ(18, tsig.getError());
+    const uint64_t otherdata = 1286978795 + 300 + 1; // time-signed + fudge + 1
+    expect_data.resize(6);
+    expect_data[0] = (otherdata >> 40);
+    expect_data[1] = ((otherdata >> 32) & 0xff);
+    expect_data[2] = ((otherdata >> 24) & 0xff);
+    expect_data[3] = ((otherdata >> 16) & 0xff);
+    expect_data[4] = ((otherdata >> 8) & 0xff);
+    expect_data[5] = (otherdata & 0xff);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        &expect_data[0], expect_data.size(),
+                        tsig.getOtherData(), tsig.getOtherLen());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithoutMAC) {
+    RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                        "rdata_tsig_fromWire3.wire"));
+    const any::TSIG& tsig(dynamic_cast<any::TSIG&>(*rdata));
+    EXPECT_EQ(16, tsig.getError());
+    EXPECT_EQ(0, tsig.getMACSize());
+    EXPECT_EQ(static_cast<const void*>(NULL), tsig.getMAC());
+}
+
+TEST_F(Rdata_TSIG_Test, createFromWireWithCompression) {
+    RdataPtr rdata(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                        "rdata_tsig_fromWire4.wire",
+                                        // we need to skip the dummy name:
+                                        Name("hmac-sha256").getLength()));
+    fromWireCommonChecks(dynamic_cast<any::TSIG&>(*rdata));
+}
+
+TEST_F(Rdata_TSIG_Test, badFromWire) {
+    // RDLENGTH is too short:
+    EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                      "rdata_tsig_fromWire5.wire"),
+                 InvalidRdataLength);
+    // RDLENGTH is too long:
+    EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                      "rdata_tsig_fromWire6.wire"),
+                 InvalidRdataLength);
+    // Algorithm name is broken:
+    EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                      "rdata_tsig_fromWire7.wire"),
+                 DNSMessageFORMERR);
+    // MAC size is bogus:
+    EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                      "rdata_tsig_fromWire8.wire"),
+                 InvalidBufferPosition);
+    // Other-data length is bogus:
+    EXPECT_THROW(rdataFactoryFromFile(RRType::TSIG(), RRClass::ANY(),
+                                      "rdata_tsig_fromWire9.wire"),
+                 InvalidBufferPosition);
+}
+
+TEST_F(Rdata_TSIG_Test, copyConstruct) {
+    const any::TSIG copy(rdata_tsig);
+    EXPECT_EQ(0, copy.compare(rdata_tsig));
+
+    // Check the copied data is valid even after the original is deleted
+    any::TSIG* copy2 = new any::TSIG(rdata_tsig);
+    any::TSIG copy3(*copy2);
+    delete copy2;
+    EXPECT_EQ(0, copy3.compare(rdata_tsig));
+}
+
+TEST_F(Rdata_TSIG_Test, createFromParams) {
+    EXPECT_EQ(0, rdata_tsig.compare(any::TSIG(Name("hmac-md5.sig-alg.reg.int"),
+                                              1286779327, 300, 0, NULL, 16020,
+                                              17, 0, NULL)));
+
+    const uint8_t fake_data[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84,
+                                  0x14, 0x02, 0x84, 0x14, 0x02, 0x84 }; 
+    EXPECT_EQ(0, any::TSIG((string(valid_text2))).compare(
+                  any::TSIG(Name("hmac-sha256"), 1286779327, 300, 12,
+                            fake_data, 16020, 16, 0, NULL)));
+
+    const uint8_t fake_data2[] = { 0x14, 0x02, 0x84, 0x14, 0x02, 0x84 };
+    EXPECT_EQ(0, any::TSIG((string(valid_text3))).compare(
+                  any::TSIG(Name("hmac-sha1"), 1286779327, 300, 12,
+                            fake_data, 16020, 18, 6, fake_data2)));
+
+    EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 1LLU << 48, 300, 12,
+                           fake_data, 16020, 18, 6, fake_data2),
+                 isc::OutOfRange);
+    EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 0, 300, 0, fake_data, 16020,
+                           18, 0, NULL),
+                 isc::InvalidParameter);
+    EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 0, 300, 12, NULL, 16020,
+                           18, 0, NULL),
+                 isc::InvalidParameter);
+    EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 0, 300, 0, NULL, 16020,
+                           18, 0, fake_data),
+                 isc::InvalidParameter);
+    EXPECT_THROW(any::TSIG(Name("hmac-sha256"), 0, 300, 0, NULL, 16020,
+                           18, 6, NULL),
+                 isc::InvalidParameter);
+}
+
+TEST_F(Rdata_TSIG_Test, assignment) {
+    any::TSIG copy((string(valid_text2)));
+    copy = rdata_tsig;
+    EXPECT_EQ(0, copy.compare(rdata_tsig));
+
+    // Check if the copied data is valid even after the original is deleted
+    any::TSIG* copy2 = new any::TSIG(rdata_tsig);
+    any::TSIG copy3((string(valid_text2)));
+    copy3 = *copy2;
+    delete copy2;
+    EXPECT_EQ(0, copy3.compare(rdata_tsig));
+
+    // Self assignment
+    copy = copy;
+    EXPECT_EQ(0, copy.compare(rdata_tsig));
+}
+
+template <typename Output>
+void
+toWireCommonChecks(Output& output) {
+    vector<uint8_t> expect_data;
+
+    output.clear();
+    expect_data.clear();
+    rdata_tsig.toWire(output);
+    // read the expected wire format data and trim the RDLEN part.
+    UnitTestUtil::readWireData("rdata_tsig_toWire1.wire", expect_data);
+    expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        &expect_data[0], expect_data.size(),
+                        output.getData(), output.getLength());
+
+    expect_data.clear();
+    output.clear();
+    any::TSIG(string(valid_text2)).toWire(output);
+    UnitTestUtil::readWireData("rdata_tsig_toWire2.wire", expect_data);
+    expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        &expect_data[0], expect_data.size(),
+                        output.getData(), output.getLength());
+
+    expect_data.clear();
+    output.clear();
+    any::TSIG(string(valid_text3)).toWire(output);
+    UnitTestUtil::readWireData("rdata_tsig_toWire3.wire", expect_data);
+    expect_data.erase(expect_data.begin(), expect_data.begin() + 2);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        &expect_data[0], expect_data.size(),
+                        output.getData(), output.getLength());
+}
+
+TEST_F(Rdata_TSIG_Test, toWireBuffer) {
+    toWireCommonChecks<OutputBuffer>(obuffer);
+}
+
+TEST_F(Rdata_TSIG_Test, toWireRenderer) {
+    toWireCommonChecks<MessageRenderer>(renderer);
+
+    // check algorithm name won't compressed when it would otherwise.
+    expect_data.clear();
+    renderer.clear();
+    renderer.writeName(Name("hmac-md5.sig-alg.reg.int"));
+    renderer.writeUint16(42); // RDLEN
+    rdata_tsig.toWire(renderer);
+    UnitTestUtil::readWireData("rdata_tsig_toWire4.wire", expect_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        &expect_data[0], expect_data.size(),
+                        renderer.getData(), renderer.getLength());
+
+    // check algorithm can be used as a compression target.
+    expect_data.clear();
+    renderer.clear();
+    renderer.writeUint16(42);
+    rdata_tsig.toWire(renderer);
+    renderer.writeName(Name("hmac-md5.sig-alg.reg.int"));
+    UnitTestUtil::readWireData("rdata_tsig_toWire5.wire", expect_data);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        &expect_data[0], expect_data.size(),
+                        renderer.getData(), renderer.getLength());
+}
+
+TEST_F(Rdata_TSIG_Test, toText) {
+    EXPECT_EQ(string(valid_text1), rdata_tsig.toText());
+    EXPECT_EQ(string(valid_text2), any::TSIG(string(valid_text2)).toText());
+    EXPECT_EQ(string(valid_text3), any::TSIG(string(valid_text3)).toText());
+    EXPECT_EQ(string(valid_text5), any::TSIG(string(valid_text5)).toText());
+}
+
+TEST_F(Rdata_TSIG_Test, compare) {
+    // test RDATAs, sorted in the ascendent order.
+    // "AAAA" encoded in BASE64 corresponds to 0x000000, so it should be the
+    // smallest data of the same length.
+    vector<any::TSIG> compare_set;
+    compare_set.push_back(any::TSIG("a.example 0 300 0 16020 0 0"));
+    compare_set.push_back(any::TSIG("example 0 300 0 16020 0 0"));
+    compare_set.push_back(any::TSIG("example 1 300 0 16020 0 0"));
+    compare_set.push_back(any::TSIG("example 1 600 0 16020 0 0"));
+    compare_set.push_back(any::TSIG("example 1 600 3 AAAA 16020 0 0"));
+    compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16020 0 0"));
+    compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16021 0 0"));
+    compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16021 1 0"));
+    compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16021 1 3 AAAA"));
+    compare_set.push_back(any::TSIG("example 1 600 3 FAKE 16021 1 3 FAKE"));
+
+    EXPECT_EQ(0, compare_set[0].compare(
+                  any::TSIG("A.EXAMPLE 0 300 0 16020 0 0")));
+
+    vector<any::TSIG>::const_iterator it;
+    vector<any::TSIG>::const_iterator it_end = compare_set.end();
+    for (it = compare_set.begin(); it != it_end - 1; ++it) {
+        EXPECT_GT(0, (*it).compare(*(it + 1)));
+        EXPECT_LT(0, (*(it + 1)).compare(*it));
+    }
+
+    // comparison attempt between incompatible RR types should be rejected
+    EXPECT_THROW(rdata_tsig.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}

+ 1 - 3
src/lib/dns/tests/rrset_unittest.cc

@@ -117,8 +117,7 @@ void
 addRdataTestCommon(const RRset& rrset) {
 addRdataTestCommon(const RRset& rrset) {
     EXPECT_EQ(2, rrset.getRdataCount());
     EXPECT_EQ(2, rrset.getRdataCount());
 
 
-    RdataIteratorPtr it = rrset.getRdataIterator();
-    it->first();
+    RdataIteratorPtr it = rrset.getRdataIterator(); // cursor is set to the 1st
     EXPECT_FALSE(it->isLast());
     EXPECT_FALSE(it->isLast());
     EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1")));
     EXPECT_EQ(0, it->getCurrent().compare(in::A("192.0.2.1")));
     it->next();
     it->next();
@@ -156,7 +155,6 @@ TEST_F(RRsetTest, addRdataPtr) {
 TEST_F(RRsetTest, iterator) {
 TEST_F(RRsetTest, iterator) {
     // Iterator for an empty RRset.
     // Iterator for an empty RRset.
     RdataIteratorPtr it = rrset_a_empty.getRdataIterator();
     RdataIteratorPtr it = rrset_a_empty.getRdataIterator();
-    it->first();
     EXPECT_TRUE(it->isLast());
     EXPECT_TRUE(it->isLast());
 
 
     // Normal case (already tested, but do it again just in case)
     // Normal case (already tested, but do it again just in case)

+ 0 - 1
src/lib/dns/tests/rrsetlist_unittest.cc

@@ -150,7 +150,6 @@ TEST_F(RRsetListTest, checkData) {
 
 
     RdataIteratorPtr it =
     RdataIteratorPtr it =
         list.findRRset(RRType::A(), RRClass::IN())->getRdataIterator();
         list.findRRset(RRType::A(), RRClass::IN())->getRdataIterator();
-    it->first();
     EXPECT_FALSE(it->isLast());
     EXPECT_FALSE(it->isLast());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
     EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
 }
 }

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

@@ -12,6 +12,14 @@ BUILT_SOURCES += rdata_rrsig_fromWire2.wire
 BUILT_SOURCES += rdata_soa_toWireUncompressed.wire
 BUILT_SOURCES += rdata_soa_toWireUncompressed.wire
 BUILT_SOURCES +=  rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
 BUILT_SOURCES +=  rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
 BUILT_SOURCES += rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire
 BUILT_SOURCES += rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire
+BUILT_SOURCES += rdata_tsig_fromWire1.wire rdata_tsig_fromWire2.wire
+BUILT_SOURCES += rdata_tsig_fromWire3.wire rdata_tsig_fromWire4.wire
+BUILT_SOURCES += rdata_tsig_fromWire5.wire rdata_tsig_fromWire6.wire
+BUILT_SOURCES += rdata_tsig_fromWire7.wire rdata_tsig_fromWire8.wire
+BUILT_SOURCES += rdata_tsig_fromWire9.wire
+BUILT_SOURCES += rdata_tsig_toWire1.wire rdata_tsig_toWire2.wire
+BUILT_SOURCES += rdata_tsig_toWire3.wire rdata_tsig_toWire4.wire
+BUILT_SOURCES += rdata_tsig_toWire5.wire
 
 
 # NOTE: keep this in sync with real file listing
 # NOTE: keep this in sync with real file listing
 # so is included in tarball
 # so is included in tarball
@@ -51,6 +59,14 @@ EXTRA_DIST += rdata_txt_fromWire5.spec rdata_unknown_fromWire
 EXTRA_DIST += rrcode16_fromWire1 rrcode16_fromWire2
 EXTRA_DIST += rrcode16_fromWire1 rrcode16_fromWire2
 EXTRA_DIST += rrcode32_fromWire1 rrcode32_fromWire2
 EXTRA_DIST += rrcode32_fromWire1 rrcode32_fromWire2
 EXTRA_DIST += rrset_toWire1 rrset_toWire2
 EXTRA_DIST += rrset_toWire1 rrset_toWire2
+EXTRA_DIST += rdata_tsig_fromWire1.spec rdata_tsig_fromWire2.spec
+EXTRA_DIST += rdata_tsig_fromWire3.spec rdata_tsig_fromWire4.spec
+EXTRA_DIST += rdata_tsig_fromWire5.spec rdata_tsig_fromWire6.spec
+EXTRA_DIST += rdata_tsig_fromWire7.spec rdata_tsig_fromWire8.spec
+EXTRA_DIST += rdata_tsig_fromWire9.spec
+EXTRA_DIST += rdata_tsig_toWire1.spec rdata_tsig_toWire2.spec
+EXTRA_DIST += rdata_tsig_toWire3.spec rdata_tsig_toWire4.spec
+EXTRA_DIST += rdata_tsig_toWire5.spec
 
 
 .spec.wire:
 .spec.wire:
 	./gen-wiredata.py -o $@ $<
 	./gen-wiredata.py -o $@ $<

+ 2 - 1
src/lib/dns/tests/testdata/edns_toWire4.spec

@@ -1,5 +1,6 @@
 #
 #
-# Same as edns_toWire1 but setting the DO bit
+# Same as edns_toWire1 but setting the DO bit, and using an unusual
+# UDP payload size
 #
 #
 [edns]
 [edns]
 do: 1
 do: 1

+ 80 - 16
src/lib/dns/tests/testdata/gen-wiredata.py.in

@@ -19,8 +19,8 @@ import configparser, re, time, sys
 from datetime import datetime
 from datetime import datetime
 from optparse import OptionParser
 from optparse import OptionParser
 
 
-re_hex = re.compile(r'0x[0-9a-fA-F]+')
-re_decimal = re.compile(r'\d+$')
+re_hex = re.compile(r'^0x[0-9a-fA-F]+')
+re_decimal = re.compile(r'^\d+$')
 re_string = re.compile(r"\'(.*)\'$")
 re_string = re.compile(r"\'(.*)\'$")
 
 
 dnssec_timefmt = '%Y%m%d%H%M%S'
 dnssec_timefmt = '%Y%m%d%H%M%S'
@@ -48,9 +48,12 @@ dict_rrtype = { 'none' : 0, 'a' : 1, 'ns' : 2, 'md' : 3, 'mf' : 4, 'cname' : 5,
                 'maila' : 254, 'any' : 255 }
                 'maila' : 254, 'any' : 255 }
 rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
 rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
 dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
 dict_rrclass = { 'in' : 1, 'ch' : 3, 'hs' : 4, 'any' : 255 }
-rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in dict_rrclass.keys()])
-dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4, 'rsasha1' : 5 }
-rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in dict_algorithm.keys()])
+rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in \
+                          dict_rrclass.keys()])
+dict_algorithm = { 'rsamd5' : 1, 'dh' : 2, 'dsa' : 3, 'ecc' : 4,
+                   'rsasha1' : 5 }
+rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in \
+                            dict_algorithm.keys()])
 
 
 header_xtables = { 'qr' : dict_qr, 'opcode' : dict_opcode,
 header_xtables = { 'qr' : dict_qr, 'opcode' : dict_opcode,
                    'rcode' : dict_rcode }
                    'rcode' : dict_rcode }
@@ -75,13 +78,17 @@ def code_totext(code, dict):
         return dict[code] + '(' + str(code) + ')'
         return dict[code] + '(' + str(code) + ')'
     return str(code)
     return str(code)
 
 
-def encode_name(name, absolute = True):
+def encode_name(name, absolute=True):
     # make sure the name is dot-terminated.  duplicate dots will be ignored
     # make sure the name is dot-terminated.  duplicate dots will be ignored
     # below.
     # below.
     name += '.'
     name += '.'
     labels = name.split('.')
     labels = name.split('.')
     wire = ''
     wire = ''
     for l in labels:
     for l in labels:
+        if len(l) > 4 and l[0:4] == 'ptr=':
+            # special meta-syntax for compression pointer
+            wire += ' %04x' % (0xc000 | int(l[4:]))
+            break
         if absolute or len(l) > 0:
         if absolute or len(l) > 0:
             wire += '%02x' % len(l)
             wire += '%02x' % len(l)
             wire += ''.join(['%02x' % ord(ch) for ch in l])
             wire += ''.join(['%02x' % ord(ch) for ch in l])
@@ -89,7 +96,9 @@ def encode_name(name, absolute = True):
             break
             break
     return wire
     return wire
 
 
-def encode_string(name):
+def encode_string(name, len=None):
+    if type(name) is int and len is not None:
+        return '%0.*x' % (len * 2, name)
     return ''.join(['%02x' % ord(ch) for ch in name])
     return ''.join(['%02x' % ord(ch) for ch in name])
 
 
 def count_namelabels(name):
 def count_namelabels(name):
@@ -121,17 +130,19 @@ def print_header(f, input_file):
 
 
 class Name:
 class Name:
     name = 'example.com'
     name = 'example.com'
-    pointer = -1                # no compression by default
+    pointer = None                # no compression by default
     def dump(self, f):
     def dump(self, f):
-        name_wire = encode_name(self.name,
-                                True if self.pointer == -1 else False)
+        name = self.name
+        if self.pointer is not None:
+            if len(name) > 0 and name[-1] != '.':
+                name += '.'
+            name += 'ptr=%d' % self.pointer
+        name_wire = encode_name(name)
         f.write('\n# DNS Name: %s' % self.name)
         f.write('\n# DNS Name: %s' % self.name)
-        if self.pointer >= 0:
+        if self.pointer is not None:
             f.write(' + compression pointer: %d' % self.pointer)
             f.write(' + compression pointer: %d' % self.pointer)
         f.write('\n')
         f.write('\n')
         f.write('%s' % name_wire)
         f.write('%s' % name_wire)
-        if self.pointer >= 0:
-            f.write(' %04x' % (0xc000 | self.pointer))
         f.write('\n')
         f.write('\n')
 
 
 class DNSHeader:
 class DNSHeader:
@@ -338,20 +349,73 @@ class RRSIG:
                 (code_totext(self.covered, rdict_rrtype),
                 (code_totext(self.covered, rdict_rrtype),
                  code_totext(self.algorithm, rdict_algorithm), labels,
                  code_totext(self.algorithm, rdict_algorithm), labels,
                  self.originalttl))
                  self.originalttl))
-        f.write('%04x %02x %02x %08x\n' % (self.covered, self.algorithm, labels,
-                                           self.originalttl))
+        f.write('%04x %02x %02x %08x\n' % (self.covered, self.algorithm,
+                                           labels, self.originalttl))
         f.write('# Expiration=%s, Inception=%s\n' %
         f.write('# Expiration=%s, Inception=%s\n' %
                 (str(self.expiration), str(self.inception)))
                 (str(self.expiration), str(self.inception)))
         f.write('%08x %08x\n' % (self.expiration, self.inception))
         f.write('%08x %08x\n' % (self.expiration, self.inception))
         f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
         f.write('# Tag=%d Signer=%s and Signature\n' % (self.tag, self.signer))
         f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
         f.write('%04x %s %s\n' % (self.tag, name_wire, sig_wire))
 
 
+class TSIG:
+    rdlen = None                # auto-calculate
+    algorithm = 'hmac-sha256'
+    time_signed = 1286978795    # arbitrarily chosen default
+    fudge = 300
+    mac_size = None             # use a common value for the algorithm
+    mac = None                  # use 'x' * mac_size
+    original_id = 2845          # arbitrarily chosen default
+    error = 0
+    other_len = None         # 6 if error is BADTIME; otherwise 0
+    other_data = None        # use time_signed + fudge + 1 for BADTIME
+    dict_macsize = { 'hmac-md5' : 16, 'hmac-sha1' : 20, 'hmac-sha256' : 32 }
+    def dump(self, f):
+        if str(self.algorithm) == 'hmac-md5':
+            name_wire = encode_name('hmac-md5.sig-alg.reg.int')
+        else:
+            name_wire = encode_name(self.algorithm)
+        rdlen = self.rdlen
+        mac_size = self.mac_size
+        if mac_size is None:
+            if self.algorithm in self.dict_macsize.keys():
+                mac_size = self.dict_macsize[self.algorithm]
+            else:
+                raise RuntimeError('TSIG Mac Size cannot be determined')
+        mac = encode_string('x' * mac_size) if self.mac is None else \
+            encode_string(self.mac, mac_size)
+        other_len = self.other_len
+        if other_len is None:
+            # 18 = BADTIME
+            other_len = 6 if self.error == 18 else 0
+        other_data = self.other_data
+        if other_data is None:
+            other_data = '%012x' % (self.time_signed + self.fudge + 1) \
+                if self.error == 18 else ''
+        else:
+            other_data = encode_string(self.other_data, other_len)
+        if rdlen is None:
+            rdlen = int(len(name_wire) / 2 + 16 + len(mac) / 2 + \
+                            len(other_data) / 2)
+        f.write('\n# TSIG RDATA (RDLEN=%d)\n' % rdlen)
+        f.write('%04x\n' % rdlen);
+        f.write('# Algorithm=%s Time-Signed=%d Fudge=%d\n' %
+                (self.algorithm, self.time_signed, self.fudge))
+        f.write('%s %012x %04x\n' % (name_wire, self.time_signed, self.fudge))
+        f.write('# MAC Size=%d MAC=(see hex)\n' % mac_size)
+        f.write('%04x%s\n' % (mac_size, ' ' + mac if len(mac) > 0 else ''))
+        f.write('# Original-ID=%d Error=%d\n' % (self.original_id, self.error))
+        f.write('%04x %04x\n' %  (self.original_id, self.error))
+        f.write('# Other-Len=%d Other-Data=(see hex)\n' % other_len)
+        f.write('%04x%s\n' % (other_len,
+                              ' ' + other_data if len(other_data) > 0 else ''))
+
 def get_config_param(section):
 def get_config_param(section):
     config_param = {'name' : (Name, {}),
     config_param = {'name' : (Name, {}),
                     'header' : (DNSHeader, header_xtables),
                     'header' : (DNSHeader, header_xtables),
                     'question' : (DNSQuestion, question_xtables),
                     'question' : (DNSQuestion, question_xtables),
                     'edns' : (EDNS, {}), 'soa' : (SOA, {}), 'txt' : (TXT, {}),
                     'edns' : (EDNS, {}), 'soa' : (SOA, {}), 'txt' : (TXT, {}),
-                    'rrsig' : (RRSIG, {}), 'nsec' : (NSEC, {})}
+                    'rrsig' : (RRSIG, {}), 'nsec' : (NSEC, {}),
+                    'tsig' : (TSIG, {}) }
     s = section
     s = section
     m = re.match('^([^:]+)/\d+$', section)
     m = re.match('^([^:]+)/\d+$', section)
     if m:
     if m:

+ 6 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire1.spec

@@ -0,0 +1,6 @@
+#
+# A simplest form of TSIG: all default parameters
+#
+[custom]
+sections: tsig
+[tsig]

+ 8 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire2.spec

@@ -0,0 +1,8 @@
+#
+# TSIG with other data (error = BADTIME(18))
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 0
+error: 18

+ 8 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire3.spec

@@ -0,0 +1,8 @@
+#
+# TSIG without MAC (error = BADSIG(16))
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 0
+error: 16

+ 11 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire4.spec

@@ -0,0 +1,11 @@
+#
+# A simplest form of TSIG, but the algorithm name is compressed (quite
+# pathological, but we accept it)
+#
+[custom]
+sections: name:tsig
+[name]
+name: hmac-sha256
+[tsig]
+algorithm: ptr=0
+mac_size: 32

+ 7 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire5.spec

@@ -0,0 +1,7 @@
+#
+# TSIG-like RDATA but RDLEN is too short.
+#
+[custom]
+sections: tsig
+[tsig]
+rdlen: 60

+ 7 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire6.spec

@@ -0,0 +1,7 @@
+#
+# TSIG-like RDATA but RDLEN is too long.
+#
+[custom]
+sections: tsig
+[tsig]
+rdlen: 63

+ 8 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire7.spec

@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but algorithm name is broken.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: "01234567890123456789012345678901234567890123456789012345678901234"
+mac_size: 32

+ 8 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire8.spec

@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but MAC size is bogus
+#
+[custom]
+sections: tsig
+[tsig]
+mac_size: 65535
+mac: "dummy data"

+ 8 - 0
src/lib/dns/tests/testdata/rdata_tsig_fromWire9.spec

@@ -0,0 +1,8 @@
+#
+# TSIG-like RDATA but Other-Data length is bogus
+#
+[custom]
+sections: tsig
+[tsig]
+other_len: 65535
+otherdata: "dummy data"

+ 11 - 0
src/lib/dns/tests/testdata/rdata_tsig_toWire1.spec

@@ -0,0 +1,11 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17

+ 13 - 0
src/lib/dns/tests/testdata/rdata_tsig_toWire2.spec

@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-sha256
+time_signed: 1286779327
+mac_size: 12
+# 0x1402... would be FAKEFAKE... if encoded in BASE64
+mac: 0x140284140284140284140284
+original_id: 16020
+error: 16

+ 15 - 0
src/lib/dns/tests/testdata/rdata_tsig_toWire3.spec

@@ -0,0 +1,15 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig
+[tsig]
+algorithm: hmac-sha1
+time_signed: 1286779327
+mac_size: 12
+# 0x1402... would be FAKEFAKE... if encoded in BASE64
+mac: 0x140284140284140284140284
+original_id: 16020
+error: 18
+other_len: 6
+other_data: 0x140284140284

+ 13 - 0
src/lib/dns/tests/testdata/rdata_tsig_toWire4.spec

@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: name:tsig
+[name]
+name: hmac-md5.sig-alg.reg.int.
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17

+ 13 - 0
src/lib/dns/tests/testdata/rdata_tsig_toWire5.spec

@@ -0,0 +1,13 @@
+#
+# An artificial TSIG RDATA for toWire test.
+#
+[custom]
+sections: tsig:name
+[tsig]
+algorithm: hmac-md5
+time_signed: 1286779327
+mac_size: 0
+original_id: 16020
+error: 17
+[name]
+name: ptr=2

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

@@ -0,0 +1,230 @@
+// 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 <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tsigkey.h>
+
+#include <dns/tests/unittest_util.h>
+
+using namespace std;
+using namespace isc::dns;
+using isc::UnitTestUtil;
+
+namespace {
+class TSIGKeyTest : public ::testing::Test {
+protected:
+    TSIGKeyTest() : secret("someRandomData"), key_name("example.com") {}
+    string secret;
+    Name key_name;
+};
+
+TEST_F(TSIGKeyTest, algorithmNames) {
+    EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), TSIGKey::HMACMD5_NAME());
+    EXPECT_EQ(Name("hmac-sha1"), TSIGKey::HMACSHA1_NAME());
+    EXPECT_EQ(Name("hmac-sha256"), TSIGKey::HMACSHA256_NAME());
+}
+
+TEST_F(TSIGKeyTest, construct) {
+    TSIGKey key(key_name, TSIGKey::HMACMD5_NAME(),
+                secret.c_str(), secret.size());
+    EXPECT_EQ(key_name, key.getKeyName());
+    EXPECT_EQ(Name("hmac-md5.sig-alg.reg.int"), key.getAlgorithmName());
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret.c_str(),
+                        secret.size(), key.getSecret(), key.getSecretLength());
+
+    EXPECT_THROW(TSIGKey(key_name, Name("unknown-alg"),
+                         secret.c_str(), secret.size()),
+                 isc::InvalidParameter);
+
+    // The algorithm name should be converted to the canonical form.
+    EXPECT_EQ("hmac-sha1.",
+              TSIGKey(key_name, Name("HMAC-sha1"),
+                      secret.c_str(),
+                      secret.size()).getAlgorithmName().toText());
+
+    // Invalid combinations of secret and secret_len:
+    EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA1_NAME(), secret.c_str(), 0),
+                 isc::InvalidParameter);
+    EXPECT_THROW(TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(), NULL, 16),
+                 isc::InvalidParameter);
+}
+
+void
+compareTSIGKeys(const TSIGKey& expect, const TSIGKey& actual) {
+    EXPECT_EQ(expect.getKeyName(), actual.getKeyName());
+    EXPECT_EQ(expect.getAlgorithmName(), actual.getAlgorithmName());
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+                        expect.getSecret(), expect.getSecretLength(),
+                        actual.getSecret(), actual.getSecretLength());
+}
+
+TEST_F(TSIGKeyTest, copyConstruct) {
+    const TSIGKey original(key_name, TSIGKey::HMACSHA256_NAME(),
+                           secret.c_str(), secret.size());
+    const TSIGKey copy(original);
+    compareTSIGKeys(original, copy);
+
+    // Check the copied data is valid even after the original is deleted
+    TSIGKey* copy2 = new TSIGKey(original);
+    TSIGKey copy3(*copy2);
+    delete copy2;
+    compareTSIGKeys(original, copy3);
+}
+
+TEST_F(TSIGKeyTest, assignment) {
+    const TSIGKey original(key_name, TSIGKey::HMACSHA256_NAME(),
+                           secret.c_str(), secret.size());
+    TSIGKey copy = original;
+    compareTSIGKeys(original, copy);
+
+    // Check if the copied data is valid even after the original is deleted
+    TSIGKey* copy2 = new TSIGKey(original);
+    TSIGKey copy3(original);
+    copy3 = *copy2;
+    delete copy2;
+    compareTSIGKeys(original, copy3);
+
+    // self assignment
+    copy = copy;
+    compareTSIGKeys(original, copy);
+}
+
+class TSIGKeyRingTest : public ::testing::Test {
+protected:
+    TSIGKeyRingTest() :
+        key_name("example.com"),
+        secretstring("anotherRandomData"),
+        secret(secretstring.c_str()),
+        secret_len(secretstring.size())
+    {}
+    TSIGKeyRing keyring;
+    Name key_name;
+private:
+    const string secretstring;
+protected:
+    const char* secret;
+    size_t secret_len;
+};
+
+TEST_F(TSIGKeyRingTest, init) {
+    EXPECT_EQ(0, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, add) {
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(1, keyring.size());
+    EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+                          secret, secret_len)));
+    // keys are identified their names, the same name of key with a different
+    // algorithm would be considered a duplicate.
+    EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+                  TSIGKey(Name("example.com"), TSIGKey::HMACSHA1_NAME(),
+                          secret, secret_len)));
+    // names are compared in a case insensitive manner.
+    EXPECT_EQ(TSIGKeyRing::EXIST, keyring.add(
+                  TSIGKey(Name("EXAMPLE.COM"), TSIGKey::HMACSHA1_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(1, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, addMore) {
+    // essentially the same test, but try adding more than 1
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(3, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, remove) {
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.remove(key_name));
+    EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.remove(key_name));
+}
+
+TEST_F(TSIGKeyRingTest, removeFromSome) {
+    // essentially the same test, but try removing from a larger set
+
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
+                          secret, secret_len)));
+
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.remove(Name("another.example")));
+    EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.remove(Name("noexist.example")));
+    EXPECT_EQ(2, keyring.size());
+}
+
+TEST_F(TSIGKeyRingTest, find) {
+    EXPECT_EQ(TSIGKeyRing::NOTFOUND, keyring.find(key_name).code);
+    EXPECT_EQ(static_cast<const TSIGKey*>(NULL), keyring.find(key_name).key);
+
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+                          secret, secret_len)));
+    const TSIGKeyRing::FindResult result(keyring.find(key_name));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
+    EXPECT_EQ(key_name, result.key->getKeyName());
+    EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result.key->getAlgorithmName());
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
+                        result.key->getSecret(),
+                        result.key->getSecretLength());
+}
+
+TEST_F(TSIGKeyRingTest, findFromSome) {
+    // essentially the same test, but search a larger set
+
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(key_name, TSIGKey::HMACSHA256_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(Name("another.example"), TSIGKey::HMACMD5_NAME(),
+                          secret, secret_len)));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, keyring.add(
+                  TSIGKey(Name("more.example"), TSIGKey::HMACSHA1_NAME(),
+                          secret, secret_len)));
+
+    const TSIGKeyRing::FindResult result(
+        keyring.find(Name("another.example")));
+    EXPECT_EQ(TSIGKeyRing::SUCCESS, result.code);
+    EXPECT_EQ(Name("another.example"), result.key->getKeyName());
+    EXPECT_EQ(TSIGKey::HMACMD5_NAME(), result.key->getAlgorithmName());
+
+    EXPECT_EQ(TSIGKeyRing::NOTFOUND,
+              keyring.find(Name("noexist.example")).code);
+    EXPECT_EQ(static_cast<const TSIGKey*>(NULL),
+              keyring.find(Name("noexist.example")).key);
+}
+
+} // end namespace

+ 0 - 33
src/lib/dns/tsig.cc

@@ -1,33 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// $Id$
-
-#include <cctype>
-#include <cassert>
-#include <iterator>
-#include <functional>
-
-#include <algorithm>
-
-#include <dns/tsig.h>
-
-using namespace std;
-using isc::dns::MessageRenderer;
-
-namespace isc {
-namespace dns {
-
-} // namespace dns
-} // namespace isc

+ 0 - 72
src/lib/dns/tsig.h

@@ -1,72 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-// $Id$
-
-#ifndef __TSIG_H
-#define __TSIG_H 1
-
-#include <string>
-#include <vector>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/name.h>
-#include <dns/message.h>
-
-namespace isc {
-namespace dns {
-
-class BadTsigKey : public Exception {
-public:
-    BadTsigKey(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-//
-// This class holds a Tsig key, including all its attributes.
-//
-class Tsig {
-public:
-    enum TsigAlgorithm {
-        HMACMD5 = 0,
-        GSS = 1,
-        HMACSHA1 = 2,
-        HMACSHA224 = 3,
-        HMACSHA265 = 4,
-        HMACSHA384 = 5,
-        HMACSHA512 = 6,
-    };
-
-    Tsig(const Name& name, TsigAlgorithm algorithm,
-         const std::string& algorithm_data) :
-         name_(name), algorithm_(algorithm), algorithm_data_(algorithm_data) {};
-
-    bool signMessage(const Message& message);
-    bool verifyMessage(const Message &message);
-
-private:
-    Name name_;
-    TsigAlgorithm algorithm_;
-    std::string algorithm_data_;
-};
-
-}
-}
-
-#endif  // __TSIG_H
-
-// Local Variables: 
-// mode: c++
-// End: 

+ 167 - 0
src/lib/dns/tsigkey.cc

@@ -0,0 +1,167 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/tsigkey.h>
+
+using namespace std;
+
+namespace isc {
+namespace dns {
+struct
+TSIGKey::TSIGKeyImpl {
+    TSIGKeyImpl(const Name& key_name, const Name& algorithm_name,
+                const void* secret, size_t secret_len) :
+        key_name_(key_name), algorithm_name_(algorithm_name),
+        secret_(static_cast<const uint8_t*>(secret),
+                static_cast<const uint8_t*>(secret) + secret_len)
+    {
+        // Convert the name to the canonical form.
+        algorithm_name_.downcase();
+    }
+    const Name key_name_;
+    Name algorithm_name_;
+    const vector<uint8_t> secret_;
+};
+
+TSIGKey::TSIGKey(const Name& key_name, const Name& algorithm_name,
+                 const void* secret, size_t secret_len) : impl_(NULL)
+{
+    if (algorithm_name != HMACMD5_NAME() &&
+        algorithm_name != HMACSHA1_NAME() &&
+        algorithm_name != HMACSHA256_NAME()) {
+        isc_throw(InvalidParameter, "Unknown TSIG algorithm is specified: " <<
+                  algorithm_name);
+    }
+    if ((secret != NULL && secret_len == 0) ||
+        (secret == NULL && secret_len != 0)) {
+        isc_throw(InvalidParameter,
+                  "TSIGKey secret and its length are inconsistent");
+    }
+
+    impl_ = new TSIGKeyImpl(key_name, algorithm_name, secret, secret_len);
+}
+
+TSIGKey::TSIGKey(const TSIGKey& source) : impl_(new TSIGKeyImpl(*source.impl_))
+{}
+
+TSIGKey&
+TSIGKey::operator=(const TSIGKey& source) {
+    if (impl_ == source.impl_) {
+        return (*this);
+    }
+
+    TSIGKeyImpl* newimpl = new TSIGKeyImpl(*source.impl_);
+    delete impl_;
+    impl_ = newimpl;
+
+    return (*this);
+}
+
+TSIGKey::~TSIGKey() {
+    delete impl_;
+}
+
+const Name&
+TSIGKey::getKeyName() const {
+    return (impl_->key_name_);
+}
+
+const Name&
+TSIGKey::getAlgorithmName() const {
+    return (impl_->algorithm_name_);
+}
+
+const void*
+TSIGKey::getSecret() const {
+    return ((impl_->secret_.size() > 0) ? &impl_->secret_[0] : NULL);
+}
+
+size_t
+TSIGKey::getSecretLength() const {
+    return (impl_->secret_.size());
+}
+
+const
+Name& TSIGKey::HMACMD5_NAME() {
+    static Name alg_name("hmac-md5.sig-alg.reg.int");
+    return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA1_NAME() {
+    static Name alg_name("hmac-sha1");
+    return (alg_name);
+}
+
+const
+Name& TSIGKey::HMACSHA256_NAME() {
+    static Name alg_name("hmac-sha256");
+    return (alg_name);
+}
+
+struct TSIGKeyRing::TSIGKeyRingImpl {
+    typedef map<Name, TSIGKey> TSIGKeyMap;
+    typedef pair<Name, TSIGKey> NameAndKey;
+    TSIGKeyMap keys;
+};
+
+TSIGKeyRing::TSIGKeyRing() : impl_(new TSIGKeyRingImpl) {
+}
+
+TSIGKeyRing::~TSIGKeyRing() {
+    delete impl_;
+}
+
+unsigned int
+TSIGKeyRing::size() const {
+    return (impl_->keys.size());
+}
+
+TSIGKeyRing::Result
+TSIGKeyRing::add(const TSIGKey& key) {
+    if (impl_->keys.insert(
+                TSIGKeyRingImpl::NameAndKey(key.getKeyName(), key)).second
+        == true) {
+        return (SUCCESS);
+    } else {
+        return (EXIST);
+    }
+}
+
+TSIGKeyRing::Result
+TSIGKeyRing::remove(const Name& key_name) {
+    return (impl_->keys.erase(key_name) == 1 ? SUCCESS : NOTFOUND);
+}
+
+TSIGKeyRing::FindResult
+TSIGKeyRing::find(const Name& key_name) {
+    TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
+        impl_->keys.find(key_name);
+    if (found == impl_->keys.end()) {
+        return (FindResult(NOTFOUND, NULL));
+    }
+    return (FindResult(SUCCESS, &((*found).second)));
+}
+
+} // namespace dns
+} // namespace isc

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

@@ -0,0 +1,300 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id$
+
+#ifndef __TSIGKEY_H
+#define __TSIGKEY_H 1
+
+namespace isc {
+namespace dns {
+
+class Name;
+
+/// \brief TSIG key.
+///
+/// This class holds a TSIG key along with some related attributes as
+/// defined in RFC2845.
+///
+/// A TSIG key consists of the following attributes:
+/// - Key name
+/// - Hash algorithm
+/// - Shared secret
+///
+/// <b>Implementation Notes</b>
+///
+/// We may add more attributes in future versions.  For example, if and when
+/// we support the TKEY protocol (RFC2930), we may need to introduce the
+/// notion of inception and expiration times.
+/// At that point we may also have to introduce a class hierarchy to handle
+/// different types of keys in a polymorphic way.
+/// At the moment we use the straightforward value-type class with minimal
+/// attributes.
+///
+/// In the TSIG protocol, hash algorithms are represented in the form of
+/// domain name.
+/// Our interfaces provide direct translation of this concept; for example,
+/// the constructor from parameters take a \c Name object to specify the
+/// algorithm.
+/// On one hand, this may be counter intuitive.
+/// An API user would rather specify "hmac-md5" instead of
+/// <code>Name("hmac-md5.sig-alg.reg.int")</code>.
+/// On the other hand, it may be more convenient for some kind of applications
+/// if we maintain the algorithm as the expected representation for
+/// protocol operations (such as sign and very a message).
+/// Considering these points, we adopt the interface closer to the protocol
+/// specification for now.
+/// To minimize the burden for API users, we also define a set of constants
+/// for commonly used algorithm names so that the users don't have to
+/// remember the actual domain names defined in the protocol specification.
+/// We may also have to add conversion routines between domain names
+/// and more intuitive representations (e.g. strings) for algorithms.
+class TSIGKey {
+public:
+    ///
+    /// \name Constructors, Assignment Operator and Destructor.
+    ///
+    //@{
+    /// \brief Constructor from key parameters
+    ///
+    /// In the current implementation, \c algorithm_name must be a known
+    /// algorithm to this implementation, which are defined via the
+    /// <code>static const</code> member functions.  For other names
+    /// an exception of class \c InvalidParameter will be thrown.
+    /// Note: This restriction may be too strict, and we may revisit it
+    /// later.
+    ///
+    /// \c secret and \c secret_len must be consistent in that the latter
+    /// is 0 if and only if the former is \c NULL;
+    /// otherwise an exception of type \c InvalidParameter will be thrown.
+    ///
+    /// This constructor internally involves resource allocation, and if
+    /// it fails, a corresponding standard exception will be thrown.
+    ///
+    /// \param key_name The name of the key as a domain name.
+    /// \param algorithm_name The hash algorithm used for this key in the
+    /// form of domain name.  For example, it can be
+    /// \c TSIGKey::HMACSHA256_NAME() for HMAC-SHA256.
+    /// \param secret Point to a binary sequence of the shared secret to be
+    /// used for this key, or \c NULL if the secret is empty.
+    /// \param secret_len The size of the binary %data (\c secret) in bytes.
+    TSIGKey(const Name& key_name, const Name& algorithm_name,
+            const void* secret, size_t secret_len);
+
+    /// \brief The copy constructor.
+    ///
+    /// It internally allocates a resource, and if it fails a corresponding
+    /// standard exception will be thrown.
+    /// This constructor never throws an exception otherwise.
+    TSIGKey(const TSIGKey& source);
+
+    /// \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.
+    TSIGKey& operator=(const TSIGKey& source);
+
+    /// The destructor.
+    ~TSIGKey();
+    //@}
+
+    ///
+    /// \name Getter Methods
+    ///
+    /// These methods never throw an exception.
+    //@{
+    /// Return the key name.
+    const Name& getKeyName() const;
+
+    /// Return the algorithm name.
+    const Name& getAlgorithmName() const;
+
+    /// Return the length of the TSIG secret in bytes.
+    size_t getSecretLength() const;
+
+    /// Return the value of the TSIG secret.
+    ///
+    /// If it returns a non NULL pointer, the memory region beginning at the
+    /// address returned by this method is valid up to the bytes specified
+    /// by the return value of \c getSecretLength().
+    ///
+    /// The memory region is only valid while the corresponding \c TSIGKey
+    /// object is valid.  The caller must hold the \c TSIGKey object while
+    /// it needs to refer to the region or it must make a local copy of the
+    /// region.
+    const void* getSecret() const;
+    //@}
+
+    ///
+    /// \name Well known algorithm names as defined in RFC2845 and RFC4635.
+    ///
+    /// Note: we begin with the "mandatory" algorithms defined in RFC4635
+    /// as a minimal initial set.
+    /// We'll add others as we see the need for them.
+    //@{
+    static const Name& HMACMD5_NAME();    ///< HMAC-MD5 (RFC2845)
+    static const Name& HMACSHA1_NAME();   ///< HMAC-SHA1 (RFC4635)
+    static const Name& HMACSHA256_NAME(); ///< HMAC-SHA256 (RFC4635)
+    //@}
+
+private:
+    struct TSIGKeyImpl;
+    const TSIGKeyImpl* impl_;
+};
+
+/// \brief A simple repository of a set of \c TSIGKey objects.
+///
+/// This is a "key ring" to maintain TSIG keys (\c TSIGKey objects) and
+/// provides trivial operations such as add, remove, and find.
+///
+/// The keys are identified by their key names.
+/// So, for example, two or more keys of the same key name but of different
+/// algorithms are considered to be the same, and cannot be stored in the
+/// key ring at the same time.
+///
+/// <b>Implementation Note:</b>
+/// For simplicity the initial implementation requests the application make
+/// a copy of keys stored in the key ring if it needs to use the keys for
+/// a long period (during which some of the keys may be removed).
+/// This is based on the observations that a single server will not hold
+/// a huge number of keys nor use keys in many different contexts (such as
+/// in different DNS transactions).
+/// If this assumption does not hold and memory consumption becomes an issue
+/// we may have to revisit the design.
+class TSIGKeyRing {
+public:
+    /// Result codes of various public methods of \c TSIGKeyRing
+    enum Result {
+        SUCCESS = 0,    ///< The operation is successful.
+        EXIST = 1,      ///< A key is already stored in \c TSIGKeyRing.
+        NOTFOUND = 2    ///< The specified key is not found in \c TSIGKeyRing.
+    };
+
+    /// \brief A helper structure to represent the search result of
+    /// <code>TSIGKeyRing::find()</code>.
+    ///
+    /// This is a straightforward pair of the result code and a pointer
+    /// to the found key to represent the result of \c find().
+    /// We use this in order to avoid overloading the return value for both
+    /// the result code ("success" or "not found") and the found object,
+    /// i.e., avoid using \c NULL to mean "not found", etc.
+    ///
+    /// This is a simple value class with no internal state, so for
+    /// convenience we allow the applications to refer to the members
+    /// directly.
+    ///
+    /// See the description of \c find() for the semantics of the member
+    /// variables.
+    struct FindResult {
+        FindResult(Result param_code, const TSIGKey* param_key) :
+            code(param_code), key(param_key)
+        {}
+        const Result code;
+        const TSIGKey* const key;
+    };
+
+    ///
+    /// \name Constructors and Destructor.
+    ///
+    /// \b Note:
+    /// The copy constructor and the assignment operator are
+    /// intentionally defined as private, making this class non copyable.
+    /// There is no technical reason why this class cannot be copied,
+    /// but since the key ring can potentially have a large number of keys,
+    /// a naive copy operation may cause unexpected overhead.
+    /// It's generally expected for an application to share the same
+    /// instance of key ring and share it throughout the program via
+    /// references, so we prevent the copy operation explicitly to avoid
+    /// unexpected copy operations.
+    //@{
+private:
+    TSIGKeyRing(const TSIGKeyRing& source);
+    TSIGKeyRing& operator=(const TSIGKeyRing& source);
+public:
+    /// \brief The default constructor.
+    ///
+    /// This constructor never throws an exception.
+    TSIGKeyRing();
+
+    /// The destructor.
+    ~TSIGKeyRing();
+    //@}
+
+    /// Return the number of keys stored in the \c TSIGKeyRing.
+    ///
+    /// This method never throws an exception.
+    unsigned int size() const;
+
+    /// Add a \c TSIGKey to the \c TSIGKeyRing.
+    ///
+    /// This method will create a local copy of the given key, so the caller
+    /// does not have to keep owning it.
+    ///
+    /// If internal resource allocation fails, a corresponding standard
+    /// exception will be thrown.
+    /// This method never throws an exception otherwise.
+    ///
+    /// \param key A \c TSIGKey to be added.
+    /// \return \c SUCCESS If the key is successfully added to the key ring.
+    /// \return \c EXIST The key ring already stores a key whose name is
+    /// identical to that of \c key.
+    Result add(const TSIGKey& key);
+
+    /// Remove a \c TSIGKey for the given name from the \c TSIGKeyRing.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param key_name The name of the key to be removed.
+    /// \return \c SUCCESS If the key is successfully removed from the key
+    /// ring.
+    /// \return \c NOTFOUND The key ring does not store the key that matches
+    /// \c key_name.
+    Result remove(const Name& key_name);
+
+    /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
+    ///
+    /// It searches the internal storage for a \c TSIGKey whose name is
+    /// \c key_name, and returns the result in the form of a \c FindResult
+    /// object as follows:
+    /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
+    /// - \c key: A pointer to the found \c TSIGKey object if one is found;
+    /// otherwise \c NULL.
+    ///
+    /// The pointer returned in the \c FindResult object is only valid until
+    /// the corresponding key is removed from the key ring.
+    /// The caller must ensure that the key is held in the key ring while
+    /// it needs to refer to it, or it must make a local copy of the key.
+    ///
+    /// This method never throws an exception.
+    ///
+    /// \param key_name The name of the key to be found.
+    /// \return A \c FindResult object enclosing the search result (see above).
+    FindResult find(const Name& key_name);
+private:
+    struct TSIGKeyRingImpl;
+    TSIGKeyRingImpl* impl_;
+};
+}
+}
+
+#endif  // __TSIGKEY_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 2 - 2
src/lib/exceptions/exceptions.h

@@ -40,7 +40,7 @@ public:
     /// file line number.
     /// file line number.
     ///
     ///
     /// @param file the file name where the exception was thrown.
     /// @param file the file name where the exception was thrown.
-    /// @param line the line in @ref file where the exception was thrown.
+    /// @param line the line in \a file where the exception was thrown.
     /// @param what a description (type) of the exception.
     /// @param what a description (type) of the exception.
     Exception(const char* file, size_t line, const char* what) :
     Exception(const char* file, size_t line, const char* what) :
         file_(file), line_(line), what_(what) {}
         file_(file), line_(line), what_(what) {}
@@ -49,7 +49,7 @@ public:
     /// file line number.
     /// file line number.
     ///
     ///
     /// @param file the file name where the exception was thrown.
     /// @param file the file name where the exception was thrown.
-    /// @param line the line in @ref file where the exception was thrown.
+    /// @param line the line in \a file where the exception was thrown.
     /// @param what a description (type) of the exception.
     /// @param what a description (type) of the exception.
     Exception(const char* file, size_t line, const std::string& what) :
     Exception(const char* file, size_t line, const std::string& what) :
         file_(file), line_(line), what_(what) {}
         file_(file), line_(line), what_(what) {}

+ 145 - 36
src/lib/python/isc/cc/data.py

@@ -56,20 +56,116 @@ def remove_null_items(d):
     for k in null_keys:
     for k in null_keys:
         del d[k]
         del d[k]
 
 
-def find(element, identifier):
-    """Returns the subelement in the given data element, raises DataNotFoundError if not found"""
-    if type(identifier) != str or (type(element) != dict and identifier != ""):
-        raise DataTypeError("identifier in merge() is not a string")
-    if type(identifier) != str or (type(element) != dict and identifier != ""):
-        raise DataTypeError("element in merge() is not a dict")
-    id_parts = identifier.split("/")
+def _concat_identifier(id_parts):
+    """Concatenates the given identifier parts into a string,
+       delimited with the '/' character.
+    """
+    return '/'.join(id_parts)
+
+def split_identifier(identifier):
+    """Splits the given identifier into a list of identifier parts,
+       as delimited by the '/' character.
+       Raises a DataTypeError if identifier is not a string."""
+    if type(identifier) != str:
+        raise DataTypeError("identifier is not a string")
+    id_parts = identifier.split('/')
     id_parts[:] = (value for value in id_parts if value != "")
     id_parts[:] = (value for value in id_parts if value != "")
+    return id_parts
+
+def split_identifier_list_indices(identifier):
+    """Finds list indexes in the given identifier, which are of the
+       format [integer].
+       Identifier must be a string.
+       This will only give the list index for the last 'part' of the
+       given identifier (as delimited by the '/' sign).
+       Raises a DataTypeError if the identifier is not a string,
+       or if the format is bad.
+       Returns a tuple, where the first element is the string part of
+       the identifier, and the second element is a list of (nested) list
+       indices.
+       Examples:
+       'a/b/c' will return ('a/b/c', None)
+       'a/b/c[1]' will return ('a/b/c', [1])
+       'a/b/c[1][2][3]' will return ('a/b/c', [1, 2, 3])
+       'a[0]/b[1]/c[2]' will return ('a[0]/b[1]/c', [2])
+    """
+    if type(identifier) != str:
+        raise DataTypeError("identifier in "
+                            "split_identifier_list_indices() "
+                            "not a string: " + str(identifier))
+
+    # We only work on the final 'part' of the identifier
+    id_parts = split_identifier(identifier)
+    id_str = id_parts[-1]
+
+    i = id_str.find('[')
+    if i < 0:
+        if identifier.find(']') >= 0:
+            raise DataTypeError("Bad format in identifier: " + str(identifier))
+        return identifier, None
+
+    # keep the non-index part of that to replace later
+    id = id_str[:i]
+    indices = []
+    while i >= 0:
+        e = id_str.find(']')
+        if e < i + 1:
+            raise DataTypeError("Bad format in identifier: " + str(identifier))
+        try:
+            indices.append(int(id_str[i+1:e]))
+        except ValueError:
+            raise DataTypeError("List index in " + identifier + " not an integer")
+        id_str = id_str[e + 1:]
+        i = id_str.find('[')
+        if i > 0:
+            raise DataTypeError("Bad format in identifier: " + str(identifier))
+    if id.find(']') >= 0 or len(id_str) > 0:
+        raise DataTypeError("Bad format in identifier: " + str(identifier))
+
+    # we replace the final part of the original identifier with
+    # the stripped string
+    id_parts[-1] = id
+    id = _concat_identifier(id_parts)
+    return id, indices
+
+def _find_child_el(element, id):
+    """Finds the child of element with the given id. If the id contains
+       [i], where i is a number, and the child element is a list, the
+       i-th element of that list is returned instead of the list itself.
+       Raises a DataTypeError if the element is of wrong type, if id
+       is not a string, or if the id string contains a bad value.
+       Raises a DataNotFoundError if the element at id could not be
+       found.
+    """
+    id, list_indices = split_identifier_list_indices(id)
+    if type(element) == dict and id in element.keys():
+        result = element[id]
+    else:
+        raise DataNotFoundError(id + " in " + str(element))
+    if type(result) == list and list_indices is not None:
+        for list_index in list_indices:
+            if list_index >= len(result):
+                raise DataNotFoundError("Element " + str(list_index) + " in " + str(result))
+            result = result[list_index]
+    return result
+
+def find(element, identifier):
+    """Returns the subelement in the given data element, raises
+       DataNotFoundError if not found.
+       Returns the given element if the identifier is an empty string.
+       Raises a DataTypeError if identifier is not a string, or if
+       identifier is not empty, and element is not a dict.
+    """
+    if type(identifier) != str:
+        raise DataTypeError("identifier in find() is not a str")
+    if identifier == "":
+        return element
+    if type(element) != dict:
+        raise DataTypeError("element in find() is not a dict")
+    id_parts = split_identifier(identifier)
     cur_el = element
     cur_el = element
     for id in id_parts:
     for id in id_parts:
-        if type(cur_el) == dict and id in cur_el.keys():
-            cur_el = cur_el[id]
-        else:
-            raise DataNotFoundError(identifier + " in " + str(element))
+        cur_el = _find_child_el(cur_el, id)
     return cur_el
     return cur_el
 
 
 def set(element, identifier, value):
 def set(element, identifier, value):
@@ -83,25 +179,46 @@ def set(element, identifier, value):
     if type(element) != dict:
     if type(element) != dict:
         raise DataTypeError("element in set() is not a dict")
         raise DataTypeError("element in set() is not a dict")
     if type(identifier) != str:
     if type(identifier) != str:
-        raise DataTypeError("identifier in set() is not a string")
-    id_parts = identifier.split("/")
-    id_parts[:] = (value for value in id_parts if value != "")
+        raise DataTypeError("identifier in set() is not a str")
+    id_parts = split_identifier(identifier)
     cur_el = element
     cur_el = element
     for id in id_parts[:-1]:
     for id in id_parts[:-1]:
-        if id in cur_el.keys():
-            cur_el = cur_el[id]
-        else:
-            if value == None:
+        try:
+            cur_el = _find_child_el(cur_el, id)
+        except DataNotFoundError:
+            if value is None:
                 # ok we are unsetting a value that wasn't set in
                 # ok we are unsetting a value that wasn't set in
                 # the first place. Simply stop.
                 # the first place. Simply stop.
                 return
                 return
             cur_el[id] = {}
             cur_el[id] = {}
             cur_el = cur_el[id]
             cur_el = cur_el[id]
-    # value can be an empty list or dict, so check for None eplicitely
-    if value != None:
-        cur_el[id_parts[-1]] = value
-    elif id_parts[-1] in cur_el:
-        del cur_el[id_parts[-1]]
+
+    id, list_indices = split_identifier_list_indices(id_parts[-1])
+    if list_indices is None:
+        # value can be an empty list or dict, so check for None eplicitely
+        if value is not None:
+            cur_el[id] = value
+        else:
+            del cur_el[id]
+    else:
+        cur_el = cur_el[id]
+        # in case of nested lists, we need to get to the next to last
+        for list_index in list_indices[:-1]:
+            if type(cur_el) != list:
+                raise DataTypeError("Element at " + identifier + " is not a list")
+            if len(cur_el) <= list_index:
+                raise DataNotFoundError("List index at " + identifier + " out of range")
+            cur_el = cur_el[list_index]
+        # value can be an empty list or dict, so check for None eplicitely
+        list_index = list_indices[-1]
+        if type(cur_el) != list:
+            raise DataTypeError("Element at " + identifier + " is not a list")
+        if len(cur_el) <= list_index:
+            raise DataNotFoundError("List index at " + identifier + " out of range")
+        if value is not None:
+            cur_el[list_index] = value
+        else:
+            del cur_el[list_index]
     return element
     return element
 
 
 def unset(element, identifier):
 def unset(element, identifier):
@@ -116,17 +233,12 @@ def find_no_exc(element, identifier):
     """Returns the subelement in the given data element, returns None
     """Returns the subelement in the given data element, returns None
        if not found, or if an error occurred (i.e. this function should
        if not found, or if an error occurred (i.e. this function should
        never raise an exception)"""
        never raise an exception)"""
-    if type(identifier) != str:
+    try:
+        return find(element, identifier)
+    except DataNotFoundError:
+        return None
+    except DataTypeError:
         return None
         return None
-    id_parts = identifier.split("/")
-    id_parts[:] = (value for value in id_parts if value != "")
-    cur_el = element
-    for id in id_parts:
-        if (type(cur_el) == dict and id in cur_el.keys()) or id=="":
-            cur_el = cur_el[id]
-        else:
-            return None
-    return cur_el
 
 
 def parse_value_str(value_str):
 def parse_value_str(value_str):
     """Parses the given string to a native python object. If the
     """Parses the given string to a native python object. If the
@@ -139,7 +251,4 @@ def parse_value_str(value_str):
     except ValueError as ve:
     except ValueError as ve:
         # simply return the string itself
         # simply return the string itself
         return value_str
         return value_str
-    except SyntaxError as ve:
-        # simply return the string itself
-        return value_str
 
 

+ 91 - 2
src/lib/python/isc/cc/tests/data_test.py

@@ -70,6 +70,11 @@ class TestData(unittest.TestCase):
         c = { "a": { "b": "c" } }
         c = { "a": { "b": "c" } }
         data.remove_identical(a, b)
         data.remove_identical(a, b)
         self.assertEqual(a, c)
         self.assertEqual(a, c)
+
+        self.assertRaises(data.DataTypeError, data.remove_identical,
+                          a, 1)
+        self.assertRaises(data.DataTypeError, data.remove_identical,
+                          1, b)
         
         
     def test_merge(self):
     def test_merge(self):
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
@@ -82,6 +87,45 @@ class TestData(unittest.TestCase):
         self.assertRaises(data.DataTypeError, data.merge, 1, d2)
         self.assertRaises(data.DataTypeError, data.merge, 1, d2)
         self.assertRaises(data.DataTypeError, data.merge, None, None)
         self.assertRaises(data.DataTypeError, data.merge, None, None)
 
 
+
+    def test_split_identifier_list_indices(self):
+        id, indices = data.split_identifier_list_indices('a')
+        self.assertEqual(id, 'a')
+        self.assertEqual(indices, None)
+        id, indices = data.split_identifier_list_indices('a[0]')
+        self.assertEqual(id, 'a')
+        self.assertEqual(indices, [0])
+        id, indices = data.split_identifier_list_indices('a[0][1]')
+        self.assertEqual(id, 'a')
+        self.assertEqual(indices, [0, 1])
+
+        # examples from the docstring
+        id, indices = data.split_identifier_list_indices('a/b/c')
+        self.assertEqual(id, 'a/b/c')
+        self.assertEqual(indices, None)
+        
+        id, indices = data.split_identifier_list_indices('a/b/c[1]')
+        self.assertEqual(id, 'a/b/c')
+        self.assertEqual(indices, [1])
+       
+        id, indices = data.split_identifier_list_indices('a/b/c[1][2][3]')
+        self.assertEqual(id, 'a/b/c')
+        self.assertEqual(indices, [1, 2, 3])
+        
+        id, indices = data.split_identifier_list_indices('a[0]/b[1]/c[2]')
+        self.assertEqual(id, 'a[0]/b[1]/c')
+        self.assertEqual(indices, [2])
+
+        # bad formats
+        self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[')
+        self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a]')
+        self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[[0]]')
+        self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[0]a')
+        self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 'a[0]a[1]')
+
+        self.assertRaises(data.DataTypeError, data.split_identifier_list_indices, 1)
+        
+
     def test_find(self):
     def test_find(self):
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
         self.assertEqual(data.find(d1, ''), d1)
         self.assertEqual(data.find(d1, ''), d1)
@@ -93,19 +137,47 @@ class TestData(unittest.TestCase):
         self.assertRaises(data.DataNotFoundError, data.find, d1, 'f')
         self.assertRaises(data.DataNotFoundError, data.find, d1, 'f')
         self.assertRaises(data.DataTypeError, data.find, d1, 1)
         self.assertRaises(data.DataTypeError, data.find, d1, 1)
         self.assertRaises(data.DataTypeError, data.find, None, 1)
         self.assertRaises(data.DataTypeError, data.find, None, 1)
+        self.assertRaises(data.DataTypeError, data.find, None, "foo")
         self.assertRaises(data.DataTypeError, data.find, "123", "123")
         self.assertRaises(data.DataTypeError, data.find, "123", "123")
         self.assertEqual(data.find("123", ""), "123")
         self.assertEqual(data.find("123", ""), "123")
+
+        d2 = { 'a': [ 1, 2, 3 ] }
+        self.assertEqual(data.find(d2, 'a[0]'), 1)
+        self.assertEqual(data.find(d2, 'a[1]'), 2)
+        self.assertEqual(data.find(d2, 'a[2]'), 3)
+        self.assertRaises(data.DataNotFoundError, data.find, d2, 'a[3]')
+        self.assertRaises(data.DataTypeError, data.find, d2, 'a[a]')
+
+        d3 = { 'a': [ { 'b': [ {}, { 'c': 'd' } ] } ] }
+        self.assertEqual(data.find(d3, 'a[0]/b[1]/c'), 'd')
+        self.assertRaises(data.DataNotFoundError, data.find, d3, 'a[1]/b[1]/c')
         
         
     def test_set(self):
     def test_set(self):
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
         d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } }
         d12 = { 'b': 1, 'c': { 'e': 3, 'f': [ 1 ] } }
+        d13 = { 'b': 1, 'c': { 'e': 3, 'f': [ 2 ] } }
+        d14 = { 'b': 1, 'c': { 'e': 3, 'f': [ { 'g': [ 1, 2 ] } ] } }
+        d15 = { 'b': 1, 'c': { 'e': 3, 'f': [ { 'g': [ 1, 3 ] } ] } }
         data.set(d1, 'a', None)
         data.set(d1, 'a', None)
         data.set(d1, 'c/d', None)
         data.set(d1, 'c/d', None)
         data.set(d1, 'c/e/', 3)
         data.set(d1, 'c/e/', 3)
         data.set(d1, 'c/f', [ 1 ] )
         data.set(d1, 'c/f', [ 1 ] )
         self.assertEqual(d1, d12)
         self.assertEqual(d1, d12)
+        data.set(d1, 'c/f[0]', 2 )
+        self.assertEqual(d1, d13)
+
+        data.set(d1, 'c/f[0]', { 'g': [ 1, 2] } )
+        self.assertEqual(d1, d14)
+        data.set(d1, 'c/f[0]/g[1]', 3)
+        self.assertEqual(d1, d15)
+        
         self.assertRaises(data.DataTypeError, data.set, d1, 1, 2)
         self.assertRaises(data.DataTypeError, data.set, d1, 1, 2)
         self.assertRaises(data.DataTypeError, data.set, 1, "", 2)
         self.assertRaises(data.DataTypeError, data.set, 1, "", 2)
+        self.assertRaises(data.DataTypeError, data.set, d1, 'c[1]', 2)
+        self.assertRaises(data.DataTypeError, data.set, d1, 'c[1][2]', 2)
+        self.assertRaises(data.DataNotFoundError, data.set, d1, 'c/f[5]', 2)
+        self.assertRaises(data.DataNotFoundError, data.set, d1, 'c/f[5][2]', 2)
+
         d3 = {}
         d3 = {}
         e3 = data.set(d3, "does/not/exist", 123)
         e3 = data.set(d3, "does/not/exist", 123)
         self.assertEqual(d3,
         self.assertEqual(d3,
@@ -114,11 +186,25 @@ class TestData(unittest.TestCase):
                          { 'does': { 'not': { 'exist': 123 } } })
                          { 'does': { 'not': { 'exist': 123 } } })
 
 
     def test_unset(self):
     def test_unset(self):
-        d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2 } }
+        d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': [ 1, 2, 3 ] } }
         data.unset(d1, 'a')
         data.unset(d1, 'a')
         data.unset(d1, 'c/d')
         data.unset(d1, 'c/d')
         data.unset(d1, 'does/not/exist')
         data.unset(d1, 'does/not/exist')
-        self.assertEqual(d1, { 'b': 1, 'c': { 'e': 2 } })
+        self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 1, 2, 3 ] } })
+        data.unset(d1, 'c/e[0]')
+        self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 2, 3 ] } })
+        data.unset(d1, 'c/e[1]')
+        self.assertEqual(d1, { 'b': 1, 'c': { 'e': [ 2 ] } })
+        # index 1 should now be out of range
+        self.assertRaises(data.DataNotFoundError, data.unset, d1, 'c/e[1]')
+        d2 = { 'a': [ { 'b': [ 1, 2 ] } ] }
+        data.unset(d2, 'a[0]/b[1]')
+        self.assertEqual(d2, { 'a': [ { 'b': [ 1 ] } ] })
+        d3 = { 'a': [ [ 1, 2 ] ] }
+        data.set(d3, "a[0][1]", 3)
+        self.assertEqual(d3, { 'a': [ [ 1, 3 ] ] })
+        data.unset(d3, 'a[0][1]')
+        self.assertEqual(d3, { 'a': [ [ 1 ] ] })
         
         
     def test_find_no_exc(self):
     def test_find_no_exc(self):
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
         d1 = { 'a': 'a', 'b': 1, 'c': { 'd': 'd', 'e': 2, 'more': { 'data': 'here' } } }
@@ -146,6 +232,9 @@ class TestData(unittest.TestCase):
         self.assertEqual(data.parse_value_str("{ \"a\": \"b\", \"c\": 1 }"), { 'a': 'b', 'c': 1 })
         self.assertEqual(data.parse_value_str("{ \"a\": \"b\", \"c\": 1 }"), { 'a': 'b', 'c': 1 })
         self.assertEqual(data.parse_value_str("[ a c"), "[ a c")
         self.assertEqual(data.parse_value_str("[ a c"), "[ a c")
 
 
+        self.assertEqual(data.parse_value_str(1), None)
+
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     #if not 'CONFIG_TESTDATA_PATH' in os.environ:
     #if not 'CONFIG_TESTDATA_PATH' in os.environ:
     #    print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files")
     #    print("You need to set the environment variable CONFIG_TESTDATA_PATH to point to the directory containing the test data files")

+ 19 - 10
src/lib/python/isc/config/ccsession.py

@@ -398,16 +398,25 @@ class UIModuleCCSession(MultiConfigData):
         module_spec = self.find_spec_part(identifier)
         module_spec = self.find_spec_part(identifier)
         if (type(module_spec) != dict or "list_item_spec" not in module_spec):
         if (type(module_spec) != dict or "list_item_spec" not in module_spec):
             raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list")
             raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list")
-        value = isc.cc.data.parse_value_str(value_str)
-        isc.config.config_data.check_type(module_spec, [value])
-        cur_list, status = self.get_value(identifier)
-        #if not cur_list:
-        #    cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
-        if not cur_list:
-            cur_list = []
-        if value in cur_list:
-            cur_list.remove(value)
-        self.set_value(identifier, cur_list)
+
+        if value_str is None:
+            # we are directly removing an 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")
+            else:
+                self.set_value(identifier, None)
+        else:
+            value = isc.cc.data.parse_value_str(value_str)
+            isc.config.config_data.check_type(module_spec, [value])
+            cur_list, status = self.get_value(identifier)
+            #if not cur_list:
+            #    cur_list = isc.cc.data.find_no_exc(self.config.data, identifier)
+            if not cur_list:
+                cur_list = []
+            if value in cur_list:
+                cur_list.remove(value)
+            self.set_value(identifier, cur_list)
 
 
     def commit(self):
     def commit(self):
         """Commit all local changes, send them through b10-cmdctl to
         """Commit all local changes, send them through b10-cmdctl to

+ 103 - 60
src/lib/python/isc/config/config_data.py

@@ -22,6 +22,7 @@ two through the classes in ccsession)
 
 
 import isc.cc.data
 import isc.cc.data
 import isc.config.module_spec
 import isc.config.module_spec
+import ast
 
 
 class ConfigDataError(Exception): pass
 class ConfigDataError(Exception): pass
 
 
@@ -56,14 +57,14 @@ def check_type(spec_part, value):
         raise isc.cc.data.DataTypeError(str(value) + " is not a map")
         raise isc.cc.data.DataTypeError(str(value) + " is not a map")
 
 
 def convert_type(spec_part, value):
 def convert_type(spec_part, value):
-    """Convert the give value(type is string) according specification 
+    """Convert the given value(type is string) according specification 
     part relevant for the value. Raises an isc.cc.data.DataTypeError 
     part relevant for the value. Raises an isc.cc.data.DataTypeError 
     exception if conversion failed.
     exception if conversion failed.
     """
     """
     if type(spec_part) == dict and 'item_type' in spec_part:
     if type(spec_part) == dict and 'item_type' in spec_part:
         data_type = spec_part['item_type']
         data_type = spec_part['item_type']
     else:
     else:
-        raise isc.cc.data.DataTypeError(str("Incorrect specification part for type convering"))
+        raise isc.cc.data.DataTypeError(str("Incorrect specification part for type conversion"))
    
    
     try:
     try:
         if data_type == "integer":
         if data_type == "integer":
@@ -81,18 +82,25 @@ def convert_type(spec_part, value):
                     ret.append(convert_type(spec_part['list_item_spec'], item))
                     ret.append(convert_type(spec_part['list_item_spec'], item))
             elif type(value) == str:    
             elif type(value) == str:    
                 value = value.split(',')
                 value = value.split(',')
-                for item in value:    
+                for item in value:
                     sub_value = item.split()
                     sub_value = item.split()
                     for sub_item in sub_value:
                     for sub_item in sub_value:
-                        ret.append(convert_type(spec_part['list_item_spec'], sub_item))
+                        ret.append(convert_type(spec_part['list_item_spec'],
+                                                sub_item))
 
 
             if ret == []:
             if ret == []:
                 raise isc.cc.data.DataTypeError(str(value) + " is not a list")
                 raise isc.cc.data.DataTypeError(str(value) + " is not a list")
 
 
             return ret
             return ret
         elif data_type == "map":
         elif data_type == "map":
-            return dict(value)
-            # todo: check types of map contents too
+            map = ast.literal_eval(value)
+            if type(map) == dict:
+                # todo: check types of map contents too
+                return map
+            else:
+                raise isc.cc.data.DataTypeError(
+                           "Value in convert_type not a string "
+                           "specifying a dict")
         else:
         else:
             return value
             return value
     except ValueError as err:
     except ValueError as err:
@@ -108,7 +116,11 @@ def find_spec_part(element, identifier):
     id_parts = identifier.split("/")
     id_parts = identifier.split("/")
     id_parts[:] = (value for value in id_parts if value != "")
     id_parts[:] = (value for value in id_parts if value != "")
     cur_el = element
     cur_el = element
-    for id in id_parts:
+
+    for id_part in id_parts:
+        # strip list selector part
+        # don't need it for the spec part, so just drop it
+        id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
         if type(cur_el) == dict and 'map_item_spec' in cur_el.keys():
         if type(cur_el) == dict and 'map_item_spec' in cur_el.keys():
             found = False
             found = False
             for cur_el_item in cur_el['map_item_spec']:
             for cur_el_item in cur_el['map_item_spec']:
@@ -158,11 +170,9 @@ def spec_name_list(spec, prefix="", recurse=False):
                     result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
                     result.extend(spec_name_list(list_el['map_item_spec'], prefix + list_el['item_name'], recurse))
                 else:
                 else:
                     name = list_el['item_name']
                     name = list_el['item_name']
-                    if list_el['item_type'] in ["list", "map"]:
-                        name += "/"
                     result.append(prefix + name)
                     result.append(prefix + name)
             else:
             else:
-                raise ConfigDataError("Bad specication")
+                raise ConfigDataError("Bad specification")
     else:
     else:
         raise ConfigDataError("Bad specication")
         raise ConfigDataError("Bad specication")
     return result
     return result
@@ -228,6 +238,20 @@ class ConfigData:
             result[item] = value
             result[item] = value
         return result
         return result
 
 
+# should we just make a class for these?
+def _create_value_map_entry(name, type, value, status = None):
+    entry = {}
+    entry['name'] = name
+    entry['type'] = type
+    entry['value'] = value
+    entry['modified'] = False
+    entry['default'] = False
+    if status == MultiConfigData.LOCAL:
+        entry['modified'] = True
+    if status == MultiConfigData.DEFAULT:
+        entry['default'] = True
+    return entry
+
 class MultiConfigData:
 class MultiConfigData:
     """This class stores the module specs, current non-default
     """This class stores the module specs, current non-default
        configuration values and 'local' (uncommitted) changes for
        configuration values and 'local' (uncommitted) changes for
@@ -272,7 +296,7 @@ class MultiConfigData:
            identifier (up to the first /) is interpreted as the module
            identifier (up to the first /) is interpreted as the module
            name. Returns None if not found, or if identifier is not a
            name. Returns None if not found, or if identifier is not a
            string."""
            string."""
-        if type(identifier) != str:
+        if type(identifier) != str or identifier == "":
             return None
             return None
         if identifier[0] == '/':
         if identifier[0] == '/':
             identifier = identifier[1:]
             identifier = identifier[1:]
@@ -336,28 +360,42 @@ class MultiConfigData:
         try:
         try:
             spec = find_spec_part(self._specifications[module].get_config_spec(), id)
             spec = find_spec_part(self._specifications[module].get_config_spec(), id)
             if 'item_default' in spec:
             if 'item_default' in spec:
-                return spec['item_default']
+                id, list_indices = isc.cc.data.split_identifier_list_indices(id)
+                if list_indices is not None and \
+                   type(spec['item_default']) == list:
+                    if len(list_indices) == 1:
+                        default_list = spec['item_default']
+                        index = list_indices[0]
+                        if index < len(default_list):
+                            return default_list[index]
+                        else:
+                            return None
+                else:
+                    return spec['item_default']
             else:
             else:
                 return None
                 return None
         except isc.cc.data.DataNotFoundError as dnfe:
         except isc.cc.data.DataNotFoundError as dnfe:
             return None
             return None
 
 
-    def get_value(self, identifier):
+    def get_value(self, identifier, default = True):
         """Returns a tuple containing value,status.
         """Returns a tuple containing value,status.
            The value contains the configuration value for the given
            The value contains the configuration value for the given
            identifier. The status reports where this value came from;
            identifier. The status reports where this value came from;
            it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
            it is one of: LOCAL, CURRENT, DEFAULT or NONE, corresponding
            (local change, current setting, default as specified by the
            (local change, current setting, default as specified by the
-           specification, or not found at all)."""
+           specification, or not found at all). Does not check and
+           set DEFAULT if the argument 'default' is False (default
+           defaults to True)"""
         value = self.get_local_value(identifier)
         value = self.get_local_value(identifier)
         if value != None:
         if value != None:
             return value, self.LOCAL
             return value, self.LOCAL
         value = self.get_current_value(identifier)
         value = self.get_current_value(identifier)
         if value != None:
         if value != None:
             return value, self.CURRENT
             return value, self.CURRENT
-        value = self.get_default_value(identifier)
-        if value != None:
-            return value, self.DEFAULT
+        if default:
+            value = self.get_default_value(identifier)
+            if value != None:
+                return value, self.DEFAULT
         return None, self.NONE
         return None, self.NONE
 
 
     def get_value_maps(self, identifier = None):
     def get_value_maps(self, identifier = None):
@@ -374,12 +412,7 @@ class MultiConfigData:
         if not identifier:
         if not identifier:
             # No identifier, so we need the list of current modules
             # No identifier, so we need the list of current modules
             for module in self._specifications.keys():
             for module in self._specifications.keys():
-                entry = {}
-                entry['name'] = module
-                entry['type'] = 'module'
-                entry['value'] = None
-                entry['modified'] = False
-                entry['default'] = False
+                entry = _create_value_map_entry(module, 'module', None)
                 result.append(entry)
                 result.append(entry)
         else:
         else:
             if identifier[0] == '/':
             if identifier[0] == '/':
@@ -389,51 +422,41 @@ class MultiConfigData:
             if spec:
             if spec:
                 spec_part = find_spec_part(spec.get_config_spec(), id)
                 spec_part = find_spec_part(spec.get_config_spec(), id)
                 if type(spec_part) == list:
                 if type(spec_part) == list:
+                    # list of items to show
                     for item in spec_part:
                     for item in spec_part:
-                        entry = {}
-                        entry['name'] = item['item_name']
-                        entry['type'] = item['item_type']
-                        value, status = self.get_value("/" + identifier + "/" + item['item_name'])
-                        entry['value'] = value
-                        if status == self.LOCAL:
-                            entry['modified'] = True
-                        else:
-                            entry['modified'] = False
-                        if status == self.DEFAULT:
-                            entry['default'] = False
-                        else:
-                            entry['default'] = False
+                        value, status = self.get_value("/" + identifier\
+                                              + "/" + item['item_name'])
+                        entry = _create_value_map_entry(item['item_name'],
+                                                        item['item_type'],
+                                                        value, status)
                         result.append(entry)
                         result.append(entry)
                 elif type(spec_part) == dict:
                 elif type(spec_part) == dict:
+                    # Sub-specification
                     item = spec_part
                     item = spec_part
                     if item['item_type'] == 'list':
                     if item['item_type'] == 'list':
                         li_spec = item['list_item_spec']
                         li_spec = item['list_item_spec']
-                        item_list, status =  self.get_value("/" + identifier)
-                        if item_list != None:
-                            for value in item_list:
-                                result_part2 = {}
-                                result_part2['name'] = li_spec['item_name']
-                                result_part2['value'] = value
-                                result_part2['type'] = li_spec['item_type']
-                                result_part2['default'] = False
-                                result_part2['modified'] = False
+                        value, status =  self.get_value("/" + identifier)
+                        if type(value) == list:
+                            for list_value in value:
+                                result_part2 = _create_value_map_entry(
+                                                   li_spec['item_name'],
+                                                   li_spec['item_type'],
+                                                   list_value)
                                 result.append(result_part2)
                                 result.append(result_part2)
+                        elif value is not None:
+                            entry = _create_value_map_entry(
+                                        li_spec['item_name'],
+                                        li_spec['item_type'],
+                                        value, status)
+                            result.append(entry)
                     else:
                     else:
-                        entry = {}
-                        entry['name'] = item['item_name']
-                        entry['type'] = item['item_type']
-                        #value, status = self.get_value("/" + identifier + "/" + item['item_name'])
                         value, status = self.get_value("/" + identifier)
                         value, status = self.get_value("/" + identifier)
-                        entry['value'] = value
-                        if status == self.LOCAL:
-                            entry['modified'] = True
-                        else:
-                            entry['modified'] = False
-                        if status == self.DEFAULT:
-                            entry['default'] = False
-                        else:
-                            entry['default'] = False
-                        result.append(entry)
+                        if value is not None:
+                            entry = _create_value_map_entry(
+                                        item['item_name'],
+                                        item['item_type'],
+                                        value, status)
+                            result.append(entry)
         return result
         return result
 
 
     def set_value(self, identifier, value):
     def set_value(self, identifier, value):
@@ -441,8 +464,28 @@ class MultiConfigData:
            there is a specification for the given identifier, the type
            there is a specification for the given identifier, the type
            is checked."""
            is checked."""
         spec_part = self.find_spec_part(identifier)
         spec_part = self.find_spec_part(identifier)
-        if spec_part != None:
+        if spec_part is not None and value is not None:
+            id, list_indices = isc.cc.data.split_identifier_list_indices(identifier)
+            if list_indices is not None \
+               and spec_part['item_type'] == 'list':
+                spec_part = spec_part['list_item_spec']
             check_type(spec_part, value)
             check_type(spec_part, value)
+
+        # Since we do not support list diffs (yet?), we need to
+        # copy the currently set list of items to _local_changes
+        # if we want to modify an element in there
+        # (for any list indices specified in the full identifier)
+        id_parts = isc.cc.data.split_identifier(identifier)
+        cur_id_part = '/'
+        for id_part in id_parts:
+            id, list_indices = isc.cc.data.split_identifier_list_indices(id_part)
+            if list_indices is not None:
+                cur_list, status = self.get_value(cur_id_part + id)
+                if status != MultiConfigData.LOCAL:
+                    isc.cc.data.set(self._local_changes,
+                                    cur_id_part + id,
+                                    cur_list)
+            cur_id_part = cur_id_part + id_part + "/"
         isc.cc.data.set(self._local_changes, identifier, value)
         isc.cc.data.set(self._local_changes, identifier, value)
  
  
     def get_config_item_list(self, identifier = None, recurse = False):
     def get_config_item_list(self, identifier = None, recurse = False):

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

@@ -637,6 +637,8 @@ class TestUIModuleCCSession(unittest.TestCase):
         self.assertEqual({'Spec2': {'item5': ['foo']}}, uccs._local_changes)
         self.assertEqual({'Spec2': {'item5': ['foo']}}, uccs._local_changes)
         uccs.add_value("Spec2/item5", "foo")
         uccs.add_value("Spec2/item5", "foo")
         self.assertEqual({'Spec2': {'item5': ['foo']}}, uccs._local_changes)
         self.assertEqual({'Spec2': {'item5': ['foo']}}, uccs._local_changes)
+        uccs.remove_value("Spec2/item5[0]", None)
+        self.assertEqual({'Spec2': {'item5': []}}, uccs._local_changes)
 
 
     def test_commit(self):
     def test_commit(self):
         fake_conn = fakeUIConn()
         fake_conn = fakeUIConn()

+ 75 - 25
src/lib/python/isc/config/tests/config_data_test.py

@@ -107,6 +107,8 @@ class TestConfigData(unittest.TestCase):
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ 1, 2 ])
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ 1, 2 ])
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, 1, "a")
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, { 'somedict': 'somevalue' }, "a")
         
         
         spec_part = find_spec_part(config_spec, "value2")
         spec_part = find_spec_part(config_spec, "value2")
         self.assertEqual(1.1, convert_type(spec_part, '1.1'))
         self.assertEqual(1.1, convert_type(spec_part, '1.1'))
@@ -146,6 +148,18 @@ class TestConfigData(unittest.TestCase):
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
         self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
 
 
+        spec_part = find_spec_part(config_spec, "value6")
+        self.assertEqual({}, convert_type(spec_part, '{}'))
+        self.assertEqual({ 'v61': 'a' }, convert_type(spec_part, '{ \'v61\': \'a\' }'))
+
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, 1.1)
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, True)
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "a")
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, "1")
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "a", "b" ])
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, [ "1", "b" ])
+        self.assertRaises(isc.cc.data.DataTypeError, convert_type, spec_part, { "a": 1 })
+
         spec_part = find_spec_part(config_spec, "value7")
         spec_part = find_spec_part(config_spec, "value7")
         self.assertEqual(['1', '2'], convert_type(spec_part, '1, 2'))
         self.assertEqual(['1', '2'], convert_type(spec_part, '1, 2'))
         self.assertEqual(['1', '2', '3'], convert_type(spec_part, '1 2  3'))
         self.assertEqual(['1', '2', '3'], convert_type(spec_part, '1 2  3'))
@@ -175,9 +189,9 @@ class TestConfigData(unittest.TestCase):
 
 
     def test_spec_name_list(self):
     def test_spec_name_list(self):
         name_list = spec_name_list(self.cd.get_module_spec().get_config_spec())
         name_list = spec_name_list(self.cd.get_module_spec().get_config_spec())
-        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list)
+        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], name_list)
         name_list = spec_name_list(self.cd.get_module_spec().get_config_spec(), "", True)
         name_list = spec_name_list(self.cd.get_module_spec().get_config_spec(), "", True)
-        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list)
+        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6/value1', 'item6/value2'], name_list)
         spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6")
         spec_part = find_spec_part(self.cd.get_module_spec().get_config_spec(), "item6")
         name_list = spec_name_list(spec_part, "item6", True)
         name_list = spec_name_list(spec_part, "item6", True)
         self.assertEqual(['item6/value1', 'item6/value2'], name_list)
         self.assertEqual(['item6/value1', 'item6/value2'], name_list)
@@ -193,7 +207,7 @@ class TestConfigData(unittest.TestCase):
         name_list = spec_name_list({ "myModule": config_spec }, "", False)
         name_list = spec_name_list({ "myModule": config_spec }, "", False)
         self.assertEqual(['myModule/'], name_list)
         self.assertEqual(['myModule/'], name_list)
         name_list = spec_name_list({ "myModule": config_spec }, "", True)
         name_list = spec_name_list({ "myModule": config_spec }, "", True)
-        self.assertEqual(['myModule/', 'myModule/value1', 'myModule/value2', 'myModule/value3', 'myModule/value4', 'myModule/value5/', 'myModule/value6/v61', 'myModule/value6/v62', 'myModule/value7/', 'myModule/value8/', 'myModule/value9/v91', 'myModule/value9/v92/v92a', 'myModule/value9/v92/v92b'], name_list)
+        self.assertEqual(['myModule/', 'myModule/value1', 'myModule/value2', 'myModule/value3', 'myModule/value4', 'myModule/value5', 'myModule/value6/v61', 'myModule/value6/v62', 'myModule/value7', 'myModule/value8', 'myModule/value9/v91', 'myModule/value9/v92/v92a', 'myModule/value9/v92/v92b'], name_list)
 
 
         self.assertRaises(ConfigDataError, spec_name_list, 1)
         self.assertRaises(ConfigDataError, spec_name_list, 1)
         self.assertRaises(ConfigDataError, spec_name_list, [ 'a' ])
         self.assertRaises(ConfigDataError, spec_name_list, [ 'a' ])
@@ -240,19 +254,19 @@ class TestConfigData(unittest.TestCase):
 
 
     def test_get_item_list(self):
     def test_get_item_list(self):
         name_list = self.cd.get_item_list()
         name_list = self.cd.get_item_list()
-        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/'], name_list)
+        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6'], name_list)
         name_list = self.cd.get_item_list("", True)
         name_list = self.cd.get_item_list("", True)
-        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5/', 'item6/value1', 'item6/value2'], name_list)
+        self.assertEqual(['item1', 'item2', 'item3', 'item4', 'item5', 'item6/value1', 'item6/value2'], name_list)
         name_list = self.cd.get_item_list("item6", False)
         name_list = self.cd.get_item_list("item6", False)
         self.assertEqual(['item6/value1', 'item6/value2'], name_list)
         self.assertEqual(['item6/value1', 'item6/value2'], name_list)
 
 
     def test_get_full_config(self):
     def test_get_full_config(self):
         full_config = self.cd.get_full_config()
         full_config = self.cd.get_full_config()
-        self.assertEqual({ "item1": 1, "item2": 1.1, "item3": True, "item4": "test", "item5/": ['a', 'b'], "item6/value1": 'default', 'item6/value2': None}, full_config)
+        self.assertEqual({ "item1": 1, "item2": 1.1, "item3": True, "item4": "test", "item5": ['a', 'b'], "item6/value1": 'default', 'item6/value2': None}, full_config)
         my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
         my_config = { "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ] }
         self.cd.set_local_config(my_config)
         self.cd.set_local_config(my_config)
         full_config = self.cd.get_full_config()
         full_config = self.cd.get_full_config()
-        self.assertEqual({ "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5/": [ "c", "d" ], "item6/value1": 'default', 'item6/value2': None}, full_config)
+        self.assertEqual({ "item1": 2, "item2": 2.2, "item3": False, "item4": "asdf", "item5": [ "c", "d" ], "item6/value1": 'default', 'item6/value2': None}, full_config)
 
 
 class TestMultiConfigData(unittest.TestCase):
 class TestMultiConfigData(unittest.TestCase):
     def setUp(self):
     def setUp(self):
@@ -342,6 +356,12 @@ class TestMultiConfigData(unittest.TestCase):
         self.assertEqual(1, value)
         self.assertEqual(1, value)
         value = self.mcd.get_default_value("/Spec2/item1")
         value = self.mcd.get_default_value("/Spec2/item1")
         self.assertEqual(1, value)
         self.assertEqual(1, value)
+        value = self.mcd.get_default_value("Spec2/item5[0]")
+        self.assertEqual('a', value)
+        value = self.mcd.get_default_value("Spec2/item5[5]")
+        self.assertEqual(None, value)
+        value = self.mcd.get_default_value("Spec2/item5[0][1]")
+        self.assertEqual(None, value)
         value = self.mcd.get_default_value("Spec2/item6/value1")
         value = self.mcd.get_default_value("Spec2/item6/value1")
         self.assertEqual('default', value)
         self.assertEqual('default', value)
         value = self.mcd.get_default_value("Spec2/item6/value2")
         value = self.mcd.get_default_value("Spec2/item6/value2")
@@ -353,20 +373,34 @@ class TestMultiConfigData(unittest.TestCase):
         module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
         module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec2.spec")
         self.mcd.set_specification(module_spec)
         self.mcd.set_specification(module_spec)
         self.mcd.set_value("Spec2/item1", 2)
         self.mcd.set_value("Spec2/item1", 2)
-        value,status = self.mcd.get_value("Spec2/item1")
+
+        value, status = self.mcd.get_value("Spec2/item1")
         self.assertEqual(2, value)
         self.assertEqual(2, value)
         self.assertEqual(MultiConfigData.LOCAL, status)
         self.assertEqual(MultiConfigData.LOCAL, status)
-        value,status = self.mcd.get_value("Spec2/item2")
+
+        value, status = self.mcd.get_value("Spec2/item2")
         self.assertEqual(1.1, value)
         self.assertEqual(1.1, value)
         self.assertEqual(MultiConfigData.DEFAULT, status)
         self.assertEqual(MultiConfigData.DEFAULT, status)
+
         self.mcd._current_config = { "Spec2": { "item3": False } }
         self.mcd._current_config = { "Spec2": { "item3": False } }
-        value,status = self.mcd.get_value("Spec2/item3")
+
+        value, status = self.mcd.get_value("Spec2/item3")
         self.assertEqual(False, value)
         self.assertEqual(False, value)
         self.assertEqual(MultiConfigData.CURRENT, status)
         self.assertEqual(MultiConfigData.CURRENT, status)
-        value,status = self.mcd.get_value("Spec2/no_such_item")
+
+        value, status = self.mcd.get_value("Spec2/no_such_item")
+        self.assertEqual(None, value)
+        self.assertEqual(MultiConfigData.NONE, status)
+
+        value, status = self.mcd.get_value("Spec2/item5[0]")
+        self.assertEqual("a", value)
+        self.assertEqual(MultiConfigData.DEFAULT, status)
+
+        value, status = self.mcd.get_value("Spec2/item5[0]", False)
         self.assertEqual(None, value)
         self.assertEqual(None, value)
         self.assertEqual(MultiConfigData.NONE, status)
         self.assertEqual(MultiConfigData.NONE, status)
 
 
+
     def test_get_value_maps(self):
     def test_get_value_maps(self):
         maps = self.mcd.get_value_maps()
         maps = self.mcd.get_value_maps()
         self.assertEqual([], maps)
         self.assertEqual([], maps)
@@ -390,29 +424,31 @@ class TestMultiConfigData(unittest.TestCase):
         self.mcd.set_value("Spec2/item3", False)
         self.mcd.set_value("Spec2/item3", False)
         maps = self.mcd.get_value_maps("/Spec2")
         maps = self.mcd.get_value_maps("/Spec2")
         self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
         self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
-                          {'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
+                          {'default': True, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
                           {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
                           {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
-                          {'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
-                          {'default': False, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
-                          {'default': False, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
+                          {'default': True, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
+                          {'default': True, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
+                          {'default': True, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
         maps = self.mcd.get_value_maps("Spec2")
         maps = self.mcd.get_value_maps("Spec2")
         self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
         self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False},
-                          {'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
+                          {'default': True, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False},
                           {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
                           {'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True},
-                          {'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
-                          {'default': False, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
-                          {'default': False, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
+                          {'default': True, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False},
+                          {'default': True, 'type': 'list', 'name': 'item5', 'value': ['a', 'b'], 'modified': False},
+                          {'default': True, 'type': 'map', 'name': 'item6', 'value': {}, 'modified': False}], maps)
         maps = self.mcd.get_value_maps("/Spec2/item5")
         maps = self.mcd.get_value_maps("/Spec2/item5")
         self.assertEqual([{'default': False, 'type': 'string', 'name': 'list_element', 'value': 'a', 'modified': False},
         self.assertEqual([{'default': False, 'type': 'string', 'name': 'list_element', 'value': 'a', 'modified': False},
                           {'default': False, 'type': 'string', 'name': 'list_element', 'value': 'b', 'modified': False}], maps)
                           {'default': False, 'type': 'string', 'name': 'list_element', 'value': 'b', 'modified': False}], maps)
+        maps = self.mcd.get_value_maps("/Spec2/item5[0]")
+        self.assertEqual([{'default': True, 'modified': False, 'name': 'list_element', 'type': 'string', 'value': 'a'}], maps)
         maps = self.mcd.get_value_maps("/Spec2/item1")
         maps = self.mcd.get_value_maps("/Spec2/item1")
         self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False}], maps)
         self.assertEqual([{'default': False, 'type': 'integer', 'name': 'item1', 'value': 2, 'modified': False}], maps)
         maps = self.mcd.get_value_maps("/Spec2/item2")
         maps = self.mcd.get_value_maps("/Spec2/item2")
-        self.assertEqual([{'default': False, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False}], maps)
+        self.assertEqual([{'default': True, 'type': 'real', 'name': 'item2', 'value': 1.1, 'modified': False}], maps)
         maps = self.mcd.get_value_maps("/Spec2/item3")
         maps = self.mcd.get_value_maps("/Spec2/item3")
         self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True}], maps)
         self.assertEqual([{'default': False, 'type': 'boolean', 'name': 'item3', 'value': False, 'modified': True}], maps)
         maps = self.mcd.get_value_maps("/Spec2/item4")
         maps = self.mcd.get_value_maps("/Spec2/item4")
-        self.assertEqual([{'default': False, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}], maps)
+        self.assertEqual([{'default': True, 'type': 'string', 'name': 'item4', 'value': 'test', 'modified': False}], maps)
 
 
         module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec")
         module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec")
         self.mcd.set_specification(module_spec)
         self.mcd.set_specification(module_spec)
@@ -429,7 +465,19 @@ class TestMultiConfigData(unittest.TestCase):
         self.mcd.set_specification(module_spec)
         self.mcd.set_specification(module_spec)
         self.mcd.set_value("Spec2/item1", 2)
         self.mcd.set_value("Spec2/item1", 2)
         self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item1", "asdf")
         self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item1", "asdf")
+
         self.mcd.set_value("Spec2/no_such_item", 4)
         self.mcd.set_value("Spec2/no_such_item", 4)
+        value, status = self.mcd.get_value("Spec2/no_such_item")
+        self.assertEqual(value, 4)
+        self.assertEqual(MultiConfigData.LOCAL, status)
+
+        self.mcd.set_value("Spec2/item5[0]", "c")
+        value, status = self.mcd.get_value("Spec2/item5[0]")
+        self.assertEqual(value, "c")
+        self.assertEqual(MultiConfigData.LOCAL, status)
+
+        self.assertRaises(isc.cc.data.DataTypeError, self.mcd.set_value, "Spec2/item5[a]", "asdf")
+        
 
 
     def test_get_config_item_list(self):
     def test_get_config_item_list(self):
         config_items = self.mcd.get_config_item_list()
         config_items = self.mcd.get_config_item_list()
@@ -441,13 +489,15 @@ class TestMultiConfigData(unittest.TestCase):
         config_items = self.mcd.get_config_item_list(None, False)
         config_items = self.mcd.get_config_item_list(None, False)
         self.assertEqual(['Spec2'], config_items)
         self.assertEqual(['Spec2'], config_items)
         config_items = self.mcd.get_config_item_list(None, True)
         config_items = self.mcd.get_config_item_list(None, True)
-        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
         config_items = self.mcd.get_config_item_list("Spec2", True)
         config_items = self.mcd.get_config_item_list("Spec2", True)
-        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
         config_items = self.mcd.get_config_item_list("Spec2")
         config_items = self.mcd.get_config_item_list("Spec2")
-        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/'], config_items)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6'], config_items)
+        config_items = self.mcd.get_config_item_list("/Spec2")
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6'], config_items)
         config_items = self.mcd.get_config_item_list("Spec2", True)
         config_items = self.mcd.get_config_item_list("Spec2", True)
-        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5/', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
+        self.assertEqual(['Spec2/item1', 'Spec2/item2', 'Spec2/item3', 'Spec2/item4', 'Spec2/item5', 'Spec2/item6/value1', 'Spec2/item6/value2'], config_items)
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

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

@@ -1,3 +1,5 @@
+SUBDIRS = . tests
+
 python_PYTHON = __init__.py master.py sqlite3_ds.py
 python_PYTHON = __init__.py master.py sqlite3_ds.py
 
 
 pythondir = $(pyexecdir)/isc/datasrc
 pythondir = $(pyexecdir)/isc/datasrc

+ 1 - 1
src/lib/python/isc/datasrc/master.py

@@ -103,7 +103,7 @@ def isname(s):
 # isttl: check whether a string is a valid TTL specifier.
 # isttl: check whether a string is a valid TTL specifier.
 # returns: boolean
 # returns: boolean
 #########################################################################
 #########################################################################
-ttl_regex = re.compile('([0-9]+[wdhms]?)+', re.I)
+ttl_regex = re.compile('([0-9]+[wdhms]?)+$', re.I)
 def isttl(s):
 def isttl(s):
     global ttl_regex
     global ttl_regex
     if ttl_regex.match(s):
     if ttl_regex.match(s):

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

@@ -0,0 +1,12 @@
+PYTESTS = master_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# later will have configure option to choose this, like: coverage run --branch
+PYCOVERAGE = $(PYTHON)
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/python/isc/log \
+	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	done

+ 35 - 0
src/lib/python/isc/datasrc/tests/master_test.py

@@ -0,0 +1,35 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from isc.datasrc.master import *
+import unittest
+
+class TestTTL(unittest.TestCase):
+    def test_ttl(self):
+        self.assertTrue(isttl('3600'))
+        self.assertTrue(isttl('1W'))
+        self.assertTrue(isttl('1w'))
+        self.assertTrue(isttl('2D'))
+        self.assertTrue(isttl('2d'))
+        self.assertTrue(isttl('30M'))
+        self.assertTrue(isttl('30m'))
+        self.assertTrue(isttl('10S'))
+        self.assertTrue(isttl('10s'))
+        self.assertTrue(isttl('2W1D'))
+        self.assertFalse(isttl('not a ttl'))
+        self.assertFalse(isttl('1X'))
+
+if __name__ == '__main__':
+    unittest.main()

+ 1 - 0
src/lib/python/isc/notify/notify_out.py

@@ -367,6 +367,7 @@ class NotifyOut:
                     zone_id = self._waiting_zones.pop(0) 
                     zone_id = self._waiting_zones.pop(0) 
                     self._notify_infos[zone_id].prepare_notify_out()
                     self._notify_infos[zone_id].prepare_notify_out()
                     self.notify_num += 1 
                     self.notify_num += 1 
+                    self._notifying_zones.append(zone_id)
 
 
     def _send_notify_message_udp(self, zone_notify_info, addrinfo):
     def _send_notify_message_udp(self, zone_notify_info, addrinfo):
         msg, qid = self._create_notify_message(zone_notify_info.zone_name, 
         msg, qid = self._create_notify_message(zone_notify_info.zone_name, 

+ 1 - 1
src/lib/python/isc/notify/tests/notify_out_test.py

@@ -156,7 +156,7 @@ class TestNotifyOut(unittest.TestCase):
         com_info = self._notify._notify_infos[('com.', 'IN')]
         com_info = self._notify._notify_infos[('com.', 'IN')]
         self._notify._notify_next_target(com_info)
         self._notify._notify_next_target(com_info)
         self.assertEqual(2, self._notify.notify_num)
         self.assertEqual(2, self._notify.notify_num)
-        self.assertEqual(0, len(self._notify._notifying_zones))
+        self.assertEqual(2, len(self._notify._notifying_zones))
     
     
     def test_handle_notify_reply(self):
     def test_handle_notify_reply(self):
         self.assertEqual(notify_out._BAD_REPLY_PACKET, self._notify._handle_notify_reply(None, b'badmsg'))
         self.assertEqual(notify_out._BAD_REPLY_PACKET, self._notify._handle_notify_reply(None, b'badmsg'))

+ 5 - 0
src/lib/python/isc/utils/Makefile.am

@@ -0,0 +1,5 @@
+SUBDIRS = tests
+
+python_PYTHON = __init__.py process.py
+
+pythondir = $(pyexecdir)/isc/utils

+ 0 - 0
src/lib/python/isc/utils/__init__.py


+ 37 - 0
src/lib/python/isc/utils/process.py

@@ -0,0 +1,37 @@
+# Copyright (C) 2010  CZ NIC
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Module to manipulate the python processes.
+
+It contains only function to rename the process, which is currently
+wrapper around setproctitle library. Does not fail if the setproctitle
+module is missing, but does nothing in that case.
+"""
+try:
+    from setproctitle import setproctitle
+except ImportError:
+    def setproctitle(_): pass
+import sys
+import os.path
+
+"""
+Rename the current process to given name (so it can be found in ps).
+If name is None, use zero'th command line argument.
+"""
+def rename(name=None):
+    if name is None:
+        name = os.path.basename(sys.argv[0])
+    setproctitle(name)

+ 12 - 0
src/lib/python/isc/utils/tests/Makefile.am

@@ -0,0 +1,12 @@
+PYTESTS = process_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# later will have configure option to choose this, like: coverage run --branch
+PYCOVERAGE = $(PYTHON)
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
+	$(PYCOVERAGE) $(abs_srcdir)/$$pytest || exit ; \
+	done

+ 39 - 0
src/lib/python/isc/utils/tests/process_test.py

@@ -0,0 +1,39 @@
+# Copyright (C) 2010  CZ NIC
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""Tests for isc.utils.process."""
+import unittest
+import isc.utils.process
+run_tests = True
+try:
+    import setproctitle
+except ImportError:
+    run_tests = False
+
+class TestRename(unittest.TestCase):
+    """Testcase for isc.process.rename."""
+    def __get_self_name(self):
+        return setproctitle.getproctitle()
+
+    @unittest.skipIf(not run_tests, "Setproctitle not installed, not testing")
+    def test_rename(self):
+        """Test if the renaming function works."""
+        isc.utils.process.rename("rename-test")
+        self.assertEqual("rename-test", self.__get_self_name())
+        isc.utils.process.rename()
+        self.assertEqual("process_test.py", self.__get_self_name())
+
+if __name__ == "__main__":
+    unittest.main()