Browse Source

Added Message and Question wrappers
Updated messageenderer, rrset, and name wrappers


git-svn-id: svn://bind10.isc.org/svn/bind10/experiments/python-binding@1813 e5f2f494-b856-4b98-b285-d166d9295462

Jelte Jansen 15 years ago
parent
commit
bf367225be

+ 11 - 0
src/lib/dns/python/TODO

@@ -0,0 +1,11 @@
+
+add statics for RRClass::IN() (RRClass.IN()) etc.
+(and replace RRClass("IN") in tests with those)
+
+__str__ for name, question, everything with to_text()
+
+All constructors based on buffers need an optional position
+argument (like question_python has now)
+
+at question.to_wire(bytes) does not seem to work right (only return
+value seems correct, while i'd like in-place addition if possible)

+ 11 - 0
src/lib/dns/python/libdns_python.cc

@@ -12,6 +12,7 @@
 // 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
+#include <structmember.h>
 
 #include "config.h"
 
@@ -33,6 +34,8 @@
 #include "rrttl_python.cc"
 #include "rdata_python.cc"
 #include "rrset_python.cc"
+#include "question_python.cc"
+#include "message_python.cc"
 
 //
 // Definition of the module
@@ -87,6 +90,14 @@ PyInit_libdns_python(void)
         return NULL;
     }
 
+    if (!initModulePart_Question(mod)) {
+        return NULL;
+    }
+
+    if (!initModulePart_Message(mod)) {
+        return NULL;
+    }
+
     return mod;
 }
 

File diff suppressed because it is too large
+ 1927 - 0
src/lib/dns/python/message_python.cc


+ 3 - 2
src/lib/dns/python/messagerenderer_python.cc

@@ -132,10 +132,11 @@ MessageRenderer_getLength(s_MessageRenderer* self)
 static PyObject*
 MessageRenderer_isTruncated(s_MessageRenderer* self)
 {
-    if (self->messagerenderer->isTruncated())
+    if (self->messagerenderer->isTruncated()) {
         Py_RETURN_TRUE;
-    else
+    } else {
         Py_RETURN_FALSE;
+    }
 }
 
 static PyObject*

+ 4 - 2
src/lib/dns/python/name_python.cc

@@ -461,6 +461,7 @@ Name_split(s_Name* self, PyObject* args)
     }
     return (PyObject*) ret;
 }
+#include <iostream>
 
 static PyObject* 
 Name_richcmp(s_Name* n1, s_Name* n2, int op)
@@ -489,10 +490,11 @@ Name_richcmp(s_Name* n1, s_Name* n2, int op)
     default:
         assert(0);              // XXX: should trigger an exception
     }
-    if (c)
+    if (c) {
         Py_RETURN_TRUE;
-    else
+    } else {
         Py_RETURN_FALSE;
+    }
 }
 
 static PyObject*

+ 316 - 0
src/lib/dns/python/question_python.cc

