Parcourir la source

Merge branch 'master' into work/sign2

Michal 'vorner' Vaner il y a 14 ans
Parent
commit
857abf9ee8
73 fichiers modifiés avec 5633 ajouts et 925 suppressions
  1. 31 5
      ChangeLog
  2. 3 1
      README
  3. 4 1
      configure.ac
  4. 1 1
      doc/Doxyfile
  5. 14 2
      doc/guide/bind10-guide.xml
  6. 8 0
      src/bin/cfgmgr/plugins/tests/Makefile.am
  7. 1 1
      src/bin/msgq/tests/msgq_test.py
  8. 1 1
      src/bin/xfrin/b10-xfrin.8
  9. 6 0
      src/bin/xfrin/b10-xfrin.xml
  10. 481 51
      src/bin/xfrin/tests/xfrin_test.py
  11. 276 76
      src/bin/xfrin/xfrin.py.in
  12. 36 14
      src/bin/xfrin/xfrin.spec
  13. 29 8
      src/bin/zonemgr/b10-zonemgr.8
  14. 53 13
      src/bin/zonemgr/b10-zonemgr.xml
  15. 55 0
      src/bin/zonemgr/tests/zonemgr_test.py
  16. 65 24
      src/bin/zonemgr/zonemgr.py.in
  17. 6 0
      src/lib/asiolink/tests/Makefile.am
  18. 19 7
      src/lib/asiolink/tests/interval_timer_unittest.cc
  19. 4 3
      src/lib/config/ccsession.cc
  20. 10 5
      src/lib/config/ccsession.h
  21. 2 1
      src/lib/config/tests/ccsession_unittests.cc
  22. 26 1
      src/lib/cryptolink/crypto_hmac.cc
  23. 11 6
      src/lib/dns/python/Makefile.am
  24. 3 3
      src/lib/dns/python/edns_python.cc
  25. 43 7
      src/lib/dns/python/message_python.cc
  26. 97 92
      src/lib/dns/python/messagerenderer_python.cc
  27. 52 0
      src/lib/dns/python/messagerenderer_python.h
  28. 249 230
      src/lib/dns/python/name_python.cc
  29. 84 0
      src/lib/dns/python/name_python.h
  30. 38 13
      src/lib/dns/python/pydnspp.cc
  31. 14 4
      src/lib/dns/python/pydnspp_common.cc
  32. 24 5
      src/lib/dns/python/pydnspp_common.h
  33. 127 0
      src/lib/dns/python/pydnspp_towire.h
  34. 2 2
      src/lib/dns/python/question_python.cc
  35. 80 77
      src/lib/dns/python/rcode_python.cc
  36. 57 0
      src/lib/dns/python/rcode_python.h
  37. 4 4
      src/lib/dns/python/rrset_python.cc
  38. 4 1
      src/lib/dns/python/tests/Makefile.am
  39. 48 1
      src/lib/dns/python/tests/message_python_test.py
  40. 530 5
      src/lib/dns/python/tests/tsig_python_test.py
  41. 30 0
      src/lib/dns/python/tests/tsig_rdata_python_test.py
  42. 97 0
      src/lib/dns/python/tests/tsigerror_python_test.py
  43. 44 0
      src/lib/dns/python/tests/tsigrecord_python_test.py
  44. 259 52
      src/lib/dns/python/tsig_python.cc
  45. 47 0
      src/lib/dns/python/tsig_python.h
  46. 369 0
      src/lib/dns/python/tsig_rdata_python.cc
  47. 57 0
      src/lib/dns/python/tsig_rdata_python.h
  48. 370 0
      src/lib/dns/python/tsigerror_python.cc
  49. 52 0
      src/lib/dns/python/tsigerror_python.h
  50. 83 0
      src/lib/dns/python/tsigerror_python_inc.cc
  51. 201 177
      src/lib/dns/python/tsigkey_python.cc
  52. 53 0
      src/lib/dns/python/tsigkey_python.h
  53. 311 0
      src/lib/dns/python/tsigrecord_python.cc
  54. 53 0
      src/lib/dns/python/tsigrecord_python.h
  55. 1 1
      src/lib/dns/rdata/any_255/tsig_250.cc
  56. 1 7
      src/lib/dns/tsigerror.h
  57. 1 0
      src/lib/python/Makefile.am
  58. 1 1
      src/lib/python/bind10_config.py.in
  59. 9 0
      src/lib/python/isc/config/config_data.py
  60. 21 0
      src/lib/python/isc/config/tests/config_data_test.py
  61. 6 2
      src/lib/python/isc/notify/notify_out.py
  62. 34 16
      src/lib/python/isc/notify/tests/notify_out_test.py
  63. 1 1
      src/lib/python/isc/testutils/Makefile.am
  64. 53 0
      src/lib/python/isc/testutils/tsigctx_mock.py
  65. 1 1
      src/lib/server_common/keyring.cc
  66. 3 1
      src/lib/server_common/tests/Makefile.am
  67. 3 1
      src/lib/util/Makefile.am
  68. 100 0
      src/lib/util/python/mkpywrapper.py.in
  69. 308 0
      src/lib/util/python/pycppwrapper_util.h
  70. 309 0
      src/lib/util/python/wrapper_template.cc
  71. 59 0
      src/lib/util/python/wrapper_template.h
  72. 14 0
      src/lib/util/pyunittests/Makefile.am
  73. 84 0
      src/lib/util/pyunittests/pyunittests_util.cc

+ 31 - 5
ChangeLog

@@ -1,3 +1,27 @@
+242.	[func]		jinmei
+	xfrin: added support for TSIG verify.  This change completes TSIG
+	support in b10-xfrin.
+	(Trac #914, git 78502c021478d97672232015b7df06a7d52e531b)
+
+241.	[func]		jinmei
+	pydnspp: added python extension for the TSIG API introduced in
+	change 235.
+	(Trac #905, git 081891b38f05f9a186814ab7d1cd5c572b8f777f)
+	(Trac #915, git 0555ab65d0e43d03b2d40c95d833dd050eea6c23)
+
+240.	[func]*		jelte
+	Updated configuration options to Xfrin, so that you can specify
+	a master address, port, and TSIG key per zone. Still only one per
+	zone at this point, and TSIG keys are (currently) only specified
+	by their full string representation.
+	(Trac #811, git 88504d121c5e08fff947b92e698a54d24d14c375)
+
+239.	[bug]		jerry
+	src/bin/xfrout: If a zone doesn't have notify slaves(only has
+	one apex ns record - the primary master name server) will cause
+	b10-xfrout uses 100% of CPU.
+	(Trac #684, git d11b5e89203a5340d4e5ca51c4c02db17c33dc1f)
+
 238.	[func]		zhang likun
 238.	[func]		zhang likun
 	Implement the simplest forwarder, which pass everything throught
 	Implement the simplest forwarder, which pass everything throught
 	except QID, port number. The response will not be cached.
 	except QID, port number. The response will not be cached.
@@ -120,11 +144,13 @@
 	reconfigure them.
 	reconfigure them.
 	(Trac #775, git 572ac2cf62e18f7eb69d670b890e2a3443bfd6e7)
 	(Trac #775, git 572ac2cf62e18f7eb69d670b890e2a3443bfd6e7)
 
 
-222.	[bug]		jerry
-	src/lib/zonemgr: Fix a bug that xfrin not checking for new copy of
-	zone on startup.  Imposes some random jitters to avoid many zones
-	need to do refresh at the same time.
-	(Trac #387, svn 9140fab9bab5f6502bd15d391fd51ac078b0b89b)
+222.	[bug]*		jerry
+	src/lib/zonemgr: Fix a bug that xfrin not checking for new
+	copy of zone on startup.  Imposes some random jitters to
+	avoid many zones need to do refresh at the same time. This
+	removed the Zonemgr/jitter_scope setting and introduced
+	Zonemgr/refresh_jitter and Zonemgr/reload_jitter.
+	(Trac #387, git 1241ddcffa16285d0a7bb01d6a8526e19fbb70cb)
 
 
 221.	[func]*		jerry
 221.	[func]*		jerry
 	src/lib/util: Create C++ utility library.
 	src/lib/util: Create C++ utility library.

+ 3 - 1
README

@@ -19,7 +19,9 @@ backends), b10-resolver recursive or forwarding DNS server, b10-cmdctl
 remote control daemon, b10-cfgmgr configuration manager, b10-xfrin
 remote control daemon, b10-cfgmgr configuration manager, b10-xfrin
 AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
 AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
 secondary manager, b10-stats statistics collection and reporting
 secondary manager, b10-stats statistics collection and reporting
-daemon, and a new libdns++ library for C++ with a python wrapper.
+daemon, b10-stats-httpd for HTTP access to XML-formatted stats,
+b10-host DNS lookup utility, 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/

+ 4 - 1
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, 20110322, bind10-dev@isc.org)
+AC_INIT(bind10-devel, 20110519, 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])
@@ -777,6 +777,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/util/io/Makefile
                  src/lib/util/io/Makefile
                  src/lib/util/io/tests/Makefile
                  src/lib/util/io/tests/Makefile
                  src/lib/util/unittests/Makefile
                  src/lib/util/unittests/Makefile
+                 src/lib/util/pyunittests/Makefile
                  src/lib/util/tests/Makefile
                  src/lib/util/tests/Makefile
                  tests/Makefile
                  tests/Makefile
                  tests/system/Makefile
                  tests/system/Makefile
@@ -842,6 +843,7 @@ AC_OUTPUT([doc/version.ent
            src/lib/cc/session_config.h.pre
            src/lib/cc/session_config.h.pre
            src/lib/cc/tests/session_unittests_config.h
            src/lib/cc/tests/session_unittests_config.h
            src/lib/log/tests/run_time_init_test.sh
            src/lib/log/tests/run_time_init_test.sh
+           src/lib/util/python/mkpywrapper.py
            src/lib/server_common/tests/data_path.h
            src/lib/server_common/tests/data_path.h
            tests/system/conf.sh
            tests/system/conf.sh
            tests/system/glue/setup.sh
            tests/system/glue/setup.sh
@@ -868,6 +870,7 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
            chmod +x src/lib/dns/tests/testdata/gen-wiredata.py
            chmod +x src/lib/log/tests/run_time_init_test.sh
            chmod +x src/lib/log/tests/run_time_init_test.sh
+           chmod +x src/lib/util/python/mkpywrapper.py
            chmod +x tests/system/conf.sh
            chmod +x tests/system/conf.sh
           ])
           ])
 AC_OUTPUT
 AC_OUTPUT

+ 1 - 1
doc/Doxyfile

@@ -1165,7 +1165,7 @@ XML_DTD                =
 # and cross-referencing information) to the XML output. Note that
 # and cross-referencing information) to the XML output. Note that
 # enabling this will significantly increase the size of the XML output.
 # enabling this will significantly increase the size of the XML output.
 
 
-XML_PROGRAMLISTING     = YES
+XML_PROGRAMLISTING     = NO
 
 
 #---------------------------------------------------------------------------
 #---------------------------------------------------------------------------
 # configuration options for the AutoGen Definitions output
 # configuration options for the AutoGen Definitions output

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

@@ -79,12 +79,24 @@
         3.1 is the minimum version which will work.
         3.1 is the minimum version which will work.
       </para>
       </para>
 
 
-      <note><para>
+      <para>
+	BIND 10 uses the Botan crypto library for C++. It requires
+	at least Botan version 1.8. To build BIND 10, install the
+	Botan libraries and development include headers.
+      </para>
+
+<!--
+TODO
+Debian and Ubuntu:
+ libgmp3-dev and libbz2-dev required for botan too
+-->
+
+      <para>
 	The authoritative server requires SQLite 3.3.9 or newer.
 	The authoritative server requires SQLite 3.3.9 or newer.
 	The <command>b10-xfrin</command>, <command>b10-xfrout</command>,
 	The <command>b10-xfrin</command>, <command>b10-xfrout</command>,
 	and <command>b10-zonemgr</command> modules require the
 	and <command>b10-zonemgr</command> modules require the
 	libpython3 library and the Python _sqlite3.so module.
 	libpython3 library and the Python _sqlite3.so module.
-      </para></note>
+      </para>
 <!-- TODO: this will change ... -->
 <!-- TODO: this will change ... -->
 
 
 <!-- TODO: list where to get these from -->
 <!-- TODO: list where to get these from -->

+ 8 - 0
src/bin/cfgmgr/plugins/tests/Makefile.am

@@ -3,6 +3,13 @@ PYTESTS = tsig_keys_test.py
 
 
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 
 
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs
+endif
+
 # test using command-line arguments, so use check-local target instead of TESTS
 # test using command-line arguments, so use check-local target instead of TESTS
 check-local:
 check-local:
 if ENABLE_PYTHON_COVERAGE
 if ENABLE_PYTHON_COVERAGE
@@ -14,6 +21,7 @@ endif
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
 	env B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
 	env B10_TEST_PLUGIN_DIR=$(abs_srcdir)/..:$(abs_builddir)/.. \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
 	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/cfgmgr:$(abs_top_builddir)/src/lib/dns/python/.libs \
+	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	done
 	done
 
 

+ 1 - 1
src/bin/msgq/tests/msgq_test.py

@@ -202,7 +202,7 @@ class SendNonblock(unittest.TestCase):
                 try:
                 try:
                     def killall(signum, frame):
                     def killall(signum, frame):
                         os.kill(queue_pid, signal.SIGTERM)
                         os.kill(queue_pid, signal.SIGTERM)
-                        sys.exit(1)
+                        os._exit(1)
                     signal.signal(signal.SIGALRM, killall)
                     signal.signal(signal.SIGALRM, killall)
                     msg = msgq.preparemsg({"type" : "ping"}, data)
                     msg = msgq.preparemsg({"type" : "ping"}, data)
                     now = time.clock()
                     now = time.clock()

+ 1 - 1
src/bin/xfrin/b10-xfrin.8

@@ -43,7 +43,7 @@ boss process\&. When triggered it can request and receive a zone transfer and st
 .ps -1
 .ps -1
 .br
 .br
 .sp
 .sp
-The Y1 prototype release only supports AXFR\&. IXFR is not implemented\&.
+This prototype release only supports AXFR\&. IXFR is not implemented\&.
 .sp .5v
 .sp .5v
 .RE
 .RE
 .PP
 .PP

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

@@ -62,6 +62,12 @@
       the zone in a BIND 10 zone data store.
       the zone in a BIND 10 zone data store.
     </para>
     </para>
 
 
+<!-- TODO:
+xfrin only does the transfer to make it as simple as possible.
+The logic for handling transfer triggers or zone management is handled
+in separate zonemgr process.
+-->
+
     <note><simpara>
     <note><simpara>
       This prototype release only supports AXFR. IXFR is not implemented.
       This prototype release only supports AXFR. IXFR is not implemented.
     </simpara></note>
     </simpara></note>

+ 481 - 51
src/bin/xfrin/tests/xfrin_test.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2009  Internet Systems Consortium.
+# Copyright (C) 2009-2011  Internet Systems Consortium.
 #
 #
 # Permission to use, copy, modify, and distribute this software for any
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
 # purpose with or without fee is hereby granted, provided that the above
@@ -15,13 +15,16 @@
 
 
 import unittest
 import unittest
 import socket
 import socket
+from isc.testutils.tsigctx_mock import MockTSIGContext
 from xfrin import *
 from xfrin import *
 
 
 #
 #
 # Commonly used (mostly constant) test parameters
 # Commonly used (mostly constant) test parameters
 #
 #
-TEST_ZONE_NAME = "example.com"
+TEST_ZONE_NAME_STR = "example.com."
+TEST_ZONE_NAME = Name(TEST_ZONE_NAME_STR)
 TEST_RRCLASS = RRClass.IN()
 TEST_RRCLASS = RRClass.IN()
+TEST_RRCLASS_STR = 'IN'
 TEST_DB_FILE = 'db_file'
 TEST_DB_FILE = 'db_file'
 TEST_MASTER_IPV4_ADDRESS = '127.0.0.1'
 TEST_MASTER_IPV4_ADDRESS = '127.0.0.1'
 TEST_MASTER_IPV4_ADDRINFO = (socket.AF_INET, socket.SOCK_STREAM,
 TEST_MASTER_IPV4_ADDRINFO = (socket.AF_INET, socket.SOCK_STREAM,
@@ -40,12 +43,12 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
 soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
 soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
                   'master.example.com. admin.example.com ' +
                   'master.example.com. admin.example.com ' +
                   '1234 3600 1800 2419200 7200')
                   '1234 3600 1800 2419200 7200')
-soa_rrset = RRset(Name(TEST_ZONE_NAME), TEST_RRCLASS, RRType.SOA(),
+soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(),
                   RRTTL(3600))
                   RRTTL(3600))
 soa_rrset.add_rdata(soa_rdata)
 soa_rrset.add_rdata(soa_rdata)
-example_axfr_question = Question(Name(TEST_ZONE_NAME), TEST_RRCLASS,
+example_axfr_question = Question(TEST_ZONE_NAME, TEST_RRCLASS,
                                  RRType.AXFR())
                                  RRType.AXFR())
-example_soa_question = Question(Name(TEST_ZONE_NAME), TEST_RRCLASS,
+example_soa_question = Question(TEST_ZONE_NAME, TEST_RRCLASS,
                                  RRType.SOA())
                                  RRType.SOA())
 default_questions = [example_axfr_question]
 default_questions = [example_axfr_question]
 default_answers = [soa_rrset]
 default_answers = [soa_rrset]
@@ -53,12 +56,12 @@ default_answers = [soa_rrset]
 class XfrinTestException(Exception):
 class XfrinTestException(Exception):
     pass
     pass
 
 
-def strip_mutable_tsig_data(data):
-    # Unfortunately we cannot easily compare TSIG RR because we can't tweak
-    # current time.  As a work around this helper function strips off the time
-    # dependent part of TSIG RDATA, i.e., the MAC (assuming HMAC-MD5) and
-    # Time Signed.
-    return data[0:-32] + data[-26:-22] + data[-6:]
+class MockCC():
+    def get_default_value(self, identifier):
+        if identifier == "zones/master_port":
+            return TEST_MASTER_PORT
+        if identifier == "zones/class":
+            return TEST_RRCLASS_STR
 
 
 class MockXfrin(Xfrin):
 class MockXfrin(Xfrin):
     # This is a class attribute of a callable object that specifies a non
     # This is a class attribute of a callable object that specifies a non
@@ -69,7 +72,8 @@ class MockXfrin(Xfrin):
     check_command_hook = None
     check_command_hook = None
 
 
     def _cc_setup(self):
     def _cc_setup(self):
-        self._tsig_key_str = None
+        self._tsig_key = None
+        self._module_cc = MockCC()
         pass
         pass
 
 
     def _get_db_file(self):
     def _get_db_file(self):
@@ -80,6 +84,16 @@ class MockXfrin(Xfrin):
         if MockXfrin.check_command_hook:
         if MockXfrin.check_command_hook:
             MockXfrin.check_command_hook()
             MockXfrin.check_command_hook()
 
 
+    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
+                    tsig_key, check_soa=True):
+        # store some of the arguments for verification, then call this
+        # method in the superclass
+        self.xfrin_started_master_addr = master_addrinfo[2][0]
+        self.xfrin_started_master_port = master_addrinfo[2][1]
+        return Xfrin.xfrin_start(self, zone_name, rrclass, db_file,
+                                 master_addrinfo, tsig_key,
+                                 check_soa)
+
 class MockXfrinConnection(XfrinConnection):
 class MockXfrinConnection(XfrinConnection):
     def __init__(self, sock_map, zone_name, rrclass, db_file, shutdown_event,
     def __init__(self, sock_map, zone_name, rrclass, db_file, shutdown_event,
                  master_addr):
                  master_addr):
@@ -131,10 +145,11 @@ class MockXfrinConnection(XfrinConnection):
                 self.response_generator()
                 self.response_generator()
         return len(data)
         return len(data)
 
 
-    def create_response_data(self, response = True, bad_qid = False,
-                             rcode = Rcode.NOERROR(),
-                             questions = default_questions,
-                             answers = default_answers):
+    def create_response_data(self, response=True, bad_qid=False,
+                             rcode=Rcode.NOERROR(),
+                             questions=default_questions,
+                             answers=default_answers,
+                             tsig_ctx=None):
         resp = Message(Message.RENDER)
         resp = Message(Message.RENDER)
         qid = self.qid
         qid = self.qid
         if bad_qid:
         if bad_qid:
@@ -148,7 +163,10 @@ class MockXfrinConnection(XfrinConnection):
         [resp.add_rrset(Message.SECTION_ANSWER, a) for a in answers]
         [resp.add_rrset(Message.SECTION_ANSWER, a) for a in answers]
 
 
         renderer = MessageRenderer()
         renderer = MessageRenderer()
-        resp.to_wire(renderer)
+        if tsig_ctx is not None:
+            resp.to_wire(renderer, tsig_ctx)
+        else:
+            resp.to_wire(renderer)
         reply_data = struct.pack('H', socket.htons(renderer.get_length()))
         reply_data = struct.pack('H', socket.htons(renderer.get_length()))
         reply_data += renderer.get_data()
         reply_data += renderer.get_data()
 
 
@@ -163,20 +181,32 @@ class TestXfrinConnection(unittest.TestCase):
                                         TEST_RRCLASS, TEST_DB_FILE,
                                         TEST_RRCLASS, TEST_DB_FILE,
                                         threading.Event(),
                                         threading.Event(),
                                         TEST_MASTER_IPV4_ADDRINFO)
                                         TEST_MASTER_IPV4_ADDRINFO)
-        self.axfr_after_soa = False
         self.soa_response_params = {
         self.soa_response_params = {
             'questions': [example_soa_question],
             'questions': [example_soa_question],
             'bad_qid': False,
             'bad_qid': False,
             'response': True,
             'response': True,
             'rcode': Rcode.NOERROR(),
             'rcode': Rcode.NOERROR(),
+            'tsig': False,
             'axfr_after_soa': self._create_normal_response_data
             'axfr_after_soa': self._create_normal_response_data
             }
             }
+        self.axfr_response_params = {
+            'tsig_1st': None,
+            'tsig_2nd': None
+            }
 
 
     def tearDown(self):
     def tearDown(self):
         self.conn.close()
         self.conn.close()
         if os.path.exists(TEST_DB_FILE):
         if os.path.exists(TEST_DB_FILE):
             os.remove(TEST_DB_FILE)
             os.remove(TEST_DB_FILE)
 
 
+    def __create_mock_tsig(self, key, error):
+        # This helper function creates a MockTSIGContext for a given key
+        # and TSIG error to be used as a result of verify (normally faked
+        # one)
+        mock_ctx = MockTSIGContext(key)
+        mock_ctx.error = error
+        return mock_ctx
+
     def test_close(self):
     def test_close(self):
         # we shouldn't be using the global asyncore map.
         # we shouldn't be using the global asyncore map.
         self.assertEqual(len(asyncore.socket_map), 0)
         self.assertEqual(len(asyncore.socket_map), 0)
@@ -216,6 +246,15 @@ class TestXfrinConnection(unittest.TestCase):
             query_question = Question(Name("example.com."), RRClass.IN(), query_type)
             query_question = Question(Name("example.com."), RRClass.IN(), query_type)
             msg.add_question(query_question)
             msg.add_question(query_question)
             return msg
             return msg
+
+        def message_has_tsig(data):
+            # a simple check if the actual data contains a TSIG RR.
+            # At our level this simple check should suffice; other detailed
+            # tests regarding the TSIG protocol are done in pydnspp.
+            msg = Message(Message.PARSE)
+            msg.from_wire(data)
+            return msg.get_tsig_record() is not None
+
         self.conn._create_query = create_msg
         self.conn._create_query = create_msg
         # soa request
         # soa request
         self.conn._send_query(RRType.SOA())
         self.conn._send_query(RRType.SOA())
@@ -225,22 +264,20 @@ class TestXfrinConnection(unittest.TestCase):
         self.assertEqual(self.conn.query_data, b'\x00\x1d\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
         self.assertEqual(self.conn.query_data, b'\x00\x1d\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
 
 
         # soa request with tsig
         # soa request with tsig
-        self.conn._tsig_ctx = TSIGContext(TSIG_KEY)
+        self.conn._tsig_key = TSIG_KEY
         self.conn._send_query(RRType.SOA())
         self.conn._send_query(RRType.SOA())
-        tsig_soa_data = strip_mutable_tsig_data(self.conn.query_data)
-        self.assertEqual(tsig_soa_data, b'\x00n\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x07example\x03com\x00\x00\x06\x00\x01\x07example\x03com\x00\x00\xfa\x00\xff\x00\x00\x00\x00\x00:\x08hmac-md5\x07sig-alg\x03reg\x03int\x00\x01,\x00\x10\x105\x00\x00\x00\x00')
+        self.assertTrue(message_has_tsig(self.conn.query_data[2:]))
 
 
         # axfr request with tsig
         # axfr request with tsig
         self.conn._send_query(RRType.AXFR())
         self.conn._send_query(RRType.AXFR())
-        tsig_axfr_data = strip_mutable_tsig_data(self.conn.query_data)
-        self.assertEqual(tsig_axfr_data, b'\x00n\x105\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x07example\x03com\x00\x00\xfc\x00\x01\x07example\x03com\x00\x00\xfa\x00\xff\x00\x00\x00\x00\x00:\x08hmac-md5\x07sig-alg\x03reg\x03int\x00\x01,\x00\x10\x105\x00\x00\x00\x00')
+        self.assertTrue(message_has_tsig(self.conn.query_data[2:]))
 
 
     def test_response_with_invalid_msg(self):
     def test_response_with_invalid_msg(self):
         self.conn.reply_data = b'aaaxxxx'
         self.conn.reply_data = b'aaaxxxx'
         self.assertRaises(XfrinTestException, self._handle_xfrin_response)
         self.assertRaises(XfrinTestException, self._handle_xfrin_response)
 
 
-    def test_response_with_tsig(self):
-        self.conn._tsig_ctx = TSIGContext(TSIG_KEY)
+    def test_response_with_tsigfail(self):
+        self.conn._tsig_key = TSIG_KEY
         # server tsig check fail, return with RCODE 9 (NOTAUTH)
         # server tsig check fail, return with RCODE 9 (NOTAUTH)
         self.conn._send_query(RRType.SOA())
         self.conn._send_query(RRType.SOA())
         self.conn.reply_data = self.conn.create_response_data(rcode=Rcode.NOTAUTH())
         self.conn.reply_data = self.conn.create_response_data(rcode=Rcode.NOTAUTH())
@@ -310,6 +347,54 @@ class TestXfrinConnection(unittest.TestCase):
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
         self.assertRaises(XfrinException, self.conn._check_soa_serial)
         self.assertRaises(XfrinException, self.conn._check_soa_serial)
 
 
+    def test_soacheck_with_tsig(self):
+        # Use a mock tsig context emulating a validly signed response
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, TSIGError.NOERROR)
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertEqual(self.conn._check_soa_serial(), XFRIN_OK)
+        self.assertEqual(self.conn._tsig_ctx.get_error(), TSIGError.NOERROR)
+
+    def test_soacheck_with_tsig_notauth(self):
+        # emulate a valid error response
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, TSIGError.BAD_SIG)
+        self.soa_response_params['rcode'] = Rcode.NOTAUTH()
+        self.conn.response_generator = self._create_soa_response_data
+
+        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+
+    def test_soacheck_with_tsig_noerror_badsig(self):
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, TSIGError.BAD_SIG)
+
+        # emulate a normal response bad verification failure due to BADSIG.
+        # According RFC2845, in this case we should ignore it and keep
+        # waiting for a valid response until a timeout.  But we immediately
+        # treat this as a final failure (just as BIND 9 does).
+        self.conn.response_generator = self._create_soa_response_data
+
+        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+
+    def test_soacheck_with_tsig_unsigned_response(self):
+        # we can use a real TSIGContext for this.  the response doesn't
+        # contain a TSIG while we sent a signed query.  RFC2845 states
+        # we should wait for a valid response in this case, but we treat
+        # it as a fatal transaction failure, too.
+        self.conn._tsig_key = TSIG_KEY
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+
+    def test_soacheck_with_unexpected_tsig_response(self):
+        # we reject unexpected TSIG in responses (following BIND 9's
+        # behavior)
+        self.soa_response_params['tsig'] = True
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertRaises(XfrinException, self.conn._check_soa_serial)
+
     def test_response_shutdown(self):
     def test_response_shutdown(self):
         self.conn.response_generator = self._create_normal_response_data
         self.conn.response_generator = self._create_normal_response_data
         self.conn._shutdown_event.set()
         self.conn._shutdown_event.set()
@@ -343,6 +428,88 @@ class TestXfrinConnection(unittest.TestCase):
         self.conn.response_generator = self._create_normal_response_data
         self.conn.response_generator = self._create_normal_response_data
         self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
         self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
 
 
+    def test_do_xfrin_with_tsig(self):
+        # use TSIG with a mock context.  we fake all verify results to
+        # emulate successful verification.
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, TSIGError.NOERROR)
+        self.conn.response_generator = self._create_normal_response_data
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_OK)
+        # We use two messages in the tests.  The same context should have been
+        # usef for both.
+        self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+
+    def test_do_xfrin_with_tsig_fail(self):
+        # TSIG verify will fail for the first message.  xfrin should fail
+        # immediately.
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, TSIGError.BAD_SIG)
+        self.conn.response_generator = self._create_normal_response_data
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+        self.assertEqual(1, self.conn._tsig_ctx.verify_called)
+
+    def test_do_xfrin_with_tsig_fail_for_second_message(self):
+        # Similar to the previous test, but first verify succeeds.  There
+        # should be a second verify attempt, which will fail, which should
+        # make xfrin fail.
+        def fake_tsig_error(ctx):
+            if self.conn._tsig_ctx.verify_called == 1:
+                return TSIGError.NOERROR
+            return TSIGError.BAD_SIG
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, fake_tsig_error)
+        self.conn.response_generator = self._create_normal_response_data
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+        self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+
+    def test_do_xfrin_with_missing_tsig(self):
+        # XFR request sent with TSIG, but the response doesn't have TSIG.
+        # xfr should fail.
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, None)
+        self.conn._tsig_ctx = MockTSIGContext(TSIG_KEY)
+        self.conn.response_generator = self._create_normal_response_data
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+        self.assertEqual(1, self.conn._tsig_ctx.verify_called)
+
+    def test_do_xfrin_with_missing_tsig_for_second_message(self):
+        # Similar to the previous test, but firt one contains TSIG and verify
+        # succeeds (due to fake).  The second message lacks TSIG.
+        #
+        # Note: this test case is actually not that trivial:  Skipping
+        # intermediate TSIG is allowed.  In this case, however, the second
+        # message is the last one, which must contain TSIG anyway, so the
+        # expected result is correct.  If/when we support skipping
+        # intermediate TSIGs, we'll need additional test cases.
+        def fake_tsig_error(ctx):
+            if self.conn._tsig_ctx.verify_called == 1:
+                return TSIGError.NOERROR
+            return TSIGError.FORMERR
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, fake_tsig_error)
+        self.conn.response_generator = self._create_normal_response_data
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+        self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+
+    def test_do_xfrin_with_unexpected_tsig(self):
+        # XFR request wasn't signed, but response includes TSIG.  Like BIND 9,
+        # we reject that.
+        self.axfr_response_params['tsig_1st'] = TSIGContext(TSIG_KEY)
+        self.conn.response_generator = self._create_normal_response_data
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+
+    def test_do_xfrin_with_unexpected_tsig_for_second_message(self):
+        # similar to the previous test, but the first message is normal.
+        # the second one contains an unexpected TSIG.  should be rejected.
+        self.axfr_response_params['tsig_2nd'] = TSIGContext(TSIG_KEY)
+        self.conn.response_generator = self._create_normal_response_data
+        self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
+
     def test_do_xfrin_empty_response(self):
     def test_do_xfrin_empty_response(self):
         # skipping the creation of response data, so the transfer will fail.
         # skipping the creation of response data, so the transfer will fail.
         self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
         self.assertEqual(self.conn.do_xfrin(False), XFRIN_FAIL)
@@ -361,6 +528,23 @@ class TestXfrinConnection(unittest.TestCase):
         self.conn.response_generator = self._create_soa_response_data
         self.conn.response_generator = self._create_soa_response_data
         self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
         self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
 
 
+    def test_do_soacheck_and_xfrin_with_tsig(self):
+        # We are going to have a SOA query/response transaction, followed by
+        # AXFR, all TSIG signed.  xfrin should use a new TSIG context for
+        # AXFR.  We are not interested in whether verify works correctly in
+        # this test, so we simply fake the results (they need to succeed for
+        # this test)
+        self.conn._tsig_key = TSIG_KEY
+        self.conn._tsig_ctx_creator = \
+            lambda key: self.__create_mock_tsig(key, TSIGError.NOERROR)
+        self.soa_response_params['tsig'] = True
+        self.conn.response_generator = self._create_soa_response_data
+        self.assertEqual(self.conn.do_xfrin(True), XFRIN_OK)
+        # We should've got 3 response messages: 1 SOA and two AXFR, but
+        # the context should be replaced for AXFR, so verify() should be
+        # called only twice for the latest context.
+        self.assertEqual(2, self.conn._tsig_ctx.verify_called)
+
     def test_do_soacheck_broken_response(self):
     def test_do_soacheck_broken_response(self):
         self.conn.response_generator = self._create_broken_response_data
         self.conn.response_generator = self._create_broken_response_data
         # XXX: TODO: this test failed here, should xfr not raise an
         # XXX: TODO: this test failed here, should xfr not raise an
@@ -388,21 +572,39 @@ class TestXfrinConnection(unittest.TestCase):
         # This helper method creates a simple sequence of DNS messages that
         # This helper method creates a simple sequence of DNS messages that
         # forms a valid XFR transaction.  It consists of two messages, each
         # forms a valid XFR transaction.  It consists of two messages, each
         # containing just a single SOA RR.
         # containing just a single SOA RR.
-        self.conn.reply_data = self.conn.create_response_data()
-        self.conn.reply_data += self.conn.create_response_data()
+        tsig_1st = self.axfr_response_params['tsig_1st']
+        tsig_2nd = self.axfr_response_params['tsig_2nd']
+        self.conn.reply_data = self.conn.create_response_data(tsig_ctx=tsig_1st)
+        self.conn.reply_data += \
+            self.conn.create_response_data(tsig_ctx=tsig_2nd)
 
 
     def _create_soa_response_data(self):
     def _create_soa_response_data(self):
         # This helper method creates a DNS message that is supposed to be
         # This helper method creates a DNS message that is supposed to be
         # used a valid response to SOA queries prior to XFR.
         # used a valid response to SOA queries prior to XFR.
+        # If tsig is True, it tries to verify the query with a locally
+        # created TSIG context (which may or may not succeed) so that the
+        # response will include a TSIG.
         # If axfr_after_soa is True, it resets the response_generator so that
         # If axfr_after_soa is True, it resets the response_generator so that
         # a valid XFR messages will follow.
         # a valid XFR messages will follow.
+
+        verify_ctx = None
+        if self.soa_response_params['tsig']:
+            # xfrin (curreently) always uses TCP.  strip off the length field.
+            query_data = self.conn.query_data[2:]
+            query_message = Message(Message.PARSE)
+            query_message.from_wire(query_data)
+            verify_ctx = TSIGContext(TSIG_KEY)
+            verify_ctx.verify(query_message.get_tsig_record(), query_data)
+
         self.conn.reply_data = self.conn.create_response_data(
         self.conn.reply_data = self.conn.create_response_data(
             bad_qid=self.soa_response_params['bad_qid'],
             bad_qid=self.soa_response_params['bad_qid'],
             response=self.soa_response_params['response'],
             response=self.soa_response_params['response'],
             rcode=self.soa_response_params['rcode'],
             rcode=self.soa_response_params['rcode'],
-            questions=self.soa_response_params['questions'])
+            questions=self.soa_response_params['questions'],
+            tsig_ctx=verify_ctx)
         if self.soa_response_params['axfr_after_soa'] != None:
         if self.soa_response_params['axfr_after_soa'] != None:
-            self.conn.response_generator = self.soa_response_params['axfr_after_soa']
+            self.conn.response_generator = \
+                self.soa_response_params['axfr_after_soa']
 
 
     def _create_broken_response_data(self):
     def _create_broken_response_data(self):
         # This helper method creates a bogus "DNS message" that only contains
         # This helper method creates a bogus "DNS message" that only contains
@@ -450,7 +652,8 @@ class TestXfrin(unittest.TestCase):
         sys.stderr = open(os.devnull, 'w')
         sys.stderr = open(os.devnull, 'w')
         self.xfr = MockXfrin()
         self.xfr = MockXfrin()
         self.args = {}
         self.args = {}
-        self.args['zone_name'] = TEST_ZONE_NAME
+        self.args['zone_name'] = TEST_ZONE_NAME_STR
+        self.args['class'] = TEST_RRCLASS_STR
         self.args['port'] = TEST_MASTER_PORT
         self.args['port'] = TEST_MASTER_PORT
         self.args['master'] = TEST_MASTER_IPV4_ADDRESS
         self.args['master'] = TEST_MASTER_IPV4_ADDRESS
         self.args['db_file'] = TEST_DB_FILE
         self.args['db_file'] = TEST_DB_FILE
@@ -464,7 +667,8 @@ class TestXfrin(unittest.TestCase):
         return self.xfr._parse_zone_name_and_class(self.args)
         return self.xfr._parse_zone_name_and_class(self.args)
 
 
     def _do_parse_master_port(self):
     def _do_parse_master_port(self):
-        return self.xfr._parse_master_and_port(self.args)
+        name, rrclass = self._do_parse_zone_name_class()
+        return self.xfr._parse_master_and_port(self.args, name, rrclass)
 
 
     def test_parse_cmd_params(self):
     def test_parse_cmd_params(self):
         name, rrclass = self._do_parse_zone_name_class()
         name, rrclass = self._do_parse_zone_name_class()
@@ -492,7 +696,7 @@ class TestXfrin(unittest.TestCase):
 
 
     def test_parse_cmd_params_bogusclass(self):
     def test_parse_cmd_params_bogusclass(self):
         self.args['zone_class'] = 'XXX'
         self.args['zone_class'] = 'XXX'
-        self.assertRaises(XfrinException, self._do_parse_zone_name_class)
+        self.assertRaises(XfrinZoneInfoException, self._do_parse_zone_name_class)
 
 
     def test_parse_cmd_params_nozone(self):
     def test_parse_cmd_params_nozone(self):
         # zone name is mandatory.
         # zone name is mandatory.
@@ -502,8 +706,7 @@ class TestXfrin(unittest.TestCase):
     def test_parse_cmd_params_nomaster(self):
     def test_parse_cmd_params_nomaster(self):
         # master address is mandatory.
         # master address is mandatory.
         del self.args['master']
         del self.args['master']
-        master_addrinfo = self._do_parse_master_port()
-        self.assertEqual(master_addrinfo[2][0], DEFAULT_MASTER)
+        self.assertRaises(XfrinException, self._do_parse_master_port)
 
 
     def test_parse_cmd_params_bad_ip4(self):
     def test_parse_cmd_params_bad_ip4(self):
         self.args['master'] = '3.3.3.3.3'
         self.args['master'] = '3.3.3.3.3'
@@ -533,6 +736,77 @@ class TestXfrin(unittest.TestCase):
     def test_command_handler_retransfer(self):
     def test_command_handler_retransfer(self):
         self.assertEqual(self.xfr.command_handler("retransfer",
         self.assertEqual(self.xfr.command_handler("retransfer",
                                                   self.args)['result'][0], 0)
                                                   self.args)['result'][0], 0)
+        self.assertEqual(self.args['master'], self.xfr.xfrin_started_master_addr)
+        self.assertEqual(int(self.args['port']), self.xfr.xfrin_started_master_port)
+
+    def test_command_handler_retransfer_short_command1(self):
+        # try it when only specifying the zone name (of unknown zone)
+        # this should fail because master address is not specified.
+        short_args = {}
+        short_args['zone_name'] = TEST_ZONE_NAME_STR
+        self.assertEqual(self.xfr.command_handler("retransfer",
+                                                  short_args)['result'][0], 1)
+
+    def test_command_handler_retransfer_short_command2(self):
+        # try it when only specifying the zone name (of known zone)
+        short_args = {}
+        short_args['zone_name'] = TEST_ZONE_NAME_STR
+
+        zones = { 'zones': [
+                  { 'name': TEST_ZONE_NAME_STR,
+                    'master_addr': TEST_MASTER_IPV4_ADDRESS,
+                    'master_port': TEST_MASTER_PORT
+                  }
+                ]}
+        self.xfr.config_handler(zones)
+        self.assertEqual(self.xfr.command_handler("retransfer",
+                                                  short_args)['result'][0], 0)
+        self.assertEqual(TEST_MASTER_IPV4_ADDRESS,
+                         self.xfr.xfrin_started_master_addr)
+        self.assertEqual(int(TEST_MASTER_PORT),
+                         self.xfr.xfrin_started_master_port)
+
+    def test_command_handler_retransfer_short_command3(self):
+        # try it when only specifying the zone name (of known zone)
+        short_args = {}
+        # test it without the trailing root dot
+        short_args['zone_name'] = TEST_ZONE_NAME_STR[:-1]
+
+        zones = { 'zones': [
+                  { 'name': TEST_ZONE_NAME_STR,
+                    'master_addr': TEST_MASTER_IPV4_ADDRESS,
+                    'master_port': TEST_MASTER_PORT
+                  }
+                ]}
+        self.xfr.config_handler(zones)
+        self.assertEqual(self.xfr.command_handler("retransfer",
+                                                  short_args)['result'][0], 0)
+        self.assertEqual(TEST_MASTER_IPV4_ADDRESS,
+                         self.xfr.xfrin_started_master_addr)
+        self.assertEqual(int(TEST_MASTER_PORT),
+                         self.xfr.xfrin_started_master_port)
+
+    def test_command_handler_retransfer_short_command4(self):
+        # try it when only specifying the zone name (of known zone, with
+        # different case)
+        short_args = {}
+
+        # swap the case of the zone name in our command
+        short_args['zone_name'] = TEST_ZONE_NAME_STR.swapcase()
+
+        zones = { 'zones': [
+                  { 'name': TEST_ZONE_NAME_STR,
+                    'master_addr': TEST_MASTER_IPV4_ADDRESS,
+                    'master_port': TEST_MASTER_PORT
+                  }
+                ]}
+        self.xfr.config_handler(zones)
+        self.assertEqual(self.xfr.command_handler("retransfer",
+                                                  short_args)['result'][0], 0)
+        self.assertEqual(TEST_MASTER_IPV4_ADDRESS,
+                         self.xfr.xfrin_started_master_addr)
+        self.assertEqual(int(TEST_MASTER_PORT),
+                         self.xfr.xfrin_started_master_port)
 
 
     def test_command_handler_retransfer_badcommand(self):
     def test_command_handler_retransfer_badcommand(self):
         self.args['master'] = 'invalid'
         self.args['master'] = 'invalid'
@@ -540,13 +814,15 @@ class TestXfrin(unittest.TestCase):
                                                   self.args)['result'][0], 1)
                                                   self.args)['result'][0], 1)
 
 
     def test_command_handler_retransfer_quota(self):
     def test_command_handler_retransfer_quota(self):
+        self.args['master'] = TEST_MASTER_IPV4_ADDRESS
+
         for i in range(self.xfr._max_transfers_in - 1):
         for i in range(self.xfr._max_transfers_in - 1):
-            self.xfr.recorder.increment(str(i) + TEST_ZONE_NAME)
+            self.xfr.recorder.increment(Name(str(i) + TEST_ZONE_NAME_STR))
         # there can be one more outstanding transfer.
         # there can be one more outstanding transfer.
         self.assertEqual(self.xfr.command_handler("retransfer",
         self.assertEqual(self.xfr.command_handler("retransfer",
                                                   self.args)['result'][0], 0)
                                                   self.args)['result'][0], 0)
         # make sure the # xfrs would excceed the quota
         # make sure the # xfrs would excceed the quota
-        self.xfr.recorder.increment(str(self.xfr._max_transfers_in) + TEST_ZONE_NAME)
+        self.xfr.recorder.increment(Name(str(self.xfr._max_transfers_in) + TEST_ZONE_NAME_STR))
         # this one should fail
         # this one should fail
         self.assertEqual(self.xfr.command_handler("retransfer",
         self.assertEqual(self.xfr.command_handler("retransfer",
                                                   self.args)['result'][0], 1)
                                                   self.args)['result'][0], 1)
@@ -570,14 +846,43 @@ class TestXfrin(unittest.TestCase):
         self.args['master'] = TEST_MASTER_IPV6_ADDRESS
         self.args['master'] = TEST_MASTER_IPV6_ADDRESS
         self.assertEqual(self.xfr.command_handler("refresh",
         self.assertEqual(self.xfr.command_handler("refresh",
                                                   self.args)['result'][0], 0)
                                                   self.args)['result'][0], 0)
+        self.assertEqual(TEST_MASTER_IPV6_ADDRESS,
+                         self.xfr.xfrin_started_master_addr)
+        self.assertEqual(int(TEST_MASTER_PORT),
+                         self.xfr.xfrin_started_master_port)
 
 
     def test_command_handler_notify(self):
     def test_command_handler_notify(self):
         # at this level, refresh is no different than retransfer.
         # at this level, refresh is no different than retransfer.
         self.args['master'] = TEST_MASTER_IPV6_ADDRESS
         self.args['master'] = TEST_MASTER_IPV6_ADDRESS
-        # ...but right now we disable the feature due to security concerns.
+        # ...but the zone is unknown so this would return an error
+        self.assertEqual(self.xfr.command_handler("notify",
+                                                  self.args)['result'][0], 1)
+
+    def test_command_handler_notify_known_zone(self):
+        # try it with a known zone
+        self.args['master'] = TEST_MASTER_IPV6_ADDRESS
+
+        # but use a different address in the actual command
+        zones = { 'zones': [
+                  { 'name': TEST_ZONE_NAME_STR,
+                    'master_addr': TEST_MASTER_IPV4_ADDRESS,
+                    'master_port': TEST_MASTER_PORT
+                  }
+                ]}
+        self.xfr.config_handler(zones)
         self.assertEqual(self.xfr.command_handler("notify",
         self.assertEqual(self.xfr.command_handler("notify",
                                                   self.args)['result'][0], 0)
                                                   self.args)['result'][0], 0)
 
 
+        # and see if we used the address from the command, and not from
+        # the config
+        # This is actually NOT the address given in the command, which
+        # would at this point not make sense, see the TODO in
+        # xfrin.py.in Xfrin.command_handler())
+        self.assertEqual(TEST_MASTER_IPV4_ADDRESS,
+                         self.xfr.xfrin_started_master_addr)
+        self.assertEqual(int(TEST_MASTER_PORT),
+                         self.xfr.xfrin_started_master_port)
+
     def test_command_handler_unknown(self):
     def test_command_handler_unknown(self):
         self.assertEqual(self.xfr.command_handler("xxx", None)['result'][0], 1)
         self.assertEqual(self.xfr.command_handler("xxx", None)['result'][0], 1)
 
 
@@ -586,20 +891,145 @@ class TestXfrin(unittest.TestCase):
         self.assertEqual(self.xfr.config_handler({'transfers_in': 3})['result'][0], 0)
         self.assertEqual(self.xfr.config_handler({'transfers_in': 3})['result'][0], 0)
         self.assertEqual(self.xfr._max_transfers_in, 3)
         self.assertEqual(self.xfr._max_transfers_in, 3)
 
 
