Browse Source

[master] Merge branch 'trac1575'

JINMEI Tatuya 13 years ago
parent
commit
2c421b58e8

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

@@ -93,6 +93,7 @@ libdns___la_SOURCES += masterload.h masterload.cc
 libdns___la_SOURCES += message.h message.cc
 libdns___la_SOURCES += messagerenderer.h messagerenderer.cc
 libdns___la_SOURCES += name.h name.cc
+libdns___la_SOURCES += nsec3hash.h nsec3hash.cc
 libdns___la_SOURCES += opcode.h opcode.cc
 libdns___la_SOURCES += rcode.h rcode.cc
 libdns___la_SOURCES += rdata.h rdata.cc

+ 129 - 0
src/lib/dns/nsec3hash.cc

@@ -0,0 +1,129 @@
+// Copyright (C) 2012  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 <stdint.h>
+
+#include <cassert>
+#include <string>
+#include <vector>
+
+#include <boost/noncopyable.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+#include <util/encode/base32hex.h>
+#include <util/hash/sha1.h>
+
+#include <dns/name.h>
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::util::encode;
+using namespace isc::util::hash;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+/// \brief A derived class of \c NSEC3Hash that implements the standard hash
+/// calculation specified in RFC5155.
+///
+/// Currently the only pre-defined algorithm in the RFC is SHA1.  So we don't
+/// over-generalize it at the moment, and rather hardocde it and assume that
+/// specific algorithm.
+///
+/// The implementation details are only open within this file, but to avoid
+/// an accidental error in this implementation we explicitly make it non
+/// copyable.
+class NSEC3HashRFC5155 : boost::noncopyable, public NSEC3Hash {
+private:
+    // This is the algorithm number for SHA1/NSEC3 as defined in RFC5155.
+    static const uint8_t NSEC3_HASH_SHA1 = 1;
+
+public:
+    NSEC3HashRFC5155(const generic::NSEC3PARAM& param) :
+        algorithm_(param.getHashalg()),
+        iterations_(param.getIterations()),
+        salt_(param.getSalt()), digest_(SHA1_HASHSIZE), obuf_(Name::MAX_WIRE)
+    {
+        if (algorithm_ != NSEC3_HASH_SHA1) {
+            isc_throw(UnknownNSEC3HashAlgorithm, "Unknown NSEC3 algorithm: " <<
+                      static_cast<unsigned int>(algorithm_));
+        }
+        SHA1Reset(&sha1_ctx_);
+    }
+
+    virtual std::string calculate(const Name& name) const;
+
+private:
+    const uint8_t algorithm_;
+    const uint16_t iterations_;
+    const vector<uint8_t> salt_;
+
+    // The following members are placeholder of work place and don't hold
+    // any state over multiple calls so can be mutable without breaking
+    // constness.
+    mutable SHA1Context sha1_ctx_;
+    mutable vector<uint8_t> digest_;
+    mutable OutputBuffer obuf_;
+};
+
+inline void
+iterateSHA1(SHA1Context* ctx, const uint8_t* input, size_t inlength,
+            const uint8_t* salt, size_t saltlen,
+            uint8_t output[SHA1_HASHSIZE])
+{
+    SHA1Reset(ctx);
+    SHA1Input(ctx, input, inlength);
+    SHA1Input(ctx, salt, saltlen); // this works whether saltlen == or > 0
+    SHA1Result(ctx, output);
+}
+
+string
+NSEC3HashRFC5155::calculate(const Name& name) const {
+    // We first need to normalize the name by converting all upper case
+    // characters in the labels to lower ones.
+    obuf_.clear();
+    Name name_copy(name);
+    name_copy.downcase();
+    name_copy.toWire(obuf_);
+
+    const uint8_t saltlen = salt_.size();
+    const uint8_t* const salt = (saltlen > 0) ? &salt_[0] : NULL;
+    uint8_t* const digest = &digest_[0];
+    assert(digest_.size() == SHA1_HASHSIZE);
+
+    iterateSHA1(&sha1_ctx_, static_cast<const uint8_t*>(obuf_.getData()),
+                obuf_.getLength(), salt, saltlen, digest);
+    for (unsigned int n = 0; n < iterations_; ++n) {
+        iterateSHA1(&sha1_ctx_, digest, SHA1_HASHSIZE, salt, saltlen, digest);
+    }
+
+    return (encodeBase32Hex(digest_));
+}
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+
+NSEC3Hash*
+NSEC3Hash::create(const generic::NSEC3PARAM& param) {
+    return (new NSEC3HashRFC5155(param));
+}
+
+} // namespace dns
+} // namespace isc