@@ -0,0 +1,316 @@
+// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// $Id: question_python.cc 1711 2010-04-14 15:14:53Z jelte $
+
+#include <dns/question.h>
+using namespace isc::dns;
+
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in the initModulePart
+// function at the end of this file
+//
+
+//
+// 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
+
+//
+// Question
+//
+
+// The s_* Class simply coverst one instantiation of the object
+typedef struct {
+    PyObject_HEAD
+    Question* question;
+} s_Question;
+
+//
+// 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
+static int Question_init(s_Question* self, PyObject* args);
+static void Question_destroy(s_Question* self);
+
+// These are the functions we export
+static PyObject* Question_getName(s_Question* self);
+static PyObject* Question_getType(s_Question* self);
+static PyObject* Question_getClass(s_Question* self);
+static PyObject* Question_toText(s_Question* self);
+// This is a second version of toText, we need one where the argument
+// is a PyObject*, for the str() function in python.
+static PyObject* Question_str(PyObject* self);
+static PyObject* Question_toWire(s_Question* self, PyObject* args);
+
+// 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
+static PyMethodDef Question_methods[] = {
+    { "get_name", (PyCFunction)Question_getName, METH_NOARGS, "Return the name" },
+    { "get_type", (PyCFunction)Question_getType, METH_NOARGS, "Return the rr type" },
+    { "get_class", (PyCFunction)Question_getClass, METH_NOARGS, "Return the rr class" },
+    { "to_text", (PyCFunction)Question_toText, METH_NOARGS, "Return the string representation" },
+    { "to_wire", (PyCFunction)Question_toWire, METH_VARARGS, "to wire format" },
+    { NULL, NULL, 0, NULL }
+};
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_Question
+// Most of the functions are not actually implemented and NULL here.
+static PyTypeObject question_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "libdns_python.Question",
+    sizeof(s_Question),                  /* tp_basicsize */
+    0,                                  /* tp_itemsize */
+    (destructor)Question_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 */
+    Question_str,                               /* tp_str */
+    NULL,                               /* tp_getattro */
+    NULL,                               /* tp_setattro */
+    NULL,                               /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT,                 /* tp_flags */
+    "C++ Question Object",               /* tp_doc */
+    NULL,                               /* tp_traverse */
+    NULL,                               /* tp_clear */
+    NULL,                               /* tp_richcompare */
+    0,                                  /* tp_weaklistoffset */
+    NULL,                               /* tp_iter */
+    NULL,                               /* tp_iternext */
+    Question_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)Question_init,                /* tp_init */
+    NULL,                               /* tp_alloc */
+    PyType_GenericNew,                  /* tp_new */
+    NULL,                               /* tp_free */
+    NULL,                               /* tp_is_gc */
+    NULL,                               /* tp_bases */
+    NULL,                               /* tp_mro */
+    NULL,                               /* tp_cache */
+    NULL,                               /* tp_subclasses */
+    NULL,                               /* tp_weaklist */
+    // Note: not sure if the following are correct.  Added them just to
+    // make the compiler happy.
+    NULL,                               /* tp_del */
+    0                                   /* tp_version_tag */
+};
+
+static int
+Question_init(s_Question* self, PyObject* args)
+{
+    // The constructor argument can be a string ("IN"), an integer (1),
+    // or a sequence of numbers between 0 and 255 (wire code)
+
+    // Note that PyArg_ParseType can set PyError, and we need to clear
+    // that if we try several like here. Otherwise the *next* python
+    // call will suddenly appear to throw an exception.
+    // (the way to do exceptions is to set PyErr and return -1)
+    s_Name* name;
+    s_RRClass* rrclass;
+    s_RRType* rrtype;
+
+    const char* b;
+    Py_ssize_t len;
+    unsigned int position = 0;
+
+    try {
+        if (PyArg_ParseTuple(args, "O!O!O!", &name_type, &name,
+                                               &rrclass_type, &rrclass,
+                                               &rrtype_type, &rrtype
+           )) {
+            self->question = new Question(*name->name, *rrclass->rrclass,
+                                          *rrtype->rrtype);
+            return 0;
+        } else if (PyArg_ParseTuple(args, "y#|I", &b, &len, &position)) {
+            PyErr_Clear();
+            InputBuffer inbuf(b, len);
+            inbuf.setPosition(position);
+            self->question = new Question(inbuf);
+            return 0;
+        }
+    } catch (isc::dns::DNSMessageFORMERR dmfe) {
+        PyErr_Clear();
+        PyErr_SetString(po_DNSMessageFORMERR, dmfe.what());
+        return -1;
+    } catch (isc::dns::IncompleteRRClass irc) {
+        PyErr_Clear();
+        PyErr_SetString(po_IncompleteRRClass, irc.what());
+        return -1;
+    } catch (isc::dns::IncompleteRRType irt) {
+        PyErr_Clear();
+        PyErr_SetString(po_IncompleteRRType, irt.what());
+        return -1;
+    }
+
+    self->question = NULL;
+    
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError,
+                    "no valid type in constructor argument");
+    return -1;
+}
+
+static void
+Question_destroy(s_Question* self)
+{
+    if (self->question != NULL)
+        delete self->question;
+    self->question = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+static PyObject*
+Question_getName(s_Question* self)
+{
+    s_Name* name;
+
+    // is this the best way to do this?
+    name = (s_Name*)name_type.tp_alloc(&name_type, 0);
+    if (name != NULL) {
+        name->name = new Name(self->question->getName());
+        if (name->name == NULL)
+          {
+            Py_DECREF(name);
+            return NULL;
+          }
+    }
+
+    return (PyObject*)name;
+}
+
+static PyObject*
+Question_getType(s_Question* self)
+{
+    s_RRType* rrtype;
+
+    rrtype = (s_RRType*)rrtype_type.tp_alloc(&rrtype_type, 0);
+    if (rrtype != NULL) {
+        rrtype->rrtype = new RRType(self->question->getType());
+        if (rrtype->rrtype == NULL)
+          {
+            Py_DECREF(rrtype);
+            return NULL;
+          }
+    }
+
+    return (PyObject*)rrtype;
+}
+
+static PyObject*
+Question_getClass(s_Question* self)
+{
+    s_RRClass* rrclass;
+
+    rrclass = (s_RRClass*)rrclass_type.tp_alloc(&rrclass_type, 0);
+    if (rrclass != NULL) {
+        rrclass->rrclass = new RRClass(self->question->getClass());
+        if (rrclass->rrclass == NULL)
+          {
+            Py_DECREF(rrclass);
+            return NULL;
+          }
+    }
+
+    return (PyObject*)rrclass;
+}
+
+
+static PyObject*
+Question_toText(s_Question* self)
+{
+    // Py_BuildValue makes python objects from native data
+    return Py_BuildValue("s", self->question->toText().c_str());
+}
+
+static PyObject*
+Question_str(PyObject* self)
+{
+    // Simply call the to_text method we already defined
+    return PyObject_CallMethod(self, (char*)"to_text", (char*)"");
+}
+
+static PyObject*
+Question_toWire(s_Question* self, PyObject* args)
+{
+    PyObject* bytes;
+    s_MessageRenderer* mr;
+    
+    if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
+        PyObject* bytes_o = bytes;
+        
+        OutputBuffer buffer(255);
+        self->question->toWire(buffer);
+        PyObject* n = PyBytes_FromStringAndSize((const char*) buffer.getData(), buffer.getLength());
+        PyObject* result = PySequence_InPlaceConcat(bytes_o, n);
+        // We need to release the object we temporarily created here
+        // to prevent memory leak
+        Py_DECREF(n);
+        return result;
+    } else if (PyArg_ParseTuple(args, "O!", &messagerenderer_type, (PyObject**) &mr)) {
+        self->question->toWire(*mr->messagerenderer);
+        // If we return NULL it is seen as an error, so use this for
+        // None returns
+        Py_RETURN_NONE;
+    }
+    PyErr_Clear();
+    PyErr_SetString(PyExc_TypeError,
+                    "toWire argument must be a sequence object or a MessageRenderer");
+    return NULL;
+}
+
+// end of Question
+
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_Question(PyObject* mod)
+{
+    // Add the exceptions to the module
+
+    // 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(&question_type) < 0) {
+        return false;
+    }
+    Py_INCREF(&question_type);
+    PyModule_AddObject(mod, "Question",
+                       (PyObject*) &question_type);
+    
+    return true;
+}

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

@@ -67,7 +67,8 @@ static PyObject* RRset_setTTL(s_RRset* self, PyObject* args);
 static PyObject* RRset_toText(s_RRset* self);
 static PyObject* RRset_toWire(s_RRset* self, PyObject* args);
 static PyObject* RRset_addRdata(s_RRset* self, PyObject* args);
-// TODO: iterator
+static PyObject* RRset_getRdata(s_RRset* self);
+// TODO: iterator?
 
 static PyMethodDef RRset_methods[] = {
     { "get_rdata_count", (PyCFunction)RRset_getRdataCount, METH_NOARGS, "Return the number of rdata fields" },
@@ -80,6 +81,7 @@ static PyMethodDef RRset_methods[] = {
     { "to_text", (PyCFunction)RRset_toText, METH_NOARGS, "Return" },
     { "to_wire", (PyCFunction)RRset_toWire, METH_VARARGS, "Return" },
     { "add_rdata", (PyCFunction)RRset_addRdata, METH_VARARGS, "Return" },
+    { "get_rdata", (PyCFunction)RRset_getRdata, METH_NOARGS, "Returns a List containing all Rdata elements" },
     { NULL, NULL, 0, NULL }
 };
 
@@ -330,6 +332,31 @@ RRset_addRdata(s_RRset* self, PyObject* args)
         return NULL;
     }
 }
+
+static PyObject*
+RRset_getRdata(s_RRset* self)
+{
+    PyObject* list = PyList_New(0);
+
+    RdataIteratorPtr it = self->rrset->getRdataIterator();
+
+    for (it->first(); !it->isLast(); it->next()) {
+        s_Rdata *rds = (s_Rdata*)rdata_type.tp_alloc(&rdata_type, 0);
+        if (rds != NULL) {
+            // hmz them iterators/shared_ptrs and private constructors
+            // make this a bit weird, so we create a new one with
+            // the data available
+            const Rdata *rd = &it->getCurrent();
+            rds->rdata = createRdata(self->rrset->getType(), self->rrset->getClass(), *rd);
+            PyList_Append(list, (PyObject*) rds);
+        } else {
+            return NULL;
+        }
+    }
+    
+    return list;
+}
+
 // end of RRset
 
 

+ 7 - 0
src/lib/dns/python/tests/libdns_python_test.in

@@ -8,7 +8,14 @@ CONFIG_PATH=@abs_top_srcdir@/src/lib/dns/python/tests
 PYTHONPATH=@abs_top_srcdir@/src/lib/python:@abs_top_srcdir@/src/lib/dns/python/.libs
 export PYTHONPATH
 
+TESTDATA_PATH=@abs_top_srcdir@/src/lib/dns/tests/testdata
+export TESTDATA_PATH
+
 cd ${BIND10_PATH}
 ${PYTHON_EXEC} -O ${CONFIG_PATH}/rrtype_python_test.py $*
 
 ${PYTHON_EXEC} -O ${CONFIG_PATH}/rrset_python_test.py $*
+
+${PYTHON_EXEC} -O ${CONFIG_PATH}/question_python_test.py $*
+
+${PYTHON_EXEC} -O ${CONFIG_PATH}/message_python_test.py $*

+ 252 - 0
src/lib/dns/python/tests/message_python_test.py