-    def test_command_handler_masters(self):
-        master_info = {'master_addr': '1.1.1.1', 'master_port':53}
-        self.assertEqual(self.xfr.config_handler(master_info)['result'][0], 0)
-
-        master_info = {'master_addr': '1111.1.1.1', 'master_port':53 }
-        self.assertEqual(self.xfr.config_handler(master_info)['result'][0], 1)
-
-        master_info = {'master_addr': '2.2.2.2', 'master_port':530000 }
-        self.assertEqual(self.xfr.config_handler(master_info)['result'][0], 1)
-
-        master_info = {'master_addr': '2.2.2.2', 'master_port':53 } 
-        self.xfr.config_handler(master_info)
-        self.assertEqual(self.xfr._master_addr, '2.2.2.2')
-        self.assertEqual(self.xfr._master_port, 53)
+    def _check_zones_config(self, config_given):
+        if 'transfers_in' in config_given:
+            self.assertEqual(config_given['transfers_in'],
+                             self.xfr._max_transfers_in)
+        for zone_config in config_given['zones']:
+            zone_name = zone_config['name']
+            zone_info = self.xfr._get_zone_info(Name(zone_name), RRClass.IN())
+            self.assertEqual(str(zone_info.master_addr), zone_config['master_addr'])
+            self.assertEqual(zone_info.master_port, zone_config['master_port'])
+            if 'tsig_key' in zone_config:
+                self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text())
+            else:
+                self.assertIsNone(zone_info.tsig_key)
+
+    def test_command_handler_zones(self):
+        config1 = { 'transfers_in': 3,
+                   'zones': [
+                   { 'name': 'test.example.',
+                    'master_addr': '192.0.2.1',
+                    'master_port': 53
+                   }
+                 ]}
+        self.assertEqual(self.xfr.config_handler(config1)['result'][0], 0)
+        self._check_zones_config(config1)
+
+        config2 = { 'transfers_in': 4,
+                   'zones': [
+                   { 'name': 'test.example.',
+                    'master_addr': '192.0.2.2',
+                    'master_port': 53,
+                    'tsig_key': "example.com:SFuWd/q99SzF8Yzd1QbB9g=="
+                   }
+                 ]}
+        self.assertEqual(self.xfr.config_handler(config2)['result'][0], 0)
+        self._check_zones_config(config2)
+
+        # test that configuring the zone multiple times fails
+        zones = { 'transfers_in': 5,
+                  'zones': [
+                  { 'name': 'test.example.',
+                    'master_addr': '192.0.2.1',
+                    'master_port': 53
+                  },
+                  { 'name': 'test.example.',
+                    'master_addr': '192.0.2.2',
+                    'master_port': 53
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        # since this has failed, we should still have the previous config
+        self._check_zones_config(config2)
+
+        zones = { 'zones': [
+                  { 'name': 'test.example.',
+                    'master_addr': '192.0.2.3',
+                    'master_port': 53,
+                    'class': 'BADCLASS'
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        self._check_zones_config(config2)
+
+        zones = { 'zones': [
+                  { 'master_addr': '192.0.2.4',
+                    'master_port': 53
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        # since this has failed, we should still have the previous config
+        self._check_zones_config(config2)
+
+        zones = { 'zones': [
+                  { 'name': 'bad..zone.',
+                    'master_addr': '192.0.2.5',
+                    'master_port': 53
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        # since this has failed, we should still have the previous config
+        self._check_zones_config(config2)
+
+        zones = { 'zones': [
+                  { 'name': '',
+                    'master_addr': '192.0.2.6',
+                    'master_port': 53
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        # since this has failed, we should still have the previous config
+        self._check_zones_config(config2)
+
+        zones = { 'zones': [
+                  { 'name': 'test.example',
+                    'master_addr': 'badaddress',
+                    'master_port': 53
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        # since this has failed, we should still have the previous config
+        self._check_zones_config(config2)
+
+        zones = { 'zones': [
+                  { 'name': 'test.example',
+                    'master_addr': '192.0.2.7',
+                    'master_port': 'bad_port'
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        # since this has failed, we should still have the previous config
+        self._check_zones_config(config2)
+
+        zones = { 'zones': [
+                  { 'name': 'test.example',
+                    'master_addr': '192.0.2.7',
+                    'master_port': 53,
+                    # using a bad TSIG key spec
+                    'tsig_key': "bad..example.com:SFuWd/q99SzF8Yzd1QbB9g=="
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        # since this has failed, we should still have the previous config
+        self._check_zones_config(config2)
+
+        # let's also add a zone that is correct too, and make sure
+        # that the new config is not partially taken
+        zones = { 'zones': [
+                  { 'name': 'test.example.',
+                    'master_addr': '192.0.2.8',
+                    'master_port': 53
+                  },
+                  { 'name': 'test2.example.',
+                    'master_addr': '192.0.2.9',
+                    'master_port': 53,
+                    'tsig_key': 'badkey'
+                  }
+                ]}
+        self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
+        # since this has failed, we should still have the previous config
+        self._check_zones_config(config2)
 
 
 
 
 def raise_interrupt():
 def raise_interrupt():

+ 276 - 76
src/bin/xfrin/xfrin.py.in

@@ -1,6 +1,6 @@
 #!@PYTHON@
 #!@PYTHON@
 
 
-# Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2009-2011  Internet Systems Consortium.
 #
 #
 # Permission to use, copy, modify, and distribute this software for any
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
 # purpose with or without fee is hereby granted, provided that the above
@@ -56,26 +56,66 @@ XFROUT_MODULE_NAME = 'Xfrout'
 ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
 ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
 REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
 REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
 ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
 ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
+
+# These two default are currently hard-coded. For config this isn't
+# necessary, but we need these defaults for optional command arguments
+# (TODO: have similar support to get default values for command
+# arguments as we do for config options)
+DEFAULT_MASTER_PORT = 53
+DEFAULT_ZONE_CLASS = RRClass.IN()
+
 __version__ = 'BIND10'
 __version__ = 'BIND10'
 # define xfrin rcode
 # define xfrin rcode
 XFRIN_OK = 0
 XFRIN_OK = 0
 XFRIN_FAIL = 1
 XFRIN_FAIL = 1
 
 
-DEFAULT_MASTER_PORT = '53'
-DEFAULT_MASTER = '127.0.0.1'
-
 def log_error(msg):
 def log_error(msg):
     sys.stderr.write("[b10-xfrin] %s\n" % str(msg))
     sys.stderr.write("[b10-xfrin] %s\n" % str(msg))
 
 
 class XfrinException(Exception):
 class XfrinException(Exception):
     pass
     pass
 
 
+class XfrinZoneInfoException(Exception):
+    """This exception is raised if there is an error in the given
+       configuration (part), or when a command does not have a required
+       argument or has bad arguments, for instance when the zone's master
+       address is not a valid IP address, when the zone does not
+       have a name, or when multiple settings are given for the same
+       zone."""
+    pass
+
+def _check_zone_name(zone_name_str):
+    """Checks if the given zone name is a valid domain name, and returns
+    it as a Name object. Raises an XfrinException if it is not."""
+    try:
+        # In the _zones dict, part of the key is the zone name,
+        # but due to a limitation in the Name class, we
+        # cannot directly use it as a dict key, and we use to_text()
+        #
+        # Downcase the name here for that reason.
+        return Name(zone_name_str, True)
+    except (EmptyLabel, TooLongLabel, BadLabelType, BadEscape,
+            TooLongName, IncompleteName) as ne:
+        raise XfrinZoneInfoException("bad zone name: " + zone_name_str + " (" + str(ne) + ")")
+
+def _check_zone_class(zone_class_str):
+    """If the given argument is a string: checks if the given class is
+       a valid one, and returns an RRClass object if so.
+       Raises XfrinZoneInfoException if not.
+       If it is None, this function returns the default RRClass.IN()"""
+    if zone_class_str is None:
+        return DEFAULT_ZONE_CLASS
+    try:
+        return RRClass(zone_class_str)
+    except InvalidRRClass as irce:
+        raise XfrinZoneInfoException("bad zone class: " + zone_class_str + " (" + str(irce) + ")")
+
 class XfrinConnection(asyncore.dispatcher):
 class XfrinConnection(asyncore.dispatcher):
     '''Do xfrin in this class. '''
     '''Do xfrin in this class. '''
 
 
     def __init__(self,
     def __init__(self,
                  sock_map, zone_name, rrclass, db_file, shutdown_event,
                  sock_map, zone_name, rrclass, db_file, shutdown_event,
-                 master_addrinfo, tsig_key_str = None, verbose = False,
+                 master_addrinfo, tsig_key = None, verbose = False,
                  idle_timeout = 60):
                  idle_timeout = 60):
         ''' idle_timeout: max idle time for read data from socket.
         ''' idle_timeout: max idle time for read data from socket.
             db_file: specify the data source file.
             db_file: specify the data source file.
@@ -94,9 +134,14 @@ class XfrinConnection(asyncore.dispatcher):
         self._shutdown_event = shutdown_event
         self._shutdown_event = shutdown_event
         self._verbose = verbose
         self._verbose = verbose
         self._master_address = master_addrinfo[2]
         self._master_address = master_addrinfo[2]
+        self._tsig_key = tsig_key
         self._tsig_ctx = None
         self._tsig_ctx = None
-        if tsig_key_str is not None:
-            self._tsig_ctx = TSIGContext(TSIGKey(tsig_key_str))
+        # tsig_ctx_creator is introduced to allow tests to use a mock class for
+        # easier tests (in normal case we always use the default)
+        self._tsig_ctx_creator = self.__create_tsig_ctx
+
+    def __create_tsig_ctx(self, key):
+        return TSIGContext(key)
 
 
     def connect_to_master(self):
     def connect_to_master(self):
         '''Connect to master in TCP.'''
         '''Connect to master in TCP.'''
@@ -136,7 +181,8 @@ class XfrinConnection(asyncore.dispatcher):
         render = MessageRenderer()
         render = MessageRenderer()
         # XXX Currently, python wrapper doesn't accept 'None' parameter in this case,
         # XXX Currently, python wrapper doesn't accept 'None' parameter in this case,
         # we should remove the if statement and use a universal interface later.
         # we should remove the if statement and use a universal interface later.
-        if self._tsig_ctx is not None:
+        if self._tsig_key is not None:
+            self._tsig_ctx = self._tsig_ctx_creator(self._tsig_key)
             msg.to_wire(render, self._tsig_ctx)
             msg.to_wire(render, self._tsig_ctx)
         else:
         else:
             msg.to_wire(render)
             msg.to_wire(render)
@@ -167,6 +213,22 @@ class XfrinConnection(asyncore.dispatcher):
 
 
         return data
         return data
 
 
+    def _check_response_tsig(self, msg, response_data):
+        tsig_record = msg.get_tsig_record()
+        if self._tsig_ctx is not None:
+            tsig_error = self._tsig_ctx.verify(tsig_record, response_data)
+            if tsig_error != TSIGError.NOERROR:
+                raise XfrinException('TSIG verify fail: %s' % str(tsig_error))
+        elif tsig_record is not None:
+            # If the response includes a TSIG while we didn't sign the query,
+            # we treat it as an error.  RFC doesn't say anything about this
+            # case, but it clearly states the server must not sign a response
+            # to an unsigned request.  Although we could be flexible, no sane
+            # implementation would return such a response, and since this is
+            # part of security mechanism, it's probably better to be more
+            # strict.
+            raise XfrinException('Unexpected TSIG in response')
+
     def _check_soa_serial(self):
     def _check_soa_serial(self):
         ''' Compare the soa serial, if soa serial in master is less than
         ''' Compare the soa serial, if soa serial in master is less than
         the soa serial in local, Finish xfrin.
         the soa serial in local, Finish xfrin.
@@ -174,7 +236,7 @@ class XfrinConnection(asyncore.dispatcher):
         True: soa serial in master is bigger
         True: soa serial in master is bigger
         '''
         '''
 
 
-        self._send_query(RRType("SOA"))
+        self._send_query(RRType.SOA())
         data_len = self._get_request_response(2)
         data_len = self._get_request_response(2)
         msg_len = socket.htons(struct.unpack('H', data_len)[0])
         msg_len = socket.htons(struct.unpack('H', data_len)[0])
         soa_response = self._get_request_response(msg_len)
         soa_response = self._get_request_response(msg_len)
@@ -185,6 +247,9 @@ class XfrinConnection(asyncore.dispatcher):
         # strict we should be (see the comment in _check_response_header())
         # strict we should be (see the comment in _check_response_header())
         self._check_response_header(msg)
         self._check_response_header(msg)
 
 
+        # TSIG related checks, including an unexpected signed response
+        self._check_response_tsig(msg, soa_response)
+
         # TODO, need select soa record from data source then compare the two
         # TODO, need select soa record from data source then compare the two
         # serial, current just return OK, since this function hasn't been used
         # serial, current just return OK, since this function hasn't been used
         # now.
         # now.
@@ -202,8 +267,7 @@ class XfrinConnection(asyncore.dispatcher):
             logstr = 'transfer of \'%s\': AXFR ' % self._zone_name
             logstr = 'transfer of \'%s\': AXFR ' % self._zone_name
             if ret == XFRIN_OK:
             if ret == XFRIN_OK:
                 self.log_msg(logstr + 'started')
                 self.log_msg(logstr + 'started')
-                # TODO: .AXFR() RRType.AXFR()
-                self._send_query(RRType(252))
+                self._send_query(RRType.AXFR())
                 isc.datasrc.sqlite3_ds.load(self._db_file, self._zone_name,
                 isc.datasrc.sqlite3_ds.load(self._db_file, self._zone_name,
                                             self._handle_xfrin_response)
                                             self._handle_xfrin_response)
 
 
@@ -274,7 +338,7 @@ class XfrinConnection(asyncore.dispatcher):
 
 
             for rdata in rrset.get_rdata():
             for rdata in rrset.get_rdata():
                 # Count the soa record count
                 # Count the soa record count
-                if rrset.get_type() == RRType("SOA"):
+                if rrset.get_type() == RRType.SOA():
                     self._soa_rr_count += 1
                     self._soa_rr_count += 1
 
 
                     # XXX: the current DNS message parser can't preserve the
                     # XXX: the current DNS message parser can't preserve the
@@ -300,6 +364,9 @@ class XfrinConnection(asyncore.dispatcher):
             msg.from_wire(recvdata)
             msg.from_wire(recvdata)
             self._check_response_status(msg)
             self._check_response_status(msg)
 
 
+            # TSIG related checks, including an unexpected signed response
+            self._check_response_tsig(msg, recvdata)
+
             answer_section = msg.get_section(Message.SECTION_ANSWER)
             answer_section = msg.get_section(Message.SECTION_ANSWER)
             for rr in self._handle_answer_section(answer_section):
             for rr in self._handle_answer_section(answer_section):
                 yield rr
                 yield rr
@@ -333,12 +400,12 @@ class XfrinConnection(asyncore.dispatcher):
 
 
 def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
 def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
                   shutdown_event, master_addrinfo, check_soa, verbose,
                   shutdown_event, master_addrinfo, check_soa, verbose,
-                  tsig_key_str):
+                  tsig_key):
     xfrin_recorder.increment(zone_name)
     xfrin_recorder.increment(zone_name)
     sock_map = {}
     sock_map = {}
     conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
     conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
                            shutdown_event, master_addrinfo,
                            shutdown_event, master_addrinfo,
-                           tsig_key_str, verbose)
+                           tsig_key, verbose)
     ret = XFRIN_FAIL
     ret = XFRIN_FAIL
     if conn.connect_to_master():
     if conn.connect_to_master():
         ret = conn.do_xfrin(check_soa)
         ret = conn.do_xfrin(check_soa)
@@ -378,12 +445,100 @@ class XfrinRecorder:
         self._lock.release()
         self._lock.release()
         return ret
         return ret
 
 
+class ZoneInfo:
+    def __init__(self, config_data, module_cc):
+        """Creates a zone_info with the config data element as
+           specified by the 'zones' list in xfrin.spec. Module_cc is
+           needed to get the defaults from the specification"""
+        self._module_cc = module_cc
+        self.set_name(config_data.get('name'))
+        self.set_master_addr(config_data.get('master_addr'))
+
+        self.set_master_port(config_data.get('master_port'))
+        self.set_zone_class(config_data.get('class'))
+        self.set_tsig_key(config_data.get('tsig_key'))
+
+    def set_name(self, name_str):
+        """Set the name for this zone given a name string.
+           Raises XfrinZoneInfoException if name_str is None or if it
+           cannot be parsed."""
+        if name_str is None:
+            raise XfrinZoneInfoException("Configuration zones list "
+                                         "element does not contain "
+                                         "'name' attribute")
+        else:
+            self.name = _check_zone_name(name_str)
+
+    def set_master_addr(self, master_addr_str):
+        """Set the master address for this zone given an IP address
+           string. Raises XfrinZoneInfoException if master_addr_str is
+           None or if it cannot be parsed."""
+        if master_addr_str is None:
+            raise XfrinZoneInfoException("master address missing from config data")
+        else:
+            try:
+                self.master_addr = isc.net.parse.addr_parse(master_addr_str)
+            except ValueError:
+                errmsg = "bad format for zone's master: " + master_addr_str
+                log_error(errmsg)
+                raise XfrinZoneInfoException(errmsg)
+
+    def set_master_port(self, master_port_str):
+        """Set the master port given a port number string. If
+           master_port_str is None, the default from the specification
+           for this module will be used. Raises XfrinZoneInfoException if
+           the string contains an invalid port number"""
+        if master_port_str is None:
+            self.master_port = self._module_cc.get_default_value("zones/master_port")
+        else:
+            try:
+                self.master_port = isc.net.parse.port_parse(master_port_str)
+            except ValueError:
+                errmsg = "bad format for zone's master port: " + master_port_str
+                log_error(errmsg)
+                raise XfrinZoneInfoException(errmsg)
+
+    def set_zone_class(self, zone_class_str):
+        """Set the zone class given an RR class str (e.g. "IN"). If
+           zone_class_str is None, it will default to what is specified
+           in the specification file for this module. Raises
+           XfrinZoneInfoException if the string cannot be parsed."""
+        # TODO: remove _str
+        self.class_str = zone_class_str or self._module_cc.get_default_value("zones/class")
+        if zone_class_str == None:
+            #TODO rrclass->zone_class
+            self.rrclass = RRClass(self._module_cc.get_default_value("zones/class"))
+        else:
+            try:
+                self.rrclass = RRClass(zone_class_str)
+            except InvalidRRClass:
+                errmsg = "invalid zone class: " + zone_class_str
+                log_error(errmsg)
+                raise XfrinZoneInfoException(errmsg)
+
+    def set_tsig_key(self, tsig_key_str):
+        """Set the tsig_key for this zone, given a TSIG key string
+           representation. If tsig_key_str is None, no TSIG key will
+           be set. Raises XfrinZoneInfoException if tsig_key_str cannot
+           be parsed."""
+        if tsig_key_str is None:
+            self.tsig_key = None
+        else:
+            try:
+                self.tsig_key = TSIGKey(tsig_key_str)
+            except InvalidParameter as ipe:
+                errmsg = "bad TSIG key string: " + tsig_key_str
+                log_error(errmsg)
+                raise XfrinZoneInfoException(errmsg)
+
+    def get_master_addr_info(self):
+        return (self.master_addr.family, socket.SOCK_STREAM,
+                (str(self.master_addr), self.master_port))
+
 class Xfrin:
 class Xfrin:
     def __init__(self, verbose = False):
     def __init__(self, verbose = False):
         self._max_transfers_in = 10
         self._max_transfers_in = 10
-        #TODO, this is the temp way to set the zone's master.
-        self._master_addr = DEFAULT_MASTER
-        self._master_port = DEFAULT_MASTER_PORT
+        self._zones = {}
         self._cc_setup()
         self._cc_setup()
         self.recorder = XfrinRecorder()
         self.recorder = XfrinRecorder()
         self._shutdown_event = threading.Event()
         self._shutdown_event = threading.Event()
@@ -402,10 +557,7 @@ class Xfrin:
                                               self.command_handler)
                                               self.command_handler)
         self._module_cc.start()
         self._module_cc.start()
         config_data = self._module_cc.get_full_config()
         config_data = self._module_cc.get_full_config()
-        self._max_transfers_in = config_data.get("transfers_in")
-        self._master_addr = config_data.get('master_addr') or self._master_addr
-        self._master_port = config_data.get('master_port') or self._master_port
-        self._tsig_key_str = config_data.get('tsig_key') or None
+        self.config_handler(config_data)
 
 
     def _cc_check_command(self):
     def _cc_check_command(self):
         '''This is a straightforward wrapper for cc.check_command,
         '''This is a straightforward wrapper for cc.check_command,
@@ -413,22 +565,42 @@ class Xfrin:
         of unit tests.'''
         of unit tests.'''
         self._module_cc.check_command(False)
         self._module_cc.check_command(False)
 
 
+    def _get_zone_info(self, name, rrclass):
+        """Returns the ZoneInfo object containing the configured data
+           for the given zone name. If the zone name did not have any
+           data, returns None"""
+        return self._zones.get((name.to_text(), rrclass.to_text()))
+
+    def _add_zone_info(self, zone_info):
+        """Add the zone info. Raises a XfrinZoneInfoException if a zone
+           with the same name and class is already configured"""
+        key = (zone_info.name.to_text(), zone_info.class_str)
+        if key in self._zones:
+            raise XfrinZoneInfoException("zone " + str(key) +
+                                       " configured multiple times")
+        self._zones[key] = zone_info
+
+    def _clear_zone_info(self):
+        self._zones = {}
+
     def config_handler(self, new_config):
     def config_handler(self, new_config):
+        # backup all config data (should there be a problem in the new
+        # data)
+        old_max_transfers_in = self._max_transfers_in
+        old_zones = self._zones
+
         self._max_transfers_in = new_config.get("transfers_in") or self._max_transfers_in
         self._max_transfers_in = new_config.get("transfers_in") or self._max_transfers_in
-        self._tsig_key_str = new_config.get('tsig_key') or None
-        if ('master_addr' in new_config) or ('master_port' in new_config):
-            # User should change the port and address together.
-            try:
-                addr = new_config.get('master_addr') or self._master_addr
-                port = new_config.get('master_port') or self._master_port
-                isc.net.parse.addr_parse(addr)
-                isc.net.parse.port_parse(port)
-                self._master_addr = addr
-                self._master_port = port
-            except ValueError:
-                errmsg = "bad format for zone's master: " + str(new_config)
-                log_error(errmsg)
-                return create_answer(1, errmsg)
+
+        if 'zones' in new_config:
+            self._clear_zone_info()
+            for zone_config in new_config.get('zones'):
+                try:
+                    zone_info = ZoneInfo(zone_config, self._module_cc)
+                    self._add_zone_info(zone_info)
+                except XfrinZoneInfoException as xce:
+                    self._zones = old_zones
+                    self._max_transfers_in = old_max_transfers_in
+                    return create_answer(1, str(xce))
 
 
         return create_answer(0)
         return create_answer(0)
 
 
@@ -453,28 +625,43 @@ class Xfrin:
                 # notify command maybe has the parameters which
                 # notify command maybe has the parameters which
                 # specify the notifyfrom address and port, according the RFC1996, zone
                 # specify the notifyfrom address and port, according the RFC1996, zone
                 # transfer should starts first from the notifyfrom, but now, let 'TODO' it.
                 # transfer should starts first from the notifyfrom, but now, let 'TODO' it.
+                # (using the value now, while we can only set one master address, would be
+                # a security hole. Once we add the ability to have multiple master addresses,
+                # we should check if it matches one of them, and then use it.)
                 (zone_name, rrclass) = self._parse_zone_name_and_class(args)
                 (zone_name, rrclass) = self._parse_zone_name_and_class(args)
-                (master_addr) = build_addr_info(self._master_addr, self._master_port)
-                ret = self.xfrin_start(zone_name,
-                                       rrclass,
-                                       self._get_db_file(),
-                                       master_addr,
-                                       self._tsig_key_str,
-                                       True)
-                answer = create_answer(ret[0], ret[1])
+                zone_info = self._get_zone_info(zone_name, rrclass)
+                if zone_info is None:
+                    # TODO what to do? no info known about zone. defaults?
+                    errmsg = "Got notification to retransfer unknown zone " + zone_name.to_text()
+                    log_error(errmsg)
+                    answer = create_answer(1, errmsg)
+                else:
+                    master_addr = zone_info.get_master_addr_info()
+                    ret = self.xfrin_start(zone_name,
+                                           rrclass,
+                                           self._get_db_file(),
+                                           master_addr,
+                                           zone_info.tsig_key,
+                                           True)
+                    answer = create_answer(ret[0], ret[1])
 
 
             elif command == 'retransfer' or command == 'refresh':
             elif command == 'retransfer' or command == 'refresh':
                 # Xfrin receives the retransfer/refresh from cmdctl(sent by bindctl).
                 # Xfrin receives the retransfer/refresh from cmdctl(sent by bindctl).
                 # If the command has specified master address, do transfer from the
                 # If the command has specified master address, do transfer from the
                 # master address, or else do transfer from the configured masters.
                 # master address, or else do transfer from the configured masters.
                 (zone_name, rrclass) = self._parse_zone_name_and_class(args)
                 (zone_name, rrclass) = self._parse_zone_name_and_class(args)
-                master_addr = self._parse_master_and_port(args)
+                master_addr = self._parse_master_and_port(args, zone_name,
+                                                          rrclass)
+                zone_info = self._get_zone_info(zone_name, rrclass)
+                tsig_key = None
+                if zone_info:
+                    tsig_key = zone_info.tsig_key
                 db_file = args.get('db_file') or self._get_db_file()
                 db_file = args.get('db_file') or self._get_db_file()
                 ret = self.xfrin_start(zone_name,
                 ret = self.xfrin_start(zone_name,
                                        rrclass,
                                        rrclass,
                                        db_file,
                                        db_file,
                                        master_addr,
                                        master_addr,
-                                       self._tsig_key_str,
+                                       tsig_key,
                                        (False if command == 'retransfer' else True))
                                        (False if command == 'retransfer' else True))
                 answer = create_answer(ret[0], ret[1])
                 answer = create_answer(ret[0], ret[1])
 
 
@@ -486,25 +673,51 @@ class Xfrin:
         return answer
         return answer
 
 
     def _parse_zone_name_and_class(self, args):
     def _parse_zone_name_and_class(self, args):
-        zone_name = args.get('zone_name')
-        if not zone_name:
+        zone_name_str = args.get('zone_name')
+        if zone_name_str is None:
             raise XfrinException('zone name should be provided')
             raise XfrinException('zone name should be provided')
 
 
-        rrclass = args.get('zone_class')
-        if not rrclass:
-            rrclass = RRClass.IN()
+        return (_check_zone_name(zone_name_str), _check_zone_class(args.get('zone_class')))
+
+    def _parse_master_and_port(self, args, zone_name, zone_class):
+        """
+        Return tuple (family, socktype, sockaddr) for address and port in given
+        args dict.
+        IPv4 and IPv6 are the only supported addresses now, so sockaddr will be
+        (address, port). The socktype is socket.SOCK_STREAM for now.
+        """
+        # check if we have configured info about this zone, in case
+        # port or master are not specified
+        zone_info = self._get_zone_info(zone_name, zone_class)
+
+        addr_str = args.get('master')
+        if addr_str is None:
+            if zone_info is not None:
+                addr = zone_info.master_addr
+            else:
+                raise XfrinException("Master address not given or "
+                                     "configured for " + zone_name.to_text())
         else:
         else:
             try:
             try:
-                rrclass = RRClass(rrclass)
-            except InvalidRRClass as e:
-                raise XfrinException('invalid RRClass: ' + rrclass)
-
-        return zone_name, rrclass
+                addr = isc.net.parse.addr_parse(addr_str)
+            except ValueError as err:
+                raise XfrinException("failed to resolve master address %s: %s" %
+                                     (addr_str, str(err)))
+
+        port_str = args.get('port')
+        if port_str is None:
+            if zone_info is not None:
+                port = zone_info.master_port
+            else:
+                port = DEFAULT_MASTER_PORT
+        else:
+            try:
+                port = isc.net.parse.port_parse(port_str)
+            except ValueError as err:
+                raise XfrinException("failed to parse port=%s: %s" %
+                                     (port_str, str(err)))
 
 
-    def _parse_master_and_port(self, args):
-        port = args.get('port') or self._master_port
-        master = args.get('master') or self._master_addr
-        return build_addr_info(master, port)
+        return (addr.family, socket.SOCK_STREAM, (str(addr), port))
 
 
     def _get_db_file(self):
     def _get_db_file(self):
         #TODO, the db file path should be got in auth server's configuration
         #TODO, the db file path should be got in auth server's configuration
@@ -567,7 +780,7 @@ class Xfrin:
         while not self._shutdown_event.is_set():
         while not self._shutdown_event.is_set():
             self._cc_check_command()
             self._cc_check_command()
 
 
-    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo, tsig_key_str,
+    def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo, tsig_key,
                     check_soa = True):
                     check_soa = True):
         if "pydnspp" not in sys.modules:
         if "pydnspp" not in sys.modules:
             return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
             return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
@@ -582,12 +795,13 @@ class Xfrin:
         xfrin_thread = threading.Thread(target = process_xfrin,
         xfrin_thread = threading.Thread(target = process_xfrin,
                                         args = (self,
                                         args = (self,
                                                 self.recorder,
                                                 self.recorder,
-                                                zone_name, rrclass,
+                                                zone_name.to_text(),
+                                                rrclass,
                                                 db_file,
                                                 db_file,
                                                 self._shutdown_event,
                                                 self._shutdown_event,
                                                 master_addrinfo, check_soa,
                                                 master_addrinfo, check_soa,
                                                 self._verbose,
                                                 self._verbose,
-                                                tsig_key_str))
+                                                tsig_key))
 
 
         xfrin_thread.start()
         xfrin_thread.start()
         return (0, 'zone xfrin is started')
         return (0, 'zone xfrin is started')
@@ -604,20 +818,6 @@ def set_signal_handler():
     signal.signal(signal.SIGTERM, signal_handler)
     signal.signal(signal.SIGTERM, signal_handler)
     signal.signal(signal.SIGINT, signal_handler)
     signal.signal(signal.SIGINT, signal_handler)
 
 
-def build_addr_info(addrstr, portstr):
-    """
-    Return tuple (family, socktype, sockaddr) for given address and port.
-    IPv4 and IPv6 are the only supported addresses now, so sockaddr will be
-    (address, port). The socktype is socket.SOCK_STREAM for now.
-    """
-    try:
-        port = isc.net.parse.port_parse(portstr)
-        addr = isc.net.parse.addr_parse(addrstr)
-        return (addr.family, socket.SOCK_STREAM, (addrstr, port))
-    except ValueError as err:
-        raise XfrinException("failed to resolve master address/port=%s/%s: %s" %
-                             (addrstr, portstr, str(err)))
-
 def set_cmd_options(parser):
 def set_cmd_options(parser):
     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")

+ 36 - 14
src/bin/xfrin/xfrin.spec

@@ -9,21 +9,43 @@
         "item_optional": false,
         "item_optional": false,
         "item_default": 10
         "item_default": 10
       },
       },
-      {
-        "item_name": "master_addr",
-        "item_type": "string",
-        "item_optional": false,
-        "item_default": ""
-      },
-      { "item_name": "master_port",
-        "item_type": "integer",
+      { "item_name": "zones",
+        "item_type": "list",
         "item_optional": false,
         "item_optional": false,
-        "item_default": 53
-      },
-      { "item_name": "tsig_key",
-        "item_type": "string",
-        "item_optional": true,
-        "item_default": ""
+        "item_default": [],
+        "list_item_spec":
+        { "item_type": "map",
+          "item_name": "zone_info",
+          "item_optional": false,
+          "item_default": {},
+          "map_item_spec": [
+          { "item_name": "name",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          },
+          { "item_name": "class",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": "IN"
+          },
+          {
+            "item_name": "master_addr",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
+          },
+          { "item_name": "master_port",
+            "item_type": "integer",
+            "item_optional": false,
+            "item_default": 53
+          },
+          { "item_name": "tsig_key",
+            "item_type": "string",
+            "item_optional": true
+          }
+          ]
+        }
       }
       }
     ],
     ],
     "commands": [
     "commands": [

+ 29 - 8
src/bin/zonemgr/b10-zonemgr.8

@@ -2,12 +2,12 @@
 .\"     Title: b10-zonemgr
 .\"     Title: b10-zonemgr
 .\"    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: October 18, 2010
+.\"      Date: May 19, 2011
 .\"    Manual: BIND10
 .\"    Manual: BIND10
 .\"    Source: BIND10
 .\"    Source: BIND10
 .\"  Language: English
 .\"  Language: English
 .\"
 .\"
-.TH "B10\-ZONEMGR" "8" "October 18, 2010" "BIND10" "BIND10"
+.TH "B10\-ZONEMGR" "8" "May 19, 2011" "BIND10" "BIND10"
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
 .\" * set default formatting
 .\" * set default formatting
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
@@ -46,11 +46,6 @@ receives its configurations from
 The configurable settings are:
 The configurable settings are:
 .PP
 .PP
 
 
-\fIjitter_scope\fR
-defines the random jitter range subtracted from the refresh and retry timers to avoid many zones from refreshing at the same time\&. The refresh or retry time actually used is a random time between the defined refresh or retry time and it multiplied by the
-\fIjitter_scope\fR\&. This is re\-evaluated after each refresh or retry\&. This value is a real number and the maximum is 0\&.5 (half of the refresh or retry time)\&. The default is 0\&.25\&. Set to 0 to disable the jitter\&.
-.PP
-
 \fIlowerbound_refresh\fR
 \fIlowerbound_refresh\fR
 defines the minimum SOA REFRESH time in seconds\&. The default is 10\&.
 defines the minimum SOA REFRESH time in seconds\&. The default is 10\&.
 .PP
 .PP
@@ -59,10 +54,36 @@ defines the minimum SOA REFRESH time in seconds\&. The default is 10\&.
 defines the minimum SOA RETRY time in seconds\&. The default is 5\&.
 defines the minimum SOA RETRY time in seconds\&. The default is 5\&.
 .PP
 .PP
 
 
+\fIrefresh_jitter\fR
+This value is a real number\&. The maximum amount is 0\&.5\&. The default is 0\&.25\&.
+.PP
+
+\fIreload_jitter\fR
+This value is a real number\&. The default is 0\&.75\&.
+.PP
+
 \fImax_transfer_timeout\fR
 \fImax_transfer_timeout\fR
 defines the maximum amount of time in seconds for a transfer\&.
 defines the maximum amount of time in seconds for a transfer\&.
 The default is 14400 (4 hours)\&.
 The default is 14400 (4 hours)\&.
 .PP
 .PP
+
+\fIsecondary_zones\fR
+is a list of slave zones that the
+\fBb10\-zonemgr\fR
+should keep timers for\&. The list items include the
+\fIname\fR
+(which defines the zone name) and the
+\fIclass\fR
+(which defaults to
+\(lqIN\(rq)\&.
+.PP
+(A deprecated configuration is
+\fIjitter_scope\fR
+which is superceded by
+\fIrefresh_jitter\fR
+and
+\fIreload_jitter\fR\&.)
+.PP
 The configuration commands are:
 The configuration commands are:
 .PP
 .PP
 
 
@@ -107,5 +128,5 @@ The
 daemon was designed in July 2010 by CNNIC for the ISC BIND 10 project\&.
 daemon was designed in July 2010 by CNNIC for the ISC BIND 10 project\&.
 .SH "COPYRIGHT"
 .SH "COPYRIGHT"
 .br
 .br
-Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC")
+Copyright \(co 2010-2011 Internet Systems Consortium, Inc. ("ISC")
 .br
 .br

+ 53 - 13
src/bin/zonemgr/b10-zonemgr.xml

@@ -2,7 +2,7 @@
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
 	       [<!ENTITY mdash "&#8212;">]>
 	       [<!ENTITY mdash "&#8212;">]>
 <!--
 <!--
- - Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-2011  Internet Systems Consortium, Inc. ("ISC")
  -
  -
  - Permission to use, copy, modify, and/or distribute this software for any
  - Permission to use, copy, modify, and/or distribute this software for any
  - purpose with or without fee is hereby granted, provided that the above
  - purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
 <refentry>
 <refentry>
 
 
   <refentryinfo>
   <refentryinfo>
-    <date>October 18, 2010</date>
+    <date>May 19, 2011</date>
   </refentryinfo>
   </refentryinfo>
 
 
   <refmeta>
   <refmeta>
@@ -36,7 +36,7 @@
 
 
   <docinfo>
   <docinfo>
     <copyright>
     <copyright>
-      <year>2010</year>
+      <year>2010-2011</year>
       <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
       <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
     </copyright>
     </copyright>
   </docinfo>
   </docinfo>
@@ -92,6 +92,39 @@
     <para>
     <para>
       The configurable settings are:
       The configurable settings are:
     </para>
     </para>
+
+    <para>
+      <varname>lowerbound_refresh</varname>
+      defines the minimum SOA REFRESH time in seconds.
+      The default is 10.
+    </para>
+
+    <para>
+      <varname>lowerbound_retry</varname>
+      defines the minimum SOA RETRY time in seconds.
+      The default is 5.
+    </para>
+
+    <para>
+      <varname>refresh_jitter</varname>
+      This value is a real number.
+      The maximum amount is 0.5.
+      The default is 0.25.
+    </para>
+<!-- TODO: needs to be documented -->
+<!-- TODO:      Set to 0 to disable the jitter.   -->
+
+    <para>
+      <varname>reload_jitter</varname>
+      This value is a real number.
+      The default is 0.75.
+    </para>
+<!-- TODO: needs to be documented -->
+<!-- TODO:      Set to 0 to disable the jitter.   -->
+<!-- what does 0 do? -->
+<!-- TODO: no max? -->
+
+<!-- TODO: remove this. This is old removed config
     <para>
     <para>
       <varname>jitter_scope</varname>
       <varname>jitter_scope</varname>
       defines the random jitter range subtracted from the refresh
       defines the random jitter range subtracted from the refresh
@@ -106,16 +139,8 @@
       The default is 0.25.
       The default is 0.25.
       Set to 0 to disable the jitter.
       Set to 0 to disable the jitter.
     </para>
     </para>
-    <para>
-      <varname>lowerbound_refresh</varname>
-      defines the minimum SOA REFRESH time in seconds.
-      The default is 10.
-    </para>
-    <para>
-      <varname>lowerbound_retry</varname>
-      defines the minimum SOA RETRY time in seconds.
-      The default is 5.
-    </para>
+-->
+
     <para>
     <para>
       <varname>max_transfer_timeout</varname>
       <varname>max_transfer_timeout</varname>
       defines the maximum amount of time in seconds for a transfer.
       defines the maximum amount of time in seconds for a transfer.
@@ -123,6 +148,21 @@
       The default is 14400 (4 hours).
       The default is 14400 (4 hours).
     </para>
     </para>
 
 
+<!-- TODO: this duplicates list in Xfrin too -->
+    <para>
+      <varname>secondary_zones</varname> is a list of slave zones
+      that the <command>b10-zonemgr</command> should keep timers for.
+      The list items include the <varname>name</varname> (which
+      defines the zone name) and the <varname>class</varname>
+      (which defaults to <quote>IN</quote>).
+    </para>
+
+    <para>
+      (A deprecated configuration is <varname>jitter_scope</varname>
+      which is superceded by <varname>refresh_jitter</varname>
+      and <varname>reload_jitter</varname>.)
+    </para>
+
 <!-- TODO: formating -->
 <!-- TODO: formating -->
     <para>
     <para>
       The configuration commands are:
       The configuration commands are:

+ 55 - 0
src/bin/zonemgr/tests/zonemgr_test.py

@@ -434,6 +434,14 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertTrue(zone_state == ZONE_REFRESHING)
         self.assertTrue(zone_state == ZONE_REFRESHING)
 
 
     def test_update_config_data(self):
     def test_update_config_data(self):
+        # make sure it doesn't fail if we only provide secondary zones
+        config_data = {
+                    "secondary_zones": [ { "name": "example.net.",
+                                           "class": "IN" } ]
+                }
+        self.zone_refresh.update_config_data(config_data)
+
+        # update all values
         config_data = {
         config_data = {
                     "lowerbound_refresh" : 60,
                     "lowerbound_refresh" : 60,
                     "lowerbound_retry" : 30,
                     "lowerbound_retry" : 30,
@@ -449,6 +457,53 @@ class TestZonemgrRefresh(unittest.TestCase):
         self.assertEqual(0.25, self.zone_refresh._refresh_jitter)
         self.assertEqual(0.25, self.zone_refresh._refresh_jitter)
         self.assertEqual(0.75, self.zone_refresh._reload_jitter)
         self.assertEqual(0.75, self.zone_refresh._reload_jitter)
 
 
+        # make sure they are not reset when we only update one
+        config_data = {
+                    "reload_jitter" : 0.35,
+                }
+        self.zone_refresh.update_config_data(config_data)
+        self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
+        self.assertEqual(30, self.zone_refresh._lowerbound_retry)
+        self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
+        self.assertEqual(0.25, self.zone_refresh._refresh_jitter)
+        self.assertEqual(0.35, self.zone_refresh._reload_jitter)
+
+        # and make sure we restore the previous config if something
+        # goes wrong
+        config_data = {
+                    "lowerbound_refresh" : 61,
+                    "lowerbound_retry" : 31,
+                    "max_transfer_timeout" : 19801,
+                    "refresh_jitter" : 0.21,
+                    "reload_jitter" : 0.71,
+                    "secondary_zones": [ { "name": "doesnotexist",
+                                           "class": "IN" } ]
+                }
+        self.assertRaises(ZonemgrException,
+                          self.zone_refresh.update_config_data,
+                          config_data)
+        self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
+        self.assertEqual(30, self.zone_refresh._lowerbound_retry)
+        self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
+        self.assertEqual(0.25, self.zone_refresh._refresh_jitter)
+        self.assertEqual(0.35, self.zone_refresh._reload_jitter)
+
+        # Make sure we accept 0 as a value
+        config_data = {
+                    "lowerbound_refresh" : 60,
+                    "lowerbound_retry" : 30,
+                    "max_transfer_timeout" : 19800,
+                    "refresh_jitter" : 0,
+                    "reload_jitter" : 0.75,
+                    "secondary_zones": []
+                }
+        self.zone_refresh.update_config_data(config_data)
+        self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
+        self.assertEqual(30, self.zone_refresh._lowerbound_retry)
+        self.assertEqual(19800, self.zone_refresh._max_transfer_timeout)
+        self.assertEqual(0, self.zone_refresh._refresh_jitter)
+        self.assertEqual(0.75, self.zone_refresh._reload_jitter)
+
     def test_shutdown(self):
     def test_shutdown(self):
         self.zone_refresh._check_sock = self.zone_refresh._master_socket
         self.zone_refresh._check_sock = self.zone_refresh._master_socket
         listener = self.zone_refresh.run_timer()
         listener = self.zone_refresh.run_timer()

+ 65 - 24
src/bin/zonemgr/zonemgr.py.in

@@ -101,6 +101,11 @@ class ZonemgrRefresh:
         self._check_sock = slave_socket
         self._check_sock = slave_socket
         self._db_file = db_file
         self._db_file = db_file
         self._zonemgr_refresh_info = {}
         self._zonemgr_refresh_info = {}
+        self._lowerbound_refresh = None
+        self._lowerbound_retry = None
+        self._max_transfer_timeout = None
+        self._refresh_jitter = None
+        self._reload_jitter = None
         self.update_config_data(config_data)
         self.update_config_data(config_data)
         self._running = False
         self._running = False
 
 
@@ -404,37 +409,73 @@ class ZonemgrRefresh:
 
 
     def update_config_data(self, new_config):
     def update_config_data(self, new_config):
         """ update ZonemgrRefresh config """
         """ update ZonemgrRefresh config """
+        # TODO: we probably want to store all this info in a nice
+        # class, so that we don't have to backup and restore every
+        # single value.
+        # TODO2: We also don't use get_default_value yet
         backup = self._zonemgr_refresh_info.copy()
         backup = self._zonemgr_refresh_info.copy()
+
+        # Get a new value, but only if it is defined (commonly used below)
+        # We don't use "value or default", because if value would be
+        # 0, we would take default
+        def val_or_default(value, default):
+            if value is not None:
+                return value
+            else:
+                return default
+
+        # store the values so we can restore them if there is a problem
+        lowerbound_refresh_backup = self._lowerbound_refresh
+        self._lowerbound_refresh = val_or_default(
+            new_config.get('lowerbound_refresh'), self._lowerbound_refresh)
+
+        lowerbound_retry_backup = self._lowerbound_retry
+        self._lowerbound_retry = val_or_default(
+            new_config.get('lowerbound_retry'), self._lowerbound_retry)
+
+        max_transfer_timeout_backup = self._max_transfer_timeout
+        self._max_transfer_timeout = val_or_default(
+            new_config.get('max_transfer_timeout'), self._max_transfer_timeout)
+
+        refresh_jitter_backup = self._refresh_jitter
+        self._refresh_jitter = val_or_default(
+            new_config.get('refresh_jitter'), self._refresh_jitter)
+
+        reload_jitter_backup = self._reload_jitter
+        self._reload_jitter = val_or_default(
+            new_config.get('reload_jitter'), self._reload_jitter)
         try:
         try:
             required = {}
             required = {}
-            # Add new zones
-            for secondary_zone in new_config.get('secondary_zones'):
-                name = secondary_zone['name']
-                # Be tolerant to sclerotic users who forget the final dot
-                if name[-1] != '.':
-                    name = name + '.'
-                name_class = (name, secondary_zone['class'])
-                required[name_class] = True
-                # Add it only if it isn't there already
-                if not name_class in self._zonemgr_refresh_info:
-                    self.zonemgr_add_zone(name_class)
-            # Drop the zones that are no longer there
-            # Do it in two phases, python doesn't like deleting while iterating
-            to_drop = []
-            for old_zone in self._zonemgr_refresh_info:
-                if not old_zone in required:
-                    to_drop.append(old_zone)
-            for drop in to_drop:
-                del self._zonemgr_refresh_info[drop]
+            secondary_zones = new_config.get('secondary_zones')
+            if secondary_zones is not None:
+                # Add new zones
+                for secondary_zone in new_config.get('secondary_zones'):
+                    name = secondary_zone['name']
+                    # Be tolerant to sclerotic users who forget the final dot
+                    if name[-1] != '.':
+                        name = name + '.'
+                    name_class = (name, secondary_zone['class'])
+                    required[name_class] = True
+                    # Add it only if it isn't there already
+                    if not name_class in self._zonemgr_refresh_info:
+                        self.zonemgr_add_zone(name_class)
+                # Drop the zones that are no longer there
+                # Do it in two phases, python doesn't like deleting while iterating
+                to_drop = []
+                for old_zone in self._zonemgr_refresh_info:
+                    if not old_zone in required:
+                        to_drop.append(old_zone)
+                for drop in to_drop:
+                    del self._zonemgr_refresh_info[drop]
         # If we are not able to find it in database, restore the original
         # If we are not able to find it in database, restore the original
         except:
         except:
             self._zonemgr_refresh_info = backup
             self._zonemgr_refresh_info = backup
+            self._lowerbound_refresh = lowerbound_refresh_backup
+            self._lowerbound_retry = lowerbound_retry_backup
+            self._max_transfer_timeout = max_transfer_timeout_backup
+            self._refresh_jitter = refresh_jitter_backup
+            self._reload_jitter = reload_jitter_backup
             raise
             raise
-        self._lowerbound_refresh = new_config.get('lowerbound_refresh')
-        self._lowerbound_retry = new_config.get('lowerbound_retry')
-        self._max_transfer_timeout = new_config.get('max_transfer_timeout')
-        self._refresh_jitter = new_config.get('refresh_jitter')
-        self._reload_jitter = new_config.get('reload_jitter')
 
 
 class Zonemgr:
 class Zonemgr:
     """Zone manager class."""
     """Zone manager class."""

+ 6 - 0
src/lib/asiolink/tests/Makefile.am

@@ -10,6 +10,12 @@ if USE_STATIC_LINK
 AM_LDFLAGS = -static
 AM_LDFLAGS = -static
 endif
 endif
 
 
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
 CLEANFILES = *.gcno *.gcda
 CLEANFILES = *.gcno *.gcda
 
 
 TESTS =
 TESTS =

+ 19 - 7
src/lib/asiolink/tests/interval_timer_unittest.cc

@@ -18,7 +18,7 @@
 #include <asio.hpp>
 #include <asio.hpp>
 #include <asiolink/asiolink.h>
 #include <asiolink/asiolink.h>
 
 
-#include <boost/date_time/posix_time/posix_time_types.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
 
 
 namespace {
 namespace {
 // TODO: Consider this margin
 // TODO: Consider this margin
@@ -166,16 +166,22 @@ TEST_F(IntervalTimerTest, startIntervalTimer) {
     io_service_.run();
     io_service_.run();
     // reaches here after timer expired
     // reaches here after timer expired
     // delta: difference between elapsed time and 100 milliseconds.
     // delta: difference between elapsed time and 100 milliseconds.
+    boost::posix_time::time_duration test_runtime =
+        boost::posix_time::microsec_clock::universal_time() - start;
+    EXPECT_FALSE(test_runtime.is_negative()) << 
+                 "test duration " << test_runtime << 
+                 " negative - clock skew?";
     boost::posix_time::time_duration delta =
     boost::posix_time::time_duration delta =
-        (boost::posix_time::microsec_clock::universal_time() - start)
-         - boost::posix_time::millisec(100);
+        test_runtime - boost::posix_time::milliseconds(100);
     if (delta.is_negative()) {
     if (delta.is_negative()) {
         delta.invert_sign();
         delta.invert_sign();
     }
     }
     // expect TimerCallBack is called; timer_called_ is true
     // expect TimerCallBack is called; timer_called_ is true
     EXPECT_TRUE(timer_called_);
     EXPECT_TRUE(timer_called_);
     // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
     // expect interval is 100 milliseconds +/- TIMER_MARGIN_MSEC.
-    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC) << 
+                "delta " << delta.total_milliseconds() << "msec " <<
+                ">= " << TIMER_MARGIN_MSEC.total_milliseconds();
 }
 }
 
 
 TEST_F(IntervalTimerTest, destructIntervalTimer) {
 TEST_F(IntervalTimerTest, destructIntervalTimer) {
@@ -283,14 +289,20 @@ TEST_F(IntervalTimerTest, overwriteIntervalTimer) {
     //   + 400 milliseconds for TimerCallBackOverwriter (stop)
     //   + 400 milliseconds for TimerCallBackOverwriter (stop)
     //   = 800 milliseconds.
     //   = 800 milliseconds.
     // delta: difference between elapsed time and 400 + 100 milliseconds
     // delta: difference between elapsed time and 400 + 100 milliseconds
+    boost::posix_time::time_duration test_runtime =
+        boost::posix_time::microsec_clock::universal_time() - start;
+    EXPECT_FALSE(test_runtime.is_negative()) << 
+                 "test duration " << test_runtime << 
+                 " negative - clock skew?";
     boost::posix_time::time_duration delta =
     boost::posix_time::time_duration delta =
-        (boost::posix_time::microsec_clock::universal_time() - start)
-         - boost::posix_time::millisec(400 + 100);
+        test_runtime - boost::posix_time::milliseconds(400 + 100);
     if (delta.is_negative()) {
     if (delta.is_negative()) {
         delta.invert_sign();
         delta.invert_sign();
     }
     }
     // expect callback function is updated: TimerCallBack is called
     // expect callback function is updated: TimerCallBack is called
     EXPECT_TRUE(timer_called_);
     EXPECT_TRUE(timer_called_);
     // expect interval is updated
     // expect interval is updated
-    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC);
+    EXPECT_TRUE(delta < TIMER_MARGIN_MSEC) << 
+                "delta " << delta.total_milliseconds() << " msec " <<
+                ">= " << TIMER_MARGIN_MSEC.total_milliseconds();
 }
 }

+ 4 - 3
src/lib/config/ccsession.cc

@@ -371,11 +371,12 @@ ModuleCCSession::checkCommand() {
 std::string
 std::string
 ModuleCCSession::addRemoteConfig(const std::string& spec_name,
 ModuleCCSession::addRemoteConfig(const std::string& spec_name,
                                  void (*handler)(const std::string& module,
                                  void (*handler)(const std::string& module,
-                                          ConstElementPtr))
+                                          ConstElementPtr),
+                                 bool spec_is_filename)
 {
 {
     std::string module_name;
     std::string module_name;
     ModuleSpec rmod_spec;
     ModuleSpec rmod_spec;
-    if (spec_name.find_first_of("./") != std::string::npos) {
+    if (spec_is_filename) {
         // It's a file name, so load it
         // It's a file name, so load it
         rmod_spec = readModuleSpecification(spec_name);
         rmod_spec = readModuleSpecification(spec_name);
         module_name =
         module_name =
@@ -402,7 +403,6 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_name,
         }
         }
     }
     }
     ConfigData rmod_config = ConfigData(rmod_spec);
     ConfigData rmod_config = ConfigData(rmod_spec);
-    session_.subscribe(module_name);
 
 
     // Get the current configuration values for that module
     // Get the current configuration values for that module
     ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
     ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
@@ -427,6 +427,7 @@ ModuleCCSession::addRemoteConfig(const std::string& spec_name,
         remote_module_handlers_[module_name] = handler;
         remote_module_handlers_[module_name] = handler;
         handler(module_name, local_config);
         handler(module_name, local_config);
     }
     }
+    session_.subscribe(module_name);
     return (module_name);
     return (module_name);
 }
 }
 
 

+ 10 - 5
src/lib/config/ccsession.h

@@ -264,22 +264,27 @@ public:
      *                  filename of the spec file to use or a name of module
      *                  filename of the spec file to use or a name of module
      *                  (in case it's a module name, the spec data is
      *                  (in case it's a module name, the spec data is
      *                  downloaded from the configuration manager, therefore
      *                  downloaded from the configuration manager, therefore
-     *                  the configuration manager must know it). A heuristic
-     *                  is used to guess which should be used - if it contains
-     *                  a slash or dot, filename is assumed, otherwise
-     *                  name of module is assumed.
+     *                  the configuration manager must know it). If
+     *                  spec_is_filenabe is true (the default), then a
+     *                  filename is assumed, otherwise a module name.
      * \param handler The handler function called whenever there's a change.
      * \param handler The handler function called whenever there's a change.
      *                Called once initally from this function. May be NULL
      *                Called once initally from this function. May be NULL
      *                if you don't want any handler to be called and you're
      *                if you don't want any handler to be called and you're
      *                fine with requesting the data through
      *                fine with requesting the data through
      *                getRemoteConfigValue() each time.
      *                getRemoteConfigValue() each time.
+     *
+     *                The handler should not throw, or it'll fall trough and
+     *                the exception will get into strange places, probably
+     *                aborting the application.
+     * \param spec_is_filename Says if spec_name is filename or module name.
      * \return The name of the module specified in the given specification
      * \return The name of the module specified in the given specification
      *         file
      *         file
      */
      */
     std::string addRemoteConfig(const std::string& spec_name,
     std::string addRemoteConfig(const std::string& spec_name,
                                 void (*handler)(const std::string& module_name,
                                 void (*handler)(const std::string& module_name,
                                                 isc::data::ConstElementPtr
                                                 isc::data::ConstElementPtr
-                                                update) = NULL);
+                                                update) = NULL,
+                                bool spec_is_filename = true);
 
 
     /**
     /**
      * Removes the module with the given name from the remote config
      * Removes the module with the given name from the remote config

+ 2 - 1
src/lib/config/tests/ccsession_unittests.cc

@@ -417,7 +417,8 @@ TEST_F(CCSessionTest, remoteConfig) {
         session.getMessages()->add(createAnswer(0, spec.getFullSpec()));
         session.getMessages()->add(createAnswer(0, spec.getFullSpec()));
         session.getMessages()->add(createAnswer(0, el("{}")));
         session.getMessages()->add(createAnswer(0, el("{}")));
 
 
-        EXPECT_NO_THROW(module_name = mccs.addRemoteConfig("Spec2"));
+        EXPECT_NO_THROW(module_name = mccs.addRemoteConfig("Spec2", NULL,
+                                                           false));
 
 
         size_t qsize(session.getMsgQueue()->size());
         size_t qsize(session.getMsgQueue()->size());
         EXPECT_TRUE(session.getMsgQueue()->get(qsize - 2)->equals(*el(
         EXPECT_TRUE(session.getMsgQueue()->get(qsize - 2)->equals(*el(

+ 26 - 1
src/lib/cryptolink/crypto_hmac.cc

@@ -17,6 +17,7 @@
 
 
 #include <boost/scoped_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 
 
+#include <botan/version.h>
 #include <botan/botan.h>
 #include <botan/botan.h>
 #include <botan/hmac.h>
 #include <botan/hmac.h>
 #include <botan/hash.h>
 #include <botan/hash.h>
@@ -70,12 +71,28 @@ public:
         // If the key length is larger than the block size, we hash the
         // If the key length is larger than the block size, we hash the
         // key itself first.
         // key itself first.
         try {
         try {
-            if (secret_len > hash->HASH_BLOCK_SIZE) {
+            // use a temp var so we don't have blocks spanning
+            // preprocessor directives
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
+            size_t block_length = hash->hash_block_size();
+#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0)
+            size_t block_length = hash->HASH_BLOCK_SIZE;
+#else
+#error "Unsupported Botan version (need 1.8 or higher)"
+            // added to suppress irrelevant compiler errors
+            size_t block_length = 0;
+#endif
+            if (secret_len > block_length) {
                 Botan::SecureVector<Botan::byte> hashed_key =
                 Botan::SecureVector<Botan::byte> hashed_key =
                     hash->process(static_cast<const Botan::byte*>(secret),
                     hash->process(static_cast<const Botan::byte*>(secret),
                                   secret_len);
                                   secret_len);
                 hmac_->set_key(hashed_key.begin(), hashed_key.size());
                 hmac_->set_key(hashed_key.begin(), hashed_key.size());
             } else {
             } else {
+                // Botan 1.8 considers len 0 a bad key. 1.9 does not,
+                // but we won't accept it anyway, and fail early
+                if (secret_len == 0) {
+                    isc_throw(BadKey, "Bad HMAC secret length: 0");
+                }
                 hmac_->set_key(static_cast<const Botan::byte*>(secret),
                 hmac_->set_key(static_cast<const Botan::byte*>(secret),
                                secret_len);
                                secret_len);
             }
             }
@@ -89,7 +106,15 @@ public:
     ~HMACImpl() { }
     ~HMACImpl() { }
 
 
     size_t getOutputLength() const {
     size_t getOutputLength() const {
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
+        return (hmac_->output_length());
+#elif BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,8,0)
         return (hmac_->OUTPUT_LENGTH);
         return (hmac_->OUTPUT_LENGTH);
+#else
+#error "Unsupported Botan version (need 1.8 or higher)"
+        // added to suppress irrelevant compiler errors
+        return 0;
+#endif
     }
     }
 
 
     void update(const void* data, const size_t len) {
     void update(const void* data, const size_t len) {

+ 11 - 6
src/lib/dns/python/Makefile.am

@@ -5,7 +5,16 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 
 pyexec_LTLIBRARIES = pydnspp.la
 pyexec_LTLIBRARIES = pydnspp.la
-pydnspp_la_SOURCES = pydnspp.cc pydnspp_common.cc
+pydnspp_la_SOURCES = pydnspp.cc pydnspp_common.cc pydnspp_towire.h
+pydnspp_la_SOURCES += name_python.cc name_python.h
+pydnspp_la_SOURCES += messagerenderer_python.cc messagerenderer_python.h
+pydnspp_la_SOURCES += rcode_python.cc rcode_python.h
+pydnspp_la_SOURCES += tsigkey_python.cc tsigkey_python.h
+pydnspp_la_SOURCES += tsigerror_python.cc tsigerror_python.h
+pydnspp_la_SOURCES += tsig_rdata_python.cc tsig_rdata_python.h
+pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
+pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
+
 pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
 pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 
 
@@ -13,19 +22,15 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 # rules
 # rules
 EXTRA_DIST = pydnspp_common.h
 EXTRA_DIST = pydnspp_common.h
 EXTRA_DIST += edns_python.cc
 EXTRA_DIST += edns_python.cc
-EXTRA_DIST += messagerenderer_python.cc
 EXTRA_DIST += message_python.cc
 EXTRA_DIST += message_python.cc
 EXTRA_DIST += rrclass_python.cc
 EXTRA_DIST += rrclass_python.cc
-EXTRA_DIST += name_python.cc
 EXTRA_DIST += opcode_python.cc
 EXTRA_DIST += opcode_python.cc
-EXTRA_DIST += rcode_python.cc
 EXTRA_DIST += rrset_python.cc
 EXTRA_DIST += rrset_python.cc
 EXTRA_DIST += question_python.cc
 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
-EXTRA_DIST += tsig_python.cc
+EXTRA_DIST += tsigerror_python_inc.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.

+ 3 - 3
src/lib/dns/python/edns_python.cc

@@ -108,7 +108,7 @@ PyMethodDef EDNS_methods[] = {
 // Most of the functions are not actually implemented and NULL here.
 // Most of the functions are not actually implemented and NULL here.
 PyTypeObject edns_type = {
 PyTypeObject edns_type = {
     PyVarObject_HEAD_INIT(NULL, 0)
     PyVarObject_HEAD_INIT(NULL, 0)
-    "libdns_python.EDNS",
+    "pydnspp.EDNS",
     sizeof(s_EDNS),                     // tp_basicsize
     sizeof(s_EDNS),                     // tp_basicsize
     0,                                  // tp_itemsize
     0,                                  // tp_itemsize
     (destructor)EDNS_destroy,           // tp_dealloc
     (destructor)EDNS_destroy,           // tp_dealloc
@@ -203,7 +203,7 @@ EDNS_init(s_EDNS* self, PyObject* args) {
         // in this context so that we can share the try-catch logic with
         // in this context so that we can share the try-catch logic with
         // EDNS_createFromRR() (see below).
         // EDNS_createFromRR() (see below).
         uint8_t extended_rcode;
         uint8_t extended_rcode;
-        self->edns = createFromRR(*name->name, *rrclass->rrclass,
+        self->edns = createFromRR(*name->cppobj, *rrclass->rrclass,
                                   *rrtype->rrtype, *rrttl->rrttl,
                                   *rrtype->rrtype, *rrttl->rrttl,
                                   *rdata->rdata, extended_rcode);
                                   *rdata->rdata, extended_rcode);
         return (self->edns != NULL ? 0 : -1);
         return (self->edns != NULL ? 0 : -1);
@@ -334,7 +334,7 @@ EDNS_createFromRR(const s_EDNS* null_self, PyObject* args) {
             return (NULL);
             return (NULL);
         }
         }
 
 
-        edns_obj->edns = createFromRR(*name->name, *rrclass->rrclass,
+        edns_obj->edns = createFromRR(*name->cppobj, *rrclass->rrclass,
                                       *rrtype->rrtype, *rrttl->rrttl,
                                       *rrtype->rrtype, *rrttl->rrttl,
                                       *rdata->rdata, extended_rcode);
                                       *rdata->rdata, extended_rcode);
         if (edns_obj->edns != NULL) {
         if (edns_obj->edns != NULL) {

+ 43 - 7
src/lib/dns/python/message_python.cc

@@ -14,6 +14,9 @@
 
 
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 #include <dns/message.h>
 #include <dns/message.h>
+#include <dns/rcode.h>
+#include <dns/tsig.h>
+
 using namespace isc::dns;
 using namespace isc::dns;
 using namespace isc::util;
 using namespace isc::util;
 
 
@@ -65,6 +68,7 @@ PyObject* Message_getOpcode(s_Message* self);
 PyObject* Message_setOpcode(s_Message* self, PyObject* args);
 PyObject* Message_setOpcode(s_Message* self, PyObject* args);
 PyObject* Message_getEDNS(s_Message* self);
 PyObject* Message_getEDNS(s_Message* self);
 PyObject* Message_setEDNS(s_Message* self, PyObject* args);
 PyObject* Message_setEDNS(s_Message* self, PyObject* args);
+PyObject* Message_getTSIGRecord(s_Message* self);
 PyObject* Message_getRRCount(s_Message* self, PyObject* args);
 PyObject* Message_getRRCount(s_Message* self, PyObject* args);
 // use direct iterators for these? (or simply lists for now?)
 // use direct iterators for these? (or simply lists for now?)
 PyObject* Message_getQuestion(s_Message* self);
 PyObject* Message_getQuestion(s_Message* self);
@@ -123,6 +127,11 @@ PyMethodDef Message_methods[] = {
     { "set_edns", reinterpret_cast<PyCFunction>(Message_setEDNS), METH_VARARGS,
     { "set_edns", reinterpret_cast<PyCFunction>(Message_setEDNS), METH_VARARGS,
       "Set EDNS for the message."
       "Set EDNS for the message."
     },
     },
+    { "get_tsig_record",
+      reinterpret_cast<PyCFunction>(Message_getTSIGRecord), METH_NOARGS,
+      "Return, if any, the TSIG record contained in the received message. "
+      "If no TSIG RR is set in the message, None will be returned."
+    },
     { "get_rr_count", reinterpret_cast<PyCFunction>(Message_getRRCount), METH_VARARGS,
     { "get_rr_count", reinterpret_cast<PyCFunction>(Message_getRRCount), METH_VARARGS,
       "Returns the number of RRs contained in the given section." },
       "Returns the number of RRs contained in the given section." },
     { "get_question", reinterpret_cast<PyCFunction>(Message_getQuestion), METH_NOARGS,
     { "get_question", reinterpret_cast<PyCFunction>(Message_getQuestion), METH_NOARGS,
@@ -336,15 +345,15 @@ Message_getRcode(s_Message* self) {
 
 
     rcode = static_cast<s_Rcode*>(rcode_type.tp_alloc(&rcode_type, 0));
     rcode = static_cast<s_Rcode*>(rcode_type.tp_alloc(&rcode_type, 0));
     if (rcode != NULL) {
     if (rcode != NULL) {
-        rcode->rcode = NULL;
+        rcode->cppobj = NULL;
         try {
         try {
-            rcode->rcode = new Rcode(self->message->getRcode());
+            rcode->cppobj = new Rcode(self->message->getRcode());
         } catch (const InvalidMessageOperation& imo) {
         } catch (const InvalidMessageOperation& imo) {
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
         } catch (...) {
         } catch (...) {
             PyErr_SetString(po_IscException, "Unexpected exception");
             PyErr_SetString(po_IscException, "Unexpected exception");
         }
         }
-        if (rcode->rcode == NULL) {
+        if (rcode->cppobj == NULL) {
             Py_DECREF(rcode);
             Py_DECREF(rcode);
             return (NULL);
             return (NULL);
         }
         }
@@ -360,7 +369,7 @@ Message_setRcode(s_Message* self, PyObject* args) {
         return (NULL);
         return (NULL);
     }
     }
     try {
     try {
-        self->message->setRcode(*rcode->rcode);
+        self->message->setRcode(*rcode->cppobj);
         Py_RETURN_NONE;
         Py_RETURN_NONE;
     } catch (const InvalidMessageOperation& imo) {
     } catch (const InvalidMessageOperation& imo) {
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
         PyErr_SetString(po_InvalidMessageOperation, imo.what());
@@ -442,6 +451,29 @@ Message_setEDNS(s_Message* self, PyObject* args) {
 }
 }
 
 
 PyObject*
 PyObject*
+Message_getTSIGRecord(s_Message* self) {
+    try {
+        const TSIGRecord* tsig_record = self->message->getTSIGRecord();
+
+        if (tsig_record == NULL) {
+            Py_RETURN_NONE;
+        }
+        return (createTSIGRecordObject(*tsig_record));
+    } catch (const InvalidMessageOperation& ex) {
+        PyErr_SetString(po_InvalidMessageOperation, ex.what());
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in getting TSIGRecord from message: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "getting TSIGRecord from message");
+    }
+    return (NULL);
+}
+
+PyObject*
 Message_getRRCount(s_Message* self, PyObject* args) {
 Message_getRRCount(s_Message* self, PyObject* args) {
     unsigned int section;
     unsigned int section;
     if (!PyArg_ParseTuple(args, "I", &section)) {
     if (!PyArg_ParseTuple(args, "I", &section)) {
@@ -652,13 +684,12 @@ Message_toWire(s_Message* self, PyObject* args) {
     s_TSIGContext* tsig_ctx = NULL;
     s_TSIGContext* tsig_ctx = NULL;
     
     
     if (PyArg_ParseTuple(args, "O!|O!", &messagerenderer_type, &mr,
     if (PyArg_ParseTuple(args, "O!|O!", &messagerenderer_type, &mr,
-                         &tsig_context_type, &tsig_ctx)) {
+                         &tsigcontext_type, &tsig_ctx)) {
         try {
         try {
             if (tsig_ctx == NULL) {
             if (tsig_ctx == NULL) {
                 self->message->toWire(*mr->messagerenderer);
                 self->message->toWire(*mr->messagerenderer);
             } else {
             } else {
-                self->message->toWire(*mr->messagerenderer,
-                                      *tsig_ctx->tsig_ctx);
+                self->message->toWire(*mr->messagerenderer, *tsig_ctx->cppobj);
             }
             }
             // If we return NULL it is seen as an error, so use this for
             // If we return NULL it is seen as an error, so use this for
             // None returns
             // None returns
@@ -667,6 +698,11 @@ Message_toWire(s_Message* self, PyObject* args) {
             PyErr_Clear();
             PyErr_Clear();
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
             PyErr_SetString(po_InvalidMessageOperation, imo.what());
             return (NULL);
             return (NULL);
+        } catch (const TSIGContextError& ex) {
+            // toWire() with a TSIG context can fail due to this if the
+            // python program has a bug.
+            PyErr_SetString(po_TSIGContextError, ex.what());
+            return (NULL);
         }
         }
     }
     }
     PyErr_Clear();
     PyErr_Clear();

+ 97 - 92
src/lib/dns/python/messagerenderer_python.cc

@@ -12,39 +12,41 @@
 // 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.
 
 
+#include <Python.h>
+
+#include <util/buffer.h>
+
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
 
 
-// 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
+#include "pydnspp_common.h"
+#include "messagerenderer_python.h"
+
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::dns::python;
 using namespace isc::util;
 using namespace isc::util;
 
 
 // MessageRenderer
 // MessageRenderer
 
 
-// since we don't use *Buffer in the python version (but work with
-// the already existing bytearray type where we use these custom buffers
-// in c++, we need to keep track of one here.
-class s_MessageRenderer : public PyObject {
-public:
-    OutputBuffer* outputbuffer;
-    MessageRenderer* messagerenderer;
-};
+s_MessageRenderer::s_MessageRenderer() : outputbuffer(NULL),
+                                         messagerenderer(NULL)
+{
+}
 
 
-static int MessageRenderer_init(s_MessageRenderer* self);
-static void MessageRenderer_destroy(s_MessageRenderer* self);
+namespace {
+int MessageRenderer_init(s_MessageRenderer* self);
+void MessageRenderer_destroy(s_MessageRenderer* self);
 
 
-static PyObject* MessageRenderer_getData(s_MessageRenderer* self);
-static PyObject* MessageRenderer_getLength(s_MessageRenderer* self);
-static PyObject* MessageRenderer_isTruncated(s_MessageRenderer* self);
-static PyObject* MessageRenderer_getLengthLimit(s_MessageRenderer* self);
-static PyObject* MessageRenderer_getCompressMode(s_MessageRenderer* self);
-static PyObject* MessageRenderer_setTruncated(s_MessageRenderer* self);
-static PyObject* MessageRenderer_setLengthLimit(s_MessageRenderer* self, PyObject* args);
-static PyObject* MessageRenderer_setCompressMode(s_MessageRenderer* self, PyObject* args);
-static PyObject* MessageRenderer_clear(s_MessageRenderer* self);
+PyObject* MessageRenderer_getData(s_MessageRenderer* self);
+PyObject* MessageRenderer_getLength(s_MessageRenderer* self);
+PyObject* MessageRenderer_isTruncated(s_MessageRenderer* self);
+PyObject* MessageRenderer_getLengthLimit(s_MessageRenderer* self);
+PyObject* MessageRenderer_getCompressMode(s_MessageRenderer* self);
+PyObject* MessageRenderer_setTruncated(s_MessageRenderer* self);
+PyObject* MessageRenderer_setLengthLimit(s_MessageRenderer* self, PyObject* args);
+PyObject* MessageRenderer_setCompressMode(s_MessageRenderer* self, PyObject* args);
+PyObject* MessageRenderer_clear(s_MessageRenderer* self);
 
 
-static PyMethodDef MessageRenderer_methods[] = {
+PyMethodDef MessageRenderer_methods[] = {
     { "get_data", reinterpret_cast<PyCFunction>(MessageRenderer_getData), METH_NOARGS,
     { "get_data", reinterpret_cast<PyCFunction>(MessageRenderer_getData), METH_NOARGS,
       "Returns the data as a bytes() object" },
       "Returns the data as a bytes() object" },
     { "get_length", reinterpret_cast<PyCFunction>(MessageRenderer_getLength), METH_NOARGS,
     { "get_length", reinterpret_cast<PyCFunction>(MessageRenderer_getLength), METH_NOARGS,
@@ -67,69 +69,14 @@ static PyMethodDef MessageRenderer_methods[] = {
     { NULL, NULL, 0, NULL }
     { NULL, NULL, 0, NULL }
 };
 };
 
 
-static PyTypeObject messagerenderer_type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "pydnspp.MessageRenderer",
-    sizeof(s_MessageRenderer),          // tp_basicsize
-    0,                                  // tp_itemsize
-    (destructor)MessageRenderer_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 MessageRenderer class encapsulates implementation details "
-    "of rendering a DNS message into a buffer in wire format. "
-    "In effect, it's simply responsible for name compression at least in the "
-    "current implementation. A MessageRenderer class object manages the "
-    "positions of names rendered in a buffer and uses that information to render "
-    "subsequent names with compression.",
-    NULL,                               // tp_traverse
-    NULL,                               // tp_clear
-    NULL,                               // tp_richcompare
-    0,                                  // tp_weaklistoffset
-    NULL,                               // tp_iter
-    NULL,                               // tp_iternext
-    MessageRenderer_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)MessageRenderer_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
-};
-
-static int
+int
 MessageRenderer_init(s_MessageRenderer* self) {
 MessageRenderer_init(s_MessageRenderer* self) {
     self->outputbuffer = new OutputBuffer(4096);
     self->outputbuffer = new OutputBuffer(4096);
     self->messagerenderer = new MessageRenderer(*self->outputbuffer);
     self->messagerenderer = new MessageRenderer(*self->outputbuffer);
     return (0);
     return (0);
 }
 }
 
 
-static void
+void
 MessageRenderer_destroy(s_MessageRenderer* self) {
 MessageRenderer_destroy(s_MessageRenderer* self) {
     delete self->messagerenderer;
     delete self->messagerenderer;
     delete self->outputbuffer;
     delete self->outputbuffer;
@@ -138,19 +85,19 @@ MessageRenderer_destroy(s_MessageRenderer* self) {
     Py_TYPE(self)->tp_free(self);
     Py_TYPE(self)->tp_free(self);
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_getData(s_MessageRenderer* self) {
 MessageRenderer_getData(s_MessageRenderer* self) {
     return (Py_BuildValue("y#",
     return (Py_BuildValue("y#",
                          self->messagerenderer->getData(),
                          self->messagerenderer->getData(),
                           self->messagerenderer->getLength()));
                           self->messagerenderer->getLength()));
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_getLength(s_MessageRenderer* self) {
 MessageRenderer_getLength(s_MessageRenderer* self) {
     return (Py_BuildValue("I", self->messagerenderer->getLength()));
     return (Py_BuildValue("I", self->messagerenderer->getLength()));
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_isTruncated(s_MessageRenderer* self) {
 MessageRenderer_isTruncated(s_MessageRenderer* self) {
     if (self->messagerenderer->isTruncated()) {
     if (self->messagerenderer->isTruncated()) {
         Py_RETURN_TRUE;
         Py_RETURN_TRUE;
@@ -159,23 +106,23 @@ MessageRenderer_isTruncated(s_MessageRenderer* self) {
     }
     }
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_getLengthLimit(s_MessageRenderer* self) {
 MessageRenderer_getLengthLimit(s_MessageRenderer* self) {
     return (Py_BuildValue("I", self->messagerenderer->getLengthLimit()));
     return (Py_BuildValue("I", self->messagerenderer->getLengthLimit()));
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_getCompressMode(s_MessageRenderer* self) {
 MessageRenderer_getCompressMode(s_MessageRenderer* self) {
     return (Py_BuildValue("I", self->messagerenderer->getCompressMode()));
     return (Py_BuildValue("I", self->messagerenderer->getCompressMode()));
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_setTruncated(s_MessageRenderer* self) {
 MessageRenderer_setTruncated(s_MessageRenderer* self) {
     self->messagerenderer->setTruncated();
     self->messagerenderer->setTruncated();
     Py_RETURN_NONE;
     Py_RETURN_NONE;
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_setLengthLimit(s_MessageRenderer* self,
 MessageRenderer_setLengthLimit(s_MessageRenderer* self,
                                PyObject* args)
                                PyObject* args)
 {
 {
@@ -195,7 +142,7 @@ MessageRenderer_setLengthLimit(s_MessageRenderer* self,
     Py_RETURN_NONE;
     Py_RETURN_NONE;
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_setCompressMode(s_MessageRenderer* self,
 MessageRenderer_setCompressMode(s_MessageRenderer* self,
                                PyObject* args)
                                PyObject* args)
 {
 {
@@ -220,14 +167,71 @@ MessageRenderer_setCompressMode(s_MessageRenderer* self,
     }
     }
 }
 }
 
 
-static PyObject*
+PyObject*
 MessageRenderer_clear(s_MessageRenderer* self) {
 MessageRenderer_clear(s_MessageRenderer* self) {
     self->messagerenderer->clear();
     self->messagerenderer->clear();
     Py_RETURN_NONE;
     Py_RETURN_NONE;
 }
 }
+} // end of unnamed namespace
 
 
 // end of MessageRenderer
 // end of MessageRenderer
-
+namespace isc {
+namespace dns {
+namespace python {
+PyTypeObject messagerenderer_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.MessageRenderer",
+    sizeof(s_MessageRenderer),          // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)MessageRenderer_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 MessageRenderer class encapsulates implementation details "
+    "of rendering a DNS message into a buffer in wire format. "
+    "In effect, it's simply responsible for name compression at least in the "
+    "current implementation. A MessageRenderer class object manages the "
+    "positions of names rendered in a buffer and uses that information to render "
+    "subsequent names with compression.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    MessageRenderer_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)MessageRenderer_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
+};
 
 
 // Module Initialization, all statics are initialized here
 // Module Initialization, all statics are initialized here
 bool
 bool
@@ -260,5 +264,6 @@ initModulePart_MessageRenderer(PyObject* mod) {
     
     
     return (true);
     return (true);
 }
 }
-
-
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 52 - 0
src/lib/dns/python/messagerenderer_python.h

@@ -0,0 +1,52 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_MESSAGERENDERER_H
+#define __PYTHON_MESSAGERENDERER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace util {
+class OutputBuffer;
+}
+namespace dns {
+class MessageRenderer;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object.
+//
+// since we don't use *Buffer in the python version (but work with
+// the already existing bytearray type where we use these custom buffers
+// in C++, we need to keep track of one here.
+class s_MessageRenderer : public PyObject {
+public:
+    s_MessageRenderer();
+    isc::util::OutputBuffer* outputbuffer;
+    MessageRenderer* messagerenderer;
+};
+
+extern PyTypeObject messagerenderer_type;
+
+bool initModulePart_MessageRenderer(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_MESSAGERENDERER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 249 - 230
src/lib/dns/python/name_python.cc

@@ -12,26 +12,18 @@
 // 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.
 
 
-//
-// Declaration of the custom exceptions
-// Initialization and addition of these go in the module init at the
-// end
-//
-static PyObject* po_EmptyLabel;
-static PyObject* po_TooLongName;
-static PyObject* po_TooLongLabel;
-static PyObject* po_BadLabelType;
-static PyObject* po_BadEscape;
-static PyObject* po_IncompleteName;
-static PyObject* po_InvalidBufferPosition;
-static PyObject* po_DNSMessageFORMERR;
+#include <Python.h>
 
 
-//
-// Declaration of enums
-// Initialization and addition of these go in the module init at the
-// end
-//
-static PyObject* po_NameRelation;
+#include <util/buffer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+
+#include "pydnspp_common.h"
+#include "messagerenderer_python.h"
+#include "name_python.h"
 
 
 //
 //
 // Definition of the classes
 // Definition of the classes
@@ -41,21 +33,20 @@ static PyObject* po_NameRelation;
 // and static wrappers around the methods we export), a list of methods,
 // and static wrappers around the methods we export), a list of methods,
 // and a type description
 // and a type description
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::dns::python;
 using namespace isc::util;
 using namespace isc::util;
+using namespace isc::util::python;
 
 
+namespace {
 // NameComparisonResult
 // NameComparisonResult
-class s_NameComparisonResult : public PyObject {
-public:
-    isc::dns::NameComparisonResult* ncr;
-};
 
 
-static int NameComparisonResult_init(s_NameComparisonResult*, PyObject*);
-static void NameComparisonResult_destroy(s_NameComparisonResult* self);
-static PyObject* NameComparisonResult_getOrder(s_NameComparisonResult* self);
-static PyObject* NameComparisonResult_getCommonLabels(s_NameComparisonResult* self);
-static PyObject* NameComparisonResult_getRelation(s_NameComparisonResult* self);
+int NameComparisonResult_init(s_NameComparisonResult*, PyObject*);
+void NameComparisonResult_destroy(s_NameComparisonResult* self);
+PyObject* NameComparisonResult_getOrder(s_NameComparisonResult* self);
+PyObject* NameComparisonResult_getCommonLabels(s_NameComparisonResult* self);
+PyObject* NameComparisonResult_getRelation(s_NameComparisonResult* self);
 
 
-static PyMethodDef NameComparisonResult_methods[] = {
+PyMethodDef NameComparisonResult_methods[] = {
     { "get_order", reinterpret_cast<PyCFunction>(NameComparisonResult_getOrder), METH_NOARGS,
     { "get_order", reinterpret_cast<PyCFunction>(NameComparisonResult_getOrder), METH_NOARGS,
       "Returns the order" },
       "Returns the order" },
     { "get_common_labels", reinterpret_cast<PyCFunction>(NameComparisonResult_getCommonLabels), METH_NOARGS,
     { "get_common_labels", reinterpret_cast<PyCFunction>(NameComparisonResult_getCommonLabels), METH_NOARGS,
@@ -65,122 +56,61 @@ static PyMethodDef NameComparisonResult_methods[] = {
     { NULL, NULL, 0, NULL }
     { NULL, NULL, 0, NULL }
 };
 };
 
 
-static PyTypeObject name_comparison_result_type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "pydnspp.NameComparisonResult",
-    sizeof(s_NameComparisonResult),           // tp_basicsize
-    0,                                        // tp_itemsize
-    (destructor)NameComparisonResult_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
-    "This is a supplemental class used only as a return value of Name.compare(). "
-    "It encapsulate a tuple of the comparison: ordering, number of common labels, "
-    "and relationship as follows:\n"
-    "- ordering: relative ordering under the DNSSEC order relation\n"
-    "- labels: the number of common significant labels of the two names being"
-    "  compared\n"
-    "- relationship: see NameComparisonResult.NameRelation\n",
-    NULL,                                     // tp_traverse
-    NULL,                                     // tp_clear
-    NULL,                                     // tp_richcompare
-    0,                                        // tp_weaklistoffset
-    NULL,                                     // tp_iter
-    NULL,                                     // tp_iternext
-    NameComparisonResult_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)NameComparisonResult_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
-};
-
-static int
+int
 NameComparisonResult_init(s_NameComparisonResult*, PyObject*) {
 NameComparisonResult_init(s_NameComparisonResult*, PyObject*) {
     PyErr_SetString(PyExc_NotImplementedError,
     PyErr_SetString(PyExc_NotImplementedError,
                     "NameComparisonResult can't be built directly");
                     "NameComparisonResult can't be built directly");
     return (-1);
     return (-1);
 }
 }
 
 
-static void
+void
 NameComparisonResult_destroy(s_NameComparisonResult* self) {
 NameComparisonResult_destroy(s_NameComparisonResult* self) {
-    delete self->ncr;
-    self->ncr = NULL;
+    delete self->cppobj;
+    self->cppobj = NULL;
     Py_TYPE(self)->tp_free(self);
     Py_TYPE(self)->tp_free(self);
 }
 }
 
 
-static PyObject* 
+PyObject*
 NameComparisonResult_getOrder(s_NameComparisonResult* self) {
 NameComparisonResult_getOrder(s_NameComparisonResult* self) {
-    return (Py_BuildValue("i", self->ncr->getOrder()));
+    return (Py_BuildValue("i", self->cppobj->getOrder()));
 }
 }
 
 
-static PyObject* 
+PyObject*
 NameComparisonResult_getCommonLabels(s_NameComparisonResult* self) {
 NameComparisonResult_getCommonLabels(s_NameComparisonResult* self) {
-    return (Py_BuildValue("I", self->ncr->getCommonLabels()));
+    return (Py_BuildValue("I", self->cppobj->getCommonLabels()));
 }
 }
 
 
-static PyObject* 
+PyObject*
 NameComparisonResult_getRelation(s_NameComparisonResult* self) {
 NameComparisonResult_getRelation(s_NameComparisonResult* self) {
-    return (Py_BuildValue("I", self->ncr->getRelation()));
+    return (Py_BuildValue("I", self->cppobj->getRelation()));
 }
 }
-
 // end of NameComparisonResult
 // end of NameComparisonResult
 
 
 // Name
 // Name
-
-class s_Name : public PyObject {
-public:
-    isc::dns::Name* name;
-    size_t position;
-};
-
-static int Name_init(s_Name* self, PyObject* args);
-static void Name_destroy(s_Name* self);
-
-static PyObject* Name_toWire(s_Name* self, PyObject* args);
-static PyObject* Name_toText(s_Name* self);
-static PyObject* Name_str(PyObject* self);
-static PyObject* Name_getLabelCount(s_Name* self);
-static PyObject* Name_at(s_Name* self, PyObject* args);
-static PyObject* Name_getLength(s_Name* self);
-
-static PyObject* Name_compare(s_Name* self, PyObject* args);
-static PyObject* Name_equals(s_Name* self, PyObject* args);
-
-static PyObject* Name_richcmp(s_Name* self, s_Name* other, int op);
-static PyObject* Name_split(s_Name* self, PyObject* args);
-static PyObject* Name_reverse(s_Name* self);
-static PyObject* Name_concatenate(s_Name* self, PyObject* args);
-static PyObject* Name_downcase(s_Name* self);
-static PyObject* Name_isWildCard(s_Name* self);
-
-static PyMethodDef Name_methods[] = {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_Name, Name> NameContainer;
+
+int Name_init(s_Name* self, PyObject* args);
+void Name_destroy(s_Name* self);
+
+PyObject* Name_toWire(s_Name* self, PyObject* args);
+PyObject* Name_toText(s_Name* self);
+PyObject* Name_str(PyObject* self);
+PyObject* Name_getLabelCount(s_Name* self);
+PyObject* Name_at(s_Name* self, PyObject* args);
+PyObject* Name_getLength(s_Name* self);
+
+PyObject* Name_compare(s_Name* self, PyObject* args);
+PyObject* Name_equals(s_Name* self, PyObject* args);
+
+PyObject* Name_richcmp(s_Name* self, s_Name* other, int op);
+PyObject* Name_split(s_Name* self, PyObject* args);
+PyObject* Name_reverse(s_Name* self);
+PyObject* Name_concatenate(s_Name* self, PyObject* args);
+PyObject* Name_downcase(s_Name* self);
+PyObject* Name_isWildCard(s_Name* self);
+
+PyMethodDef Name_methods[] = {
     { "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
     { "at", reinterpret_cast<PyCFunction>(Name_at), METH_VARARGS,
       "Returns the integer value of the name data at the specified position" },
       "Returns the integer value of the name data at the specified position" },
     { "get_length", reinterpret_cast<PyCFunction>(Name_getLength), METH_NOARGS,
     { "get_length", reinterpret_cast<PyCFunction>(Name_getLength), METH_NOARGS,
@@ -217,63 +147,7 @@ static PyMethodDef Name_methods[] = {
     { NULL, NULL, 0, NULL }
     { NULL, NULL, 0, NULL }
 };
 };
 
 
-static PyTypeObject name_type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "pydnspp.Name",
-    sizeof(s_Name),                     // tp_basicsize
-    0,                                  // tp_itemsize
-    (destructor)Name_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
-    Name_str,                           // tp_str
-    NULL,                               // tp_getattro
-    NULL,                               // tp_setattro
-    NULL,                               // tp_as_buffer
-    Py_TPFLAGS_DEFAULT,                 // tp_flags
-    "The Name class encapsulates DNS names.\n"
-    "It provides interfaces to construct a name from string or wire-format data, "
-    "transform a name into a string or wire-format data, compare two names, get "
-    "access to various properties of a name, etc.",
-    NULL,                               // tp_traverse
-    NULL,                               // tp_clear
-    (richcmpfunc)Name_richcmp,          // tp_richcompare
-    0,                                  // tp_weaklistoffset
-    NULL,                               // tp_iter
-    NULL,                               // tp_iternext
-    Name_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)Name_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
-    // Note: not sure if the following are correct.  Added them just to
-    // make the compiler happy.
-    NULL,                               // tp_del
-    0                                   // tp_version_tag
-};
-
-
-static int
+int
 Name_init(s_Name* self, PyObject* args) {
 Name_init(s_Name* self, PyObject* args) {
     const char* s;
     const char* s;
     PyObject* downcase = Py_False;
     PyObject* downcase = Py_False;
@@ -286,7 +160,7 @@ Name_init(s_Name* self, PyObject* args) {
         try {
         try {
             const std::string n(s);
             const std::string n(s);
 
 
-            self->name = new Name(n, downcase == Py_True);
+            self->cppobj = new Name(n, downcase == Py_True);
             self->position = 0;
             self->position = 0;
         } catch (const EmptyLabel&) {
         } catch (const EmptyLabel&) {
             PyErr_SetString(po_EmptyLabel, "EmptyLabel");
             PyErr_SetString(po_EmptyLabel, "EmptyLabel");
@@ -339,7 +213,7 @@ Name_init(s_Name* self, PyObject* args) {
             InputBuffer buffer(bytes, len);
             InputBuffer buffer(bytes, len);
 
 
             buffer.setPosition(position);
             buffer.setPosition(position);
-            self->name = new Name(buffer, downcase == Py_True);
+            self->cppobj = new Name(buffer, downcase == Py_True);
             self->position = buffer.getPosition();
             self->position = buffer.getPosition();
         } catch (const InvalidBufferPosition&) {
         } catch (const InvalidBufferPosition&) {
             PyErr_SetString(po_InvalidBufferPosition,
             PyErr_SetString(po_InvalidBufferPosition,
@@ -361,14 +235,14 @@ Name_init(s_Name* self, PyObject* args) {
     return (-1);
     return (-1);
 }
 }
 
 
-static void
+void
 Name_destroy(s_Name* self) {
 Name_destroy(s_Name* self) {
-    delete self->name;
-    self->name = NULL;
+    delete self->cppobj;
+    self->cppobj = NULL;
     Py_TYPE(self)->tp_free(self);
     Py_TYPE(self)->tp_free(self);
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_at(s_Name* self, PyObject* args) {
 Name_at(s_Name* self, PyObject* args) {
     int pos;
     int pos;
     if (!PyArg_ParseTuple(args, "i", &pos)) {
     if (!PyArg_ParseTuple(args, "i", &pos)) {
@@ -382,7 +256,7 @@ Name_at(s_Name* self, PyObject* args) {
     }
     }
 
 
     try {
     try {
-        return (Py_BuildValue("I", self->name->at(pos)));
+        return (Py_BuildValue("I", self->cppobj->at(pos)));
     } catch (const isc::OutOfRange&) {
     } catch (const isc::OutOfRange&) {
         PyErr_SetString(PyExc_IndexError,
         PyErr_SetString(PyExc_IndexError,
                         "name index out of range");
                         "name index out of range");
@@ -390,22 +264,22 @@ Name_at(s_Name* self, PyObject* args) {
     }
     }
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_getLength(s_Name* self) {
 Name_getLength(s_Name* self) {
-    return (Py_BuildValue("i", self->name->getLength()));
+    return (Py_BuildValue("i", self->cppobj->getLength()));
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_getLabelCount(s_Name* self) {
 Name_getLabelCount(s_Name* self) {
-    return (Py_BuildValue("i", self->name->getLabelCount()));
+    return (Py_BuildValue("i", self->cppobj->getLabelCount()));
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_toText(s_Name* self) {
 Name_toText(s_Name* self) {
-    return (Py_BuildValue("s", self->name->toText().c_str()));
+    return (Py_BuildValue("s", self->cppobj->toText().c_str()));
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_str(PyObject* self) {
 Name_str(PyObject* self) {
     // Simply call the to_text method we already defined
     // Simply call the to_text method we already defined
     // str() is not defined in the c++ version, only to_text
     // str() is not defined in the c++ version, only to_text
@@ -415,7 +289,7 @@ Name_str(PyObject* self) {
                                 const_cast<char*>("")));
                                 const_cast<char*>("")));
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_toWire(s_Name* self, PyObject* args) {
 Name_toWire(s_Name* self, PyObject* args) {
     PyObject* bytes;
     PyObject* bytes;
     s_MessageRenderer* mr;
     s_MessageRenderer* mr;
@@ -424,7 +298,7 @@ Name_toWire(s_Name* self, PyObject* args) {
         PyObject* bytes_o = bytes;
         PyObject* bytes_o = bytes;
 
 
         OutputBuffer buffer(Name::MAX_WIRE);
         OutputBuffer buffer(Name::MAX_WIRE);
-        self->name->toWire(buffer);
+        self->cppobj->toWire(buffer);
         PyObject* name_bytes = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
         PyObject* name_bytes = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
         PyObject* result = PySequence_InPlaceConcat(bytes_o, name_bytes);
         PyObject* result = PySequence_InPlaceConcat(bytes_o, name_bytes);
         // We need to release the object we temporarily created here
         // We need to release the object we temporarily created here
@@ -432,7 +306,7 @@ Name_toWire(s_Name* self, PyObject* args) {
         Py_DECREF(name_bytes);
         Py_DECREF(name_bytes);
         return (result);
         return (result);
     } else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
     } else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &mr)) {
-        self->name->toWire(*mr->messagerenderer);
+        self->cppobj->toWire(*mr->messagerenderer);
         // If we return NULL it is seen as an error, so use this for
         // If we return NULL it is seen as an error, so use this for
         // None returns
         // None returns
         Py_RETURN_NONE;
         Py_RETURN_NONE;
@@ -443,7 +317,7 @@ Name_toWire(s_Name* self, PyObject* args) {
     return (NULL);
     return (NULL);
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_compare(s_Name* self, PyObject* args) {
 Name_compare(s_Name* self, PyObject* args) {
     s_Name* other;
     s_Name* other;
 
 
@@ -452,26 +326,26 @@ Name_compare(s_Name* self, PyObject* args) {
 
 
     s_NameComparisonResult* ret = PyObject_New(s_NameComparisonResult, &name_comparison_result_type);
     s_NameComparisonResult* ret = PyObject_New(s_NameComparisonResult, &name_comparison_result_type);
     if (ret != NULL) {
     if (ret != NULL) {
-        ret->ncr = new NameComparisonResult(
-            self->name->compare(*other->name));
+        ret->cppobj = new NameComparisonResult(
+            self->cppobj->compare(*other->cppobj));
     }
     }
     return (ret);
     return (ret);
 }
 }
 
 
-static PyObject* 
+PyObject*
 Name_equals(s_Name* self, PyObject* args) {
 Name_equals(s_Name* self, PyObject* args) {
     s_Name* other;
     s_Name* other;
 
 
     if (!PyArg_ParseTuple(args, "O!", &name_type, &other))
     if (!PyArg_ParseTuple(args, "O!", &name_type, &other))
         return (NULL);
         return (NULL);
 
 
-    if (self->name->equals(*other->name))
+    if (self->cppobj->equals(*other->cppobj))
         Py_RETURN_TRUE;
         Py_RETURN_TRUE;
     else
     else
         Py_RETURN_FALSE;
         Py_RETURN_FALSE;
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_split(s_Name* self, PyObject* args) {
 Name_split(s_Name* self, PyObject* args) {
     int first, n;
     int first, n;
     s_Name* ret = NULL;
     s_Name* ret = NULL;
@@ -485,14 +359,14 @@ Name_split(s_Name* self, PyObject* args) {
         }
         }
         ret = PyObject_New(s_Name, &name_type);
         ret = PyObject_New(s_Name, &name_type);
         if (ret != NULL) {
         if (ret != NULL) {
-            ret->name = NULL;
+            ret->cppobj = NULL;
             try {
             try {
-                ret->name = new Name(self->name->split(first, n));
+                ret->cppobj = new Name(self->cppobj->split(first, n));
             } catch(const isc::OutOfRange& oor) {
             } catch(const isc::OutOfRange& oor) {
                 PyErr_SetString(PyExc_IndexError, oor.what());
                 PyErr_SetString(PyExc_IndexError, oor.what());
-                ret->name = NULL;
+                ret->cppobj = NULL;
             }
             }
-            if (ret->name == NULL) {
+            if (ret->cppobj == NULL) {
                 Py_DECREF(ret);
                 Py_DECREF(ret);
                 return (NULL);
                 return (NULL);
             }
             }
@@ -507,14 +381,14 @@ Name_split(s_Name* self, PyObject* args) {
         }
         }
         ret = PyObject_New(s_Name, &name_type);
         ret = PyObject_New(s_Name, &name_type);
         if (ret != NULL) {
         if (ret != NULL) {
-            ret->name = NULL;
+            ret->cppobj = NULL;
             try {
             try {
-                ret->name = new Name(self->name->split(n));
+                ret->cppobj = new Name(self->cppobj->split(n));
             } catch(const isc::OutOfRange& oor) {
             } catch(const isc::OutOfRange& oor) {
                 PyErr_SetString(PyExc_IndexError, oor.what());
                 PyErr_SetString(PyExc_IndexError, oor.what());
-                ret->name = NULL;
+                ret->cppobj = NULL;
             }
             }
-            if (ret->name == NULL) {
+            if (ret->cppobj == NULL) {
                 Py_DECREF(ret);
                 Py_DECREF(ret);
                 return (NULL);
                 return (NULL);
             }
             }
@@ -526,14 +400,13 @@ Name_split(s_Name* self, PyObject* args) {
                     "No valid type in split argument");
                     "No valid type in split argument");
     return (ret);
     return (ret);
 }
 }
-#include <iostream>
 
 
 //
 //
 // richcmp defines the ==, !=, >, <, >= and <= operators in python
 // richcmp defines the ==, !=, >, <, >= and <= operators in python
 // It is translated to a function that gets 3 arguments, an object,
 // It is translated to a function that gets 3 arguments, an object,
 // an object to compare to, and an operator.
 // an object to compare to, and an operator.
 //
 //
-static PyObject*
+PyObject*
 Name_richcmp(s_Name* self, s_Name* other, int op) {
 Name_richcmp(s_Name* self, s_Name* other, int op) {
     bool c;
     bool c;
 
 
@@ -545,22 +418,22 @@ Name_richcmp(s_Name* self, s_Name* other, int op) {
 
 
     switch (op) {
     switch (op) {
     case Py_LT:
     case Py_LT:
-        c = *self->name < *other->name;
+        c = *self->cppobj < *other->cppobj;
         break;
         break;
     case Py_LE:
     case Py_LE:
-        c = *self->name <= *other->name;
+        c = *self->cppobj <= *other->cppobj;
         break;
         break;
     case Py_EQ:
     case Py_EQ:
-        c = *self->name == *other->name;
+        c = *self->cppobj == *other->cppobj;
         break;
         break;
     case Py_NE:
     case Py_NE:
-        c = *self->name != *other->name;
+        c = *self->cppobj != *other->cppobj;
         break;
         break;
     case Py_GT:
     case Py_GT:
-        c = *self->name > *other->name;
+        c = *self->cppobj > *other->cppobj;
         break;
         break;
     case Py_GE:
     case Py_GE:
-        c = *self->name >= *other->name;
+        c = *self->cppobj >= *other->cppobj;
         break;
         break;
     default:
     default:
         PyErr_SetString(PyExc_IndexError,
         PyErr_SetString(PyExc_IndexError,
@@ -574,13 +447,13 @@ Name_richcmp(s_Name* self, s_Name* other, int op) {
     }
     }
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_reverse(s_Name* self) {
 Name_reverse(s_Name* self) {
     s_Name* ret = PyObject_New(s_Name, &name_type);
     s_Name* ret = PyObject_New(s_Name, &name_type);
 
 
     if (ret != NULL) {
     if (ret != NULL) {
-        ret->name = new Name(self->name->reverse());
-        if (ret->name == NULL) {
+        ret->cppobj = new Name(self->cppobj->reverse());
+        if (ret->cppobj == NULL) {
             Py_DECREF(ret);
             Py_DECREF(ret);
             return (NULL);
             return (NULL);
         }
         }
@@ -588,7 +461,7 @@ Name_reverse(s_Name* self) {
     return (ret);
     return (ret);
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_concatenate(s_Name* self, PyObject* args) {
 Name_concatenate(s_Name* self, PyObject* args) {
     s_Name* other;
     s_Name* other;
 
 
@@ -598,7 +471,7 @@ Name_concatenate(s_Name* self, PyObject* args) {
     s_Name* ret = PyObject_New(s_Name, &name_type);
     s_Name* ret = PyObject_New(s_Name, &name_type);
     if (ret != NULL) {
     if (ret != NULL) {
         try {
         try {
-            ret->name = new Name(self->name->concatenate(*other->name));
+            ret->cppobj = new Name(self->cppobj->concatenate(*other->cppobj));
         } catch (const TooLongName& tln) {
         } catch (const TooLongName& tln) {
             PyErr_SetString(po_TooLongName, tln.what());
             PyErr_SetString(po_TooLongName, tln.what());
             return (NULL);
             return (NULL);
@@ -607,23 +480,159 @@ Name_concatenate(s_Name* self, PyObject* args) {
     return (ret);
     return (ret);
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_downcase(s_Name* self) {
 Name_downcase(s_Name* self) {
-    self->name->downcase();
+    self->cppobj->downcase();
     Py_INCREF(self);
     Py_INCREF(self);
     return (self);
     return (self);
 }
 }
 
 
-static PyObject*
+PyObject*
 Name_isWildCard(s_Name* self) {
 Name_isWildCard(s_Name* self) {
-    if (self->name->isWildcard()) {
+    if (self->cppobj->isWildcard()) {
         Py_RETURN_TRUE;
         Py_RETURN_TRUE;
     } else {
     } else {
         Py_RETURN_FALSE;
         Py_RETURN_FALSE;
     }
     }
 }
 }
 // end of Name
 // end of Name
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+
+//
+// Definition of the custom exceptions
+// Initialization and addition of these go in the module init at the
+// end
+//
+PyObject* po_EmptyLabel;
+PyObject* po_TooLongName;
+PyObject* po_TooLongLabel;
+PyObject* po_BadLabelType;
+PyObject* po_BadEscape;
+PyObject* po_IncompleteName;
+PyObject* po_InvalidBufferPosition;
+PyObject* po_DNSMessageFORMERR;
 
 
+//
+// Definition of enums
+// Initialization and addition of these go in the module init at the
+// end
+//
+PyObject* po_NameRelation;
+
+PyTypeObject name_comparison_result_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.NameComparisonResult",
+    sizeof(s_NameComparisonResult),           // tp_basicsize
+    0,                                        // tp_itemsize
+    (destructor)NameComparisonResult_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
+    "This is a supplemental class used only as a return value of Name.compare(). "
+    "It encapsulate a tuple of the comparison: ordering, number of common labels, "
+    "and relationship as follows:\n"
+    "- ordering: relative ordering under the DNSSEC order relation\n"
+    "- labels: the number of common significant labels of the two names being"
+    "  compared\n"
+    "- relationship: see NameComparisonResult.NameRelation\n",
+    NULL,                                     // tp_traverse
+    NULL,                                     // tp_clear
+    NULL,                                     // tp_richcompare
+    0,                                        // tp_weaklistoffset
+    NULL,                                     // tp_iter
+    NULL,                                     // tp_iternext
+    NameComparisonResult_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)NameComparisonResult_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
+};
+
+PyTypeObject name_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.Name",
+    sizeof(s_Name),                     // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)Name_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
+    Name_str,                           // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The Name class encapsulates DNS names.\n"
+    "It provides interfaces to construct a name from string or wire-format data, "
+    "transform a name into a string or wire-format data, compare two names, get "
+    "access to various properties of a name, etc.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    (richcmpfunc)Name_richcmp,          // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    Name_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)Name_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
+    // Note: not sure if the following are correct.  Added them just to
+    // make the compiler happy.
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
 
 
 // Module Initialization, all statics are initialized here
 // Module Initialization, all statics are initialized here
 bool
 bool
@@ -669,7 +678,7 @@ initModulePart_Name(PyObject* mod) {
     addClassVariable(name_type, "COMPRESS_POINTER_MARK16", Py_BuildValue("I", Name::COMPRESS_POINTER_MARK16));
     addClassVariable(name_type, "COMPRESS_POINTER_MARK16", Py_BuildValue("I", Name::COMPRESS_POINTER_MARK16));
 
 
     s_Name* root_name = PyObject_New(s_Name, &name_type);
     s_Name* root_name = PyObject_New(s_Name, &name_type);
-    root_name->name = new Name(Name::ROOT_NAME());
+    root_name->cppobj = new Name(Name::ROOT_NAME());
     PyObject* po_ROOT_NAME = root_name;
     PyObject* po_ROOT_NAME = root_name;
     addClassVariable(name_type, "ROOT_NAME", po_ROOT_NAME);
     addClassVariable(name_type, "ROOT_NAME", po_ROOT_NAME);
 
 
@@ -706,3 +715,13 @@ initModulePart_Name(PyObject* mod) {
 
 
     return (true);
     return (true);
 }
 }
+
+PyObject*
+createNameObject(const Name& source) {
+    NameContainer container = PyObject_New(s_Name, &name_type);
+    container.set(new Name(source));
+    return (container.release());
+}
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 84 - 0
src/lib/dns/python/name_python.h

@@ -0,0 +1,84 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_NAME_H
+#define __PYTHON_NAME_H 1
+
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+namespace isc {
+namespace dns {
+class NameComparisonResult;
+class Name;
+
+namespace python {
+
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in the module init at the
+// end
+//
+extern PyObject* po_EmptyLabel;
+extern PyObject* po_TooLongName;
+extern PyObject* po_TooLongLabel;
+extern PyObject* po_BadLabelType;
+extern PyObject* po_BadEscape;
+extern PyObject* po_IncompleteName;
+extern PyObject* po_InvalidBufferPosition;
+extern PyObject* po_DNSMessageFORMERR;
+
+//
+// Declaration of enums
+// Initialization and addition of these go in the module init at the
+// end
+//
+extern PyObject* po_NameRelation;
+
+// The s_* Class simply covers one instantiation of the object.
+class s_NameComparisonResult : public PyObject {
+public:
+    s_NameComparisonResult() : cppobj(NULL) {}
+    NameComparisonResult* cppobj;
+};
+
+class s_Name : public PyObject {
+public:
+    s_Name() : cppobj(NULL), position(0) {}
+    Name* cppobj;
+    size_t position;
+};
+
+extern PyTypeObject name_comparison_result_type;
+extern PyTypeObject name_type;
+
+bool initModulePart_Name(PyObject* mod);
+
+/// This is A simple shortcut to create a python Name object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* createNameObject(const Name& source);
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_NAME_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 38 - 13
src/lib/dns/python/pydnspp.cc

@@ -32,22 +32,37 @@
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <util/buffer.h>
 #include <util/buffer.h>
+
 #include <dns/exceptions.h>
 #include <dns/exceptions.h>
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/messagerenderer.h>
 #include <dns/messagerenderer.h>
 
 
-#include <dns/python/pydnspp_common.h>
-
+#include "pydnspp_common.h"
+#include "messagerenderer_python.h"
+#include "name_python.h"
+#include "rcode_python.h"
+#include "tsigkey_python.h"
+#include "tsig_rdata_python.h"
+#include "tsigerror_python.h"
+#include "tsigrecord_python.h"
+#include "tsig_python.h"
+
+namespace isc {
+namespace dns {
+namespace python {
 // For our 'general' isc::Exceptions
 // For our 'general' isc::Exceptions
-static PyObject* po_IscException;
-static PyObject* po_InvalidParameter;
+PyObject* po_IscException;
+PyObject* po_InvalidParameter;
 
 
 // For our own isc::dns::Exception
 // For our own isc::dns::Exception
-static PyObject* po_DNSMessageBADVERS;
+PyObject* po_DNSMessageBADVERS;
+}
+}
+}
 
 
 // order is important here!
 // order is important here!
-#include <dns/python/messagerenderer_python.cc>
-#include <dns/python/name_python.cc>           // needs Messagerenderer
+using namespace isc::dns::python;
+
 #include <dns/python/rrclass_python.cc>        // needs Messagerenderer
 #include <dns/python/rrclass_python.cc>        // needs Messagerenderer
 #include <dns/python/rrtype_python.cc>         // needs Messagerenderer
 #include <dns/python/rrtype_python.cc>         // needs Messagerenderer
 #include <dns/python/rrttl_python.cc>          // needs Messagerenderer
 #include <dns/python/rrttl_python.cc>          // needs Messagerenderer
@@ -55,17 +70,15 @@ 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/tsig_python.cc>           // needs tsigkey
 #include <dns/python/opcode_python.cc>
 #include <dns/python/opcode_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
 #include <dns/python/message_python.cc>        // needs RRset, Question
 #include <dns/python/message_python.cc>        // needs RRset, Question
 
 
 //
 //
 // Definition of the module
 // Definition of the module
 //
 //
-static PyModuleDef pydnspp = {
+namespace {
+PyModuleDef pydnspp = {
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
     "pydnspp",
     "pydnspp",
     "Python bindings for the classes in the isc::dns namespace.\n\n"
     "Python bindings for the classes in the isc::dns namespace.\n\n"
@@ -80,10 +93,11 @@ static PyModuleDef pydnspp = {
     NULL,
     NULL,
     NULL
     NULL
 };
 };
+}
 
 
 PyMODINIT_FUNC
 PyMODINIT_FUNC
 PyInit_pydnspp(void) {
 PyInit_pydnspp(void) {
-    PyObject *mod = PyModule_Create(&pydnspp);
+    PyObject* mod = PyModule_Create(&pydnspp);
     if (mod == NULL) {
     if (mod == NULL) {
         return (NULL);
         return (NULL);
     }
     }
@@ -154,10 +168,21 @@ PyInit_pydnspp(void) {
         return (NULL);
         return (NULL);
     }
     }
 
 
+    if (!initModulePart_TSIG(mod)) {
+        return (NULL);
+    }
+
+    if (!initModulePart_TSIGError(mod)) {
+        return (NULL);
+    }
+
+    if (!initModulePart_TSIGRecord(mod)) {
+        return (NULL);
+    }
+
     if (!initModulePart_TSIGContext(mod)) {
     if (!initModulePart_TSIGContext(mod)) {
         return (NULL);
         return (NULL);
     }
     }
 
 
     return (mod);
     return (mod);
 }
 }
-

+ 14 - 4
src/lib/dns/python/pydnspp_common.cc

@@ -15,6 +15,9 @@
 #include <Python.h>
 #include <Python.h>
 #include <pydnspp_common.h>
 #include <pydnspp_common.h>
 
 
+namespace isc {
+namespace dns {
+namespace python {
 int
 int
 readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
 readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
     PyObject* el = NULL;
     PyObject* el = NULL;
@@ -44,8 +47,15 @@ readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence) {
 }
 }
 
 
 
 
-void addClassVariable(PyTypeObject& c, const char* name,
-                      PyObject* obj)
-{
-    PyDict_SetItemString(c.tp_dict, name, obj);
+int
+addClassVariable(PyTypeObject& c, const char* name, PyObject* obj) {
+    if (obj == NULL) {
+        PyErr_SetString(PyExc_ValueError,
+                        "NULL object is specified for a class variable");
+        return (-1);
+    }
+    return (PyDict_SetItemString(c.tp_dict, name, obj));
+}
+}
+}
 }
 }

+ 24 - 5
src/lib/dns/python/pydnspp_common.h

@@ -15,9 +15,22 @@
 #ifndef __LIBDNS_PYTHON_COMMON_H
 #ifndef __LIBDNS_PYTHON_COMMON_H
 #define __LIBDNS_PYTHON_COMMON_H 1
 #define __LIBDNS_PYTHON_COMMON_H 1
 
 
-//
-// Shared functions for python/c API
-//
+#include <Python.h>
+
+#include <stdexcept>
+#include <string>
+
+#include <util/python/pycppwrapper_util.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+// For our 'general' isc::Exceptions
+extern PyObject* po_IscException;
+extern PyObject* po_InvalidParameter;
+
+// For our own isc::dns::Exception
+extern PyObject* po_DNSMessageBADVERS;
 
 
 // This function reads 'bytes' from a sequence
 // This function reads 'bytes' from a sequence
 // This sequence can be anything that implements the Sequence interface,
 // This sequence can be anything that implements the Sequence interface,
@@ -31,6 +44,12 @@
 // case nothing is removed
 // case nothing is removed
 int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
 int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
 
 
-void addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
-
+int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
+} // namespace python
+} // namespace dns
+} // namespace isc
 #endif // __LIBDNS_PYTHON_COMMON_H
 #endif // __LIBDNS_PYTHON_COMMON_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 127 - 0
src/lib/dns/python/pydnspp_towire.h

@@ -0,0 +1,127 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __LIBDNS_PYTHON_TOWIRE_H
+#define __LIBDNS_PYTHON_TOWIRE_H 1
+
+#include <Python.h>
+
+#include <stdexcept>
+#include <string>
+
+#include <dns/messagerenderer.h>
+
+#include <util/buffer.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "messagerenderer_python.h"
+
+namespace isc {
+namespace dns {
+namespace python {
+
+// The following two templated structures are a helper to use the same
+// toWire() template implementation for two types of toWire() methods:
+// return an integer or have no return value.
+template <typename CPPCLASS>
+struct ToWireCallVoid {
+    ToWireCallVoid(CPPCLASS& cppobj) : cppobj_(cppobj) {}
+    int operator()(AbstractMessageRenderer& renderer) const {
+        cppobj_.toWire(renderer);
+        return (0);
+    }
+    const CPPCLASS& cppobj_;
+};
+
+template <typename CPPCLASS>
+struct ToWireCallInt {
+    ToWireCallInt(CPPCLASS& cppobj) : cppobj_(cppobj) {}
+    int operator()(AbstractMessageRenderer& renderer) const {
+        return (cppobj_.toWire(renderer));
+    }
+    const CPPCLASS& cppobj_;
+};
+
+// This templated function gives a common implementation of the toWire()
+// wrapper for various libdns++ classes.  PYSTRUCT and CPPCLASS are
+// (C++ binding of) python and (pure) C++ classes (e.g., s_Name and Name),
+// and TOWIRECALLER is either ToWireCallVoid<CPPCLASS> or
+// ToWireCallInt<CPPCLASS>, depending on the toWire() method of the class
+// returns a value or not.
+//
+// See, e.g., tsigrecord_python.cc for how to use it.
+//
+// This should be able to be used without modification for most classes that
+// have toWire().  But if the underlying toWire() has an extra argument, the
+// definition will need to be adjusted accordingly.
+template <typename PYSTRUCT, typename CPPCLASS, typename TOWIRECALLER>
+PyObject*
+toWireWrapper(const PYSTRUCT* const self, PyObject* args) {
+    try {
+        // To OutputBuffer version
+        PyObject* bytes; // this won't have own reference, no risk of leak.
+        if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
+            // render the object into a buffer (this can throw)
+            isc::util::OutputBuffer buffer(0);
+            self->cppobj->toWire(buffer);
+
+            // convert the rendered data into PyObject.  This could leak later,
+            // so we need to store it in a container.
+            PyObject* rd_bytes = PyBytes_FromStringAndSize(
+                static_cast<const char*>(buffer.getData()),
+                buffer.getLength());
+            isc::util::python::PyObjectContainer rd_bytes_container(rd_bytes);
+
+            // concat the latest data to the given existing sequence.  concat
+            // operation could fail, so we use a container to clean it up
+            // safely should that happen.
+            PyObject* result = PySequence_InPlaceConcat(bytes, rd_bytes);
+            isc::util::python::PyObjectContainer result_container(result);
+
+            return (result_container.release());
+        }
+
+        // To MessageRenderer version
+        s_MessageRenderer* renderer;
+        if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, &renderer)) {
+            const unsigned int n = TOWIRECALLER(*self->cppobj)(
+                *renderer->messagerenderer);
+
+            return (Py_BuildValue("I", n));
+        }
+    } catch (const std::exception& ex) {
+        const std::string ex_what =
+            "Failed to render an libdns++ object wire-format: "
+            + std::string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(po_IscException, "Unexpectedly failed to render an "
+                        "libdns++ object wire-format.");
+        return (NULL);
+    }
+
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError,
+                    "Incorrect arguments for a to_wire() method");
+    return (NULL);
+}
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __LIBDNS_PYTHON_TOWIRE_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 2 - 2
src/lib/dns/python/question_python.cc

@@ -144,7 +144,7 @@ Question_init(s_Question* self, PyObject* args) {
                                                &rrclass_type, &rrclass,
                                                &rrclass_type, &rrclass,
                                                &rrtype_type, &rrtype
                                                &rrtype_type, &rrtype
            )) {
            )) {
-            self->question = QuestionPtr(new Question(*name->name, *rrclass->rrclass,
+            self->question = QuestionPtr(new Question(*name->cppobj, *rrclass->rrclass,
                                           *rrtype->rrtype));
                                           *rrtype->rrtype));
             return (0);
             return (0);
         } else if (PyArg_ParseTuple(args, "y#|I", &b, &len, &position)) {
         } else if (PyArg_ParseTuple(args, "y#|I", &b, &len, &position)) {
@@ -189,7 +189,7 @@ Question_getName(s_Question* self) {
     // is this the best way to do this?
     // is this the best way to do this?
     name = static_cast<s_Name*>(name_type.tp_alloc(&name_type, 0));
     name = static_cast<s_Name*>(name_type.tp_alloc(&name_type, 0));
     if (name != NULL) {
     if (name != NULL) {
-        name->name = new Name(self->question->getName());
+        name->cppobj = new Name(self->question->getName());
     }
     }
 
 
     return (name);
     return (name);

+ 80 - 77
src/lib/dns/python/rcode_python.cc

@@ -12,9 +12,17 @@
 // 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.
 
 
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
 #include <dns/rcode.h>
 #include <dns/rcode.h>
 
 
+#include "pydnspp_common.h"
+#include "rcode_python.h"
+
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::dns::python;
 
 
 //
 //
 // Declaration of the custom exceptions (None for this class)
 // Declaration of the custom exceptions (None for this class)
@@ -27,25 +35,14 @@ using namespace isc::dns;
 // and static wrappers around the methods we export), a list of methods,
 // and static wrappers around the methods we export), a list of methods,
 // and a type description
 // and a type description
 
 
-namespace {
 //
 //
 // Rcode
 // Rcode
 //
 //
 
 
-// We added a helper variable static_code here
-// Since we can create Rcodes dynamically with Rcode(int), but also
-// use the static globals (Rcode::NOERROR() etc), we use this
-// variable to see if the code came from one of the latter, in which
-// case Rcode_destroy should not free it (the other option is to
-// allocate new Rcodes for every use of the static ones, but this
-// seems more efficient).
-class s_Rcode : public PyObject {
-public:
-    s_Rcode() : rcode(NULL), static_code(false) {}
-    const Rcode* rcode;
-    bool static_code;
-};
+// Trivial constructor.
+s_Rcode::s_Rcode() : cppobj(NULL), static_code(false) {}
 
 
+namespace {
 int Rcode_init(s_Rcode* const self, PyObject* args);
 int Rcode_init(s_Rcode* const self, PyObject* args);
 void Rcode_destroy(s_Rcode* const self);
 void Rcode_destroy(s_Rcode* const self);
 
 
@@ -118,57 +115,6 @@ PyMethodDef Rcode_methods[] = {
     { NULL, NULL, 0, NULL }
     { NULL, NULL, 0, NULL }
 };
 };
 
 
-PyTypeObject rcode_type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "pydnspp.Rcode",
-    sizeof(s_Rcode),                    // tp_basicsize
-    0,                                  // tp_itemsize
-    (destructor)Rcode_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
-    Rcode_str,                          // tp_str
-    NULL,                               // tp_getattro
-    NULL,                               // tp_setattro
-    NULL,                               // tp_as_buffer
-    Py_TPFLAGS_DEFAULT,                 // tp_flags
-    "The Rcode class objects represent standard RCODEs"
-    "of the header section of DNS messages.",
-    NULL,                               // tp_traverse
-    NULL,                               // tp_clear
-    (richcmpfunc)Rcode_richcmp,         // tp_richcompare
-    0,                                  // tp_weaklistoffset
-    NULL,                               // tp_iter
-    NULL,                               // tp_iternext
-    Rcode_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)Rcode_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
 int
 Rcode_init(s_Rcode* const self, PyObject* args) {
 Rcode_init(s_Rcode* const self, PyObject* args) {
     long code = 0;
     long code = 0;
@@ -193,9 +139,9 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
     }
     }
     try {
     try {
         if (ext_code == -1) {
         if (ext_code == -1) {
-            self->rcode = new Rcode(code);
+            self->cppobj = new Rcode(code);
         } else {
         } else {
-            self->rcode = new Rcode(code, ext_code);
+            self->cppobj = new Rcode(code, ext_code);
         }
         }
         self->static_code = false;
         self->static_code = false;
     } catch (const isc::OutOfRange& ex) {
     } catch (const isc::OutOfRange& ex) {
@@ -211,27 +157,27 @@ Rcode_init(s_Rcode* const self, PyObject* args) {
 void
 void
 Rcode_destroy(s_Rcode* const self) {
 Rcode_destroy(s_Rcode* const self) {
     // Depending on whether we created the rcode or are referring
     // Depending on whether we created the rcode or are referring
-    // to a global one, we do or do not delete self->rcode here
+    // to a global one, we do or do not delete self->cppobj here
     if (!self->static_code) {
     if (!self->static_code) {
-        delete self->rcode;
+        delete self->cppobj;
     }
     }
-    self->rcode = NULL;
+    self->cppobj = NULL;
     Py_TYPE(self)->tp_free(self);
     Py_TYPE(self)->tp_free(self);
 }
 }
 
 
 PyObject*
 PyObject*
 Rcode_getCode(const s_Rcode* const self) {
 Rcode_getCode(const s_Rcode* const self) {
-    return (Py_BuildValue("I", self->rcode->getCode()));
+    return (Py_BuildValue("I", self->cppobj->getCode()));
 }
 }
 
 
 PyObject*
 PyObject*
 Rcode_getExtendedCode(const s_Rcode* const self) {
 Rcode_getExtendedCode(const s_Rcode* const self) {
-    return (Py_BuildValue("I", self->rcode->getExtendedCode()));
+    return (Py_BuildValue("I", self->cppobj->getExtendedCode()));
 }
 }
 
 
 PyObject*
 PyObject*
 Rcode_toText(const s_Rcode* const self) {
 Rcode_toText(const s_Rcode* const self) {
-    return (Py_BuildValue("s", self->rcode->toText().c_str()));
+    return (Py_BuildValue("s", self->cppobj->toText().c_str()));
 }
 }
 
 
 PyObject*
 PyObject*
@@ -245,7 +191,7 @@ PyObject*
 Rcode_createStatic(const Rcode& rcode) {
 Rcode_createStatic(const Rcode& rcode) {
     s_Rcode* ret = PyObject_New(s_Rcode, &rcode_type);
     s_Rcode* ret = PyObject_New(s_Rcode, &rcode_type);
     if (ret != NULL) {
     if (ret != NULL) {
-        ret->rcode = &rcode;
+        ret->cppobj = &rcode;
         ret->static_code = true;
         ret->static_code = true;
     }
     }
     return (ret);
     return (ret);
@@ -357,10 +303,10 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
         PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
         PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
         return (NULL);
         return (NULL);
     case Py_EQ:
     case Py_EQ:
-        c = (*self->rcode == *other->rcode);
+        c = (*self->cppobj == *other->cppobj);
         break;
         break;
     case Py_NE:
     case Py_NE:
-        c = (*self->rcode != *other->rcode);
+        c = (*self->cppobj != *other->cppobj);
         break;
         break;
     case Py_GT:
     case Py_GT:
         PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
         PyErr_SetString(PyExc_TypeError, "Unorderable type; Rcode");
@@ -374,6 +320,61 @@ Rcode_richcmp(const s_Rcode* const self, const s_Rcode* const other,
     else
     else
         Py_RETURN_FALSE;
         Py_RETURN_FALSE;
 }
 }
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+PyTypeObject rcode_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.Rcode",
+    sizeof(s_Rcode),                    // tp_basicsize
+    0,                                  // tp_itemsize
+    (destructor)Rcode_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
+    Rcode_str,                          // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The Rcode class objects represent standard RCODEs"
+    "of the header section of DNS messages.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    reinterpret_cast<richcmpfunc>(Rcode_richcmp),         // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    Rcode_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)Rcode_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
+};
 
 
 // Module Initialization, all statics are initialized here
 // Module Initialization, all statics are initialized here
 bool
 bool
@@ -428,4 +429,6 @@ initModulePart_Rcode(PyObject* mod) {
 
 
     return (true);
     return (true);
 }
 }
-} // end of unnamed namespace
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 57 - 0
src/lib/dns/python/rcode_python.h

@@ -0,0 +1,57 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_RCODE_H
+#define __PYTHON_RCODE_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class Rcode;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object.
+//
+// We added a helper variable static_code here
+// Since we can create Rcodes dynamically with Rcode(int), but also
+// use the static globals (Rcode::NOERROR() etc), we use this
+// variable to see if the code came from one of the latter, in which
+// case Rcode_destroy should not free it (the other option is to
+// allocate new Rcodes for every use of the static ones, but this
+// seems more efficient).
+//
+// Follow-up note: we don't have to use the proxy function in the python lib;
+// we can just define class specific constants directly (see TSIGError).
+// We should make this cleanup later.
+class s_Rcode : public PyObject {
+public:
+    s_Rcode();
+    const Rcode* cppobj;
+    bool static_code;
+};
+
+extern PyTypeObject rcode_type;
+
+bool initModulePart_Rcode(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_RCODE_H
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -168,7 +168,7 @@ RRset_init(s_RRset* self, PyObject* args) {
                                            &rrtype_type, &rrtype,
                                            &rrtype_type, &rrtype,
                                            &rrttl_type, &rrttl
                                            &rrttl_type, &rrttl
        )) {
        )) {
-        self->rrset = RRsetPtr(new RRset(*name->name, *rrclass->rrclass,
+        self->rrset = RRsetPtr(new RRset(*name->cppobj, *rrclass->rrclass,
                                 *rrtype->rrtype, *rrttl->rrttl));
                                 *rrtype->rrtype, *rrttl->rrttl));
         return (0);
         return (0);
     }
     }
@@ -197,8 +197,8 @@ RRset_getName(s_RRset* self) {
     // is this the best way to do this?
     // is this the best way to do this?
     name = static_cast<s_Name*>(name_type.tp_alloc(&name_type, 0));
     name = static_cast<s_Name*>(name_type.tp_alloc(&name_type, 0));
     if (name != NULL) {
     if (name != NULL) {
-        name->name = new Name(self->rrset->getName());
-        if (name->name == NULL)
+        name->cppobj = new Name(self->rrset->getName());
+        if (name->cppobj == NULL)
           {
           {
             Py_DECREF(name);
             Py_DECREF(name);
             return (NULL);
             return (NULL);
@@ -265,7 +265,7 @@ RRset_setName(s_RRset* self, PyObject* args) {
     if (!PyArg_ParseTuple(args, "O!", &name_type, &name)) {
     if (!PyArg_ParseTuple(args, "O!", &name_type, &name)) {
         return (NULL);
         return (NULL);
     }
     }
-    self->rrset->setName(*name->name);
+    self->rrset->setName(*name->cppobj);
     Py_RETURN_NONE;
     Py_RETURN_NONE;
 }
 }
 
 

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

@@ -12,7 +12,10 @@ 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 += tsig_python_test.py
 PYTESTS += tsig_python_test.py
+PYTESTS += tsig_rdata_python_test.py
+PYTESTS += tsigerror_python_test.py
 PYTESTS += tsigkey_python_test.py
 PYTESTS += tsigkey_python_test.py
+PYTESTS += tsigrecord_python_test.py
 
 
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += testutil.py
 EXTRA_DIST += testutil.py
@@ -33,7 +36,7 @@ if ENABLE_PYTHON_COVERAGE
 endif
 endif
 	for pytest in $(PYTESTS) ; do \
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	echo Running test: $$pytest ; \
-	env PYTHONPATH=$(abs_top_srcdir)/src/lib/dns/.libs:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
+	env PYTHONPATH=$(abs_top_builddir)/src/lib/util/pyunittests/.libs:$(abs_top_srcdir)/src/lib/dns/.libs:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs \
 	TESTDATA_PATH=$(abs_top_srcdir)/src/lib/dns/tests/testdata:$(abs_top_builddir)/src/lib/dns/tests/testdata \
 	TESTDATA_PATH=$(abs_top_srcdir)/src/lib/dns/tests/testdata:$(abs_top_builddir)/src/lib/dns/tests/testdata \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \

+ 48 - 1
src/lib/dns/python/tests/message_python_test.py

@@ -422,7 +422,54 @@ test.example.com. 3600 IN A 192.0.2.2
                           factoryFromFile,
                           factoryFromFile,
                           message_parse,
                           message_parse,
                           "message_fromWire9")
                           "message_fromWire9")
-    
+
+    def test_from_wire_with_tsig(self):
+        # Initially there should be no TSIG
+        self.assertEqual(None, self.p.get_tsig_record())
+
+        # getTSIGRecord() is only valid in the parse mode.
+        self.assertRaises(InvalidMessageOperation, self.r.get_tsig_record)
+
+        factoryFromFile(self.p, "message_toWire2.wire")
+        tsig_rr = self.p.get_tsig_record()
+        self.assertEqual(Name("www.example.com"), tsig_rr.get_name())
+        self.assertEqual(85, tsig_rr.get_length())
+        self.assertEqual(TSIGKey.HMACMD5_NAME,
+                         tsig_rr.get_rdata().get_algorithm())
+
+        # If we clear the message for reuse, the recorded TSIG will be cleared.
+        self.p.clear(Message.PARSE)
+        self.assertEqual(None, self.p.get_tsig_record())
+
+    def test_from_wire_with_tsigcompressed(self):
+        # Mostly same as fromWireWithTSIG, but the TSIG owner name is
+        # compressed.
+        factoryFromFile(self.p, "message_fromWire12.wire");
+        tsig_rr = self.p.get_tsig_record()
+        self.assertEqual(Name("www.example.com"), tsig_rr.get_name())
+        # len(www.example.com) = 17, but when fully compressed, the length is
+        # 2 bytes.  So the length of the record should be 15 bytes shorter.
+        self.assertEqual(70, tsig_rr.get_length())
+
+    def test_from_wire_with_badtsig(self):
+        # Multiple TSIG RRs
+        self.assertRaises(DNSMessageFORMERR, factoryFromFile,
+                          self.p, "message_fromWire13.wire")
+        self.p.clear(Message.PARSE)
+
+        # TSIG in the answer section (must be in additional)
+        self.assertRaises(DNSMessageFORMERR, factoryFromFile,
+                          self.p, "message_fromWire14.wire")
+        self.p.clear(Message.PARSE)
+
+        # TSIG is not the last record.
+        self.assertRaises(DNSMessageFORMERR, factoryFromFile,
+                          self.p, "message_fromWire15.wire")
+        self.p.clear(Message.PARSE)
+
+        # Unexpected RR Class (this will fail in constructing TSIGRecord)
+        self.assertRaises(DNSMessageFORMERR, factoryFromFile,
+                          self.p, "message_fromWire16.wire")
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

+ 530 - 5
src/lib/dns/python/tests/tsig_python_test.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2010  Internet Systems Consortium.
+# Copyright (C) 2011  Internet Systems Consortium.
 #
 #
 # Permission to use, copy, modify, and distribute this software for any
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
 # purpose with or without fee is hereby granted, provided that the above
@@ -13,17 +13,542 @@
 # 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.
 
 
-import unittest
+import base64, sys, time, unittest
 from pydnspp import *
 from pydnspp import *
+from testutil import *
+from pyunittests_util import fix_current_time
+
+# bit-wise constant flags to configure DNS header flags for test
+# messages.
+QR_FLAG = 0x1
+AA_FLAG = 0x2
+RD_FLAG = 0x4
+
+COMMON_EXPECTED_MAC = b"\x22\x70\x26\xad\x29\x7b\xee\xe7\x21\xce\x6c\x6f\xff\x1e\x9e\xf3"
+DUMMY_DATA = b"\xdd" * 100
 
 
 class TSIGContextTest(unittest.TestCase):
 class TSIGContextTest(unittest.TestCase):
     tsig_key = TSIGKey('www.example.com:SFuWd/q99SzF8Yzd1QbB9g==')
     tsig_key = TSIGKey('www.example.com:SFuWd/q99SzF8Yzd1QbB9g==')
 
 
     def setUp(self):
     def setUp(self):
-        # In the minimal implementation, we simply check constructing a
-        # TSIGContext doesn't cause any disruption.  We can add more tests
-        # later.
+        # make sure we don't use faked time unless explicitly do so in tests
+        fix_current_time(None)
+        self.qid = 0x2d65
+        self.test_name = Name("www.example.com")
         self.tsig_ctx = TSIGContext(self.tsig_key)
         self.tsig_ctx = TSIGContext(self.tsig_key)
+        self.tsig_verify_ctx = TSIGContext(self.tsig_key)
+        self.keyring = TSIGKeyRing()
+        self.message = Message(Message.RENDER)
+        self.renderer = MessageRenderer()
+        self.test_class = RRClass.IN()
+        self.test_ttl = RRTTL(86400)
+        self.secret = base64.b64decode(b"SFuWd/q99SzF8Yzd1QbB9g==")
+        self.tsig_ctx = TSIGContext(TSIGKey(self.test_name,
+                                            TSIGKey.HMACMD5_NAME,
+                                            self.secret))
+        self.badkey_name = Name("badkey.example.com")
+        self.dummy_record = TSIGRecord(self.badkey_name,
+                                       TSIG("hmac-md5.sig-alg.reg.int. " + \
+                                                "1302890362 300 0 11621 " + \
+                                                "0 0"))
+
+    def tearDown(self):
+        # reset any faked current time setting (it would affect other tests)
+        fix_current_time(None)
+
+    # Note: intentionally use camelCase so that we can easily copy-paste
+    # corresponding C++ tests.
+    def createMessageAndSign(self, id, qname, ctx, message_flags=RD_FLAG,
+                             qtype=RRType.A(), answer_data=None,
+                             answer_type=None, add_question=True,
+                             rcode=Rcode.NOERROR()):
+        self.message.clear(Message.RENDER)
+        self.message.set_qid(id)
+        self.message.set_opcode(Opcode.QUERY())
+        self.message.set_rcode(rcode)
+        if (message_flags & QR_FLAG) != 0:
+            self.message.set_header_flag(Message.HEADERFLAG_QR)
+        if (message_flags & AA_FLAG) != 0:
+            self.message.set_header_flag(Message.HEADERFLAG_AA)
+        if (message_flags & RD_FLAG) != 0:
+            self.message.set_header_flag(Message.HEADERFLAG_RD)
+        if add_question:
+            self.message.add_question(Question(qname, self.test_class, qtype))
+        if answer_data is not None:
+            if answer_type is None:
+                answer_type = qtype
+            answer_rrset = RRset(qname, self.test_class, answer_type,
+                                 self.test_ttl)
+            answer_rrset.add_rdata(Rdata(answer_type, self.test_class,
+                                         answer_data))
+            self.message.add_rrset(Message.SECTION_ANSWER, answer_rrset)
+        self.renderer.clear()
+        self.message.to_wire(self.renderer)
+
+        if ctx.get_state() == TSIGContext.STATE_INIT:
+            expected_new_state = TSIGContext.STATE_SENT_REQUEST
+        else:
+            expected_new_state = TSIGContext.STATE_SENT_RESPONSE
+        tsig = ctx.sign(id, self.renderer.get_data())
+
+        return tsig
+
+    # Note: intentionally use camelCase so that we can easily copy-paste
+    # corresponding C++ tests.
+    def createMessageFromFile(self, file):
+        self.message.clear(Message.PARSE)
+        self.received_data = read_wire_data(file)
+        self.message.from_wire(self.received_data)
+
+    # Note: intentionally use camelCase so that we can easily copy-paste
+    # corresponding C++ tests.
+    def commonSignChecks(self, tsig, expected_qid, expected_timesigned,
+                         expected_mac, expected_error=0,
+                         expected_otherdata=None,
+                         expected_algorithm=TSIGKey.HMACMD5_NAME):
+        tsig_rdata = tsig.get_rdata()
+        self.assertEqual(expected_algorithm, tsig_rdata.get_algorithm())
+        self.assertEqual(expected_timesigned, tsig_rdata.get_timesigned())
+        self.assertEqual(300, tsig_rdata.get_fudge())
+        self.assertEqual(expected_mac, tsig_rdata.get_mac())
+        self.assertEqual(expected_qid, tsig_rdata.get_original_id())
+        self.assertEqual(expected_error, tsig_rdata.get_error())
+        self.assertEqual(expected_otherdata, tsig_rdata.get_other_data())
+
+    def test_initial_state(self):
+        # Until signing or verifying, the state should be INIT
+        self.assertEqual(TSIGContext.STATE_INIT, self.tsig_ctx.get_state())
+
+        # And there should be no error code.
+        self.assertEqual(TSIGError(Rcode.NOERROR()), self.tsig_ctx.get_error())
+
+    # Note: intentionally use camelCase so that we can easily copy-paste
+    # corresponding C++ tests.
+    def commonVerifyChecks(self, ctx, record, data, expected_error,
+                           expected_new_state=\
+                               TSIGContext.STATE_VERIFIED_RESPONSE):
+        self.assertEqual(expected_error, ctx.verify(record, data))
+        self.assertEqual(expected_error, ctx.get_error())
+        self.assertEqual(expected_new_state, ctx.get_state())
+
+    def test_from_keyring(self):
+        # Construct a TSIG context with an empty key ring.  Key shouldn't be
+        # found, and the BAD_KEY error should be recorded.
+        ctx = TSIGContext(self.test_name, TSIGKey.HMACMD5_NAME, self.keyring)
+        self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+        self.assertEqual(TSIGError.BAD_KEY, ctx.get_error())
+        # check get_error() doesn't cause ref leak.  Note: we can't
+        # realiably do this check for get_state(), as it returns an integer
+        # object, which could have many references
+        self.assertEqual(1, sys.getrefcount(ctx.get_error()))
+
+        # Add a matching key (we don't use the secret so leave it empty), and
+        # construct it again.  This time it should be constructed with a valid
+        # key.
+        self.keyring.add(TSIGKey(self.test_name, TSIGKey.HMACMD5_NAME, b""))
+        ctx = TSIGContext(self.test_name, TSIGKey.HMACMD5_NAME, self.keyring)
+        self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+        self.assertEqual(TSIGError.NOERROR, ctx.get_error())
+
+        # Similar to the first case except that the key ring isn't empty but
+        # it doesn't contain a matching key.
+        ctx = TSIGContext(self.test_name, TSIGKey.HMACSHA1_NAME, self.keyring)
+        self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+        self.assertEqual(TSIGError.BAD_KEY, ctx.get_error())
+
+        ctx = TSIGContext(Name("different-key.example"),
+                          TSIGKey.HMACMD5_NAME, self.keyring)
+        self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+        self.assertEqual(TSIGError.BAD_KEY, ctx.get_error())
+
+        # "Unknown" algorithm name will result in BADKEY, too.
+        ctx = TSIGContext(self.test_name, Name("unknown.algorithm"),
+                          self.keyring)
+        self.assertEqual(TSIGContext.STATE_INIT, ctx.get_state())
+        self.assertEqual(TSIGError.BAD_KEY, ctx.get_error())
+
+    def test_sign(self):
+        fix_current_time(0x4da8877a)
+        tsig = self.createMessageAndSign(self.qid, self.test_name,
+                                         self.tsig_ctx)
+        self.commonSignChecks(tsig, self.qid, 0x4da8877a, COMMON_EXPECTED_MAC)
+
+    # Same test as sign, but specifying the key name with upper-case (i.e.
+    # non canonical) characters.  The digest must be the same.  It should
+    # actually be ensured at the level of TSIGKey, but we confirm that at
+    # this level, too.
+    def test_sign_using_uppercase_keyname(self):
+        fix_current_time(0x4da8877a)
+        cap_ctx = TSIGContext(TSIGKey(Name("WWW.EXAMPLE.COM"),
+                                      TSIGKey.HMACMD5_NAME, self.secret))
+        tsig = self.createMessageAndSign(self.qid, self.test_name, cap_ctx)
+        self.commonSignChecks(tsig, self.qid, 0x4da8877a, COMMON_EXPECTED_MAC)
+
+    # Same as the previous test, but for the algorithm name.
+    def test_sign_using_uppercase_algorithm_name(self):
+        fix_current_time(0x4da8877a)
+        cap_ctx = TSIGContext(TSIGKey(self.test_name,
+                                      Name("HMAC-md5.SIG-alg.REG.int"),
+                                      self.secret))
+        tsig = self.createMessageAndSign(self.qid, self.test_name, cap_ctx)
+        self.commonSignChecks(tsig, self.qid, 0x4da8877a, COMMON_EXPECTED_MAC)
+
+    # Sign the message using the actual time, and check the accuracy of it.
+    # We cannot reasonably predict the expected MAC, so don't bother to
+    # check it.
+    def test_sign_at_actual_time(self):
+        now = int(time.time())
+        tsig = self.createMessageAndSign(self.qid, self.test_name,
+                                         self.tsig_ctx)
+        tsig_rdata = tsig.get_rdata()
+
+        # Check the resulted time signed is in the range of [now, now + 5]
+        self.assertTrue(now <= tsig_rdata.get_timesigned())
+        self.assertTrue(now + 5 >= tsig_rdata.get_timesigned())
+
+    def test_bad_data(self):
+        self.assertRaises(TypeError, self.tsig_ctx.sign, None, 10)
+
+    def test_verify_bad_data(self):
+        # the data must at least hold the DNS message header and the specified
+        # TSIG.
+        bad_len = 12 + self.dummy_record.get_length() - 1
+        self.assertRaises(InvalidParameter, self.tsig_ctx.verify,
+                          self.dummy_record, DUMMY_DATA[:bad_len])
+
+    def test_sign_using_hmacsha1(self):
+        fix_current_time(0x4dae7d5f)
+
+        secret = base64.b64decode(b"MA+QDhXbyqUak+qnMFyTyEirzng=")
+        sha1_ctx = TSIGContext(TSIGKey(self.test_name, TSIGKey.HMACSHA1_NAME,
+                                       secret))
+        qid = 0x0967
+        expected_mac = b"\x41\x53\x40\xc7\xda\xf8\x24\xed\x68\x4e\xe5\x86" + \
+            b"\xf7\xb5\xa6\x7a\x2f\xeb\xc0\xd3"
+        tsig = self.createMessageAndSign(qid, self.test_name, sha1_ctx)
+        self.commonSignChecks(tsig, qid, 0x4dae7d5f, expected_mac,
+                              0, None, TSIGKey.HMACSHA1_NAME)
+
+    def test_verify_then_sign_response(self):
+        fix_current_time(0x4da8877a)
+
+        self.createMessageFromFile("message_toWire2.wire")
+        self.commonVerifyChecks(self.tsig_verify_ctx,
+                                self.message.get_tsig_record(),
+                                self.received_data, TSIGError.NOERROR,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+        tsig = self.createMessageAndSign(self.qid, self.test_name,
+                                         self.tsig_verify_ctx,
+                                         QR_FLAG|AA_FLAG|RD_FLAG,
+                                         RRType.A(), "192.0.2.1")
+
+        expected_mac = b"\x8f\xcd\xa6\x6a\x7c\xd1\xa3\xb9\x94\x8e\xb1\x86" + \
+            b"\x9d\x38\x4a\x9f"
+        self.commonSignChecks(tsig, self.qid, 0x4da8877a, expected_mac)
+
+    def test_verify_uppercase_names(self):
+        fix_current_time(0x4da8877a)
+
+        self.createMessageFromFile("tsig_verify9.wire")
+        self.commonVerifyChecks(self.tsig_verify_ctx,
+                                self.message.get_tsig_record(),
+                                self.received_data, TSIGError.NOERROR,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+    def test_verify_forward_message(self):
+        fix_current_time(0x4da8877a)
+
+        self.createMessageFromFile("tsig_verify6.wire")
+        self.commonVerifyChecks(self.tsig_verify_ctx,
+                                self.message.get_tsig_record(),
+                                self.received_data, TSIGError.NOERROR,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+    def test_sign_continuation(self):
+        fix_current_time(0x4da8e951)
+
+        axfr_qid = 0x3410
+        zone_name = Name("example.com")
+
+        tsig = self.createMessageAndSign(axfr_qid, zone_name, self.tsig_ctx,
+                                         0, RRType.AXFR())
+
+        received_data = read_wire_data("tsig_verify1.wire")
+        self.commonVerifyChecks(self.tsig_verify_ctx, tsig, received_data,
+                                TSIGError.NOERROR,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+        tsig = self.createMessageAndSign(axfr_qid, zone_name,
+                                         self.tsig_verify_ctx,
+                                         AA_FLAG|QR_FLAG, RRType.AXFR(),
+                                         "ns.example.com. root.example.com." +\
+                                         " 2011041503 7200 3600 2592000 1200",
+                                         RRType.SOA())
+
+        received_data = read_wire_data("tsig_verify2.wire")
+        self.commonVerifyChecks(self.tsig_ctx, tsig, received_data,
+                                TSIGError.NOERROR)
+
+        expected_mac = b"\x10\x24\x58\xf7\xf6\x2d\xdd\x7d\x63\x8d\x74" +\
+            b"\x60\x34\x13\x09\x68"
+        tsig = self.createMessageAndSign(axfr_qid, zone_name,
+                                         self.tsig_verify_ctx,
+                                         AA_FLAG|QR_FLAG, RRType.AXFR(),
+                                         "ns.example.com.", RRType.NS(),
+                                         False)
+        self.commonSignChecks(tsig, axfr_qid, 0x4da8e951, expected_mac)
+
+        received_data = read_wire_data("tsig_verify3.wire")
+        self.commonVerifyChecks(self.tsig_ctx, tsig, received_data,
+                                TSIGError.NOERROR)
+
+    def test_badtime_response(self):
+        fix_current_time(0x4da8b9d6)
+
+        test_qid = 0x7fc4
+        tsig = self.createMessageAndSign(test_qid, self.test_name,
+                                         self.tsig_ctx, 0, RRType.SOA())
+
+        # "advance the clock" and try validating, which should fail due to
+        # BADTIME
+        fix_current_time(0x4da8be86)
+        self.commonVerifyChecks(self.tsig_verify_ctx, tsig, DUMMY_DATA,
+                                TSIGError.BAD_TIME,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+        # make and sign a response in the context of TSIG error.
+        tsig = self.createMessageAndSign(test_qid, self.test_name,
+                                         self.tsig_verify_ctx,
+                                         QR_FLAG, RRType.SOA(), None, None,
+                                         True, Rcode.NOTAUTH())
+
+        expected_otherdata = b"\x00\x00\x4d\xa8\xbe\x86"
+        expected_mac = b"\xd4\xb0\x43\xf6\xf4\x44\x95\xec\x8a\x01\x26" +\
+            b"\x0e\x39\x15\x9d\x76"
+
+        self.commonSignChecks(tsig, self.message.get_qid(), 0x4da8b9d6,
+                              expected_mac,
+                              18,     # error: BADTIME
+                              expected_otherdata)
+
+    def test_badtime_response2(self):
+        fix_current_time(0x4da8b9d6)
+
+        tsig = self.createMessageAndSign(self.qid, self.test_name,
+                                         self.tsig_ctx, 0, RRType.SOA())
+
+        # "rewind the clock" and try validating, which should fail due to
+        # BADTIME
+        fix_current_time(0x4da8b9d6 - 600)
+        self.commonVerifyChecks(self.tsig_verify_ctx, tsig, DUMMY_DATA,
+                           TSIGError.BAD_TIME,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+    # Test various boundary conditions.  We intentionally use the magic
+    # number of 300 instead of the constant variable for testing.
+    # In the okay cases, signature is not correct, but it's sufficient to
+    # check the error code isn't BADTIME for the purpose of this test.
+    def test_badtime_boundaries(self):
+        fix_current_time(0x4da8b9d6)
+
+        tsig = self.createMessageAndSign(self.qid, self.test_name,
+                                         self.tsig_ctx, 0, RRType.SOA())
+                                         
+        fix_current_time(0x4da8b9d6 + 301)
+        self.assertEqual(TSIGError.BAD_TIME,
+                         self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+        fix_current_time(0x4da8b9d6 + 300)
+        self.assertNotEqual(TSIGError.BAD_TIME,
+                            self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+        fix_current_time(0x4da8b9d6 - 301)
+        self.assertEqual(TSIGError.BAD_TIME,
+                         self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+        fix_current_time(0x4da8b9d6 - 300)
+        self.assertNotEqual(TSIGError.BAD_TIME,
+                            self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+    def test_badtime_overflow(self):
+        fix_current_time(200)
+        tsig = self.createMessageAndSign(self.qid, self.test_name,
+                                         self.tsig_ctx, 0, RRType.SOA())
+
+        # This should be in the okay range, but since "200 - fudge" overflows
+        # and we compare them as 64-bit unsigned integers, it results in a
+        # false positive (we intentionally accept that).
+        fix_current_time(100)
+        self.assertEqual(TSIGError.BAD_TIME,
+                         self.tsig_verify_ctx.verify(tsig, DUMMY_DATA))
+
+    def test_badsig_response(self):
+        fix_current_time(0x4da8877a)
+
+        # Try to sign a simple message with bogus secret.  It should fail
+        # with BADSIG.
+        self.createMessageFromFile("message_toWire2.wire")
+        bad_ctx = TSIGContext(TSIGKey(self.test_name, TSIGKey.HMACMD5_NAME,
+                                      DUMMY_DATA))
+        self.commonVerifyChecks(bad_ctx, self.message.get_tsig_record(),
+                                self.received_data, TSIGError.BAD_SIG,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+        # Sign the same message (which doesn't matter for this test) with the
+        # context of "checked state".
+        tsig = self.createMessageAndSign(self.qid, self.test_name, bad_ctx)
+        self.commonSignChecks(tsig, self.message.get_qid(), 0x4da8877a, None,
+                              16)   # 16: BADSIG
+
+    def test_badkey_response(self):
+        # A similar test as badsigResponse but for BADKEY
+        fix_current_time(0x4da8877a)
+        tsig_ctx = TSIGContext(self.badkey_name, TSIGKey.HMACMD5_NAME,
+                               self.keyring)
+        self.commonVerifyChecks(tsig_ctx, self.dummy_record, DUMMY_DATA,
+                                TSIGError.BAD_KEY,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+        sig = self.createMessageAndSign(self.qid, self.test_name, tsig_ctx)
+        self.assertEqual(self.badkey_name, sig.get_name())
+        self.commonSignChecks(sig, self.qid, 0x4da8877a, None, 17) # 17: BADKEY
+
+    def test_badkey_for_response(self):
+        # "BADKEY" case for a response to a signed message
+        self.createMessageAndSign(self.qid, self.test_name, self.tsig_ctx)
+        self.commonVerifyChecks(self.tsig_ctx, self.dummy_record, DUMMY_DATA,
+                                TSIGError.BAD_KEY,
+                                TSIGContext.STATE_SENT_REQUEST)
+
+        # A similar case with a different algorithm
+        dummy_record = TSIGRecord(self.test_name,
+                                  TSIG("hmac-sha1. 1302890362 300 0 "
+                                       "11621 0 0"))
+        self.commonVerifyChecks(self.tsig_ctx, dummy_record, DUMMY_DATA,
+                                TSIGError.BAD_KEY,
+                                TSIGContext.STATE_SENT_REQUEST)
+
+    # According to RFC2845 4.6, if TSIG verification fails the client
+    # should discard that message and wait for another signed response.
+    # This test emulates that situation.
+    def test_badsig_then_validate(self):
+        fix_current_time(0x4da8877a)
+
+        self.createMessageAndSign(self.qid, self.test_name, self.tsig_ctx)
+        self.createMessageFromFile("tsig_verify4.wire")
+
+        self.commonVerifyChecks(self.tsig_ctx, self.message.get_tsig_record(),
+                                self.received_data, TSIGError.BAD_SIG,
+                                TSIGContext.STATE_SENT_REQUEST)
+
+        self.createMessageFromFile("tsig_verify5.wire")
+        self.commonVerifyChecks(self.tsig_ctx, self.message.get_tsig_record(),
+                                self.received_data, TSIGError.NOERROR,
+                                TSIGContext.STATE_VERIFIED_RESPONSE)
+
+    # Similar to the previous test, but the first response doesn't contain
+    # TSIG.
+    def test_nosig_then_validate(self):
+        fix_current_time(0x4da8877a)
+        self.createMessageAndSign(self.qid, self.test_name, self.tsig_ctx)
+
+        self.commonVerifyChecks(self.tsig_ctx, None, DUMMY_DATA,
+                           TSIGError.FORMERR, TSIGContext.STATE_SENT_REQUEST)
+
+        self.createMessageFromFile("tsig_verify5.wire")
+        self.commonVerifyChecks(self.tsig_ctx, self.message.get_tsig_record(),
+                                self.received_data, TSIGError.NOERROR,
+                                TSIGContext.STATE_VERIFIED_RESPONSE)
+
+    # Similar to the previous test, but the first response results in BADTIME.
+    def test_badtime_then_validate(self):
+        fix_current_time(0x4da8877a)
+        tsig = self.createMessageAndSign(self.qid, self.test_name,
+                                         self.tsig_ctx)
+
+        # "advance the clock" and try validating, which should fail due to
+        # BADTIME
+        fix_current_time(0x4da8877a + 600)
+        self.commonVerifyChecks(self.tsig_ctx, tsig, DUMMY_DATA,
+                           TSIGError.BAD_TIME, TSIGContext.STATE_SENT_REQUEST)
+
+        # revert the clock again.
+        fix_current_time(0x4da8877a)
+        self.createMessageFromFile("tsig_verify5.wire")
+        self.commonVerifyChecks(self.tsig_ctx, self.message.get_tsig_record(),
+                                self.received_data, TSIGError.NOERROR,
+                                TSIGContext.STATE_VERIFIED_RESPONSE)
+
+    # We don't allow empty MAC unless the TSIG error is BADSIG or BADKEY.
+    def test_empty_mac(self):
+        fix_current_time(0x4da8877a)
+
+        self.createMessageFromFile("tsig_verify7.wire")
+
+        self.commonVerifyChecks(self.tsig_verify_ctx,
+                                self.message.get_tsig_record(),
+                                self.received_data,
+                                TSIGError.BAD_SIG,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+        # If the empty MAC comes with a BADKEY error, the error is passed
+        # transparently.
+        self.createMessageFromFile("tsig_verify8.wire")
+        self.commonVerifyChecks(self.tsig_verify_ctx,
+                                self.message.get_tsig_record(),
+                                self.received_data,
+                                TSIGError.BAD_KEY,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
+
+    # Once the context is used for sending a signed response, it shouldn't
+    # be used for further verification.
+    def test_verify_after_sendresponse(self):
+        fix_current_time(0x4da8877a)
+
+        self.createMessageFromFile("message_toWire2.wire")
+        self.tsig_verify_ctx.verify(self.message.get_tsig_record(),
+                                    self.received_data)
+        self.assertEqual(TSIGContext.STATE_RECEIVED_REQUEST,
+                         self.tsig_verify_ctx.get_state())
+        self.createMessageAndSign(self.qid, self.test_name,
+                                  self.tsig_verify_ctx,
+                                  QR_FLAG|AA_FLAG|RD_FLAG, RRType.A(),
+                                  "192.0.2.1")
+        self.assertEqual(TSIGContext.STATE_SENT_RESPONSE,
+                         self.tsig_verify_ctx.get_state())
+
+        # Now trying further verification.
+        self.createMessageFromFile("message_toWire2.wire")
+        self.assertRaises(TSIGContextError, self.tsig_verify_ctx.verify,
+                          self.message.get_tsig_record(), self.received_data)
+
+    # Likewise, once the context verifies a response, it shouldn't for
+    # signing any more.
+    def test_sign_after_verified(self):
+        fix_current_time(0x4da8877a)
+
+        self.createMessageAndSign(self.qid, self.test_name, self.tsig_ctx)
+        self.createMessageFromFile("tsig_verify5.wire")
+        self.tsig_ctx.verify(self.message.get_tsig_record(),
+                             self.received_data)
+        self.assertEqual(TSIGContext.STATE_VERIFIED_RESPONSE,
+                         self.tsig_ctx.get_state())
+
+        # Now trying further signing.
+        self.assertRaises(TSIGContextError, self.createMessageAndSign,
+                          self.qid, self.test_name, self.tsig_ctx)
+
+    # Too short MAC should be rejected.
+    # Note: when we implement RFC4635-based checks, the error code will
+    # (probably) be FORMERR.
+    def test_too_short_mac(self):
+        fix_current_time(0x4da8877a)
+        self.createMessageFromFile("tsig_verify10.wire")
+        self.commonVerifyChecks(self.tsig_verify_ctx,
+                                self.message.get_tsig_record(),
+                                self.received_data, TSIGError.BAD_SIG,
+                                TSIGContext.STATE_RECEIVED_REQUEST)
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

+ 30 - 0
src/lib/dns/python/tests/tsig_rdata_python_test.py

@@ -0,0 +1,30 @@
+# Copyright (C) 2011  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.
+
+import unittest
+import sys
+from pydnspp import *
+
+class TSIGRdataTest(unittest.TestCase):
+    VALID_TEXT1 = "hmac-md5.sig-alg.reg.int. 1286779327 300 0 16020 BADKEY 0"
+    def test_from_string(self):
+        tsig = TSIG(self.VALID_TEXT1)
+        self.assertEqual(Name("hmac-md5.sig-alg.reg.int"),
+                         tsig.get_algorithm())
+        # check there's no leak in creating the name object:
+        self.assertEqual(1, sys.getrefcount(tsig.get_algorithm()))
+
+if __name__ == '__main__':
+    unittest.main()

+ 97 - 0
src/lib/dns/python/tests/tsigerror_python_test.py

@@ -0,0 +1,97 @@
+# Copyright (C) 2011  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.
+
+import unittest
+import sys
+from pydnspp import *
+
+class TSIGErrorTest(unittest.TestCase):
+    def test_from_code(self):
+        self.assertEqual(0, TSIGError(0).get_code())
+        self.assertEqual(18, TSIGError(18).get_code())
+        self.assertEqual(65535, TSIGError(65535).get_code())
+        self.assertRaises(ValueError, TSIGError, 65536)
+        self.assertRaises(ValueError, TSIGError, -1)
+        self.assertRaises(TypeError, TSIGError, "not yet supported")
+
+    def test_from_rcode(self):
+        # We use RCODE for code values from 0-15.
+        self.assertEqual(0, TSIGError(Rcode.NOERROR()).get_code())
+        self.assertEqual(15, TSIGError(Rcode(15)).get_code())
+
+        # From error code 16 TSIG errors define a separate space, so passing
+        # corresponding RCODE for such code values should be prohibited.
+        self.assertRaises(ValueError, TSIGError, Rcode(16))
+
+    def test_constants(self):
+        # We'll only test arbitrarily chosen subsets of the codes.
+        # This class is quite simple, so it should be suffice.
+        self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError(16).get_code())
+        self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError(17).get_code())
+        self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError(18).get_code())
+
+        self.assertEqual(0, TSIGError.NOERROR.get_code())
+        self.assertEqual(9, TSIGError.NOTAUTH.get_code())
+        self.assertEqual(14, TSIGError.RESERVED14.get_code())
+        self.assertEqual(TSIGError.BAD_SIG_CODE, TSIGError.BAD_SIG.get_code())
+        self.assertEqual(TSIGError.BAD_KEY_CODE, TSIGError.BAD_KEY.get_code())
+        self.assertEqual(TSIGError.BAD_TIME_CODE, TSIGError.BAD_TIME.get_code())
+
+    def test_equal(self):
+        self.assertTrue(TSIGError.NOERROR == TSIGError(Rcode.NOERROR()))
+        self.assertTrue(TSIGError(Rcode.NOERROR()) == TSIGError.NOERROR)
+
+        self.assertTrue(TSIGError.BAD_SIG == TSIGError(16))
+        self.assertTrue(TSIGError(16) == TSIGError.BAD_SIG)
+
+    def test_nequal(self):
+        self.assertTrue(TSIGError.BAD_KEY != TSIGError(Rcode.NOERROR()))
+        self.assertTrue(TSIGError(Rcode.NOERROR()) != TSIGError.BAD_KEY)
+
+    def test_to_text(self):
+        # TSIGError derived from the standard Rcode
+        self.assertEqual("NOERROR", TSIGError(Rcode.NOERROR()).to_text())
+
+        # Well known TSIG errors
+        self.assertEqual("BADSIG", TSIGError.BAD_SIG.to_text())
+        self.assertEqual("BADKEY", TSIGError.BAD_KEY.to_text())
+        self.assertEqual("BADTIME", TSIGError.BAD_TIME.to_text())
+
+        # Unknown (or not yet supported) codes.  Simply converted as numeric.
+        self.assertEqual("19", TSIGError(19).to_text());
+        self.assertEqual("65535", TSIGError(65535).to_text());
+
+        # also check str() works same way
+        self.assertEqual("NOERROR", str(TSIGError(Rcode.NOERROR())))
+        self.assertEqual("BADSIG", str(TSIGError.BAD_SIG))
+
+    def test_to_rcode(self):
+        # TSIGError derived from the standard Rcode
+        self.assertEqual(Rcode.NOERROR(), TSIGError(Rcode.NOERROR()).to_rcode())
+
+        # Well known TSIG errors
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_SIG.to_rcode())
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_KEY.to_rcode())
+        self.assertEqual(Rcode.NOTAUTH(), TSIGError.BAD_TIME.to_rcode())
+
+        # Unknown (or not yet supported) codes are treated as SERVFAIL.
+        self.assertEqual(Rcode.SERVFAIL(), TSIGError(19).to_rcode())
+        self.assertEqual(Rcode.SERVFAIL(), TSIGError(65535).to_rcode())
+
+        # Check there's no redundant refcount (which would cause leak)
+        self.assertEqual(1, sys.getrefcount(TSIGError.BAD_SIG.to_rcode()))
+
+if __name__ == '__main__':
+    unittest.main()

+ 44 - 0
src/lib/dns/python/tests/tsigrecord_python_test.py

@@ -0,0 +1,44 @@
+# Copyright (C) 2011  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.
+
+import unittest
+import sys
+from pydnspp import *
+
+class TSIGRecordTest(unittest.TestCase):
+    def setUp(self):
+        self.test_name = Name("www.example.com")
+        self.test_rdata = TSIG("hmac-md5.sig-alg.reg.int. 1302890362 " + \
+                                   "300 16 2tra2tra2tra2tra2tra2g== " + \
+                                   "11621 0 0")
+        self.test_record = TSIGRecord(self.test_name, self.test_rdata)
+
+    def test_getname(self):
+        self.assertEqual(self.test_name, self.test_record.get_name())
+        self.assertEqual(1, sys.getrefcount(self.test_record.get_name()))
+
+    def test_get_length(self):
+        # see the C++ test for the magic number
+        self.assertEqual(85, self.test_record.get_length())
+
+    def test_to_text(self):
+        expected_text = "www.example.com. 0 ANY TSIG " + \
+            "hmac-md5.sig-alg.reg.int. 1302890362 300 16 " + \
+            "2tra2tra2tra2tra2tra2g== 11621 NOERROR 0\n"
+        self.assertEqual(expected_text, self.test_record.to_text())
+        self.assertEqual(expected_text, str(self.test_record))
+
+if __name__ == '__main__':
+    unittest.main()

+ 259 - 52
src/lib/dns/python/tsig_python.cc

@@ -12,9 +12,30 @@
 // 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.
 
 
+#define PY_SSIZE_T_CLEAN        // need for "y#" below
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <exceptions/exceptions.h>
+
+#include <util/python/pycppwrapper_util.h>
+
 #include <dns/tsig.h>
 #include <dns/tsig.h>
 
 
+#include "pydnspp_common.h"
+#include "name_python.h"
+#include "tsigkey_python.h"
+#include "tsigerror_python.h"
+#include "tsigrecord_python.h"
+#include "tsig_python.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::util::python;
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::dns::python;
 
 
 //
 //
 // Definition of the classes
 // Definition of the classes
@@ -24,13 +45,17 @@ using namespace isc::dns;
 // and static wrappers around the methods we export), a list of methods,
 // and static wrappers around the methods we export), a list of methods,
 // and a type description
 // and a type description
 
 
-namespace {
-// The s_* Class simply covers one instantiation of the object
+//
+// TSIGContext
+//
 
 
-class s_TSIGContext : public PyObject {
-public:
-    TSIGContext* tsig_ctx;
-};
+// Trivial constructor.
+s_TSIGContext::s_TSIGContext() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIGContext, TSIGContext> TSIGContextContainer;
 
 
 //
 //
 // We declare the functions here, the definitions are below
 // We declare the functions here, the definitions are below
@@ -41,6 +66,12 @@ public:
 int TSIGContext_init(s_TSIGContext* self, PyObject* args);
 int TSIGContext_init(s_TSIGContext* self, PyObject* args);
 void TSIGContext_destroy(s_TSIGContext* self);
 void TSIGContext_destroy(s_TSIGContext* self);
 
 
+// Class specific methods
+PyObject* TSIGContext_getState(s_TSIGContext* self);
+PyObject* TSIGContext_getError(s_TSIGContext* self);
+PyObject* TSIGContext_sign(s_TSIGContext* self, PyObject* args);
+PyObject* TSIGContext_verify(s_TSIGContext* self, PyObject* args);
+
 // These are the functions we export
 // These are the functions we export
 // For a minimal support, we don't need them.
 // For a minimal support, we don't need them.
 
 
@@ -51,18 +82,180 @@ void TSIGContext_destroy(s_TSIGContext* self);
 // 3. Argument type
 // 3. Argument type
 // 4. Documentation
 // 4. Documentation
 PyMethodDef TSIGContext_methods[] = {
 PyMethodDef TSIGContext_methods[] = {
+    { "get_state", reinterpret_cast<PyCFunction>(TSIGContext_getState),
+      METH_NOARGS,
+      "Return the current state of the context (mainly for tests)" },
+    { "get_error", reinterpret_cast<PyCFunction>(TSIGContext_getError),
+      METH_NOARGS,
+      "Return the TSIG error as a result of the latest verification" },
+    { "sign",
+      reinterpret_cast<PyCFunction>(TSIGContext_sign), METH_VARARGS,
+      "Sign a DNS message." },
+    { "verify",
+      reinterpret_cast<PyCFunction>(TSIGContext_verify), METH_VARARGS,
+      "Verify a DNS message." },
     { NULL, NULL, 0, NULL }
     { NULL, NULL, 0, NULL }
 };
 };
 
 
+int
+TSIGContext_init(s_TSIGContext* self, PyObject* args) {
+    try {
+        // "From key" constructor
+        const s_TSIGKey* tsigkey_obj;
+        if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey_obj)) {
+            self->cppobj = new TSIGContext(*tsigkey_obj->cppobj);
+            return (0);
+        }
+
+        // "From key param + keyring" constructor
+        PyErr_Clear();
+        const s_Name* keyname_obj;
+        const s_Name* algname_obj;
+        const s_TSIGKeyRing* keyring_obj;
+        if (PyArg_ParseTuple(args, "O!O!O!", &name_type, &keyname_obj,
+                             &name_type, &algname_obj, &tsigkeyring_type,
+                             &keyring_obj)) {
+            self->cppobj = new TSIGContext(*keyname_obj->cppobj,
+                                           *algname_obj->cppobj,
+                                           *keyring_obj->cppobj);
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct TSIGContext object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing TSIGContext");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIGContext constructor");
+
+    return (-1);
+}
+
+void
+TSIGContext_destroy(s_TSIGContext* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGContext_getState(s_TSIGContext* self) {
+    return (Py_BuildValue("I", self->cppobj->getState()));
+}
+
+PyObject*
+TSIGContext_getError(s_TSIGContext* self) {
+    try {
+        PyObjectContainer container(createTSIGErrorObject(
+                                        self->cppobj->getError()));
+        return (Py_BuildValue("O", container.get()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpectedly failed to get TSIGContext error: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in TSIGContext.get_error");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIGContext_sign(s_TSIGContext* self, PyObject* args) {
+    long qid = 0;
+    const char* mac;
+    Py_ssize_t mac_size;
+
+    if (PyArg_ParseTuple(args, "ly#", &qid, &mac, &mac_size)) {
+        if (qid < 0 || qid > 0xffff) {
+            PyErr_SetString(PyExc_ValueError,
+                            "TSIGContext.sign: QID out of range");
+            return (NULL);
+        }
+
+        try {
+            ConstTSIGRecordPtr record = self->cppobj->sign(qid, mac, mac_size);
+            return (createTSIGRecordObject(*record));
+        } catch (const TSIGContextError& ex) {
+            PyErr_SetString(po_TSIGContextError, ex.what());
+        } catch (const exception& ex) {
+            const string ex_what = "Unexpected failure in TSIG sign: " +
+                string(ex.what());
+            PyErr_SetString(po_IscException, ex_what.c_str());
+        } catch (...) {
+            PyErr_SetString(PyExc_SystemError,
+                            "Unexpected failure in TSIG sign");
+        }
+    } else {
+        PyErr_SetString(PyExc_TypeError,
+                        "Invalid arguments to TSIGContext.sign");
+    }
+
+    return (NULL);
+}
+
+PyObject*
+TSIGContext_verify(s_TSIGContext* self, PyObject* args) {
+    const char* data;
+    Py_ssize_t data_len;
+    s_TSIGRecord* py_record;
+    PyObject* py_maybe_none;
+    TSIGRecord* record;
+
+    if (PyArg_ParseTuple(args, "O!y#", &tsigrecord_type, &py_record,
+                         &data, &data_len)) {
+        record = py_record->cppobj;
+    } else if (PyArg_ParseTuple(args, "Oy#", &py_maybe_none, &data,
+                                &data_len)) {
+        record = NULL;
+    } else {
+        PyErr_SetString(PyExc_TypeError,
+                        "Invalid arguments to TSIGContext.verify");
+        return (NULL);
+    }
+    PyErr_Clear();
+
+    try {
+        const TSIGError error = self->cppobj->verify(record, data, data_len);
+        return (createTSIGErrorObject(error));
+    } catch (const TSIGContextError& ex) {
+        PyErr_SetString(po_TSIGContextError, ex.what());
+    } catch (const InvalidParameter& ex) {
+        PyErr_SetString(po_InvalidParameter, ex.what());
+    } catch (const exception& ex) {
+        const string ex_what = "Unexpected failure in TSIG verify: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in TSIG verify");
+    }
+
+    return (NULL);
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// Definition of class specific exception(s)
+PyObject* po_TSIGContextError;
+
 // This defines the complete type for reflection in python and
 // This defines the complete type for reflection in python and
-// parsing of PyObject* to s_EDNS
+// parsing of PyObject* to s_TSIGContext
 // Most of the functions are not actually implemented and NULL here.
 // Most of the functions are not actually implemented and NULL here.
-PyTypeObject tsig_context_type = {
+PyTypeObject tsigcontext_type = {
     PyVarObject_HEAD_INIT(NULL, 0)
     PyVarObject_HEAD_INIT(NULL, 0)
-    "libdns_python.TSIGContext",
-    sizeof(s_TSIGContext),              // tp_basicsize
+    "pydnspp.TSIGContext",
+    sizeof(s_TSIGContext),                 // tp_basicsize
     0,                                  // tp_itemsize
     0,                                  // tp_itemsize
-    (destructor)TSIGContext_destroy,    // tp_dealloc
+    reinterpret_cast<destructor>(TSIGContext_destroy),       // tp_dealloc
     NULL,                               // tp_print
     NULL,                               // tp_print
     NULL,                               // tp_getattr
     NULL,                               // tp_getattr
     NULL,                               // tp_setattr
     NULL,                               // tp_setattr
@@ -77,16 +270,22 @@ PyTypeObject tsig_context_type = {
     NULL,                               // tp_getattro
     NULL,                               // tp_getattro
     NULL,                               // tp_setattro
     NULL,                               // tp_setattro
     NULL,                               // tp_as_buffer
     NULL,                               // tp_as_buffer
-    Py_TPFLAGS_DEFAULT,                 // tp_flags
-    "The TSIGContext class maintains a context of a signed session of "
-    "DNS transactions by TSIG.",
+
+    // We allow the python version of TSIGContext to act as a base class.
+    // From pure design point of view, this is wrong because it's not intended
+    // to be inherited.  However, cryptographic operations are generally
+    // difficult to test, so it would be very advantageous if we can define
+    // a mock context class.
+    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, // tp_flags
+
+    "The TSIGContext class objects is...(COMPLETE THIS)",
     NULL,                               // tp_traverse
     NULL,                               // tp_traverse
     NULL,                               // tp_clear
     NULL,                               // tp_clear
-    NULL,                               // tp_richcompare
+    NULL, // tp_richcompare
     0,                                  // tp_weaklistoffset
     0,                                  // tp_weaklistoffset
     NULL,                               // tp_iter
     NULL,                               // tp_iter
     NULL,                               // tp_iternext
     NULL,                               // tp_iternext
-    TSIGContext_methods,                // tp_methods
+    TSIGContext_methods,                   // tp_methods
     NULL,                               // tp_members
     NULL,                               // tp_members
     NULL,                               // tp_getset
     NULL,                               // tp_getset
     NULL,                               // tp_base
     NULL,                               // tp_base
@@ -94,7 +293,7 @@ PyTypeObject tsig_context_type = {
     NULL,                               // tp_descr_get
     NULL,                               // tp_descr_get
     NULL,                               // tp_descr_set
     NULL,                               // tp_descr_set
     0,                                  // tp_dictoffset
     0,                                  // tp_dictoffset
-    (initproc)TSIGContext_init,         // tp_init
+    reinterpret_cast<initproc>(TSIGContext_init),            // tp_init
     NULL,                               // tp_alloc
     NULL,                               // tp_alloc
     PyType_GenericNew,                  // tp_new
     PyType_GenericNew,                  // tp_new
     NULL,                               // tp_free
     NULL,                               // tp_free
@@ -108,50 +307,58 @@ PyTypeObject tsig_context_type = {
     0                                   // tp_version_tag
     0                                   // tp_version_tag
 };
 };
 
 
-int
-TSIGContext_init(s_TSIGContext* self, PyObject* args) {
-    const s_TSIGKey* tsigkey_obj;
-
-    try {
-        if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey_obj)) {
-            self->tsig_ctx = new TSIGContext(*tsigkey_obj->tsigkey);
-            return (0);
-        }
-    } catch (...) {
-        PyErr_SetString(po_IscException, "Unexpected exception");
-        return (-1);
-    }
-
-    PyErr_Clear();
-    PyErr_SetString(PyExc_TypeError,
-                    "Invalid arguments to TSIGContext constructor");
-
-    return (-1);
-}
-
-void
-TSIGContext_destroy(s_TSIGContext* const self) {
-    delete self->tsig_ctx;
-    self->tsig_ctx = NULL;
-    Py_TYPE(self)->tp_free(self);
-}
-
 // Module Initialization, all statics are initialized here
 // Module Initialization, all statics are initialized here
 bool
 bool
 initModulePart_TSIGContext(PyObject* mod) {
 initModulePart_TSIGContext(PyObject* mod) {
     // We initialize the static description object with PyType_Ready(),
     // We initialize the static description object with PyType_Ready(),
     // then add it to the module. This is not just a check! (leaving
     // then add it to the module. This is not just a check! (leaving
     // this out results in segmentation faults)
     // this out results in segmentation faults)
-    if (PyType_Ready(&tsig_context_type) < 0) {
+    if (PyType_Ready(&tsigcontext_type) < 0) {
         return (false);
         return (false);
     }
     }
-    Py_INCREF(&tsig_context_type);
-    void* p = &tsig_context_type;
-    PyModule_AddObject(mod, "TSIGContext", static_cast<PyObject*>(p));
+    void* p = &tsigcontext_type;
+    if (PyModule_AddObject(mod, "TSIGContext",
+                           static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsigcontext_type);
+
+    try {
+        // Class specific exceptions
+        po_TSIGContextError = PyErr_NewException("pydnspp.TSIGContextError",
+                                                 po_IscException, NULL);
+        PyObjectContainer(po_TSIGContextError).installToModule(
+            mod, "TSIGContextError");
+
+        // Constant class variables
+        installClassVariable(tsigcontext_type, "STATE_INIT",
+                             Py_BuildValue("I", TSIGContext::INIT));
+        installClassVariable(tsigcontext_type, "STATE_SENT_REQUEST",
+                             Py_BuildValue("I", TSIGContext::SENT_REQUEST));
+        installClassVariable(tsigcontext_type, "STATE_RECEIVED_REQUEST",
+                             Py_BuildValue("I", TSIGContext::RECEIVED_REQUEST));
+        installClassVariable(tsigcontext_type, "STATE_SENT_RESPONSE",
+                             Py_BuildValue("I", TSIGContext::SENT_RESPONSE));
+        installClassVariable(tsigcontext_type, "STATE_VERIFIED_RESPONSE",
+                             Py_BuildValue("I",
+                                           TSIGContext::VERIFIED_RESPONSE));
 
 
-    addClassVariable(tsig_context_type, "DEFAULT_FUDGE",
-                     Py_BuildValue("H", TSIGContext::DEFAULT_FUDGE));
+        installClassVariable(tsigcontext_type, "DEFAULT_FUDGE",
+                             Py_BuildValue("H", TSIGContext::DEFAULT_FUDGE));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in TSIGContext initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in TSIGContext initialization");
+        return (false);
+    }
 
 
     return (true);
     return (true);
 }
 }
-} // end of anonymous namespace
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 47 - 0
src/lib/dns/python/tsig_python.h

@@ -0,0 +1,47 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIGCONTEXT_H
+#define __PYTHON_TSIGCONTEXT_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGContext;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGContext : public PyObject {
+public:
+    s_TSIGContext();
+    TSIGContext* cppobj;
+};
+
+extern PyTypeObject tsigcontext_type;
+
+// Class specific exceptions
+extern PyObject* po_TSIGContextError;
+
+bool initModulePart_TSIGContext(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGCONTEXT_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 369 - 0
src/lib/dns/python/tsig_rdata_python.cc

@@ -0,0 +1,369 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/rdataclass.h>
+
+#include "pydnspp_common.h"
+#include "pydnspp_towire.h"
+#include "name_python.h"
+#include "tsig_rdata_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::dns::python;
+
+//
+// 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
+
+//
+// TSIG RDATA
+//
+
+// Trivial constructor.
+s_TSIG::s_TSIG() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIG, any::TSIG> TSIGContainer;
+
+//
+// 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 TSIG_init(s_TSIG* self, PyObject* args);
+void TSIG_destroy(s_TSIG* self);
+
+// These are the functions we export
+// ADD/REMOVE/MODIFY THE FOLLOWING AS APPROPRIATE FOR THE ACTUAL CLASS.
+//
+PyObject* TSIG_toText(const s_TSIG* const self);
+PyObject* TSIG_getAlgorithm(const s_TSIG* const self);
+PyObject* TSIG_getTimeSigned(const s_TSIG* const self);
+PyObject* TSIG_getFudge(const s_TSIG* const self);
+PyObject* TSIG_getOriginalID(const s_TSIG* const self);
+PyObject* TSIG_getError(const s_TSIG* const self);
+PyObject* TSIG_getMAC(const s_TSIG* const self);
+PyObject* TSIG_getOtherData(const s_TSIG* const self);
+PyObject* TSIG_str(PyObject* self);
+PyObject* TSIG_richcmp(const s_TSIG* const self,
+                       const s_TSIG* const other, int op);
+PyObject* TSIG_toWire(const s_TSIG* self, PyObject* args);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// 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 TSIG_methods[] = {
+    { "get_algorithm", reinterpret_cast<PyCFunction>(TSIG_getAlgorithm),
+      METH_NOARGS,
+      "Return the algorithm name." },
+    { "get_timesigned", reinterpret_cast<PyCFunction>(TSIG_getTimeSigned),
+      METH_NOARGS,
+      "Return the value of the Time Signed field. "
+      "The returned value does not exceed 2^48-1."
+    },
+    { "get_fudge", reinterpret_cast<PyCFunction>(TSIG_getFudge),
+      METH_NOARGS,
+      "Return the value of the Fudge field." },
+    { "get_original_id", reinterpret_cast<PyCFunction>(TSIG_getOriginalID),
+      METH_NOARGS,
+      "Return the value of the Original ID field." },
+    { "get_error", reinterpret_cast<PyCFunction>(TSIG_getError),
+      METH_NOARGS,
+      "Return the value of the Error field." },
+    { "get_mac", reinterpret_cast<PyCFunction>(TSIG_getMAC),
+      METH_NOARGS,
+      "Return the value of the MAC field."
+      "If it's empty, return None." },
+    { "get_other_data", reinterpret_cast<PyCFunction>(TSIG_getOtherData),
+      METH_NOARGS,
+      "Return the value of the Other Data field."
+      "If it's empty, return None." },
+    { "to_text", reinterpret_cast<PyCFunction>(TSIG_toText), METH_NOARGS,
+      "Returns the text representation" },
+    { "to_wire", reinterpret_cast<PyCFunction>(TSIG_toWire), METH_VARARGS,
+      "Converts the TSIG object to wire format.\n"
+      "The argument can be either a MessageRenderer or an object that "
+      "implements the sequence interface. If the object is mutable "
+      "(for instance a bytearray()), the wire data is added in-place.\n"
+      "If it is not (for instance a bytes() object), a new object is "
+      "returned" },
+    { NULL, NULL, 0, NULL }
+};
+
+int
+TSIG_init(s_TSIG* self, PyObject* args) {
+    try {
+        // constructor from string
+        const char* rdata_str;
+        if (PyArg_ParseTuple(args, "s", &rdata_str)) {
+            self->cppobj = new any::TSIG(string(rdata_str));
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct TSIG object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing TSIG");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIG constructor");
+
+    return (-1);
+}
+
+void
+TSIG_destroy(s_TSIG* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIG_getAlgorithm(const s_TSIG* const self) {
+    try {
+        return (createNameObject(self->cppobj->getAlgorithm()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to get TSIG algorithm: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "getting TSIG algorithm");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIG_getTimeSigned(const s_TSIG* const self) {
+    return (Py_BuildValue("K", self->cppobj->getTimeSigned()));
+}
+
+PyObject*
+TSIG_getFudge(const s_TSIG* const self) {
+    return (Py_BuildValue("H", self->cppobj->getFudge()));
+}
+
+PyObject*
+TSIG_getOriginalID(const s_TSIG* const self) {
+    return (Py_BuildValue("H", self->cppobj->getOriginalID()));
+}
+
+PyObject*
+TSIG_getError(const s_TSIG* const self) {
+    return (Py_BuildValue("H", self->cppobj->getError()));
+}
+
+PyObject*
+TSIG_getMAC(const s_TSIG* const self) {
+    return (Py_BuildValue("y#", self->cppobj->getMAC(),
+                          self->cppobj->getMACSize()));
+}
+
+PyObject*
+TSIG_getOtherData(const s_TSIG* const self) {
+    return (Py_BuildValue("y#", self->cppobj->getOtherData(),
+                          self->cppobj->getOtherLen()));
+}
+
+PyObject*
+TSIG_toText(const s_TSIG* const self) {
+    try {
+        // toText() could throw, so we need to catch any exceptions below.
+        return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert TSIG object to text: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting TSIG object to text");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIG_str(PyObject* self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject*
+TSIG_toWire(const s_TSIG* const self, PyObject* args) {
+    typedef any::TSIG TSIGRdata;
+    return (toWireWrapper<s_TSIG, TSIGRdata, ToWireCallVoid<const TSIGRdata> >(
+                self, args));
+}
+
+PyObject* 
+TSIG_richcmp(const s_TSIG* const self,
+                   const s_TSIG* const other,
+                   const int op)
+{
+    bool c = false;
+
+    // Check for null and if the types match. If different type,
+    // simply return False
+    if (other == NULL || (self->ob_type != other->ob_type)) {
+        Py_RETURN_FALSE;
+    }
+
+    // Only equals and not equals here, unorderable type
+    const int cmp = self->cppobj->compare(*other->cppobj);
+    switch (op) {
+    case Py_EQ:
+        c = (cmp == 0);
+        break;
+    case Py_NE:
+        c = (cmp != 0);
+        break;
+    case Py_GT:
+        c = (cmp > 0);
+        break;
+    case Py_GE:
+        c = (cmp >= 0);
+        break;
+    case Py_LT:
+        c = (cmp < 0);
+        break;
+    case Py_LE:
+        c = (cmp <= 0);
+        break;
+    default:
+        PyErr_SetString(PyExc_IndexError,
+                        "Unhandled rich comparison operator for TSIG");
+        return (NULL);
+    }
+    if (c) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_TSIG
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsig_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.TSIG",
+    sizeof(s_TSIG),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(TSIG_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
+    TSIG_str,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The TSIG class objects represents the TSIG RDATA as defined in RFC2845.",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    reinterpret_cast<richcmpfunc>(TSIG_richcmp), // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    TSIG_methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    // At the moment, we leave tp_base NULL as we won't use this class
+    // in a polymorphic way for our immediate need.
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    reinterpret_cast<initproc>(TSIG_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
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIG(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(&tsig_type) < 0) {
+        return (false);
+    }
+    void* p = &tsig_type;
+    if (PyModule_AddObject(mod, "TSIG", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsig_type);
+
+    return (true);
+}
+
+PyObject*
+createTSIGObject(const any::TSIG& source) {
+    TSIGContainer container = PyObject_New(s_TSIG, &tsig_type);
+    container.set(new any::TSIG(source));
+    return (container.release());
+}
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 57 - 0
src/lib/dns/python/tsig_rdata_python.h

@@ -0,0 +1,57 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIG_H
+#define __PYTHON_TSIG_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace any {
+class TSIG;
+}
+}
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIG : public PyObject {
+public:
+    s_TSIG();
+    const rdata::any::TSIG* cppobj;
+};
+
+extern PyTypeObject tsig_type;
+
+bool initModulePart_TSIG(PyObject* mod);
+
+/// This is A simple shortcut to create a python TSIG object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* createTSIGObject(const rdata::any::TSIG& source);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIG_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 370 - 0
src/lib/dns/python/tsigerror_python.cc

@@ -0,0 +1,370 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/tsigerror.h>
+
+#include "pydnspp_common.h"
+#include "rcode_python.h"
+#include "tsigerror_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+//
+// 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
+
+//
+// TSIGError
+//
+
+// Trivial constructor.
+s_TSIGError::s_TSIGError() : cppobj(NULL) {
+}
+
+// Import pydoc text
+#include "tsigerror_python_inc.cc"
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIGError, TSIGError> TSIGErrorContainer;
+
+//
+// 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 TSIGError_init(s_TSIGError* self, PyObject* args);
+void TSIGError_destroy(s_TSIGError* self);
+
+// These are the functions we export
+PyObject* TSIGError_getCode(const s_TSIGError* const self);
+PyObject* TSIGError_toText(const s_TSIGError* const self);
+PyObject* TSIGError_toRcode(const s_TSIGError* const self);
+PyObject* TSIGError_str(PyObject* self);
+PyObject* TSIGError_richcmp(const s_TSIGError* const self,
+                            const s_TSIGError* const other, int op);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// 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 TSIGError_methods[] = {
+    { "get_code", reinterpret_cast<PyCFunction>(TSIGError_getCode),
+      METH_NOARGS,
+      TSIGError_getCode_doc },
+    { "to_text", reinterpret_cast<PyCFunction>(TSIGError_toText), METH_NOARGS,
+      TSIGError_toText_doc },
+    { "to_rcode", reinterpret_cast<PyCFunction>(TSIGError_toRcode),
+      METH_NOARGS,
+      TSIGError_toRcode_doc },
+    { NULL, NULL, 0, NULL }
+};
+
+int
+TSIGError_init(s_TSIGError* self, PyObject* args) {
+    try {
+        // Constructor from the code value
+        long code = 0;
+        if (PyArg_ParseTuple(args, "l", &code)) {
+            if (code < 0 || code > 0xffff) {
+                PyErr_SetString(PyExc_ValueError, "TSIG error out of range");
+                return (-1);
+            }
+            self->cppobj = new TSIGError(code);
+            return (0);
+        }
+
+        // Constructor from Rcode
+        PyErr_Clear();
+        s_Rcode* py_rcode;
+        if (PyArg_ParseTuple(args, "O!", &rcode_type, &py_rcode)) {
+            self->cppobj = new TSIGError(*py_rcode->cppobj);
+            return (0);
+        }
+    } catch (const isc::OutOfRange& ex) {
+        const string ex_what = "Failed to construct TSIGError object: " +
+            string(ex.what());
+        PyErr_SetString(PyExc_ValueError, ex_what.c_str());
+        return (-1);
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct TSIGError object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing TSIGError");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIGError constructor");
+
+    return (-1);
+}
+
+void
+TSIGError_destroy(s_TSIGError* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGError_getCode(const s_TSIGError* const self) {
+    return (Py_BuildValue("I", self->cppobj->getCode()));
+}
+
+PyObject*
+TSIGError_toText(const s_TSIGError* const self) {
+    try {
+        // toText() could throw, so we need to catch any exceptions below.
+        return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert TSIGError object to text: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting TSIGError object to text");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIGError_str(PyObject* self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject*
+TSIGError_toRcode(const s_TSIGError* const self) {
+    typedef CPPPyObjectContainer<s_Rcode, Rcode> RcodePyObjectContainer;
+
+    try {
+        RcodePyObjectContainer rcode_container(PyObject_New(s_Rcode,
+                                                            &rcode_type));
+        rcode_container.set(new Rcode(self->cppobj->toRcode()));
+        return (rcode_container.release());
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert TSIGError to Rcode: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting TSIGError to Rcode");
+    }
+    return (NULL);
+}
+
+PyObject* 
+TSIGError_richcmp(const s_TSIGError* const self,
+                   const s_TSIGError* const other,
+                   const int op)
+{
+    bool c = false;
+
+    // Check for null and if the types match. If different type,
+    // simply return False
+    if (other == NULL || (self->ob_type != other->ob_type)) {
+        Py_RETURN_FALSE;
+    }
+
+    // Only equals and not equals here, unorderable type
+    switch (op) {
+    case Py_LT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_LE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_EQ:
+        c = (*self->cppobj == *other->cppobj);
+        break;
+    case Py_NE:
+        c = (*self->cppobj != *other->cppobj);
+        break;
+    case Py_GT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    case Py_GE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; TSIGError");
+        return (NULL);
+    }
+    if (c) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_TSIGError
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsigerror_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.TSIGError",
+    sizeof(s_TSIGError),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(TSIGError_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
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    TSIGError_str,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    TSIGError_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    reinterpret_cast<richcmpfunc>(TSIGError_richcmp), // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    TSIGError_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
+    reinterpret_cast<initproc>(TSIGError_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
+};
+
+namespace {
+// Trivial shortcut to create and install TSIGError constants.
+inline void
+installTSIGErrorConstant(const char* name, const TSIGError& val) {
+    TSIGErrorContainer container(PyObject_New(s_TSIGError, &tsigerror_type));
+    container.installAsClassVariable(tsigerror_type, name, new TSIGError(val));
+}
+}
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGError(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(&tsigerror_type) < 0) {
+        return (false);
+    }
+    void* p = &tsigerror_type;
+    if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsigerror_type);
+
+    try {
+        // Constant class variables
+        // Error codes (bare values)
+        installClassVariable(tsigerror_type, "BAD_SIG_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_SIG_CODE));
+        installClassVariable(tsigerror_type, "BAD_KEY_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_KEY_CODE));
+        installClassVariable(tsigerror_type, "BAD_TIME_CODE",
+                             Py_BuildValue("H", TSIGError::BAD_TIME_CODE));
+
+        // Error codes (constant objects)
+        installTSIGErrorConstant("NOERROR", TSIGError::NOERROR());
+        installTSIGErrorConstant("FORMERR", TSIGError::FORMERR());
+        installTSIGErrorConstant("SERVFAIL", TSIGError::SERVFAIL());
+        installTSIGErrorConstant("NXDOMAIN", TSIGError::NXDOMAIN());
+        installTSIGErrorConstant("NOTIMP", TSIGError::NOTIMP());
+        installTSIGErrorConstant("REFUSED", TSIGError::REFUSED());
+        installTSIGErrorConstant("YXDOMAIN", TSIGError::YXDOMAIN());
+        installTSIGErrorConstant("YXRRSET", TSIGError::YXRRSET());
+        installTSIGErrorConstant("NXRRSET", TSIGError::NXRRSET());
+        installTSIGErrorConstant("NOTAUTH", TSIGError::NOTAUTH());
+        installTSIGErrorConstant("NOTZONE", TSIGError::NOTZONE());
+        installTSIGErrorConstant("RESERVED11", TSIGError::RESERVED11());
+        installTSIGErrorConstant("RESERVED12", TSIGError::RESERVED12());
+        installTSIGErrorConstant("RESERVED13", TSIGError::RESERVED13());
+        installTSIGErrorConstant("RESERVED14", TSIGError::RESERVED14());
+        installTSIGErrorConstant("RESERVED15", TSIGError::RESERVED15());
+        installTSIGErrorConstant("BAD_SIG", TSIGError::BAD_SIG());
+        installTSIGErrorConstant("BAD_KEY", TSIGError::BAD_KEY());
+        installTSIGErrorConstant("BAD_TIME", TSIGError::BAD_TIME());
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in TSIGError initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in TSIGError initialization");
+        return (false);
+    }
+
+    return (true);
+}
+
+PyObject*
+createTSIGErrorObject(const TSIGError& source) {
+    TSIGErrorContainer container = PyObject_New(s_TSIGError, &tsigerror_type);
+    container.set(new TSIGError(source));
+    return (container.release());
+}
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 52 - 0
src/lib/dns/python/tsigerror_python.h

@@ -0,0 +1,52 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIGERROR_H
+#define __PYTHON_TSIGERROR_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGError;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGError : public PyObject {
+public:
+    s_TSIGError();
+    const TSIGError* cppobj;
+};
+
+extern PyTypeObject tsigerror_type;
+
+bool initModulePart_TSIGError(PyObject* mod);
+
+/// This is A simple shortcut to create a python TSIGError object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* createTSIGErrorObject(const TSIGError& source);
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGERROR_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 83 - 0
src/lib/dns/python/tsigerror_python_inc.cc

@@ -0,0 +1,83 @@
+namespace {
+const char* const TSIGError_doc = "\n\
+TSIG errors.\n\
+\n\
+\n\
+The TSIGError class objects represent standard errors related to TSIG\n\
+protocol operations as defined in related specifications, mainly in\n\
+RFC2845.\n\
+\n\
+TSIGError(error_code)\n\
+\n\
+Constructor from the code value.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+Parameters:\n\
+  error_code: The underlying 16-bit error code value of the TSIGError.\n\
+\n\
+TSIGError(rcode)\n\
+\n\
+Constructor from Rcode.\n\
+\n\
+As defined in RFC2845, error code values from 0 to 15 (inclusive) are\n\
+derived from the DNS RCODEs, which are represented via the Rcode class\n\
+in this library. This constructor works as a converter from these\n\
+RCODEs to corresponding TSIGError objects.\n\
+\n\
+Exceptions:\n\
+  ValueError: Given rcode is not convertible to TSIGErrors.\n\
+\n\
+Parameters:\n\
+  rcode: the Rcode from which the TSIGError should be derived.\n\
+\n\
+";
+const char* const TSIGError_getCode_doc = "get_code() -> integer\n\
+\n\
+Returns the TSIGCode error code value.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+Return Value(s):\n\
+  The underlying code value corresponding to the TSIGError.\n\
+";
+const char* const TSIGError_toText_doc = "to_text() -> string\n\
+\n\
+Convert the TSIGError to a string.\n\
+\n\
+For codes derived from RCODEs up to 15, this method returns the same\n\
+string as Rcode.to_text() for the corresponding code. For other pre-\n\
+defined code values (see TSIGError.CodeValue), this method returns a\n\
+string representation of the \"mnemonic' used for the enum and\n\
+constant objects as defined in RFC2845. For example, the string for\n\
+code value 16 is \"BADSIG\", etc. For other code values it returns a\n\
+string representation of the decimal number of the value, e.g. \"32\",\n\
+\"100\", etc.\n\
+\n\
+Exceptions:\n\
+  None\n\
+\n\
+Return Value(s):\n\
+  A string representation of the TSIGError.\n\
+";
+const char* const TSIGError_toRcode_doc = "to_rcode() -> Rcode\n\
+\n\
+Convert the TSIGError to a Rcode.\n\
+\n\
+This method returns an Rcode object that is corresponding to the TSIG\n\
+error. The returned Rcode is expected to be used by a verifying server\n\
+to specify the RCODE of a response when TSIG verification fails.\n\
+\n\
+Specifically, this method returns Rcode.NOTAUTH() for the TSIG\n\
+specific errors, BADSIG, BADKEY, BADTIME, as described in RFC2845. For\n\
+errors derived from the standard Rcode (code 0-15), it returns the\n\
+corresponding Rcode. For others, this method returns Rcode.SERVFAIL()\n\
+as a last resort.\n\
+\n\
+Exceptions:\n\
+  None: \n\
+\n\
+";
+}

+ 201 - 177
src/lib/dns/python/tsigkey_python.cc

@@ -12,12 +12,24 @@
 // 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.
 
 
-#include <new>
+#include <Python.h>
 
 
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/name.h>
 #include <dns/tsigkey.h>
 #include <dns/tsigkey.h>
+#include <dns/rdata.h>
 
 
+#include "pydnspp_common.h"
+#include "name_python.h"
+#include "tsigkey_python.h"
+
+using namespace std;
+using namespace isc::util::python;
 using namespace isc::dns;
 using namespace isc::dns;
-using namespace isc::dns::rdata;
+using namespace isc::dns::python;
 
 
 //
 //
 // Definition of the classes
 // Definition of the classes
@@ -27,19 +39,15 @@ using namespace isc::dns::rdata;
 // and static wrappers around the methods we export), a list of methods,
 // and static wrappers around the methods we export), a list of methods,
 // and a type description
 // and a type description
 
 
-namespace {
 //
 //
 // TSIGKey
 // TSIGKey
 //
 //
 
 
 // The s_* Class simply covers one instantiation of the object
 // The s_* Class simply covers one instantiation of the object
 
 
-class s_TSIGKey : public PyObject {
-public:
-    s_TSIGKey() : tsigkey(NULL) {}
-    TSIGKey* tsigkey;
-};
+s_TSIGKey::s_TSIGKey() : cppobj(NULL) {}
 
 
+namespace {
 //
 //
 // We declare the functions here, the definitions are below
 // We declare the functions here, the definitions are below
 // the type definition of the object, since both can use the other
 // the type definition of the object, since both can use the other
@@ -78,12 +86,105 @@ PyMethodDef TSIGKey_methods[] = {
     { NULL, NULL, 0, NULL }
     { NULL, NULL, 0, NULL }
 };
 };
 
 
+int
+TSIGKey_init(s_TSIGKey* self, PyObject* args) {
+    try {
+        const char* str;
+        if (PyArg_ParseTuple(args, "s", &str)) {
+            self->cppobj = new TSIGKey(str);
+            return (0);
+        }
+
+        PyErr_Clear();
+        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) == 0) {
+            if (secret_len == 0) {
+                secret = NULL;
+            }
+            self->cppobj = new TSIGKey(*key_name->cppobj,
+                                       *algorithm_name->cppobj,
+                                       secret, secret_len);
+            return (0);
+        }
+    } catch (const isc::InvalidParameter& ex) {
+        PyErr_SetString(po_InvalidParameter, ex.what());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException, "Unexpected exception");
+        return (-1);
+    }
+
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIGKey constructor");
+
+    return (-1);
+}
+
+void
+TSIGKey_destroy(s_TSIGKey* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+TSIGKey_getKeyName(const s_TSIGKey* const self) {
+    try {
+        return (createNameObject(self->cppobj->getKeyName()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to get key name of TSIGKey: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "getting key name of TSIGKey");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIGKey_getAlgorithmName(const s_TSIGKey* const self) {
+    try {
+        return (createNameObject(self->cppobj->getAlgorithmName()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to get algorithm name of TSIGKey: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "getting algorithm name of TSIGKey");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIGKey_getSecret(const s_TSIGKey* const self) {
+    return (Py_BuildValue("y#", self->cppobj->getSecret(),
+                          self->cppobj->getSecretLength()));
+}
+
+PyObject*
+TSIGKey_toText(const s_TSIGKey* self) {
+    return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
 // This defines the complete type for reflection in python and
 // This defines the complete type for reflection in python and
 // parsing of PyObject* to s_EDNS
 // parsing of PyObject* to s_EDNS
 // Most of the functions are not actually implemented and NULL here.
 // Most of the functions are not actually implemented and NULL here.
 PyTypeObject tsigkey_type = {
 PyTypeObject tsigkey_type = {
     PyVarObject_HEAD_INIT(NULL, 0)
     PyVarObject_HEAD_INIT(NULL, 0)
-    "libdns_python.TSIGKey",
+    "pydnspp.TSIGKey",
     sizeof(s_TSIGKey),                  // tp_basicsize
     sizeof(s_TSIGKey),                  // tp_basicsize
     0,                                  // tp_itemsize
     0,                                  // tp_itemsize
     (destructor)TSIGKey_destroy,        // tp_dealloc
     (destructor)TSIGKey_destroy,        // tp_dealloc
@@ -132,89 +233,6 @@ PyTypeObject tsigkey_type = {
     0                                   // tp_version_tag
     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 char* str;
-
-    const s_Name* key_name;
-    const s_Name* algorithm_name;
-    PyObject* bytes_obj;
-    const char* secret;
-    Py_ssize_t secret_len;
-
-
-    try {
-        if (PyArg_ParseTuple(args, "s", &str)) {
-            self->tsigkey = new TSIGKey(str);
-            return (0);
-        } else 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) {
-                self->tsigkey = new TSIGKey(*key_name->name,
-                                            *algorithm_name->name,
-                                            secret, secret_len);
-            return (0);
-        }
-    } catch (const isc::InvalidParameter& ex) {
-        PyErr_SetString(po_InvalidParameter, ex.what());
-        return (-1);
-    } catch (...) {
-        PyErr_SetString(po_IscException, "Unexpected exception");
-        return (-1);
-    }
-
-    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()));
-}
-
-PyObject*
-TSIGKey_toText(const s_TSIGKey* self) {
-    return (Py_BuildValue("s", self->tsigkey->toText().c_str()));
-}
-
 // Module Initialization, all statics are initialized here
 // Module Initialization, all statics are initialized here
 bool
 bool
 initModulePart_TSIGKey(PyObject* mod) {
 initModulePart_TSIGKey(PyObject* mod) {
@@ -224,33 +242,37 @@ initModulePart_TSIGKey(PyObject* mod) {
     if (PyType_Ready(&tsigkey_type) < 0) {
     if (PyType_Ready(&tsigkey_type) < 0) {
         return (false);
         return (false);
     }
     }
-    Py_INCREF(&tsigkey_type);
     void* p = &tsigkey_type;
     void* p = &tsigkey_type;
     if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
     if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
-        Py_DECREF(&tsigkey_type);
         return (false);
         return (false);
     }
     }
+    Py_INCREF(&tsigkey_type);
 
 
-    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;
+    try {
+        // Constant class variables
+        installClassVariable(tsigkey_type, "HMACMD5_NAME",
+                             createNameObject(TSIGKey::HMACMD5_NAME()));
+        installClassVariable(tsigkey_type, "HMACSHA1_NAME",
+                             createNameObject(TSIGKey::HMACSHA1_NAME()));
+        installClassVariable(tsigkey_type, "HMACSHA256_NAME",
+                             createNameObject(TSIGKey::HMACSHA256_NAME()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in TSIGKey initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in TSIGKey initialization");
+        return (false);
     }
     }
-    addClassVariable(tsigkey_type, "HMACSHA256_NAME", name);
 
 
     return (true);
     return (true);
-
-  cleanup:
-    Py_DECREF(&tsigkey_type);
-    return (false);
 }
 }
+} // namespace python
+} // namespace dns
+} // namespace isc
 //
 //
 // End of TSIGKey
 // End of TSIGKey
 //
 //
@@ -263,12 +285,9 @@ initModulePart_TSIGKey(PyObject* mod) {
 
 
 // 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;
-};
+s_TSIGKeyRing::s_TSIGKeyRing() : cppobj(NULL) {}
 
 
+namespace {
 //
 //
 // We declare the functions here, the definitions are below
 // We declare the functions here, the definitions are below
 // the type definition of the object, since both can use the other
 // the type definition of the object, since both can use the other
@@ -296,56 +315,6 @@ PyMethodDef TSIGKeyRing_methods[] = {
     { NULL, NULL, 0, NULL }
     { 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
 int
 TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
 TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
     if (!PyArg_ParseTuple(args, "")) {
     if (!PyArg_ParseTuple(args, "")) {
@@ -355,8 +324,8 @@ TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
         return (-1);
         return (-1);
     }
     }
     
     
-    self->keyring = new(nothrow) TSIGKeyRing();
-    if (self->keyring == NULL) {
+    self->cppobj = new(nothrow) TSIGKeyRing();
+    if (self->cppobj == NULL) {
         PyErr_SetString(po_IscException, "Allocating TSIGKeyRing failed");
         PyErr_SetString(po_IscException, "Allocating TSIGKeyRing failed");
         return (-1);
         return (-1);
     }
     }
@@ -366,14 +335,14 @@ TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
 
 
 void
 void
 TSIGKeyRing_destroy(s_TSIGKeyRing* self) {
 TSIGKeyRing_destroy(s_TSIGKeyRing* self) {
-    delete self->keyring;
-    self->keyring = NULL;
+    delete self->cppobj;
+    self->cppobj = NULL;
     Py_TYPE(self)->tp_free(self);
     Py_TYPE(self)->tp_free(self);
 }
 }
 
 
 PyObject*
 PyObject*
 TSIGKeyRing_size(const s_TSIGKeyRing* const self) {
 TSIGKeyRing_size(const s_TSIGKeyRing* const self) {
-    return (Py_BuildValue("I", self->keyring->size()));
+    return (Py_BuildValue("I", self->cppobj->size()));
 }
 }
 
 
 PyObject*
 PyObject*
@@ -383,7 +352,7 @@ TSIGKeyRing_add(const s_TSIGKeyRing* const self, PyObject* args) {
     if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey)) {
     if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey)) {
         try {
         try {
             const TSIGKeyRing::Result result =
             const TSIGKeyRing::Result result =
-                self->keyring->add(*tsigkey->tsigkey);
+                self->cppobj->add(*tsigkey->cppobj);
             return (Py_BuildValue("I", result));
             return (Py_BuildValue("I", result));
         } catch (...) {
         } catch (...) {
             PyErr_SetString(po_IscException, "Unexpected exception");
             PyErr_SetString(po_IscException, "Unexpected exception");
@@ -403,7 +372,7 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
 
 
     if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
     if (PyArg_ParseTuple(args, "O!", &name_type, &key_name)) {
         const TSIGKeyRing::Result result =
         const TSIGKeyRing::Result result =
-            self->keyring->remove(*key_name->name);
+            self->cppobj->remove(*key_name->cppobj);
         return (Py_BuildValue("I", result));
         return (Py_BuildValue("I", result));
     }
     }
 
 
@@ -421,14 +390,14 @@ TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
     if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name,
     if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name,
                          &name_type, &algorithm_name)) {
                          &name_type, &algorithm_name)) {
         const TSIGKeyRing::FindResult result =
         const TSIGKeyRing::FindResult result =
-            self->keyring->find(*key_name->name, *algorithm_name->name);
+            self->cppobj->find(*key_name->cppobj, *algorithm_name->cppobj);
         if (result.key != NULL) {
         if (result.key != NULL) {
             s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
             s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
             if (key == NULL) {
             if (key == NULL) {
                 return (NULL);
                 return (NULL);
             }
             }
-            key->tsigkey = new(nothrow) TSIGKey(*result.key);
-            if (key->tsigkey == NULL) {
+            key->cppobj = new(nothrow) TSIGKey(*result.key);
+            if (key->cppobj == NULL) {
                 Py_DECREF(key);
                 Py_DECREF(key);
                 PyErr_SetString(po_IscException,
                 PyErr_SetString(po_IscException,
                                 "Allocating TSIGKey object failed");
                                 "Allocating TSIGKey object failed");
@@ -442,6 +411,60 @@ TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
 
 
     return (NULL);
     return (NULL);
 }
 }
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+PyTypeObject tsigkeyring_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.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
+};
 
 
 bool
 bool
 initModulePart_TSIGKeyRing(PyObject* mod) {
 initModulePart_TSIGKeyRing(PyObject* mod) {
@@ -465,5 +488,6 @@ initModulePart_TSIGKeyRing(PyObject* mod) {
 
 
     return (true);
     return (true);
 }
 }
-
-} // end of unnamed namespace
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 53 - 0
src/lib/dns/python/tsigkey_python.h

@@ -0,0 +1,53 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIGKEY_H
+#define __PYTHON_TSIGKEY_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGKey;
+class TSIGKeyRing;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGKey : public PyObject {
+public:
+    s_TSIGKey();
+    TSIGKey* cppobj;
+};
+
+class s_TSIGKeyRing : public PyObject {
+public:
+    s_TSIGKeyRing();
+    TSIGKeyRing* cppobj;
+};
+
+extern PyTypeObject tsigkey_type;
+extern PyTypeObject tsigkeyring_type;
+
+bool initModulePart_TSIGKey(PyObject* mod);
+bool initModulePart_TSIGKeyRing(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGKEY_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 311 - 0
src/lib/dns/python/tsigrecord_python.cc

@@ -0,0 +1,311 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/tsigrecord.h>
+
+#include "pydnspp_common.h"
+#include "pydnspp_towire.h"
+#include "name_python.h"
+#include "tsig_rdata_python.h"
+#include "tsigrecord_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+//
+// 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
+
+//
+// TSIGRecord
+//
+
+// Trivial constructor.
+s_TSIGRecord::s_TSIGRecord() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_TSIGRecord, TSIGRecord> TSIGRecordContainer;
+
+//
+// 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 TSIGRecord_init(s_TSIGRecord* self, PyObject* args);
+void TSIGRecord_destroy(s_TSIGRecord* self);
+PyObject* TSIGRecord_toText(const s_TSIGRecord* const self);
+PyObject* TSIGRecord_str(PyObject* self);
+PyObject* TSIGRecord_toWire(const s_TSIGRecord* self, PyObject* args);
+PyObject* TSIGRecord_getName(const s_TSIGRecord* self);
+PyObject* TSIGRecord_getLength(const s_TSIGRecord* self);
+PyObject* TSIGRecord_getRdata(const s_TSIGRecord* self);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// 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 TSIGRecord_methods[] = {
+    { "get_name", reinterpret_cast<PyCFunction>(TSIGRecord_getName),
+      METH_NOARGS,
+      "Return the owner name of the TSIG RR, which is the TSIG key name" },
+    { "get_length", reinterpret_cast<PyCFunction>(TSIGRecord_getLength),
+      METH_NOARGS,
+      "Return the length of the TSIG record" },
+    { "get_rdata", reinterpret_cast<PyCFunction>(TSIGRecord_getRdata),
+      METH_NOARGS,
+      "Return the RDATA of the TSIG RR" },
+    { "to_text", reinterpret_cast<PyCFunction>(TSIGRecord_toText), METH_NOARGS,
+      "Returns the text representation" },
+    { "to_wire", reinterpret_cast<PyCFunction>(TSIGRecord_toWire),
+      METH_VARARGS,
+      "Converts the TSIGRecord object to wire format.\n"
+      "The argument can be either a MessageRenderer or an object that "
+      "implements the sequence interface. If the object is mutable "
+      "(for instance a bytearray()), the wire data is added in-place.\n"
+      "If it is not (for instance a bytes() object), a new object is "
+      "returned" },
+    { NULL, NULL, 0, NULL }
+};
+
+int
+TSIGRecord_init(s_TSIGRecord* self, PyObject* args) {
+    try {
+        const s_Name* py_name;
+        const s_TSIG* py_tsig;
+        if (PyArg_ParseTuple(args, "O!O!", &name_type, &py_name,
+                             &tsig_type, &py_tsig)) {
+            self->cppobj = new TSIGRecord(*py_name->cppobj, *py_tsig->cppobj);
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct TSIGRecord object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing TSIGRecord");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to TSIGRecord constructor");
+
+    return (-1);
+}
+
+// This is a template of typical code logic of python object destructor.
+// In many cases you can use it without modification, but check that carefully.
+void
+TSIGRecord_destroy(s_TSIGRecord* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+// This should be able to be used without modification as long as the
+// underlying C++ class has toText().
+PyObject*
+TSIGRecord_toText(const s_TSIGRecord* const self) {
+    try {
+        // toText() could throw, so we need to catch any exceptions below.
+        return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert TSIGRecord object to text: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting TSIGRecord object to text");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIGRecord_str(PyObject* self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject*
+TSIGRecord_toWire(const s_TSIGRecord* const self, PyObject* args) {
+    typedef ToWireCallInt<const TSIGRecord> ToWireCall;
+    PyObject* (*towire_fn)(const s_TSIGRecord* const, PyObject*) =
+        toWireWrapper<s_TSIGRecord, TSIGRecord, ToWireCall>;
+    return (towire_fn(self, args));
+}
+
+PyObject*
+TSIGRecord_getName(const s_TSIGRecord* const self) {
+    try {
+        return (createNameObject(self->cppobj->getName()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to get TSIGRecord name: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "getting TSIGRecord name");
+    }
+    return (NULL);
+}
+
+PyObject*
+TSIGRecord_getLength(const s_TSIGRecord* const self) {
+    return (Py_BuildValue("H", self->cppobj->getLength()));
+}
+
+PyObject*
+TSIGRecord_getRdata(const s_TSIGRecord* const self) {
+    try {
+        return (createTSIGObject(self->cppobj->getRdata()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to get TSIGRecord RDATA: " + string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "getting TSIGRecord RDATA");
+    }
+    return (NULL);
+}
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_TSIGRecord
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject tsigrecord_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.TSIGRecord",
+    sizeof(s_TSIGRecord),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(TSIGRecord_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
+    TSIGRecord_str,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The TSIGRecord class objects is...(COMPLETE THIS)",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    TSIGRecord_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
+    reinterpret_cast<initproc>(TSIGRecord_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
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_TSIGRecord(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(&tsigrecord_type) < 0) {
+        return (false);
+    }
+    void* p = &tsigrecord_type;
+    if (PyModule_AddObject(mod, "TSIGRecord", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&tsigrecord_type);
+
+    // The following template is the typical procedure for installing class
+    // variables.  If the class doesn't have a class variable, remove the
+    // entire try-catch clauses.
+    try {
+        // Constant class variables
+        installClassVariable(tsigrecord_type, "TSIG_TTL",
+                             Py_BuildValue("I", 0));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in TSIGRecord initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in TSIGRecord initialization");
+        return (false);
+    }
+
+    return (true);
+}
+
+PyObject*
+createTSIGRecordObject(const TSIGRecord& source) {
+    TSIGRecordContainer container = PyObject_New(s_TSIGRecord,
+                                                 &tsigrecord_type);
+    container.set(new TSIGRecord(source));
+    return (container.release());
+}
+} // namespace python
+} // namespace dns
+} // namespace isc

+ 53 - 0
src/lib/dns/python/tsigrecord_python.h

@@ -0,0 +1,53 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYTHON_TSIGRECORD_H
+#define __PYTHON_TSIGRECORD_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class TSIGRecord;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_TSIGRecord : public PyObject {
+public:
+    s_TSIGRecord();
+    TSIGRecord* cppobj;
+};
+
+extern PyTypeObject tsigrecord_type;
+
+bool initModulePart_TSIGRecord(PyObject* mod);
+
+/// This is A simple shortcut to create a python TSIGRecord object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* createTSIGRecordObject(const TSIGRecord& source);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_TSIGRECORD_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 1
src/lib/dns/rdata/any_255/tsig_250.cc

@@ -71,7 +71,7 @@ getToken(istringstream& iss, const string& full_input) {
     string token;
     string token;
     iss >> token;
     iss >> token;
     if (iss.bad() || iss.fail()) {
     if (iss.bad() || iss.fail()) {
-        isc_throw(InvalidRdataText, "Invalid TSIG text: parse error" <<
+        isc_throw(InvalidRdataText, "Invalid TSIG text: parse error " <<
                   full_input);
                   full_input);
     }
     }
     return (token);
     return (token);

+ 1 - 7
src/lib/dns/tsigerror.h

@@ -22,17 +22,11 @@
 
 
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
-
-class RRClass;
-
 /// TSIG errors
 /// TSIG errors
 ///
 ///
 /// The \c TSIGError class objects represent standard errors related to
 /// The \c TSIGError class objects represent standard errors related to
 /// TSIG protocol operations as defined in related specifications, mainly
 /// TSIG protocol operations as defined in related specifications, mainly
 /// in RFC2845.
 /// in RFC2845.
-///
-/// (RCODEs) of the header section of DNS messages, and extended response
-/// codes as defined in the EDNS specification.
 class TSIGError {
 class TSIGError {
 public:
 public:
     /// Constants for pre-defined TSIG error values.
     /// Constants for pre-defined TSIG error values.
@@ -58,7 +52,7 @@ public:
     ///
     ///
     /// \exception None
     /// \exception None
     ///
     ///
-    /// \param code The underlying 16-bit error code value of the \c TSIGError.
+    /// \param error_code The underlying 16-bit error code value of the \c TSIGError.
     explicit TSIGError(uint16_t error_code) : code_(error_code) {}
     explicit TSIGError(uint16_t error_code) : code_(error_code) {}
 
 
     /// Constructor from \c Rcode.
     /// Constructor from \c Rcode.

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

@@ -1,6 +1,7 @@
 SUBDIRS = isc
 SUBDIRS = isc
 
 
 python_PYTHON =	bind10_config.py
 python_PYTHON =	bind10_config.py
+pythondir = $(pyexecdir)
 
 
 # Explicitly define DIST_COMMON so ${python_PYTHON} is not included
 # Explicitly define DIST_COMMON so ${python_PYTHON} is not included
 # as we don't want the generated file included in distributed tarfile.
 # as we don't want the generated file included in distributed tarfile.

+ 1 - 1
src/lib/python/bind10_config.py.in

@@ -27,6 +27,7 @@ def reload():
                                            "@PACKAGE_NAME@",
                                            "@PACKAGE_NAME@",
                                            "msgq_socket").replace("${prefix}",
                                            "msgq_socket").replace("${prefix}",
                                                                   "@prefix@")
                                                                   "@prefix@")
+    PREFIX = "@prefix@"
 
 
     # If B10_FROM_SOURCE is set in the environment, we use data files
     # If B10_FROM_SOURCE is set in the environment, we use data files
     # from a directory relative to the value of that variable, or, if defined,
     # from a directory relative to the value of that variable, or, if defined,
@@ -44,7 +45,6 @@ def reload():
         PLUGIN_PATHS = [os.environ["B10_FROM_SOURCE"] +
         PLUGIN_PATHS = [os.environ["B10_FROM_SOURCE"] +
                             '/src/bin/cfgmgr/plugins']
                             '/src/bin/cfgmgr/plugins']
     else:
     else:
-        PREFIX = "@prefix@"
         DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
         DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
         PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
         PLUGIN_PATHS = ["@prefix@/share/@PACKAGE@/config_plugins"]
     # For testing the plugins so they can find their own spec files
     # For testing the plugins so they can find their own spec files

+ 9 - 0
src/lib/python/isc/config/config_data.py

@@ -213,6 +213,15 @@ class ConfigData:
             return spec['item_default'], True
             return spec['item_default'], True
         return None, False
         return None, False
 
 
+    def get_default_value(self, identifier):
+        """Returns the default from the specification, or None if there
+           is no default"""
+        spec = find_spec_part(self.specification.get_config_spec(), identifier)
+        if spec and 'item_default' in spec:
+            return spec['item_default']
+        else:
+            return None
+
     def get_module_spec(self):
     def get_module_spec(self):
         """Returns the ModuleSpec object associated with this ConfigData"""
         """Returns the ModuleSpec object associated with this ConfigData"""
         return self.specification
         return self.specification

+ 21 - 0
src/lib/python/isc/config/tests/config_data_test.py

@@ -237,6 +237,27 @@ class TestConfigData(unittest.TestCase):
         self.assertEqual(None, value)
         self.assertEqual(None, value)
         self.assertEqual(False, default)
         self.assertEqual(False, default)
 
 
+    def test_get_default_value(self):
+        self.assertEqual(1, self.cd.get_default_value("item1"))
+        self.assertEqual('default', self.cd.get_default_value("item6/value1"))
+        self.assertEqual(None, self.cd.get_default_value("item6/value2"))
+
+        # set some local values to something else, and see if we
+        # still get the default
+        self.cd.set_local_config({"item1": 2, "item6": { "value1": "asdf" } })
+
+        self.assertEqual((2, False), self.cd.get_value("item1"))
+        self.assertEqual(1, self.cd.get_default_value("item1"))
+        self.assertEqual(('asdf', False), self.cd.get_value("item6/value1"))
+        self.assertEqual('default', self.cd.get_default_value("item6/value1"))
+
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.cd.get_default_value,
+                          "does_not_exist/value1")
+        self.assertRaises(isc.cc.data.DataNotFoundError,
+                          self.cd.get_default_value,
+                          "item6/doesnotexist")
+
     def test_set_local_config(self):
     def test_set_local_config(self):
         self.cd.set_local_config({"item1": 2})
         self.cd.set_local_config({"item1": 2})
         value, default = self.cd.get_value("item1")
         value, default = self.cd.get_value("item1")

+ 6 - 2
src/lib/python/isc/notify/notify_out.py

@@ -142,13 +142,17 @@ class NotifyOut:
         if zone_id not in self._notify_infos:
         if zone_id not in self._notify_infos:
             return
             return
 
 
+        # Has no slave servers, skip it.
+        if (len(self._notify_infos[zone_id].notify_slaves) <= 0):
+            return
+
         with self._lock:
         with self._lock:
             if (self.notify_num >= _MAX_NOTIFY_NUM) or (zone_id in self._notifying_zones):
             if (self.notify_num >= _MAX_NOTIFY_NUM) or (zone_id in self._notifying_zones):
                 if zone_id not in self._waiting_zones:
                 if zone_id not in self._waiting_zones:
                     self._waiting_zones.append(zone_id)
                     self._waiting_zones.append(zone_id)
             else:
             else:
                 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)
                 self._notifying_zones.append(zone_id)
 
 
     def _dispatcher(self, started_event):
     def _dispatcher(self, started_event):
@@ -300,7 +304,7 @@ class NotifyOut:
         try:
         try:
             r_fds, w, e = select.select(valid_socks, [], [], block_timeout)
             r_fds, w, e = select.select(valid_socks, [], [], block_timeout)
         except select.error as err:
         except select.error as err:
-            if err.args[0] != EINTR:
+            if err.args[0] != errno.EINTR:
                 return {}, {}
                 return {}, {}
 
 
         if self._read_sock in r_fds: # user has called shutdown()
         if self._read_sock in r_fds: # user has called shutdown()

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

@@ -99,36 +99,51 @@ class TestNotifyOut(unittest.TestCase):
         self._notify._notify_infos[('example.org.', 'IN')] = MockZoneNotifyInfo('example.org.', 'IN')
         self._notify._notify_infos[('example.org.', 'IN')] = MockZoneNotifyInfo('example.org.', 'IN')
         self._notify._notify_infos[('example.org.', 'CH')] = MockZoneNotifyInfo('example.org.', 'CH')
         self._notify._notify_infos[('example.org.', 'CH')] = MockZoneNotifyInfo('example.org.', 'CH')
 
 
-        info = self._notify._notify_infos[('example.net.', 'IN')]
-        info.notify_slaves.append(('127.0.0.1', 53))
-        info.notify_slaves.append(('1.1.1.1', 5353))
+        net_info = self._notify._notify_infos[('example.net.', 'IN')]
+        net_info.notify_slaves.append(('127.0.0.1', 53))
+        net_info.notify_slaves.append(('1.1.1.1', 5353))
+        com_info = self._notify._notify_infos[('example.com.', 'IN')]
+        com_info.notify_slaves.append(('1.1.1.1', 5353))
+        com_ch_info = self._notify._notify_infos[('example.com.', 'CH')]
+        com_ch_info.notify_slaves.append(('1.1.1.1', 5353))
 
 
     def tearDown(self):
     def tearDown(self):
         self._db_file.close()
         self._db_file.close()
         os.unlink(self._db_file.name)
         os.unlink(self._db_file.name)
 
 
     def test_send_notify(self):
     def test_send_notify(self):
+        notify_out._MAX_NOTIFY_NUM = 2
+
         self._notify.send_notify('example.net')
         self._notify.send_notify('example.net')
         self.assertEqual(self._notify.notify_num, 1)
         self.assertEqual(self._notify.notify_num, 1)
-        self.assertEqual(self._notify._notifying_zones[0], ('example.net.','IN'))
+        self.assertEqual(self._notify._notifying_zones[0], ('example.net.', 'IN'))
 
 
         self._notify.send_notify('example.com')
         self._notify.send_notify('example.com')
         self.assertEqual(self._notify.notify_num, 2)
         self.assertEqual(self._notify.notify_num, 2)
-        self.assertEqual(self._notify._notifying_zones[1], ('example.com.','IN'))
+        self.assertEqual(self._notify._notifying_zones[1], ('example.com.', 'IN'))
 
 
-        notify_out._MAX_NOTIFY_NUM = 3
+        # notify_num is equal to MAX_NOTIFY_NUM, append it to waiting_zones list.
         self._notify.send_notify('example.com', 'CH')
         self._notify.send_notify('example.com', 'CH')
-        self.assertEqual(self._notify.notify_num, 3)
-        self.assertEqual(self._notify._notifying_zones[2], ('example.com.','CH'))
-
-        self._notify.send_notify('example.org.')
-        self.assertEqual(self._notify._waiting_zones[0], ('example.org.', 'IN'))
-        self._notify.send_notify('example.org.')
+        self.assertEqual(self._notify.notify_num, 2)
         self.assertEqual(1, len(self._notify._waiting_zones))
         self.assertEqual(1, len(self._notify._waiting_zones))
 
 
+        # zone_id is already in notifying_zones list, append it to waiting_zones list.
+        self._notify.send_notify('example.net')
+        self.assertEqual(2, len(self._notify._waiting_zones))
+        self.assertEqual(self._notify._waiting_zones[1], ('example.net.', 'IN'))
+
+        # zone_id is already in waiting_zones list, skip it.
+        self._notify.send_notify('example.net')
+        self.assertEqual(2, len(self._notify._waiting_zones))
+
+        # has no slave masters, skip it.
         self._notify.send_notify('example.org.', 'CH')
         self._notify.send_notify('example.org.', 'CH')
+        self.assertEqual(self._notify.notify_num, 2)
+        self.assertEqual(2, len(self._notify._waiting_zones))
+
+        self._notify.send_notify('example.org.')
+        self.assertEqual(self._notify.notify_num, 2)
         self.assertEqual(2, len(self._notify._waiting_zones))
         self.assertEqual(2, len(self._notify._waiting_zones))
-        self.assertEqual(self._notify._waiting_zones[1], ('example.org.', 'CH'))
 
 
     def test_wait_for_notify_reply(self):
     def test_wait_for_notify_reply(self):
         self._notify.send_notify('example.net.')
         self._notify.send_notify('example.net.')
@@ -171,6 +186,7 @@ class TestNotifyOut(unittest.TestCase):
         self._notify.send_notify('example.net.')
         self._notify.send_notify('example.net.')
         self._notify.send_notify('example.com.')
         self._notify.send_notify('example.com.')
         notify_out._MAX_NOTIFY_NUM = 2
         notify_out._MAX_NOTIFY_NUM = 2
+        # zone example.org. has no slave servers.
         self._notify.send_notify('example.org.')
         self._notify.send_notify('example.org.')
         self._notify.send_notify('example.com.', 'CH')
         self._notify.send_notify('example.com.', 'CH')
 
 
@@ -179,17 +195,19 @@ class TestNotifyOut(unittest.TestCase):
         self.assertEqual(0, info.notify_try_num)
         self.assertEqual(0, info.notify_try_num)
         self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
         self.assertEqual(info.get_current_notify_target(), ('1.1.1.1', 5353))
         self.assertEqual(2, self._notify.notify_num)
         self.assertEqual(2, self._notify.notify_num)
+        self.assertEqual(1, len(self._notify._waiting_zones))
 
 
         self._notify._notify_next_target(info)
         self._notify._notify_next_target(info)
         self.assertEqual(0, info.notify_try_num)
         self.assertEqual(0, info.notify_try_num)
         self.assertIsNone(info.get_current_notify_target())
         self.assertIsNone(info.get_current_notify_target())
         self.assertEqual(2, self._notify.notify_num)
         self.assertEqual(2, self._notify.notify_num)
-        self.assertEqual(1, len(self._notify._waiting_zones))
+        self.assertEqual(0, len(self._notify._waiting_zones))
 
 
         example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
         example_com_info = self._notify._notify_infos[('example.com.', 'IN')]
         self._notify._notify_next_target(example_com_info)
         self._notify._notify_next_target(example_com_info)
-        self.assertEqual(2, self._notify.notify_num)
-        self.assertEqual(2, len(self._notify._notifying_zones))
+        self.assertEqual(1, self._notify.notify_num)
+        self.assertEqual(1, len(self._notify._notifying_zones))
+        self.assertEqual(0, len(self._notify._waiting_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'))

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

@@ -1 +1 @@
-EXTRA_DIST = __init__.py parse_args.py
+EXTRA_DIST = __init__.py parse_args.py tsigctx_mock.py

+ 53 - 0
src/lib/python/isc/testutils/tsigctx_mock.py

@@ -0,0 +1,53 @@
+# Copyright (C) 2011  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 pydnspp import *
+
+class MockTSIGContext(TSIGContext):
+    """Tthis is a mock of TSIGContext class for testing.
+    Via its "error" attribute, you can fake the result of verify(), thereby
+    you can test many of TSIG related tests without requiring actual crypto
+    setups.  "error" should be either a TSIGError type value or a callable
+    object (typically a function).  In the latter case, the callable object
+    will take the context as a parameter, and is expected to return a
+    TSIGError object.
+    """
+
+    def __init__(self, tsig_key):
+        super().__init__(tsig_key)
+        self.error = None
+        self.verify_called = 0  # number of verify() called
+
+    def sign(self, qid, data):
+        """Transparently delegate the processing to the super class.
+        It doesn't matter much anyway because normal applications that would
+        be implemented in Python normally won't call TSIGContext.sign()
+        directly.
+        """
+        return super().sign(qid, data)
+
+    def verify(self, tsig_record, data):
+        self.verify_called += 1
+        # call real "verify" so that we can notice any misue (which would
+        # result in exception.
+        super().verify(tsig_record, data)
+        return self.get_error()
+
+    def get_error(self):
+        if self.error is None:
+            return super().get_error()
+        if hasattr(self.error, '__call__'):
+            return self.error(self)
+        return self.error

+ 1 - 1
src/lib/server_common/keyring.cc

@@ -44,7 +44,7 @@ initKeyring(config::ModuleCCSession& session) {
         // We are already initialized
         // We are already initialized
         return;
         return;
     }
     }
-    session.addRemoteConfig("tsig_keys", updateKeyring);
+    session.addRemoteConfig("tsig_keys", updateKeyring, false);
 }
 }
 
 
 void
 void

+ 3 - 1
src/lib/server_common/tests/Makefile.am

@@ -28,7 +28,7 @@ TESTS += run_unittests
 run_unittests_SOURCES  = run_unittests.cc
 run_unittests_SOURCES  = run_unittests.cc
 run_unittests_SOURCES += portconfig_unittest.cc
 run_unittests_SOURCES += portconfig_unittest.cc
 run_unittests_SOURCES += keyring_test.cc
 run_unittests_SOURCES += keyring_test.cc
-run_unittests_SOURCES += data_path.h
+nodist_run_unittests_SOURCES = data_path.h
 
 
 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)
@@ -45,3 +45,5 @@ run_unittests_LDADD += $(top_builddir)/src/lib/config/tests/libfake_session.la
 endif
 endif
 
 
 noinst_PROGRAMS = $(TESTS)
 noinst_PROGRAMS = $(TESTS)
+
+EXTRA_DIST = testdata/spec.spec

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

@@ -1,4 +1,4 @@
-SUBDIRS = . tests unittests io
+SUBDIRS = . tests unittests io pyunittests
 # The io/tests is hack, because otherwise we can not order these directories
 # The io/tests is hack, because otherwise we can not order these directories
 # properly. Unittests use io and io/tests use unittest.
 # properly. Unittests use io and io/tests use unittest.
 
 
@@ -24,5 +24,7 @@ libutil_la_SOURCES += encode/binary_from_base16.h
 libutil_la_SOURCES += random/qid_gen.h random/qid_gen.cc
 libutil_la_SOURCES += random/qid_gen.h random/qid_gen.cc
 libutil_la_SOURCES += random/random_number_generator.h
 libutil_la_SOURCES += random/random_number_generator.h
 
 
+EXTRA_DIST = python/pycppwrapper_util.h
+
 libutil_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libutil_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 CLEANFILES = *.gcno *.gcda
 CLEANFILES = *.gcno *.gcda

+ 100 - 0
src/lib/util/python/mkpywrapper.py.in

@@ -0,0 +1,100 @@
+#!@PYTHON@
+
+# Copyright (C) 2011  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.
+
+"""This utility program generates a C++ header and implementation files
+that can be used as a template of C++ python binding for a C++ class.
+
+Usage: ./mkpywrapper.py ClassName
+(the script should be run on this directory)
+
+It will generate two files: classname_python.h and classname_python.cc,
+many of whose definitions are in the namespace isc::MODULE_NAME::python.
+By default MODULE_NAME will be 'dns' (because this tool is originally
+intended to be used for the C++ python binding of the DNS library), but
+can be changed via the -m command line option.
+
+The generated files contain code fragments that are commonly used in
+C++ python binding implementations.  It will define a class named
+s_ClassName which is a derived class of PyModule and can meet the
+requirement of the CPPPyObjectContainer template class (see
+pycppwrapper_util.h).  It also defines (and declares in the header file)
+"classname_type", which is of PyTypeObject and is intended to be used
+to define details of the python bindings for the ClassName class.
+
+In many cases the header file can be used as a startpoint of the
+binding development without modification.  But you may want to make
+ClassName::cppobj a constant variable (and you should if you can).
+Many definitions of classname_python.cc should also be able to be used
+just as defined, but some will need to be changed or removed.  In
+particular, you should at least adjust ClassName_init().  You'll
+probably also need to add more definitions to that file to provide
+complete features of the C++ class.
+"""
+
+import datetime, string, sys
+from optparse import OptionParser
+
+# Remember the current year to produce the copyright boilerplate
+YEAR = datetime.date.today().timetuple()[0]
+
+def dump_file(out_file, temp_file, class_name, module):
+    for line in temp_file.readlines():
+        line = line.replace("@YEAR@", str(YEAR))
+        line = line.replace("@CPPCLASS@_H", class_name.upper() + "_H")
+        line = line.replace("@CPPCLASS@", class_name)
+        line = line.replace("@cppclass@", class_name.lower())
+        line = line.replace("@MODULE@", module)
+        out_file.write(line)
+
+def dump_wrappers(class_name, output, module):
+    try:
+        if output == "-":
+            header_file = sys.stdout
+        else:
+            header_file = open(output + "_python.h", "w")
+        header_template_file = open("wrapper_template.h", "r")
+        if output == "-":
+            impl_file = sys.stdout
+        else:
+            impl_file = open(output + "_python.cc", "w")
+        impl_template_file = open("wrapper_template.cc", "r")
+    except:
+        sys.stderr.write('Failed to open C++ file(s)\n')
+        sys.exit(1)
+    dump_file(header_file, header_template_file, class_name, module)
+    dump_file(impl_file, impl_template_file, class_name, module)
+
+usage = '''usage: %prog [options] class_name'''
+
+if __name__ == "__main__":
+    parser = OptionParser(usage=usage)
+    parser.add_option('-o', '--output', action='store', dest='output',
+                      default=None, metavar='FILE',
+                      help='prefix of output file names [default: derived from the class name]')
+    parser.add_option('-m', '--module', action='store', dest='module',
+                      default='dns',
+                      help='C++ module name of the wrapper (for namespaces) [default: dns]')
+    (options, args) = parser.parse_args()
+
+    if len(args) == 0:
+        parser.error('input file is missing')
+
+    class_name = args[0]
+    if not options.output:
+        options.output = class_name.lower()
+
+    dump_wrappers(class_name, options.output, options.module)

+ 308 - 0
src/lib/util/python/pycppwrapper_util.h

@@ -0,0 +1,308 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __PYCPPWRAPPER_UTIL_H
+#define __PYCPPWRAPPER_UTIL_H 1
+
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
+/**
+ * @file pycppwrapper_util.h
+ * @short Shared definitions for python/C(++) API
+ *
+ * This utility defines a set of convenient wrappers for the python C API
+ * to use it safely from our C++ bindings.  The python C API has many pitfalls
+ * such as not-so-consistent reference count policies.  Also, many existing
+ * examples are careless about error handling.  It's easy to find on the net
+ * example (even of "production use") python extensions like this:
+ *
+ * \code
+ *     new_exception = PyErr_NewException("mymodule.Exception", NULL, NULL);
+ *     // new_exception can be NULL, in which case the call to
+ *     // PyModule_AddObject will cause a surprising disruption.
+ *     PyModule_AddObject(mymodule, "Exception", new_exception); \endcode
+ *
+ * When using the python C API with C++, we should also be careful about
+ * exception safety.  The underlying C++ code (including standard C++ libraries
+ * and memory allocation) can throw exceptions, in which case we need to
+ * make sure any intermediate python objects are cleaned up (we also need to
+ * catch the C++ exceptions inside the binding and convert them to python
+ * errors, but that's a different subject).  This is not a trivial task
+ * because the python objects are represented as bare C pointers (so there's
+ * no destructor) and we need to address the exception safety along with python
+ * reference counters (so we cannot naively apply standard smart pointers).
+ *
+ * This utility tries to help address these issues.
+ *
+ * Also, it's intentional that this is a header-only utility.  This way the
+ * C++ loadable module won't depend on another C++ library (which is not
+ * necessarily wrong, but would increase management cost such as link-time
+ * troubles only for a small utility feature).
+ */
+
+namespace isc {
+namespace util {
+namespace python {
+
+/// This is thrown inside this utility when it finds a NULL pointer is passed
+/// when it should not be NULL.
+class PyCPPWrapperException : public isc::Exception {
+public:
+    PyCPPWrapperException(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// This helper class is similar to the standard autoptr and manages PyObject
+/// using some kind of RAII techniques.  It is, however, customized for the
+/// python C API.
+///
+/// A PyObjectContainer object is constructed with a pointer to PyObject,
+/// which is often just created dynamically.  The caller will eventually
+/// attach the object to a different python object (often a module or class)
+/// via specific methods or directly return it to the python interpreter.
+///
+/// There are two cases in destructing the object: with or without decreasing
+/// a reference to the PyObject.  If the object is intended to be an argument
+/// to another python C library that increases the reference to the object for
+/// itself, we should normally release our own reference; otherwise the
+/// reference will leak and the object won't be garbage collected.  Also, when
+/// an unexpected error happens in the form of C++ exception, we should
+/// release the reference to prevent resource leak.
+///
+/// In some other cases, we should simply give our reference to the caller.
+/// That is the case when the created object itself is a return value of
+/// an extended python method written in the C++ binding.  Likewise, some
+/// python C library functions "steal" the reference.  In these cases we
+/// should not decrease the reference; otherwise it would cause duplicate free.
+///
+/// By default, the destructor of this class releases the reference to the
+/// PyObject.  If this behavior is desirable, you can extract the original
+/// bare pointer to the PyObject by the \c get() method.  If you don't want
+/// the reference to be decreased, the original bare pointer should be
+/// extracted using the \c release() method.
+///
+/// There are two convenience methods for commonly used operations:
+/// \c installAsClassVariable() to add the PyObject as a class variable
+/// and \c installToModule to add the PyObject to a specified python module.
+/// These methods (at least to some extent) take care of the reference to
+/// the object (either release or keep) depending on the usage context so
+/// that the user don't have to worry about it.
+///
+/// On construction, this class expects the pointer can be NULL.
+/// If it happens it immediately throws a \c PyCPPWrapperException exception.
+/// This behavior is to convert failures in the python C API (such as
+/// PyObject_New() returning NULL) to C++ exception so that we can unify
+/// error handling in the style of C++ exceptions.
+///
+/// Examples 1: To create a tuple of two python objects, do this:
+///
+/// \code
+///     try {
+///         PyObjectContainer container0(Py_BuildValue("I", 0));
+///         PyObjectContainer container1(Py_BuildValue("s", cppobj.toText().c_str()));
+///         return (Py_BuildValue("OO", container0.get(), container1.get()));
+///     } catch { ... set python exception, etc ... } \endcode
+///
+/// Commonly deployed buggy implementation to achieve this would be like this:
+/// \code
+///    return (Py_BuildValue("OO", Py_BuildValue("I", 0),
+///                          Py_BuildValue("s", cppobj.toText().c_str())));
+/// \endcode
+/// One clear bug of this code is that references to the element objects of
+/// the tuple will leak.
+/// (Assuming \c cppobj.toText() can throw) this code is also not exception
+/// safe; if \c cppobj.toText() throws the reference to the first object
+/// will leak, even if the code tried to do the necessary cleanup in the
+/// successful case.
+/// Further, this code naively passes the result of the first two calls to
+/// \c Py_BuildValue() to the third one even if they can be NULL.
+/// In this specific case, it happens to be okay because \c Py_BuildValue()
+/// accepts NULL and treats it as an indication of error.  But not all
+/// python C library works that way (remember, the API is so inconsistent)
+/// and we need to refer to the API manual every time we have to worry about
+/// passing a NULL object to a library function.  We'd certainly like to
+/// avoid such development overhead.  The code using \c PyObjectContainer
+/// addresses all these problems.
+///
+/// Examples 2: Install a (constant) variable to a class.
+///
+/// \code
+///    try {
+///        // installClassVariable is a wrapper of
+///        // PyObjectContainer::installAsClassVariable.  See below.
+///        installClassVariable(myclass_type, "SOME_CONSTANT",
+///                             Py_BuildValue("I", 0));
+///    } catch { ... }
+/// \endcode
+///
+/// Examples 3: Install a custom exception to a module.
+///
+/// \code
+///    PyObject* new_exception; // publicly visible
+///    ...
+///    try {
+///        new_exception = PyErr_NewException("mymodule.NewException",
+///                                           NULL, NULL);
+///        PyObjectContainer(new_exception).installToModule(mymodule,
+///                                                         "NewException");
+///    } catch { ... }
+/// \endcode
+///
+/// Note that \c installToModule() keeps the reference to \c new_exception
+/// by default.  This is a common practice when we introduce a custom
+/// exception in a python biding written in C/C++.  See the code comment
+/// of the method for more details.
+struct PyObjectContainer {
+    PyObjectContainer(PyObject* obj) : obj_(obj) {
+        if (obj_ == NULL) {
+            isc_throw(PyCPPWrapperException, "Unexpected NULL PyObject, "
+                      "probably due to short memory");
+        }
+    }
+    virtual ~PyObjectContainer() {
+        if (obj_ != NULL) {
+            Py_DECREF(obj_);
+        }
+    }
+    PyObject* get() {
+        return (obj_);
+    }
+    PyObject* release() {
+        PyObject* ret = obj_;
+        obj_ = NULL;
+        return (ret);
+    }
+
+    // Install the enclosed PyObject to the specified python class 'pyclass'
+    // as a variable named 'name'.
+    void installAsClassVariable(PyTypeObject& pyclass, const char* name) {
+        if (PyDict_SetItemString(pyclass.tp_dict, name, obj_) < 0) {
+            isc_throw(PyCPPWrapperException, "Failed to set a class variable, "
+                      "probably due to short memory");
+        }
+        // Ownership successfully transferred to the class object.  We'll let
+        // it be released in the destructor.
+    }
+
+    // Install the enclosed PyObject to the specified module 'mod' as an
+    // object named 'name'.
+    // By default, this method explicitly keeps the reference to the object
+    // even after the module "steals" it.  To cancel this behavior and give
+    // the reference to the module completely, the third parameter 'keep_ref'
+    // should be set to false.
+    void installToModule(PyObject* mod, const char* name,
+                         bool keep_ref = true)
+    {
+        if (PyModule_AddObject(mod, name, obj_) < 0) {
+            isc_throw(PyCPPWrapperException, "Failed to add an object to "
+                      "module, probably due to short memory");
+        }
+        // PyModule_AddObject has "stolen" the reference, so unless we
+        // have to retain it ourselves we don't (shouldn't) decrease it.
+        // However, we actually often need to keep our own reference because
+        // objects added to a module are often referenced via non local
+        // C/C++ variables in various places of the C/C++ code.  In order
+        // for the code to run safely even if some buggy/evil python program
+        // performs 'del mod.obj', we need the extra reference.  See, e.g.:
+        // http://docs.python.org/py3k/c-api/init.html#Py_Initialize
+        // http://mail.python.org/pipermail/python-dev/2005-June/054238.html
+        if (keep_ref) {
+            Py_INCREF(obj_);
+        }
+        obj_ = NULL;
+    }
+
+protected:
+    PyObject* obj_;
+};
+
+/// This templated class is a derived class of \c PyObjectContainer and
+/// manages C++-class based python objects.
+///
+/// The template parameter \c PYSTRUCT must be a derived class (structure) of
+/// \c PyObject that has a member variable named \c cppobj, which must be a
+/// a pointer to \c CPPCLASS (the second template parameter).
+///
+/// For example, to define a custom python class based on a C++ class, MyClass,
+/// we'd define a class (struct) named \c s_MyClass like this:
+/// \code
+///    class s_MyClass : public PyObject {
+///    public:
+///       s_MyClass() : cppobj(NULL) {}
+///       MyClass* cppobj;
+///    };
+/// \endcode
+///
+/// And, to build and return a python version of MyClass object, write the
+/// following C++ code:
+/// \code
+///    typedef CPPPyObjectContainer<s_MyClass, MyClass> MyContainer;
+///    try {
+///        // below, myclass_type is of \c PyTypeObject that defines
+///        // a python class (type) for MyClass
+///        MyContainer container(PyObject_New(s_MyClass, myclass_type));
+///        container.set(new MyClass());
+///        return (container.release()); // give the reference to the caller
+///    } catch { ... }
+/// \endcode
+///
+/// This code prevents bugs like NULL pointer dereference when \c PyObject_New
+/// fails or resource leaks when new'ing \c MyClass results in an exception.
+/// Note that we use \c release() (derived from the base class) instead of
+/// \c get(); in this case we should simply pass the reference generated in
+/// \c PyObject_New() to the caller.
+template <typename PYSTRUCT, typename CPPCLASS>
+struct CPPPyObjectContainer : public PyObjectContainer {
+    CPPPyObjectContainer(PYSTRUCT* obj) : PyObjectContainer(obj) {}
+
+    // This method associates a C++ object with the corresponding python
+    // object enclosed in this class.
+    void set(CPPCLASS* value) {
+        if (value == NULL) {
+            isc_throw(PyCPPWrapperException, "Unexpected NULL C++ object, "
+                      "probably due to short memory");
+        }
+        static_cast<PYSTRUCT*>(obj_)->cppobj = value;
+    }
+
+    // This is a convenience short cut to associate a C++ object with the
+    // python object and install it to the specified python class \c pyclass
+    // as a variable named \c name.
+    void installAsClassVariable(PyTypeObject& pyclass, const char* name,
+                                CPPCLASS* value)
+    {
+        set(value);
+        PyObjectContainer::installAsClassVariable(pyclass, name);
+    }
+};
+
+/// A shortcut function to install a python class variable.
+///
+/// It installs a python object \c obj to a specified class \c pyclass
+/// as a variable named \c name.
+inline void
+installClassVariable(PyTypeObject& pyclass, const char* name, PyObject* obj) {
+    PyObjectContainer(obj).installAsClassVariable(pyclass, name);
+}
+
+} // namespace python
+} // namespace util
+} // namespace isc
+#endif // __PYCPPWRAPPER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 309 - 0
src/lib/util/python/wrapper_template.cc

@@ -0,0 +1,309 @@
+// Copyright (C) @YEAR@  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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include "@cppclass@_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::@MODULE@;
+using namespace isc::@MODULE@::python;
+
+//
+// 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
+
+//
+// @CPPCLASS@
+//
+
+// Trivial constructor.
+s_@CPPCLASS@::s_@CPPCLASS@() : cppobj(NULL) {
+}
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_@CPPCLASS@, @CPPCLASS@> @CPPCLASS@Container;
+
+//
+// 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 @CPPCLASS@_init(s_@CPPCLASS@* self, PyObject* args);
+void @CPPCLASS@_destroy(s_@CPPCLASS@* self);
+
+// These are the functions we export
+// ADD/REMOVE/MODIFY THE FOLLOWING AS APPROPRIATE FOR THE ACTUAL CLASS.
+//
+PyObject* @CPPCLASS@_toText(const s_@CPPCLASS@* const self);
+PyObject* @CPPCLASS@_str(PyObject* self);
+PyObject* @CPPCLASS@_richcmp(const s_@CPPCLASS@* const self,
+                            const s_@CPPCLASS@* const other, int op);
+
+// This is quite specific pydnspp.  For other wrappers this should probably
+// be removed.
+PyObject* @CPPCLASS@_toWire(const s_@CPPCLASS@* self, PyObject* args);
+
+// These are the functions we export
+// For a minimal support, we don't need them.
+
+// 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 @CPPCLASS@_methods[] = {
+    { "to_text", reinterpret_cast<PyCFunction>(@CPPCLASS@_toText), METH_NOARGS,
+      "Returns the text representation" },
+    // This is quite specific pydnspp.  For other wrappers this should probably
+    // be removed:
+    { "to_wire", reinterpret_cast<PyCFunction>(@CPPCLASS@_toWire), METH_VARARGS,
+      "Converts the @CPPCLASS@ object to wire format.\n"
+      "The argument can be either a MessageRenderer or an object that "
+      "implements the sequence interface. If the object is mutable "
+      "(for instance a bytearray()), the wire data is added in-place.\n"
+      "If it is not (for instance a bytes() object), a new object is "
+      "returned" },
+    { NULL, NULL, 0, NULL }
+};
+
+// This is a template of typical code logic of python class initialization
+// with C++ backend.  You'll need to adjust it according to details of the
+// actual C++ class.
+int
+@CPPCLASS@_init(s_@CPPCLASS@* self, PyObject* args) {
+    try {
+        if (PyArg_ParseTuple(args, "REPLACE ME")) {
+            // YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
+            self->cppobj = new @CPPCLASS@(/*NECESSARY PARAMS*/);
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct @CPPCLASS@ object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(po_IscException,
+                        "Unexpected exception in constructing @CPPCLASS@");
+        return (-1);
+    }
+
+    PyErr_SetString(PyExc_TypeError,
+                    "Invalid arguments to @CPPCLASS@ constructor");
+
+    return (-1);
+}
+
+// This is a template of typical code logic of python object destructor.
+// In many cases you can use it without modification, but check that carefully.
+void
+@CPPCLASS@_destroy(s_@CPPCLASS@* const self) {
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+// This should be able to be used without modification as long as the
+// underlying C++ class has toText().
+PyObject*
+@CPPCLASS@_toText(const s_@CPPCLASS@* const self) {
+    try {
+        // toText() could throw, so we need to catch any exceptions below.
+        return (Py_BuildValue("s", self->cppobj->toText().c_str()));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to convert @CPPCLASS@ object to text: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+                        "converting @CPPCLASS@ object to text");
+    }
+    return (NULL);
+}
+
+PyObject*
+@CPPCLASS@_str(PyObject* self) {
+    // Simply call the to_text method we already defined
+    return (PyObject_CallMethod(self, const_cast<char*>("to_text"),
+                                const_cast<char*>("")));
+}
+
+PyObject* 
+@CPPCLASS@_richcmp(const s_@CPPCLASS@* const self,
+                   const s_@CPPCLASS@* const other,
+                   const int op)
+{
+    bool c = false;
+
+    // Check for null and if the types match. If different type,
+    // simply return False
+    if (other == NULL || (self->ob_type != other->ob_type)) {
+        Py_RETURN_FALSE;
+    }
+
+    // Only equals and not equals here, unorderable type
+    switch (op) {
+    case Py_LT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_LE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_EQ:
+        c = (*self->cppobj == *other->cppobj);
+        break;
+    case Py_NE:
+        c = (*self->cppobj != *other->cppobj);
+        break;
+    case Py_GT:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    case Py_GE:
+        PyErr_SetString(PyExc_TypeError, "Unorderable type; @CPPCLASS@");
+        return (NULL);
+    }
+    if (c) {
+        Py_RETURN_TRUE;
+    } else {
+        Py_RETURN_FALSE;
+    }
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace @MODULE@ {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_@CPPCLASS@
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject @cppclass@_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "pydnspp.@CPPCLASS@",
+    sizeof(s_@CPPCLASS@),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    reinterpret_cast<destructor>(@CPPCLASS@_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
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    @CPPCLASS@_str,                       // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    "The @CPPCLASS@ class objects is...(COMPLETE THIS)",
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    // THIS MAY HAVE TO BE CHANGED TO NULL:
+    reinterpret_cast<richcmpfunc>(@CPPCLASS@_richcmp), // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    @CPPCLASS@_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
+    reinterpret_cast<initproc>(@CPPCLASS@_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
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_@CPPCLASS@(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(&@cppclass@_type) < 0) {
+        return (false);
+    }
+    void* p = &@cppclass@_type;
+    if (PyModule_AddObject(mod, "@CPPCLASS@", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&@cppclass@_type);
+
+    // The following template is the typical procedure for installing class
+    // variables.  If the class doesn't have a class variable, remove the
+    // entire try-catch clauses.
+    try {
+        // Constant class variables
+        installClassVariable(@cppclass@_type, "REPLACE_ME",
+                             Py_BuildValue("REPLACE ME"));
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in @CPPCLASS@ initialization: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (false);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError,
+                        "Unexpected failure in @CPPCLASS@ initialization");
+        return (false);
+    }
+
+    return (true);
+}
+
+PyObject*
+create@CPPCLASS@Object(const @CPPCLASS@& source) {
+    @CPPCLASS@Container container =
+        PyObject_New(s_@CPPCLASS@, &@cppclass@_type);
+    container.set(new @CPPCLASS@(source));
+    return (container.release());
+}
+} // namespace python
+} // namespace @MODULE@
+} // namespace isc

+ 59 - 0
src/lib/util/python/wrapper_template.h

@@ -0,0 +1,59 @@
+// Copyright (C) @YEAR@  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 __PYTHON_@CPPCLASS@_H
+#define __PYTHON_@CPPCLASS@_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace @MODULE@ {
+class @CPPCLASS@;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_@CPPCLASS@ : public PyObject {
+public:
+    s_@CPPCLASS@();
+    @CPPCLASS@* cppobj;
+};
+
+extern PyTypeObject @cppclass@_type;
+
+bool initModulePart_@CPPCLASS@(PyObject* mod);
+
+// Note: this utility function works only when @CPPCLASS@ is a copy
+// constructable.
+// And, it would only be useful when python binding needs to create this
+// object frequently.  Otherwise, it would (or should) probably better to
+// remove the declaration and definition of this function.
+//
+/// This is A simple shortcut to create a python @CPPCLASS@ object (in the
+/// form of a pointer to PyObject) with minimal exception safety.
+/// On success, it returns a valid pointer to PyObject with a reference
+/// counter of 1; if something goes wrong it throws an exception (it never
+/// returns a NULL pointer).
+/// This function is expected to be called with in a try block
+/// followed by necessary setup for python exception.
+PyObject* create@CPPCLASS@Object(const @CPPCLASS@& source);
+
+} // namespace python
+} // namespace @MODULE@
+} // namespace isc
+#endif // __PYTHON_@CPPCLASS@_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 14 - 0
src/lib/util/pyunittests/Makefile.am

@@ -0,0 +1,14 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+pyexec_LTLIBRARIES = pyunittests_util.la
+pyunittests_util_la_SOURCES = pyunittests_util.cc
+pyunittests_util_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
+pyunittests_util_la_LDFLAGS = $(PYTHON_LDFLAGS)
+
+# Python prefers .so, while some OSes (specifically MacOS) use a different
+# suffix for dynamic objects.  -module is necessary to work this around.
+pyunittests_util_la_LDFLAGS += -module
+pyunittests_util_la_LIBADD = $(top_builddir)/src/lib/util/libutil.la
+pyunittests_util_la_LIBADD += $(PYTHON_LIB)

+ 84 - 0
src/lib/util/pyunittests/pyunittests_util.cc

@@ -0,0 +1,84 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <Python.h>
+
+#include <stdint.h>
+
+// see util/time_utilities.h
+namespace isc {
+namespace util {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+
+namespace {
+int64_t fake_current_time;
+
+int64_t
+getFakeTime() {
+    return (fake_current_time);
+}
+
+PyObject*
+fixCurrentTime(PyObject*, PyObject* args) {
+    PyObject* maybe_none;
+    if (PyArg_ParseTuple(args, "L", &fake_current_time)) {
+        isc::util::detail::gettimeFunction = getFakeTime;
+    } else if (PyArg_ParseTuple(args, "O", &maybe_none) &&
+               maybe_none == Py_None) {
+        isc::util::detail::gettimeFunction = NULL;
+    } else {
+         PyErr_SetString(PyExc_TypeError, "Invalid arguments to "
+                         "pyunittests_util.fix_current_time");
+         return (NULL);
+    }
+
+    PyErr_Clear();
+    Py_RETURN_NONE;
+}
+
+PyMethodDef PyUnittestsUtilMethods[] = {
+    { "fix_current_time", fixCurrentTime, METH_VARARGS,
+      "Fix the current system time at the specified (fake) value.\n\n"
+      "This is useful for testing modules that depend on the current time.\n"
+      "Note that it only affects C++ modules that use gettimeWrapper() "
+      "defined in libutil, which allows a hook for testing.\n"
+      "If an integer (signed 64bit) is given, the current time will be fixed "
+      "to that value; if None is specified (which is the default) the use of "
+      "faked time will be canceled."
+    },
+    { NULL, NULL, 0, NULL}
+};
+
+PyModuleDef pyunittests_util = {
+    { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
+    "pyunittests_util",
+    "This module is a collection of utilities useful for testing "
+    "the BIND 10 C++ binding modules.",
+    -1,
+    PyUnittestsUtilMethods,
+    NULL,
+    NULL,
+    NULL,
+    NULL
+};
+} // end of unnamed namespace
+
+PyMODINIT_FUNC
+PyInit_pyunittests_util(void) {
+    return (PyModule_Create(&pyunittests_util));
+}