+ 134 - 0
src/lib/dns/nsec3hash.h

@@ -0,0 +1,134 @@
+// Copyright (C) 2012  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 __NSEC3HASH_H
+#define __NSEC3HASH_H 1
+
+#include <string>
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dns {
+class Name;
+
+namespace rdata {
+namespace generic {
+class NSEC3PARAM;
+}
+}
+
+/// \brief An exception that is thrown for when an \c NSEC3Hash object is
+/// constructed with an unknown hash algorithm.
+///
+/// A specific exception class is used so that the caller can selectively
+/// catch this exception, e.g., while loading a zone, and handle it
+/// accordingly.
+class UnknownNSEC3HashAlgorithm : public isc::Exception {
+public:
+    UnknownNSEC3HashAlgorithm(const char* file, size_t line,
+                              const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+/// \brief A calculator of NSEC3 hashes.
+///
+/// This is an abstract base class that defines a simple interface to
+/// calculating NSEC3 hash values as defined in RFC5155.
+///
+/// (Derived classes of) this class is designed to be "stateless" in that it
+/// basically doesn't hold mutable state once constructed, and hash
+/// calculation solely depends on the parameters given on construction and
+/// input to the \c calculate() method.  In that sense this could be a
+/// single free function rather than  a class, but we decided to provide the
+/// functionality as a class for two reasons: NSEC3 hash calculations would
+/// often take place more than one time in a single query or validation
+/// process, so it would be more efficient if we could hold some internal
+/// resources used for the calculation and reuse it over multiple calls to
+/// \c calculate() (a concrete implementation in this library actually does
+/// this); Second, we may want to customize the hash calculation logic for
+/// testing purposes or for other future extensions.  For example, we may
+/// want to use a fake calculator for tests that returns pre-defined hash
+/// values (so a slight change to the test input wouldn't affect the test
+/// result).  Using classes from this base would make it possible more
+/// transparently to the application.
+///
+/// A specific derived class instance must be created by the factory method,
+/// \c create().
+///
+/// There can be several ways to extend this class in future.  Those include:
+/// - Allow customizing the factory method so the application change the
+///   behavior dynamically.
+/// - Allow to construct the class from a tuple of parameters, that is,
+///   integers for algorithm, iterations and flags, and opaque salt data.
+///   For example, we might want to use that version for validators.
+/// - Allow producing hash value as binary data
+/// - Allow updating NSEC3 parameters of a class object so we can still reuse
+///   the internal resources for different sets of parameters.
+class NSEC3Hash {
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is defined as protected to prevent this class from being directly
+    /// instantiated even if the class definition is modified (accidentally
+    /// or intentionally) to have no pure virtual methods.
+    NSEC3Hash() {}
+
+public:
+    /// \brief Factory method of NSECHash from NSEC3PARAM RDATA.
+    ///
+    /// The hash algorithm given via \c param must be known to the
+    /// implementation.  Otherwise \c UnknownNSEC3HashAlgorithm exception
+    /// will be thrown.
+    ///
+    /// This method creates an \c NSEC3Hash object using \c new.  The caller
+    /// is responsible for releasing it with \c delete that is compatible to
+    /// the one used in this library.  In practice, the application would
+    /// generally need to store the returned pointer in some form of smart
+    /// pointer; otherwise the resulting code will be quite fragile against
+    /// exceptions (and in this case the application doesn't have to worry
+    /// about explicit \c delete).
+    ///
+    /// \throw UnknownNSEC3HashAlgorithm The specified algorithm in \c param
+    /// is unknown.
+    /// \throw std::bad_alloc Internal resource allocation failure.
+    ///
+    /// \param param NSEC3 parameters used for subsequent calculation.
+    /// \return A pointer to a concrete derived object of \c NSEC3Hash.
+    static NSEC3Hash* create(const rdata::generic::NSEC3PARAM& param);
+
+    /// \brief The destructor.
+    virtual ~NSEC3Hash() {}
+
+    /// \brief Calculate the NSEC3 hash.
+    ///
+    /// This method calculates the NSEC3 hash value for the given \c name
+    /// with the hash parameters (algorithm, iterations and salt) given at
+    /// construction, and returns the value as a base32hex-encoded string
+    /// (without containing any white spaces).  All US-ASCII letters in the
+    /// string will be upper cased.
+    ///
+    /// \param name The domain name for which the hash value is to be
+    /// calculated.
+    /// \return Base32hex-encoded string of the hash value.
+    virtual std::string calculate(const Name& name) const = 0;
+};
+
+}
+}
+#endif  // __NSEC3HASH_H
+
+// Local Variables:
+// mode: c++
+// End:

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

@@ -7,6 +7,7 @@ AM_CXXFLAGS = $(B10_CXXFLAGS)
 lib_LTLIBRARIES = libpydnspp.la
 libpydnspp_la_SOURCES = pydnspp_common.cc pydnspp_common.h pydnspp_towire.h
 libpydnspp_la_SOURCES += name_python.cc name_python.h
+libpydnspp_la_SOURCES += nsec3hash_python.cc nsec3hash_python.h
 libpydnspp_la_SOURCES += rrset_python.cc rrset_python.h
 libpydnspp_la_SOURCES += rrclass_python.cc rrclass_python.h
 libpydnspp_la_SOURCES += rrtype_python.cc rrtype_python.h
@@ -41,6 +42,7 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
 
 EXTRA_DIST = tsigerror_python_inc.cc
 EXTRA_DIST += message_python_inc.cc
+EXTRA_DIST += nsec3hash_python_inc.cc
 
 # Python prefers .so, while some OSes (specifically MacOS) use a different
 # suffix for dynamic objects.  -module is necessary to work this around.

+ 214 - 0
src/lib/dns/python/nsec3hash_python.cc

@@ -0,0 +1,214 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+
+#include "pydnspp_common.h"
+#include "name_python.h"
+#include "nsec3hash_python.h"
+#include "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;
+
+// Import pydoc text
+#include "nsec3hash_python_inc.cc"
+
+// Trivial constructor.
+s_NSEC3Hash::s_NSEC3Hash() : cppobj(NULL) {
+}
+
+namespace {
+int
+NSEC3Hash_init(PyObject* po_self, PyObject* args, PyObject*) {
+    s_NSEC3Hash* const self = static_cast<s_NSEC3Hash*>(po_self);
+    try {
+        PyObject* po_rdata;
+        if (PyArg_ParseTuple(args, "O", &po_rdata)) {
+            if (!PyRdata_Check(po_rdata)) {
+                PyErr_Format(PyExc_TypeError,
+                             "param must be an Rdata of type NSEC3HASH, "
+                             "not %.200s", po_rdata->ob_type->tp_name);
+                return (-1);
+            }
+            self->cppobj = NSEC3Hash::create(
+                dynamic_cast<const generic::NSEC3PARAM&>(
+                    PyRdata_ToRdata(po_rdata)));
+            return (0);
+        }
+    } catch (const UnknownNSEC3HashAlgorithm& ex) {
+        PyErr_SetString(po_UnknownNSEC3HashAlgorithm, ex.what());
+        return (-1);
+    } catch (const exception& ex) {
+        const string ex_what = "Failed to construct NSEC3Hash object: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (-1);
+    }
+
+    return (-1);
+}
+
+void
+NSEC3Hash_destroy(PyObject* po_self) {
+    s_NSEC3Hash* self = static_cast<s_NSEC3Hash*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+NSEC3Hash_calculate(PyObject* po_self, PyObject* args) {
+    s_NSEC3Hash* const self = static_cast<s_NSEC3Hash*>(po_self);
+
+    try {
+        PyObject* po_name;
+        if (PyArg_ParseTuple(args, "O", &po_name)) {
+            if (!PyName_Check(po_name)) {
+                PyErr_Format(PyExc_TypeError,
+                             "name must be a Name, not %.200s",
+                             po_name->ob_type->tp_name);
+                return (NULL);
+            }
+            const string hash =
+                self->cppobj->calculate(PyName_ToName(po_name));
+            return (Py_BuildValue("s", hash.c_str()));
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Unexpected failure in NSEC3Hash.calculate: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+
+    return (NULL);
+}
+
+// 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 NSEC3Hash_methods[] = {
+    { "calculate", NSEC3Hash_calculate, METH_VARARGS, NSEC3Hash_calculate_doc },
+    { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+//
+// Declaration of the custom exceptions
+// Initialization and addition of these go in pydnspp.cc
+//
+PyObject* po_UnknownNSEC3HashAlgorithm;
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_NSEC3Hash
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject nsec3hash_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "dns.NSEC3Hash",
+    sizeof(s_NSEC3Hash),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    NSEC3Hash_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
+    NSEC3Hash_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    NSEC3Hash_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
+    NSEC3Hash_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 (nothing right now) are initialized here
+bool
+initModulePart_NSEC3Hash(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(&nsec3hash_type) < 0) {
+        return (false);
+    }
+    void* p = &nsec3hash_type;
+    if (PyModule_AddObject(mod, "NSEC3Hash", static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&nsec3hash_type);
+
+    return (true);
+}
+} // namespace python
+} // namespace dns
+} // namespace isc

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

@@ -0,0 +1,47 @@
+// Copyright (C) 2012  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_NSEC3HASH_H
+#define __PYTHON_NSEC3HASH_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class NSEC3Hash;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_NSEC3Hash : public PyObject {
+public:
+    s_NSEC3Hash();
+    NSEC3Hash* cppobj;
+};
+
+extern PyTypeObject nsec3hash_type;
+
+// Public exception object.
+extern PyObject* po_UnknownNSEC3HashAlgorithm;
+
+bool initModulePart_NSEC3Hash(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // __PYTHON_NSEC3HASH_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 45 - 0
src/lib/dns/python/nsec3hash_python_inc.cc

@@ -0,0 +1,45 @@
+namespace {
+// Modifications
+//   - removed intermediate details note, mainly for brevity
+//   - removed std::bad_alloc
+const char* const NSEC3Hash_doc = "\
+A calculator of NSEC3 hashes.\n\
+\n\
+This is a simple class that encapsulates the algorithm of calculating\n\
+NSEC3 hash values as defined in RFC5155.\n\
+\n\
+NSEC3Hash(param)\n\
+\n\
+    Constructor from NSEC3PARAM RDATA.\n\
+\n\
+    The hash algorithm given via param must be known to the\n\
+    implementation. Otherwise UnknownNSEC3HashAlgorithm exception will\n\
+    be thrown.\n\
+\n\
+    Exceptions:\n\
+      UnknownNSEC3HashAlgorithm The specified algorithm in param is\n\
+                 unknown.\n\
+\n\
+    Parameters:\n\
+      param      NSEC3 parameters used for subsequent calculation.\n\
+\n\
+";
+
+const char* const NSEC3Hash_calculate_doc = "\
+calculate(Name) -> string\n\
+\n\
+Calculate the NSEC3 hash.\n\
+\n\
+This method calculates the NSEC3 hash value for the given name with\n\
+the hash parameters (algorithm, iterations and salt) given at\n\
+construction, and returns the value in a base32hex-encoded string\n\
+(without containing any white spaces). All US-ASCII letters in the\n\
+string will be upper cased.\n\
+\n\
+Parameters:\n\
+  name       The domain name for which the hash value is to be\n\
+             calculated.\n\
+\n\
+Return Value(s): Base32hex-encoded string of the hash value.\n\
+";
+} // unnamed namespace

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

@@ -39,6 +39,7 @@
 #include "message_python.h"
 #include "messagerenderer_python.h"
 #include "name_python.h"
+#include "nsec3hash_python.h"
 #include "opcode_python.h"
 #include "pydnspp_common.h"
 #include "pydnspp_towire.h"
@@ -164,6 +165,10 @@ initModulePart_Message(PyObject* mod) {
             PyErr_NewException("pydnspp.DNSMessageBADVERS", NULL, NULL);
         PyObjectContainer(po_DNSMessageBADVERS).installToModule(
             mod, "DNSMessageBADVERS");
+        po_UnknownNSEC3HashAlgorithm =
+            PyErr_NewException("pydnspp.UnknownNSEC3HashAlgorithm", NULL, NULL);
+        PyObjectContainer(po_UnknownNSEC3HashAlgorithm).installToModule(
+            mod, "UnknownNSEC3HashAlgorithm");
     } catch (const std::exception& ex) {
         const std::string ex_what =
             "Unexpected failure in Message initialization: " +
@@ -777,6 +782,10 @@ PyInit_pydnspp(void) {
         return (NULL);
     }
 
+    if (!initModulePart_NSEC3Hash(mod)) {
+        return (NULL);
+    }
+
     if (!initModulePart_RRClass(mod)) {
         return (NULL);
     }

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

@@ -3,6 +3,7 @@ PYTESTS = edns_python_test.py
 PYTESTS += message_python_test.py
 PYTESTS += messagerenderer_python_test.py
 PYTESTS += name_python_test.py
+PYTESTS += nsec3hash_python_test.py
 PYTESTS += question_python_test.py
 PYTESTS += opcode_python_test.py
 PYTESTS += rcode_python_test.py

+ 77 - 0
src/lib/dns/python/tests/nsec3hash_python_test.py

@@ -0,0 +1,77 @@
+# Copyright (C) 2012  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
+from pydnspp import *
+
+class NSEC3HashTest(unittest.TestCase):
+    '''These tests are mostly straightforward conversion of C++ tests
+    except for python specific type checks.
+
+    '''
+
+    def setUp(self):
+        self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(), RRClass.IN(),
+                                         "1 0 12 aabbccdd"))
+
+    def test_bad_construct(self):
+        # missing parameter
+        self.assertRaises(TypeError, NSEC3Hash)
+
+        # invalid type of argument
+        self.assertRaises(TypeError, NSEC3Hash, "1 0 12 aabbccdd")
+
+        # additional parameter
+        self.assertRaises(TypeError, NSEC3Hash, Rdata(RRType.NSEC3PARAM(),
+                                                      RRClass.IN(),
+                                                      "1 0 12 aabbccdd"), 1)
+
+    def test_unknown_algorithm(self):
+        self.assertRaises(UnknownNSEC3HashAlgorithm, NSEC3Hash,
+                          Rdata(RRType.NSEC3PARAM(), RRClass.IN(),
+                                "2 0 12 aabbccdd"))
+
+    def test_calculate(self):
+        # A couple of normal cases from the RFC5155 example.
+        self.assertEqual("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+                         self.test_hash.calculate(Name("example")))
+        self.assertEqual("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
+                         self.test_hash.calculate(Name("a.example")))
+
+        # Check case-insensitiveness
+        self.assertEqual("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+                         self.test_hash.calculate(Name("EXAMPLE")))
+
+        # Some boundary cases: 0-iteration and empty salt.  Borrowed from the
+        # .com zone data.
+        self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(),
+                                         RRClass.IN(),"1 0 0 -"))
+        self.assertEqual("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+                         self.test_hash.calculate(Name("com")))
+
+        # Using unusually large iterations, something larger than the 8-bit
+        #range.  (expected hash value generated by BIND 9's dnssec-signzone)
+        self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(),
+                                         RRClass.IN(), "1 0 256 AABBCCDD"))
+        self.assertEqual("COG6A52MJ96MNMV3QUCAGGCO0RHCC2Q3",
+                         self.test_hash.calculate(Name("example.org")))
+
+    def test_calculate_badparam(self):
+        self.assertRaises(TypeError, self.test_hash.calculate, "example")
+        self.assertRaises(TypeError, self.test_hash.calculate)
+        self.assertRaises(TypeError, self.test_hash.calculate, Name("."), 1)
+
+if __name__ == '__main__':
+    unittest.main()

+ 4 - 1
src/lib/dns/rdata/generic/nsec3param_51.cc

@@ -67,8 +67,11 @@ NSEC3PARAM::NSEC3PARAM(const string& nsec3param_str) :
         isc_throw(InvalidRdataText, "NSEC3PARAM flags out of range");
     }
 
+    const string salt_str = saltbuf.str();
     vector<uint8_t> salt;
-    decodeHex(saltbuf.str(), salt);
+    if (salt_str != "-") { // "-" means an empty salt, no need to touch vector
+        decodeHex(saltbuf.str(), salt);
+    }
 
     impl_ = new NSEC3PARAMImpl(hashalg, flags, iterations, salt);
 }

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

@@ -21,6 +21,7 @@ run_unittests_SOURCES = unittest_util.h unittest_util.cc
 run_unittests_SOURCES += edns_unittest.cc
 run_unittests_SOURCES += messagerenderer_unittest.cc
 run_unittests_SOURCES += name_unittest.cc
+run_unittests_SOURCES += nsec3hash_unittest.cc
 run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc
 run_unittests_SOURCES += rrttl_unittest.cc
 run_unittests_SOURCES += opcode_unittest.cc

+ 73 - 0
src/lib/dns/tests/nsec3hash_unittest.cc

@@ -0,0 +1,73 @@
+// Copyright (C) 2012  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 <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <dns/nsec3hash.h>
+#include <dns/rdataclass.h>
+
+using boost::scoped_ptr;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+typedef scoped_ptr<NSEC3Hash> NSEC3HashPtr;
+
+class NSEC3HashTest : public ::testing::Test {
+protected:
+    NSEC3HashTest() :
+        test_hash(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")))
+    {}
+
+    // An NSEC3Hash object commonly used in tests.  Parameters are borrowed
+    // from the RFC5155 example.  Construction of this object implicitly
+    // checks a successful case of the creation.
+    NSEC3HashPtr test_hash;
+};
+
+TEST_F(NSEC3HashTest, unknownAlgorithm) {
+    EXPECT_THROW(NSEC3HashPtr(
+                     NSEC3Hash::create(
+                         generic::NSEC3PARAM("2 0 12 aabbccdd"))),
+                     UnknownNSEC3HashAlgorithm);
+}
+
+TEST_F(NSEC3HashTest, calculate) {
+    // A couple of normal cases from the RFC5155 example.
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+    EXPECT_EQ("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
+              test_hash->calculate(Name("a.example")));
+
+    // Check case-insensitiveness
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("EXAMPLE")));
+
+    // Some boundary cases: 0-iteration and empty salt.  Borrowed from the
+    // .com zone data.
+    EXPECT_EQ("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+              NSEC3HashPtr(NSEC3Hash::create(generic::NSEC3PARAM("1 0 0 -")))
+              ->calculate(Name("com")));
+
+    // Using unusually large iterations, something larger than the 8-bit range.
+    // (expected hash value generated by BIND 9's dnssec-signzone)
+    EXPECT_EQ("COG6A52MJ96MNMV3QUCAGGCO0RHCC2Q3",
+              NSEC3HashPtr(NSEC3Hash::create(
+                               generic::NSEC3PARAM("1 0 256 AABBCCDD")))
+              ->calculate(Name("example.org")));
+}
+
+} // end namespace

+ 13 - 2
src/lib/dns/tests/rdata_nsec3param_unittest.cc

@@ -40,9 +40,20 @@ using namespace isc::dns::rdata;
 
 namespace {
 class Rdata_NSEC3PARAM_Test : public RdataTest {
-    // there's nothing to specialize
+public:
+    Rdata_NSEC3PARAM_Test() : nsec3param_txt("1 0 1 D399EAAB") {}
+    const string nsec3param_txt;
 };
-string nsec3param_txt("1 0 1 D399EAAB");
+
+TEST_F(Rdata_NSEC3PARAM_Test, fromText) {
+    // With a salt
+    EXPECT_EQ(1, generic::NSEC3PARAM(nsec3param_txt).getHashalg());
+    EXPECT_EQ(0, generic::NSEC3PARAM(nsec3param_txt).getFlags());
+    // (salt is checked in the toText test)
+
+    // With an empty salt
+    EXPECT_EQ(0, generic::NSEC3PARAM("1 0 0 -").getSalt().size());
+}
 
 TEST_F(Rdata_NSEC3PARAM_Test, toText) {
     const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);

+ 9 - 8
src/lib/util/python/wrapper_template.cc

@@ -33,14 +33,6 @@ using namespace isc::@MODULE@;
 using namespace isc::@MODULE@::python;
 
 //
-// Definition of the classes
-//
-
-// For each class, we need a struct, a helper functions (init, destroy,
-// and static wrappers around the methods we export), a list of methods,
-// and a type description
-
-//
 // @CPPCLASS@
 //
 
@@ -52,6 +44,7 @@ namespace {
 // Shortcut type which would be convenient for adding class variables safely.
 typedef CPPPyObjectContainer<s_@CPPCLASS@, @CPPCLASS@> @CPPCLASS@Container;
 
+@REMOVE_THIS_ON_RELEASE@
 // This is a template of typical code logic of python class initialization
 // with C++ backend.  You'll need to adjust it according to details of the
 // actual C++ class.
@@ -60,6 +53,7 @@ int
     s_@CPPCLASS@* self = static_cast<s_@CPPCLASS@*>(po_self);
     try {
         if (PyArg_ParseTuple(args, "REPLACE ME")) {
+            @REMOVE_THIS_ON_RELEASE@
             // YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
             self->cppobj = new @CPPCLASS@(/*NECESSARY PARAMS*/);
             return (0);
@@ -74,6 +68,7 @@ int
         return (-1);
     }
 
+    @REMOVE_THIS_ON_RELEASE@
     // If we are here PyArg_ParseTuple() failed and TypeError should have
     // been set.  If the constructor is more complicated and the control
     // could reach this point for other reasons, an appropriate Python
@@ -82,6 +77,7 @@ int
     return (-1);
 }
 
+@REMOVE_THIS_ON_RELEASE@
 // 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
@@ -92,6 +88,7 @@ void
     Py_TYPE(self)->tp_free(self);
 }
 
+@REMOVE_THIS_ON_RELEASE@
 // This should be able to be used without modification as long as the
 // underlying C++ class has toText().
 PyObject*
@@ -119,6 +116,7 @@ PyObject*
                                 const_cast<char*>("")));
 }
 
+@REMOVE_THIS_ON_RELEASE@
 // This is quite specific isc.dns.  For other wrappers this should probably
 // be removed.
 PyObject* @CPPCLASS@_toWire(PyObject* self, PyObject* args) {
@@ -175,6 +173,8 @@ PyObject*
 PyMethodDef @CPPCLASS@_methods[] = {
     { "to_text", @CPPCLASS@_toText, METH_NOARGS,
       @CPPCLASS@_toText_doc },
+
+    @REMOVE_THIS_ON_RELEASE@
     // This is quite specific isc.dns.  For other wrappers this should probably
     // be removed:
     { "to_wire", @CPPCLASS@_toWire, METH_VARARGS,
@@ -256,6 +256,7 @@ initModulePart_@CPPCLASS@(PyObject* mod) {
     }
     Py_INCREF(&@cppclass@_type);
 
+    @REMOVE_THIS_ON_RELEASE@
     // 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.