Browse Source

[trac915] added new support for the rest of TSIG related implementations
and detailed tests.

JINMEI Tatuya 14 years ago
parent
commit
3bb68553bb

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

@@ -5,9 +5,15 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 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_LDFLAGS = $(PYTHON_LDFLAGS)
 

+ 16 - 8
src/lib/dns/python/pydnspp.cc

@@ -38,6 +38,14 @@
 #include <dns/messagerenderer.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 {
@@ -52,14 +60,9 @@ PyObject* po_DNSMessageBADVERS;
 }
 }
 
-#include "rcode_python.h"
-#include "tsigerror_python.h"
-
 // order is important here!
 using namespace isc::dns::python;
 
-#include <dns/python/messagerenderer_python.cc>
-#include <dns/python/name_python.cc>           // needs Messagerenderer
 #include <dns/python/rrclass_python.cc>        // needs Messagerenderer
 #include <dns/python/rrtype_python.cc>         // needs Messagerenderer
 #include <dns/python/rrttl_python.cc>          // needs Messagerenderer
@@ -67,8 +70,6 @@ using namespace isc::dns::python;
 #include <dns/python/rrset_python.cc>          // needs Rdata, RRTTL
 #include <dns/python/question_python.cc>       // needs RRClass, RRType, RRTTL,
                                                // 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/edns_python.cc>           // needs Messagerenderer, Rcode
 #include <dns/python/message_python.cc>        // needs RRset, Question
@@ -167,14 +168,21 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_TSIG(mod)) {
+        return (NULL);
+    }
+
     if (!initModulePart_TSIGError(mod)) {
         return (NULL);
     }
 
+    if (!initModulePart_TSIGRecord(mod)) {
+        return (NULL);
+    }
+
     if (!initModulePart_TSIGContext(mod)) {
         return (NULL);
     }
 
     return (mod);
 }
-

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

@@ -12,8 +12,10 @@ PYTESTS += rrset_python_test.py
 PYTESTS += rrttl_python_test.py
 PYTESTS += rrtype_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 += tsigrecord_python_test.py
 
 EXTRA_DIST = $(PYTESTS)
 EXTRA_DIST += testutil.py
@@ -34,7 +36,7 @@ if ENABLE_PYTHON_COVERAGE
 endif
 	for pytest in $(PYTESTS) ; do \
 	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 \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	$(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,
                           message_parse,
                           "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__':
     unittest.main()

+ 529 - 4
src/lib/dns/python/tests/tsig_python_test.py

@@ -13,17 +13,542 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-import unittest
+import base64, sys, time, unittest
 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):
     tsig_key = TSIGKey('www.example.com:SFuWd/q99SzF8Yzd1QbB9g==')
 
     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_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__':
     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()

+ 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()

+ 250 - 49
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
 // 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 "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::python;
 
 //
 // Definition of the classes
@@ -24,12 +45,17 @@ using namespace isc::dns;
 // and static wrappers around the methods we export), a list of methods,
 // and a type description
 
+//
+// TSIGContext
+//
+
+// Trivial constructor.
+s_TSIGContext::s_TSIGContext() : cppobj(NULL) {
+}
+
 namespace {
-// The s_* Class simply covers one instantiation of the object
-class s_TSIGContext : public PyObject {
-public:
-    TSIGContext* tsig_ctx;
-};
+// 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
@@ -40,6 +66,12 @@ public:
 int TSIGContext_init(s_TSIGContext* self, PyObject* args);
 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
 // For a minimal support, we don't need them.
 
@@ -50,18 +82,180 @@ void TSIGContext_destroy(s_TSIGContext* self);
 // 3. Argument type
 // 4. Documentation
 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 }
 };
 
+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
-// parsing of PyObject* to s_EDNS
+// parsing of PyObject* to s_TSIGContext
 // Most of the functions are not actually implemented and NULL here.