@@ -0,0 +1,252 @@
+# Copyright (C) 2009  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.
+
+#
+# Tests for the rrtype part of the libdns_python module
+#
+
+import unittest
+import os
+from libdns_python import *
+
+if "TESTDATA_PATH" in os.environ:
+    testdata_path = os.environ["TESTDATA_PATH"]
+else:
+    testdata_path = "../tests/testdata"
+
+def read_wire_data(filename):
+    data = bytes()
+    file = open(testdata_path + os.sep + filename, "r")
+    for line in file:
+        line = line.strip()
+        if line == "" or line.startswith("#"):
+            pass
+        else:
+            cur_data = bytes.fromhex(line)
+            data += cur_data
+
+    return data
+
+def factoryFromFile(message, file):
+    data = read_wire_data(file)
+    message.from_wire(data)
+    pass
+
+class MessageTest(unittest.TestCase):
+    
+    def test_RcodeConstruct(self):
+        # normal cases
+        self.assertEqual(0, Rcode(0).get_code())
+        self.assertEqual(0xfff, Rcode(0xfff).get_code()) # possible max code
+    
+        # should fail on attempt of construction with an out of range code
+        self.assertRaises(OverflowError, Rcode, 0x1000)
+        self.assertRaises(OverflowError, Rcode, 0xffff)
+    
+    def test_RcodeToText(self):
+        self.assertEqual("NOERROR", Rcode.NOERROR().to_text())
+        self.assertEqual("BADVERS", Rcode.BADVERS().to_text())
+        self.assertEqual("17", Rcode(Rcode.BADVERS().get_code() + 1).to_text())
+        self.assertEqual("4095", Rcode(0xfff).to_text())
+    
+    
+    def test_fromWire(self):
+        test_name = Name("test.example.com");
+        
+        message_parse = Message(0)
+        factoryFromFile(message_parse, "message_fromWire1")
+        self.assertEqual(0x1035, message_parse.get_qid())
+        self.assertEqual(Opcode.QUERY(), message_parse.get_opcode())
+        self.assertEqual(Rcode.NOERROR(), message_parse.get_rcode())
+        self.assertTrue(message_parse.get_header_flag(MessageFlag.QR()))
+        self.assertTrue(message_parse.get_header_flag(MessageFlag.RD()))
+        self.assertTrue(message_parse.get_header_flag(MessageFlag.AA()))
+    
+        #QuestionPtr q = *message_parse.beginQuestion()
+        q = message_parse.get_question()[0]
+        self.assertEqual(test_name, q.get_name())
+        self.assertEqual(RRType("A"), q.get_type())
+        self.assertEqual(RRClass("IN"), q.get_class())
+        self.assertEqual(1, message_parse.get_rr_count(Section.QUESTION()))
+        self.assertEqual(2, message_parse.get_rr_count(Section.ANSWER()))
+        self.assertEqual(0, message_parse.get_rr_count(Section.AUTHORITY()))
+        self.assertEqual(0, message_parse.get_rr_count(Section.ADDITIONAL()))
+    
+        #RRsetPtr rrset = *message_parse.beginSection(Section.ANSWER())
+        rrset = message_parse.get_section(Section.ANSWER())[0]
+        self.assertEqual(test_name, rrset.get_name())
+        self.assertEqual(RRType("A"), rrset.get_type())
+        self.assertEqual(RRClass("IN"), rrset.get_class())
+        ## TTL should be 3600, even though that of the 2nd RR is 7200
+        self.assertEqual(RRTTL(3600), rrset.get_ttl())
+        rdata = rrset.get_rdata();
+        self.assertEqual("192.0.2.1", rdata[0].to_text())
+        self.assertEqual("192.0.2.2", rdata[1].to_text())
+        self.assertEqual(2, len(rdata))
+    
+    def test_GetEDNS0DOBit(self):
+        message_parse = Message(PARSE)
+        ## Without EDNS0, DNSSEC is considered to be unsupported.
+        factoryFromFile(message_parse, "message_fromWire1")
+        self.assertFalse(message_parse.is_dnssec_supported())
+    
+        ## If DO bit is on, DNSSEC is considered to be supported.
+        message_parse.clear(PARSE)
+        factoryFromFile(message_parse, "message_fromWire2")
+        self.assertTrue(message_parse.is_dnssec_supported())
+    
+        ## If DO bit is off, DNSSEC is considered to be unsupported.
+        message_parse.clear(PARSE)
+        factoryFromFile(message_parse, "message_fromWire3")
+        self.assertFalse(message_parse.is_dnssec_supported())
+    
+    def test_SetEDNS0DOBit(self):
+        # By default, it's false, and we can enable/disable it.
+        message_parse = Message(PARSE)
+        message_render = Message(RENDER)
+        self.assertFalse(message_render.is_dnssec_supported())
+        message_render.set_dnssec_supported(True)
+        self.assertTrue(message_render.is_dnssec_supported())
+        message_render.set_dnssec_supported(False)
+        self.assertFalse(message_render.is_dnssec_supported())
+    
+        ## A message in the parse mode doesn't allow this flag to be set.
+        self.assertRaises(InvalidMessageOperation,
+                          message_parse.set_dnssec_supported,
+                          True)
+        ## Once converted to the render mode, it works as above
+        message_parse.make_response()
+        self.assertFalse(message_parse.is_dnssec_supported())
+        message_parse.set_dnssec_supported(True)
+        self.assertTrue(message_parse.is_dnssec_supported())
+        message_parse.set_dnssec_supported(False)
+        self.assertFalse(message_parse.is_dnssec_supported())
+    
+    def test_GetEDNS0UDPSize(self):
+        # Without EDNS0, the default max UDP size is used.
+        message_parse = Message(PARSE)
+        factoryFromFile(message_parse, "message_fromWire1")
+        self.assertEqual(DEFAULT_MAX_UDPSIZE, message_parse.get_udp_size())
+    
+        ## If the size specified in EDNS0 > default max, use it.
+        message_parse.clear(PARSE)
+        factoryFromFile(message_parse, "message_fromWire2")
+        self.assertEqual(4096, message_parse.get_udp_size())
+    
+        ## If the size specified in EDNS0 < default max, keep using the default.
+        message_parse.clear(PARSE)
+        factoryFromFile(message_parse, "message_fromWire8")
+        self.assertEqual(DEFAULT_MAX_UDPSIZE, message_parse.get_udp_size())
+    
+    def test_SetEDNS0UDPSize(self):
+        # The default size if unspecified
+        message_render = Message(RENDER)
+        message_parse = Message(PARSE)
+        self.assertEqual(DEFAULT_MAX_UDPSIZE, message_render.get_udp_size())
+        # A common buffer size with EDNS, should succeed
+        message_render.set_udp_size(4096)
+        self.assertEqual(4096, message_render.get_udp_size())
+        # Unusual large value, but accepted
+        message_render.set_udp_size(0xffff)
+        self.assertEqual(0xffff, message_render.get_udp_size())
+        # Too small is value is rejected
+        self.assertRaises(InvalidMessageUDPSize, message_render.set_udp_size, 511)
+    
+        # A message in the parse mode doesn't allow the set operation.
+        self.assertRaises(InvalidMessageOperation, message_parse.set_udp_size, 4096)
+        ## Once converted to the render mode, it works as above.
+        message_parse.make_response()
+        message_parse.set_udp_size(4096)
+        self.assertEqual(4096, message_parse.get_udp_size())
+        message_parse.set_udp_size(0xffff)
+        self.assertEqual(0xffff, message_parse.get_udp_size())
+        self.assertRaises(InvalidMessageUDPSize, message_parse.set_udp_size, 511)
+    
+    def test_EDNS0ExtCode(self):
+        # Extended Rcode = BADVERS
+        message_parse = Message(PARSE)
+        factoryFromFile(message_parse, "message_fromWire10")
+        self.assertEqual(Rcode.BADVERS(), message_parse.get_rcode())
+    
+        # Maximum extended Rcode
+        message_parse.clear(PARSE)
+        factoryFromFile(message_parse, "message_fromWire11")
+        self.assertEqual(0xfff, message_parse.get_rcode().get_code())
+    
+    def test_BadEDNS0(self):
+        message_parse = Message(PARSE)
+        # OPT RR in the answer section
+        self.assertRaises(DNSMessageFORMERR,
+                          factoryFromFile,
+                          message_parse,
+                          "message_fromWire4")
+
+        # multiple OPT RRs (in the additional section)
+        message_parse.clear(PARSE)
+        self.assertRaises(DNSMessageFORMERR,
+                          factoryFromFile,
+                          message_parse,
+                          "message_fromWire5")
+
+        ## OPT RR of a non root name
+        message_parse.clear(PARSE)
+        self.assertRaises(DNSMessageFORMERR,
+                          factoryFromFile,
+                          message_parse,
+                          "message_fromWire6")
+                          
+        # Compressed owner name of OPT RR points to a root name.
+        # Not necessarily bogus, but very unusual and mostly pathological.
+        # We accept it, but is it okay?
+        message_parse.clear(PARSE)
+        factoryFromFile(message_parse, "message_fromWire7")
+
+        # Unsupported Version
+        message_parse.clear(PARSE)
+        self.assertRaises(DNSMessageBADVERS,
+                          factoryFromFile,
+                          message_parse,
+                          "message_fromWire9")
+    
+    def test_toWire(self):
+        message_render = Message(RENDER)
+        message_render.set_qid(0x1035)
+        message_render.set_opcode(Opcode.QUERY())
+        message_render.set_rcode(Rcode.NOERROR())
+        message_render.set_header_flag(MessageFlag.QR())
+        message_render.set_header_flag(MessageFlag.RD())
+        message_render.set_header_flag(MessageFlag.AA())
+        #message_render.addQuestion(Question(Name("test.example.com"), RRClass.IN(),
+                                            #RRType.A()))
+        rrset = RRset(Name("test.example.com"), RRClass("IN"),
+                                            RRType("A"), RRTTL(3600))
+        #rrset.add_rdata(in.A("192.0.2.1"))
+        #rrset.addRdata(in.A("192.0.2.2"))
+        #message_render.addRRset(Section.ANSWER(), rrset)
+    
+        #self.assertEqual(1, message_render.get_rr_count(Section.QUESTION()))
+        #self.assertEqual(2, message_render.get_rr_count(Section.ANSWER()))
+        self.assertEqual(0, message_render.get_rr_count(Section.AUTHORITY()))
+        self.assertEqual(0, message_render.get_rr_count(Section.ADDITIONAL()))
+
+        renderer = MessageRenderer()
+        message_render.to_wire(renderer)
+        #vector<unsigned char> data;
+        #UnitTestUtil.readWireData("testdata/message_toWire1", data)
+        #EXPECT_PRED_FORMAT4(UnitTestUtil.matchWireData, obuffer.getData(),
+                            #obuffer.getLength(), &data[0], data.size())
+
+if __name__ == '__main__':
+    unittest.main()

+ 102 - 0
src/lib/dns/python/tests/question_python_test.py

@@ -0,0 +1,102 @@
+# Copyright (C) 2009  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.
+
+#
+# Tests for the rrtype part of the libdns_python module
+#
+
+import unittest
+import os
+from libdns_python import *
+
+if "TESTDATA_PATH" in os.environ:
+    testdata_path = os.environ["TESTDATA_PATH"]
+else:
+    testdata_path = "../tests/testdata"
+
+def read_wire_data(filename):
+    data = bytes()
+    file = open(testdata_path + os.sep + filename, "r")
+    for line in file:
+        line = line.strip()
+        if line == "" or line.startswith("#"):
+            pass
+        else:
+            cur_data = bytes.fromhex(line)
+            data += cur_data
+
+    return data
+
+def question_from_wire(file, position = 0):
+    data = read_wire_data(file)
+    return Question(data, position)
+
+
+class QuestionTest(unittest.TestCase):
+    def setUp(self):
+        self.example_name1 = Name("foo.example.com")
+        self.example_name2 = Name("bar.example.com")
+        self.test_question1 = Question(self.example_name1, RRClass("IN"), RRType("NS"))
+        self.test_question2 = Question(self.example_name2, RRClass("CH"), RRType("A"))
+
+    def test_QuestionTest_fromWire(self):
+        
+        q = question_from_wire("question_fromWire")
+
+        self.assertEqual(self.example_name1, q.get_name())
+        self.assertEqual(RRClass("IN"), q.get_class())
+        self.assertEqual(RRType("NS"), q.get_type())
+    
+        # owner name of the second Question is compressed.  It's uncommon
+        # (to have multiple questions), but isn't prohibited by the protocol.
+        q = question_from_wire("question_fromWire", 21)
+        self.assertEqual(self.example_name2, q.get_name())
+        self.assertEqual(RRClass("CH"), q.get_class())
+        self.assertEqual(RRType("A"), q.get_type())
+    
+        # Pathological cases: Corresponding exceptions will be thrown from
+        # the underlying parser.
+        self.assertRaises(DNSMessageFORMERR,
+                          question_from_wire,
+                          "question_fromWire", 31)
+        self.assertRaises(IncompleteRRClass,
+                          question_from_wire,
+                          "question_fromWire", 36)
+    
+    
+    def test_QuestionTest_to_text(self):
+    
+        self.assertEqual("foo.example.com. IN NS\n", self.test_question1.to_text())
+        self.assertEqual("bar.example.com. CH A\n", self.test_question2.to_text())
+    
+    
+    def test_QuestionTest_to_wireBuffer(self):
+        obuffer = bytes()
+        obuffer = self.test_question1.to_wire(obuffer)
+        obuffer = self.test_question2.to_wire(obuffer)
+        wiredata = read_wire_data("question_toWire1")
+        self.assertEqual(obuffer, wiredata)
+    
+    
+    def test_QuestionTest_to_wireRenderer(self):
+        renderer = MessageRenderer()
+        self.test_question1.to_wire(renderer)
+        self.test_question2.to_wire(renderer)
+        wiredata = read_wire_data("question_toWire2")
+        self.assertEqual(renderer.get_data(), wiredata)
+    
+
+if __name__ == '__main__':
+    unittest.main()