-PyTypeObject tsig_context_type = {
+PyTypeObject tsigcontext_type = {
     PyVarObject_HEAD_INIT(NULL, 0)
     "libdns_python.TSIGContext",
-    sizeof(s_TSIGContext),              // tp_basicsize
+    sizeof(s_TSIGContext),                 // tp_basicsize
     0,                                  // tp_itemsize
-    (destructor)TSIGContext_destroy,    // tp_dealloc
+    reinterpret_cast<destructor>(TSIGContext_destroy),       // tp_dealloc
     NULL,                               // tp_print
     NULL,                               // tp_getattr
     NULL,                               // tp_setattr
@@ -77,15 +271,14 @@ PyTypeObject tsig_context_type = {
     NULL,                               // tp_setattro
     NULL,                               // tp_as_buffer
     Py_TPFLAGS_DEFAULT,                 // tp_flags
-    "The TSIGContext class maintains a context of a signed session of "
-    "DNS transactions by TSIG.",
+    "The TSIGContext class objects is...(COMPLETE THIS)",
     NULL,                               // tp_traverse
     NULL,                               // tp_clear
-    NULL,                               // tp_richcompare
+    NULL, // tp_richcompare
     0,                                  // tp_weaklistoffset
     NULL,                               // tp_iter
     NULL,                               // tp_iternext
-    TSIGContext_methods,                // tp_methods
+    TSIGContext_methods,                   // tp_methods
     NULL,                               // tp_members
     NULL,                               // tp_getset
     NULL,                               // tp_base
@@ -93,7 +286,7 @@ PyTypeObject tsig_context_type = {
     NULL,                               // tp_descr_get
     NULL,                               // tp_descr_set
     0,                                  // tp_dictoffset
-    (initproc)TSIGContext_init,         // tp_init
+    reinterpret_cast<initproc>(TSIGContext_init),            // tp_init
     NULL,                               // tp_alloc
     PyType_GenericNew,                  // tp_new
     NULL,                               // tp_free
@@ -107,50 +300,58 @@ PyTypeObject tsig_context_type = {
     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
 bool
 initModulePart_TSIGContext(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_context_type) < 0) {
+    if (PyType_Ready(&tsigcontext_type) < 0) {
+        return (false);
+    }
+    void* p = &tsigcontext_type;
+    if (PyModule_AddObject(mod, "TSIGContext",
+                           static_cast<PyObject*>(p)) < 0) {
         return (false);
     }
-    Py_INCREF(&tsig_context_type);
-    void* p = &tsig_context_type;
-    PyModule_AddObject(mod, "TSIGContext", static_cast<PyObject*>(p));
+    Py_INCREF(&tsigcontext_type);
+
+    try {
+        // Class specific exceptions
+        po_TSIGContextError = PyErr_NewException("pydnspp.TSIGContextError",
+                                                 po_IscException, NULL);
+        PyObjectContainer(po_TSIGContextError).installToModule(
+            mod, "TSIGContextError");
 
-    addClassVariable(tsig_context_type, "DEFAULT_FUDGE",
-                     Py_BuildValue("H", TSIGContext::DEFAULT_FUDGE));
+        // 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));
+
+        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);
 }
-} // 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:

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

@@ -0,0 +1,367 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <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)
+    "libdns_python.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:

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

@@ -12,6 +12,8 @@
 // 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>
 
@@ -99,6 +101,7 @@ TSIGError_init(s_TSIGError* self, PyObject* args) {
             return (0);
         }
 
+        PyErr_Clear();
         s_Rcode* py_rcode;
         if (PyArg_ParseTuple(args, "O!", &rcode_type, &py_rcode)) {
             self->cppobj = new TSIGError(*py_rcode->cppobj);
@@ -352,6 +355,13 @@ initModulePart_TSIGError(PyObject* mod) {
 
     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

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

@@ -34,6 +34,14 @@ 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

+ 200 - 176
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
 // 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/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::rdata;
+using namespace isc::dns::python;
 
 //
 // 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 a type description
 
-namespace {
 //
 // TSIGKey
 //
 
 // The s_* Class simply covers one instantiation of the object
 
-class s_TSIGKey : public PyObject {
-public:
-    s_TSIGKey() : tsigkey(NULL) {}
-    TSIGKey* tsigkey;
-};
+s_TSIGKey::s_TSIGKey() : cppobj(NULL) {}
 
+namespace {
 //
 // We declare the functions here, the definitions are below
 // the type definition of the object, since both can use the other
@@ -78,6 +86,99 @@ PyMethodDef TSIGKey_methods[] = {
     { 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
 // parsing of PyObject* to s_EDNS
 // Most of the functions are not actually implemented and NULL here.
@@ -132,89 +233,6 @@ PyTypeObject tsigkey_type = {
     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
 bool
 initModulePart_TSIGKey(PyObject* mod) {
@@ -224,33 +242,37 @@ initModulePart_TSIGKey(PyObject* mod) {
     if (PyType_Ready(&tsigkey_type) < 0) {
         return (false);
     }
-    Py_INCREF(&tsigkey_type);
     void* p = &tsigkey_type;
     if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
-        Py_DECREF(&tsigkey_type);
         return (false);
     }
+    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);
-
-  cleanup:
-    Py_DECREF(&tsigkey_type);
-    return (false);
 }
+} // namespace python
+} // namespace dns
+} // namespace isc
 //
 // End of TSIGKey
 //
@@ -263,12 +285,9 @@ initModulePart_TSIGKey(PyObject* mod) {
 
 // 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
 // the type definition of the object, since both can use the other
@@ -296,56 +315,6 @@ PyMethodDef TSIGKeyRing_methods[] = {
     { NULL, NULL, 0, NULL }
 };
 
-PyTypeObject tsigkeyring_type = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "libdns_python.TSIGKeyRing",
-    sizeof(s_TSIGKeyRing),              // tp_basicsize
-    0,                                  // tp_itemsize
-    (destructor)TSIGKeyRing_destroy,    // tp_dealloc
-    NULL,                               // tp_print
-    NULL,                               // tp_getattr
-    NULL,                               // tp_setattr
-    NULL,                               // tp_reserved
-    NULL,                               // tp_repr
-    NULL,                               // tp_as_number
-    NULL,                               // tp_as_sequence
-    NULL,                               // tp_as_mapping
-    NULL,                               // tp_hash 
-    NULL,                               // tp_call
-    NULL,                               // tp_str
-    NULL,                               // tp_getattro
-    NULL,                               // tp_setattro
-    NULL,                               // tp_as_buffer
-    Py_TPFLAGS_DEFAULT,                 // tp_flags
-    "A simple repository of a set of TSIGKey objects.",
-    NULL,                               // tp_traverse
-    NULL,                               // tp_clear
-    NULL,                               // tp_richcompare
-    0,                                  // tp_weaklistoffset
-    NULL,                               // tp_iter
-    NULL,                               // tp_iternext
-    TSIGKeyRing_methods,                // tp_methods
-    NULL,                               // tp_members
-    NULL,                               // tp_getset
-    NULL,                               // tp_base
-    NULL,                               // tp_dict
-    NULL,                               // tp_descr_get
-    NULL,                               // tp_descr_set
-    0,                                  // tp_dictoffset
-    (initproc)TSIGKeyRing_init,         // tp_init
-    NULL,                               // tp_alloc
-    PyType_GenericNew,                  // tp_new
-    NULL,                               // tp_free
-    NULL,                               // tp_is_gc
-    NULL,                               // tp_bases
-    NULL,                               // tp_mro
-    NULL,                               // tp_cache
-    NULL,                               // tp_subclasses
-    NULL,                               // tp_weaklist
-    NULL,                               // tp_del
-    0                                   // tp_version_tag
-};
-
 int
 TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
     if (!PyArg_ParseTuple(args, "")) {
@@ -355,8 +324,8 @@ TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
         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");
         return (-1);
     }
@@ -366,14 +335,14 @@ TSIGKeyRing_init(s_TSIGKeyRing* self, PyObject* args) {
 
 void
 TSIGKeyRing_destroy(s_TSIGKeyRing* self) {
-    delete self->keyring;
-    self->keyring = NULL;
+    delete self->cppobj;
+    self->cppobj = NULL;
     Py_TYPE(self)->tp_free(self);
 }
 
 PyObject*
 TSIGKeyRing_size(const s_TSIGKeyRing* const self) {
-    return (Py_BuildValue("I", self->keyring->size()));
+    return (Py_BuildValue("I", self->cppobj->size()));
 }
 
 PyObject*
@@ -383,7 +352,7 @@ TSIGKeyRing_add(const s_TSIGKeyRing* const self, PyObject* args) {
     if (PyArg_ParseTuple(args, "O!", &tsigkey_type, &tsigkey)) {
         try {
             const TSIGKeyRing::Result result =
-                self->keyring->add(*tsigkey->tsigkey);
+                self->cppobj->add(*tsigkey->cppobj);
             return (Py_BuildValue("I", result));
         } catch (...) {
             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)) {
         const TSIGKeyRing::Result result =
-            self->keyring->remove(*key_name->name);
+            self->cppobj->remove(*key_name->cppobj);
         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,
                          &name_type, &algorithm_name)) {
         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) {
             s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
             if (key == NULL) {
                 return (NULL);
             }
-            key->tsigkey = new(nothrow) TSIGKey(*result.key);
-            if (key->tsigkey == NULL) {
+            key->cppobj = new(nothrow) TSIGKey(*result.key);
+            if (key->cppobj == NULL) {
                 Py_DECREF(key);
                 PyErr_SetString(po_IscException,
                                 "Allocating TSIGKey object failed");
@@ -442,6 +411,60 @@ TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
 
     return (NULL);
 }
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+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
+};
 
 bool
 initModulePart_TSIGKeyRing(PyObject* mod) {
@@ -465,5 +488,6 @@ initModulePart_TSIGKeyRing(PyObject* mod) {
 
     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:

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

@@ -0,0 +1,309 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <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)
+    "libdns_python.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: