Parcourir la source

[2107merge] Merge branch 'trac2107' into trac2107merge with fixing conflicts.

Conflict resolution is mostly straightforward adujstment for the latest
interface, but it's not super trivial and should be reviewed.
JINMEI Tatuya il y a 12 ans
Parent
commit
0aeb26af21

+ 4 - 5
src/lib/datasrc/memory/Makefile.am

@@ -10,10 +10,9 @@ CLEANFILES = *.gcno *.gcda datasrc_messages.h datasrc_messages.cc
 
 noinst_LTLIBRARIES = libdatasrc_memory.la
 
-libdatasrc_memory_la_SOURCES = \
-	rdataset.h rdataset.cc \
-	rdata_serialization.h rdata_serialization.cc \
-	domaintree.h
-libdatasrc_memory_la_SOURCES += zone_data.h
+libdatasrc_memory_la_SOURCES = domaintree.h
+libdatasrc_memory_la_SOURCES += rdataset.h rdataset.cc
+libdatasrc_memory_la_SOURCES += rdata_serialization.h rdata_serialization.cc
+libdatasrc_memory_la_SOURCES += zone_data.h zone_data.cc
 libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
 EXTRA_DIST  = rdata_serialization_priv.cc

Fichier diff supprimé car celui-ci est trop grand
+ 276 - 249
src/lib/datasrc/memory/domaintree.h


+ 66 - 0
src/lib/datasrc/memory/rdataset.h

@@ -194,6 +194,43 @@ public:
     static void destroy(util::MemorySegment& mem_sgmt, dns::RRClass rrclass,
                         RdataSet* rdataset);
 
+    /// \brief Find \c RdataSet of given RR type from a list (const version).
+    ///
+    /// This function is a convenient shortcut for commonly used operation of
+    /// finding a given type of \c RdataSet from a linked list of them.
+    ///
+    /// It follows the linked list of \c RdataSet objects (via their \c next
+    /// member) starting the given head, until it finds an object of the
+    /// given RR type.  If found, it returns a (bare) pointer to the object;
+    /// if not found in the entire list, it returns NULL.  The head pointer
+    /// can be NULL, in which case this function will simply return NULL.
+    ///
+    /// \note This function is defined as a (static) class method to
+    /// clarify its an operation for \c RdataSet objects and to make the
+    /// name shorter.  But its implementation does not depend on private
+    /// members of the class, and it should be kept if and when this method
+    /// needs to be extended, unless there's a reason other than simply
+    /// because it's already a member function.
+    ///
+    /// \param rdata_head A pointer to \c RdataSet from which the search
+    /// starts.  It can be NULL.
+    /// \param type The RRType of \c RdataSet to find.
+    /// \return A pointer to the found \c RdataSet or NULL if none found.
+    static const RdataSet*
+    find(const RdataSet* rdataset_head, const dns::RRType& type) {
+        return (find<const RdataSet>(rdataset_head, type));
+    }
+
+    /// \brief Find \c RdataSet of given RR type from a list (non const
+    /// version).
+    ///
+    /// This is similar to the const version, except it takes and returns non
+    /// const pointers.
+    static RdataSet*
+    find(RdataSet* rdataset_head, const dns::RRType& type) {
+        return (find<RdataSet>(rdataset_head, type));
+    }
+
     typedef boost::interprocess::offset_ptr<RdataSet> RdataSetPtr;
     typedef boost::interprocess::offset_ptr<const RdataSet> ConstRdataSetPtr;
 
@@ -228,6 +265,20 @@ private:
     static const size_t MANY_RRSIG_COUNT = (1 << 3) - 1;
 
 public:
+    /// \brief Return the bare pointer to the next node.
+    ///
+    /// In such an operation as iterating over a linked list of \c RdataSet
+    /// object, using this method is generally more efficient than using
+    /// the \c next member directly because it prevents unintentional
+    /// creation of offset pointer objects.  While the application can
+    /// get the same result by directly calling get() on \c next, it would
+    /// help encourage the use of more efficient usage if we provide an
+    /// explicit accessor.
+    const RdataSet* getNext() const { return (next.get()); }
+
+    /// \brief Return the bare pointer to the next node, mutable version.
+    RdataSet* getNext() { return (next.get()); }
+
     /// \brief Return the number of RDATAs stored in the \c RdataSet.
     size_t getRdataCount() const { return (rdata_count_); }
 
@@ -293,6 +344,21 @@ private:
         return (reinterpret_cast<uint16_t*>(this + 1));
     }
 
+    // Shared by both mutable and immutable versions of find()
+    template <typename RdataSetType>
+    static RdataSetType*
+    find(RdataSetType* rdataset_head, const dns::RRType& type) {
+        for (RdataSetType* rdataset = rdataset_head;
+             rdataset != NULL;
+             rdataset = rdataset->getNext()) // use getNext() for efficiency
+        {
+            if (rdataset->type == type) {
+                return (rdataset);
+            }
+        }
+        return (NULL);
+    }
+
     /// \brief The constructor.
     ///
     /// An object of this class is always expected to be created by the

+ 63 - 0
src/lib/datasrc/memory/segment_object_holder.h

@@ -0,0 +1,63 @@
+// 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 DATASRC_MEMORY_SEGMENT_OBJECT_HOLDER_H
+#define DATASRC_MEMORY_SEGMENT_OBJECT_HOLDER_H 1
+
+#include <util/memory_segment.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace detail {
+
+// A simple holder to create and use some objects in this implementation
+// in an exception safe manner.   It works like std::auto_ptr but much
+// more simplified.
+// template parameter T is the type of object allocated by mem_sgmt.
+// template parameter ARG_T is the type that will be passed to destroy()
+// (deleter functor, etc).  It must be copyable.
+template <typename T, typename ARG_T>
+class SegmentObjectHolder {
+public:
+    SegmentObjectHolder(util::MemorySegment& mem_sgmt, T* obj, ARG_T arg) :
+        mem_sgmt_(mem_sgmt), obj_(obj), arg_(arg)
+    {}
+    ~SegmentObjectHolder() {
+        if (obj_ != NULL) {
+            T::destroy(mem_sgmt_, obj_, arg_);
+        }
+    }
+    T* get() { return (obj_); }
+    T* release() {
+        T* ret = obj_;
+        obj_ = NULL;
+        return (ret);
+    }
+private:
+    util::MemorySegment& mem_sgmt_;
+    T* obj_;
+    ARG_T arg_;
+};
+
+} // detail
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_SEGMENT_OBJECT_HOLDER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 3 - 0
src/lib/datasrc/memory/tests/Makefile.am

@@ -22,6 +22,9 @@ run_unittests_SOURCES += rdata_serialization_unittest.cc
 run_unittests_SOURCES += rdataset_unittest.cc
 run_unittests_SOURCES += domaintree_unittest.cc
 run_unittests_SOURCES += zone_table_unittest.cc
+run_unittests_SOURCES += zone_data_unittest.cc
+run_unittests_SOURCES += memory_segment_test.h
+run_unittests_SOURCES += segment_object_holder_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)

+ 29 - 22
src/lib/datasrc/memory/tests/domaintree_unittest.cc

@@ -59,18 +59,13 @@ const size_t Name::MAX_LABELS;
 
 namespace {
 
-class DeleterType {
-public:
-    DeleterType() {}
-
-    void operator()(util::MemorySegment&, int* i) const {
-        delete i;
-    }
-};
+void deleteData(int* i) {
+    delete i;
+}
 
-typedef DomainTree<int, DeleterType> TestDomainTree;
-typedef DomainTreeNode<int, DeleterType> TestDomainTreeNode;
-typedef DomainTreeNodeChain<int, DeleterType> TestDomainTreeNodeChain;
+typedef DomainTree<int> TestDomainTree;
+typedef DomainTreeNode<int> TestDomainTreeNode;
+typedef DomainTreeNodeChain<int> TestDomainTreeNodeChain;
 
 class TreeHolder {
 public:
@@ -78,7 +73,7 @@ public:
         mem_sgmt_(mem_sgmt), tree_(tree)
     {}
     ~TreeHolder() {
-        TestDomainTree::destroy(mem_sgmt_, tree_);
+        TestDomainTree::destroy(mem_sgmt_, tree_, deleteData);
     }
     TestDomainTree* get() { return (tree_); }
 private:
@@ -102,12 +97,13 @@ protected:
         int name_count = sizeof(domain_names) / sizeof(domain_names[0]);
         for (int i = 0; i < name_count; ++i) {
             dtree.insert(mem_sgmt_, Name(domain_names[i]), &dtnode);
-            dtnode->setData(mem_sgmt_, new int(i + 1));
+            // Check the node doesn't have any data initially.
+            EXPECT_EQ(static_cast<int*>(NULL),
+                      dtnode->setData(new int(i + 1)));
 
             dtree_expose_empty_node.insert(mem_sgmt_, Name(domain_names[i]),
                                             &dtnode);
-            dtnode->setData(mem_sgmt_, new int(i + 1));
-
+            EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(i + 1)));
         }
     }
 
@@ -125,13 +121,22 @@ TEST_F(DomainTreeTest, nodeCount) {
 
     // Delete all nodes, then the count should be set to 0.  This also tests
     // the behavior of deleteAllNodes().
-    dtree.deleteAllNodes(mem_sgmt_);
+    dtree.deleteAllNodes(mem_sgmt_, deleteData);
     EXPECT_EQ(0, dtree.getNodeCount());
 }
 
 TEST_F(DomainTreeTest, setGetData) {
-    dtnode->setData(mem_sgmt_, new int(11));
+    // set new data to an existing node.  It should have some data.
+    int* newdata = new int(11);
+    int* olddata = dtnode->setData(newdata);
+    EXPECT_NE(static_cast<int*>(NULL), olddata);
+    deleteData(olddata);
     EXPECT_EQ(11, *(dtnode->getData()));
+
+    // clear the node.  we should get the new data back we just passed.
+    olddata = dtnode->setData(NULL);
+    EXPECT_EQ(newdata, olddata);
+    deleteData(olddata);
 }
 
 TEST_F(DomainTreeTest, insertNames) {
@@ -151,7 +156,9 @@ TEST_F(DomainTreeTest, insertNames) {
                                                   Name("example.com"),
                                                   &dtnode));
     EXPECT_EQ(17, dtree.getNodeCount());
-    dtnode->setData(mem_sgmt_, new int(12));
+    // ad data to it; also make sure it doesn't have data right now
+    // (otherwise it would leak)
+    EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(12)));
 
     // return ALREADYEXISTS, since node "example.com" already has
     // been explicitly inserted
@@ -381,7 +388,7 @@ performCallbackTest(TestDomainTree& dtree,
     EXPECT_EQ(TestDomainTree::SUCCESS, dtree.insert(mem_sgmt,
                                                   Name("callback.example"),
                                                   &dtnode));
-    dtnode->setData(mem_sgmt, new int(1));
+    EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(1)));
     EXPECT_FALSE(dtnode->getFlag(TestDomainTreeNode::FLAG_CALLBACK));
 
     // enable/re-disable callback
@@ -397,7 +404,7 @@ performCallbackTest(TestDomainTree& dtree,
     EXPECT_EQ(TestDomainTree::SUCCESS, dtree.insert(mem_sgmt,
                                                   Name("sub.callback.example"),
                                                   &subdtnode));
-    subdtnode->setData(mem_sgmt, new int(2));
+    EXPECT_EQ(static_cast<int*>(NULL), subdtnode->setData(new int(2)));
     TestDomainTreeNode* parentdtnode;
     EXPECT_EQ(TestDomainTree::ALREADYEXISTS, dtree.insert(mem_sgmt,
                                                         Name("example"),
@@ -997,7 +1004,7 @@ TEST_F(DomainTreeTest, root) {
     TreeHolder tree_holder(mem_sgmt_, TestDomainTree::create(mem_sgmt_));
     TestDomainTree& root(*tree_holder.get());
     root.insert(mem_sgmt_, Name::ROOT_NAME(), &dtnode);
-    dtnode->setData(mem_sgmt_, new int(1));
+    EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(1)));
 
     EXPECT_EQ(TestDomainTree::EXACTMATCH,
               root.find(Name::ROOT_NAME(), &cdtnode));
@@ -1009,7 +1016,7 @@ TEST_F(DomainTreeTest, root) {
     // Insert a new name that better matches the query name.  find() should
     // find the better one.
     root.insert(mem_sgmt_, Name("com"), &dtnode);
-    dtnode->setData(mem_sgmt_, new int(2));
+    EXPECT_EQ(static_cast<int*>(NULL), dtnode->setData(new int(2)));
     EXPECT_EQ(TestDomainTree::PARTIALMATCH,
               root.find(Name("example.com"), &cdtnode));
     EXPECT_EQ(dtnode, cdtnode);

+ 62 - 0
src/lib/datasrc/memory/tests/memory_segment_test.h

@@ -0,0 +1,62 @@
+// 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 DATASRC_MEMORY_SEGMENT_TEST_H
+#define DATASRC_MEMORY_SEGMENT_TEST_H 1
+
+#include <util/memory_segment_local.h>
+
+#include <cstddef>              // for size_t
+#include <new>                  // for bad_alloc
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace test {
+
+// A special memory segment that can be used for tests.  It normally behaves
+// like a "local" memory segment.  If "throw count" is set to non 0 via
+// setThrowCount(), it continues the normal behavior until the specified
+// number of calls to allocate(), exclusive, and throws an exception at the
+// next call.  For example, if count is set to 3, the next two calls to
+// allocate() will succeed, and the 3rd call will fail with an exception.
+// This segment object can be used after the exception is thrown, and the
+// count is internally reset to 0.
+class MemorySegmentTest : public isc::util::MemorySegmentLocal {
+public:
+    MemorySegmentTest() : throw_count_(0) {}
+    virtual void* allocate(std::size_t size) {
+        if (throw_count_ > 0) {
+            if (--throw_count_ == 0) {
+                throw std::bad_alloc();
+            }
+        }
+        return (isc::util::MemorySegmentLocal::allocate(size));
+    }
+    void setThrowCount(std::size_t count) { throw_count_ = count; }
+
+private:
+    std::size_t throw_count_;
+};
+
+} // namespace test
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_SEGMENT_TEST_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 19 - 0
src/lib/datasrc/memory/tests/rdataset_unittest.cc

@@ -115,6 +115,25 @@ TEST_F(RdataSetTest, create) {
     RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
 }
 
+TEST_F(RdataSetTest, getNext) {
+    RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
+                                          ConstRRsetPtr());
+
+    // By default, the next pointer should be NULL (already tested in other
+    // test cases), which should be the case with getNext().  We test both
+    // mutable and immutable versions of getNext().
+    EXPECT_EQ(static_cast<RdataSet*>(NULL), rdataset->getNext());
+    EXPECT_EQ(static_cast<const RdataSet*>(NULL),
+              static_cast<const RdataSet*>(rdataset)->getNext());
+
+    // making a link (it would form an infinite loop, but it doesn't matter
+    // in this test), and check the pointer returned by getNext().
+    rdataset->next = rdataset;
+    EXPECT_EQ(rdataset, static_cast<const RdataSet*>(rdataset)->getNext());
+
+    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+}
+
 // A helper function to create an RRset containing the given number of
 // unique RDATAs.
 ConstRRsetPtr

+ 67 - 0
src/lib/datasrc/memory/tests/segment_object_holder_unittest.cc

@@ -0,0 +1,67 @@
+// 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 <util/memory_segment_local.h>
+
+#include <datasrc/memory/segment_object_holder.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util;
+using namespace isc::datasrc::memory;
+using namespace isc::datasrc::memory::detail;
+
+namespace {
+const int TEST_ARG_VAL = 42;    // arbitrary chosen magic number
+
+class TestObject {
+public:
+    static void destroy(MemorySegment& sgmt, TestObject* obj, int arg) {
+        sgmt.deallocate(obj, sizeof(*obj));
+        EXPECT_EQ(TEST_ARG_VAL, arg);
+    }
+};
+
+void
+useHolder(MemorySegment& sgmt, TestObject* obj, bool release) {
+    // Create a holder object, check the return value of get(), and,
+    // if requested, release the held object.  At the end of function
+    // the holder is destructed, and if the object hasn't been released by
+    // then, it should be deallocated.  Passed argument is checked in its
+    // deallocate().
+
+    typedef SegmentObjectHolder<TestObject, int> HolderType;
+    HolderType holder(sgmt, obj, TEST_ARG_VAL);
+    EXPECT_EQ(obj, holder.get());
+    if (release) {
+        EXPECT_EQ(obj, holder.release());
+    }
+}
+
+TEST(SegmentObjectHolderTest, foo) {
+    MemorySegmentLocal sgmt;
+    void* p = sgmt.allocate(sizeof(TestObject));
+    TestObject* obj = new(p) TestObject;
+
+    // Use holder, and release the content.  The memory shouldn't be
+    // deallocated.
+    useHolder(sgmt, obj, true);
+    EXPECT_FALSE(sgmt.allMemoryDeallocated());
+
+    // Use holder, and let it deallocate the object.  The memory segment
+    // should now be empty.
+    useHolder(sgmt, obj, false);
+    EXPECT_TRUE(sgmt.allMemoryDeallocated());
+}
+}

+ 255 - 0
src/lib/datasrc/memory/tests/zone_data_unittest.cc

@@ -0,0 +1,255 @@
+// 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 "memory_segment_test.h"
+
+#include <dns/rdataclass.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/labelsequence.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/memory/rdata_serialization.h>
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+
+#include <testutils/dnsmessage_test.h>
+
+#include <gtest/gtest.h>
+
+#include <new>                  // for bad_alloc
+#include <string>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::datasrc::memory;
+using namespace isc::datasrc::memory::test;
+using namespace isc::testutils;
+
+namespace {
+
+// With this single fixture we'll test both NSEC3Data and ZoneData
+class ZoneDataTest : public ::testing::Test {
+protected:
+    ZoneDataTest() :
+        nsec3_data_(NULL), param_rdata_("1 0 12 aabbccdd"),
+        param_rdata_nosalt_("1 1 10 -"),
+        param_rdata_largesalt_("2 0 5 " + std::string(255 * 2, 'a')),
+        nsec3_rdata_("1 0 12 aabbccdd TDK23RP6 SOA"),
+        nsec3_rdata_nosalt_("1 1 10 - TDK23RP6 SOA"),
+        nsec3_rdata_largesalt_("2 0 5 " + std::string(255 * 2, 'a') +
+                               " TDK23RP6 SOA"),
+        zname_("example.com"),
+        zone_data_(ZoneData::create(mem_sgmt_, zname_)),
+        a_rrset_(textToRRset("www.example.com. 3600 IN A 192.0.2.1")),
+        aaaa_rrset_(textToRRset("www.example.com. 3600 IN AAAA 2001:db8::1")),
+        nsec3_rrset_(textToRRset("TDK23RP6.example.com. 3600 IN NSEC3 "
+                                 "1 0 12 aabbccdd TDK23RP6 SOA"))
+    {}
+    void TearDown() {
+        if (nsec3_data_ != NULL) {
+            NSEC3Data::destroy(mem_sgmt_, nsec3_data_, RRClass::IN());
+        }
+        if (zone_data_ != NULL) {
+            ZoneData::destroy(mem_sgmt_, zone_data_, RRClass::IN());
+        }
+        // detect any memory leak in the test memory segment
+        EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated());
+    }
+
+    MemorySegmentTest mem_sgmt_;
+    NSEC3Data* nsec3_data_;
+    const generic::NSEC3PARAM param_rdata_, param_rdata_nosalt_,
+        param_rdata_largesalt_;
+    const generic::NSEC3 nsec3_rdata_, nsec3_rdata_nosalt_,
+        nsec3_rdata_largesalt_;
+    const Name zname_;
+    ZoneData* zone_data_;
+    const ConstRRsetPtr a_rrset_, aaaa_rrset_, nsec3_rrset_;
+    RdataEncoder encoder_;
+};
+
+// Shared by both test cases using NSEC3 and NSEC3PARAM Rdata
+template <typename RdataType>
+void
+checkNSEC3Data(MemorySegmentTest& mem_sgmt, const RdataType& expect_rdata) {
+    NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt, expect_rdata);
+
+    // Internal tree should be created and empty.
+    EXPECT_EQ(0, nsec3_data->getNSEC3Tree().getNodeCount());
+
+    EXPECT_EQ(expect_rdata.getHashalg(), nsec3_data->hashalg);
+    EXPECT_EQ(expect_rdata.getFlags(), nsec3_data->flags);
+    EXPECT_EQ(expect_rdata.getIterations(), nsec3_data->iterations);
+    EXPECT_EQ(expect_rdata.getSalt().size(), nsec3_data->getSaltLen());
+    if (expect_rdata.getSalt().size() > 0) {
+        EXPECT_EQ(0, memcmp(&expect_rdata.getSalt()[0],
+                            nsec3_data->getSaltData(),
+                            expect_rdata.getSalt().size()));
+    }
+
+    NSEC3Data::destroy(mem_sgmt, nsec3_data, RRClass::IN());
+}
+
+void
+checkFindRdataSet(const ZoneTree& tree, const Name& name, RRType type,
+                  const RdataSet* expected_set)
+{
+    ZoneNode* node = NULL;
+    tree.find(name, &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    EXPECT_EQ(expected_set, RdataSet::find(node->getData(), type));
+}
+
+TEST_F(ZoneDataTest, createNSEC3Data) {
+    // Create an NSEC3Data object from various types of RDATA (of NSEC3PARAM
+    // and of NSEC3), check if the resulting parameters match.
+    checkNSEC3Data(mem_sgmt_, param_rdata_); // one 'usual' form of params
+    checkNSEC3Data(mem_sgmt_, param_rdata_nosalt_); // empty salt
+    checkNSEC3Data(mem_sgmt_, param_rdata_largesalt_); // max-len salt
+
+    // Same concepts of the tests, using NSEC3 RDATA.
+    checkNSEC3Data(mem_sgmt_, nsec3_rdata_);
+    checkNSEC3Data(mem_sgmt_, nsec3_rdata_nosalt_);
+    checkNSEC3Data(mem_sgmt_, nsec3_rdata_largesalt_);
+}
+
+TEST_F(ZoneDataTest, addNSEC3) {
+    nsec3_data_ = NSEC3Data::create(mem_sgmt_, param_rdata_);
+
+    ZoneNode* node = NULL;
+    nsec3_data_->insertName(mem_sgmt_, nsec3_rrset_->getName(), &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    EXPECT_TRUE(node->isEmpty()); // initially it should be empty
+
+    RdataSet* rdataset_nsec3 =
+        RdataSet::create(mem_sgmt_, encoder_, nsec3_rrset_, ConstRRsetPtr());
+    node->setData(rdataset_nsec3);
+
+    // Confirm we can find the added ones from the zone data.
+    checkFindRdataSet(nsec3_data_->getNSEC3Tree(), nsec3_rrset_->getName(),
+                      RRType::NSEC3(), rdataset_nsec3);
+
+    // TearDown() will confirm there's no leak on destroy
+}
+
+TEST_F(ZoneDataTest, getOriginNode) {
+    EXPECT_EQ(LabelSequence(zname_), zone_data_->getOriginNode()->getLabels());
+}
+
+TEST_F(ZoneDataTest, exceptionSafetyOnCreate) {
+    // Note: below, we use our knowledge of how memory allocation happens
+    // within the NSEC3Data, the zone data and the underlying domain tree
+    // implementation.  We'll emulate rare situations where allocate() fails
+    // with an exception, and confirm it doesn't cause any harsh disruption
+    // or leak.
+
+    // Creating internal NSEC3 tree will succeed, but allocation of NSEC3Data
+    // will fail due to bad_alloc.  It shouldn't cause memory leak
+    // (that would be caught in TearDown()).
+    mem_sgmt_.setThrowCount(2);
+    EXPECT_THROW(NSEC3Data::create(mem_sgmt_, param_rdata_), std::bad_alloc);
+
+    // allocate() will throw on the insertion of the origin node.
+    mem_sgmt_.setThrowCount(2);
+    EXPECT_THROW(ZoneData::create(mem_sgmt_, zname_), std::bad_alloc);
+
+    // allocate() will throw on creating the zone data.
+    mem_sgmt_.setThrowCount(3);
+    EXPECT_THROW(ZoneData::create(mem_sgmt_, zname_), std::bad_alloc);
+
+    // These incomplete create() attempts shouldn't cause memory leak
+    // (that would be caught in TearDown()).
+}
+
+TEST_F(ZoneDataTest, addRdataSets) {
+    // Insert a name to the zone, and add a couple the data (RdataSet) objects
+    // to the corresponding node.
+
+    ZoneNode* node = NULL;
+    zone_data_->insertName(mem_sgmt_, a_rrset_->getName(), &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    EXPECT_TRUE(node->isEmpty()); // initially it should be empty
+
+    RdataSet* rdataset_a =
+        RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr());
+    node->setData(rdataset_a);
+
+    RdataSet* rdataset_aaaa =
+        RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset_, ConstRRsetPtr());
+    // make a linked list and replace the list head
+    rdataset_aaaa->next = rdataset_a;
+    node->setData(rdataset_aaaa);
+
+    // Confirm we can find the added ones from the zone data.
+    checkFindRdataSet(zone_data_->getZoneTree(), a_rrset_->getName(),
+                      RRType::A(), rdataset_a);
+    checkFindRdataSet(zone_data_->getZoneTree(), a_rrset_->getName(),
+                      RRType::AAAA(), rdataset_aaaa);
+    // There's no NS (or anything other than AAAA or A) RdataSet in the list
+    checkFindRdataSet(zone_data_->getZoneTree(), a_rrset_->getName(),
+                      RRType::NS(), NULL);
+
+    // TearDown() will confirm there's no leak on destroy
+}
+
+TEST_F(ZoneDataTest, getSetNSEC3Data) {
+    // Initially there's no NSEC3 data
+    EXPECT_EQ(static_cast<NSEC3Data*>(NULL), zone_data_->getNSEC3Data());
+    // isNSEC3Signed is true iff zone data has non NULL NSEC3 data
+    EXPECT_FALSE(zone_data_->isNSEC3Signed());
+
+    // Set a new one.  The set method should return NULL.  The get method
+    // should return the new one.
+    NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt_, param_rdata_);
+    NSEC3Data* old_nsec3_data = zone_data_->setNSEC3Data(nsec3_data);
+    EXPECT_EQ(static_cast<NSEC3Data*>(NULL), old_nsec3_data);
+    EXPECT_EQ(nsec3_data, zone_data_->getNSEC3Data());
+    EXPECT_TRUE(zone_data_->isNSEC3Signed());
+
+    // Replace an existing one with a yet another one.
+    // We're responsible for destroying the old one.
+    NSEC3Data* nsec3_data2 = NSEC3Data::create(mem_sgmt_, nsec3_rdata_);
+    old_nsec3_data = zone_data_->setNSEC3Data(nsec3_data2);
+    EXPECT_EQ(nsec3_data, old_nsec3_data);
+    EXPECT_EQ(nsec3_data2, zone_data_->getNSEC3Data());
+    EXPECT_TRUE(zone_data_->isNSEC3Signed());
+    NSEC3Data::destroy(mem_sgmt_, old_nsec3_data, RRClass::IN());
+
+    // Setting NULL clears any existing one.
+    old_nsec3_data = zone_data_->setNSEC3Data(NULL);
+    EXPECT_EQ(nsec3_data2, old_nsec3_data);
+    EXPECT_EQ(static_cast<NSEC3Data*>(NULL), zone_data_->getNSEC3Data());
+    EXPECT_FALSE(zone_data_->isNSEC3Signed());
+
+    // Then set it again.  The zone data should destroy it on its own
+    // destruction.
+    zone_data_->setNSEC3Data(old_nsec3_data);
+}
+
+TEST_F(ZoneDataTest, isSigned) {
+    // By default it's considered unsigned
+    EXPECT_FALSE(zone_data_->isSigned());
+
+    // declare it's signed, the isSigned() says so too
+    zone_data_->setSigned(true);
+    EXPECT_TRUE(zone_data_->isSigned());
+
+    // change it to unsigned again
+    zone_data_->setSigned(false);
+    EXPECT_FALSE(zone_data_->isSigned());
+}
+}

+ 23 - 16
src/lib/datasrc/memory/tests/zone_table_unittest.cc

@@ -55,21 +55,23 @@ private:
 
 class ZoneTableTest : public ::testing::Test {
 protected:
-    ZoneTableTest() : zname1(Name("example.com")),
+    ZoneTableTest() : zclass_(RRClass::IN()),
+                      zname1(Name("example.com")),
                       zname2(Name("example.net")),
                       zname3(Name("example")),
-                      zone_table(ZoneTable::create(mem_sgmt_))
+                      zone_table(ZoneTable::create(mem_sgmt_, zclass_))
     {}
     ~ZoneTableTest() {
         if (zone_table != NULL) {
-            ZoneTable::destroy(mem_sgmt_, zone_table);
+            ZoneTable::destroy(mem_sgmt_, zone_table, zclass_);
         }
     }
     void TearDown() {
-        ZoneTable::destroy(mem_sgmt_, zone_table);
+        ZoneTable::destroy(mem_sgmt_, zone_table, zclass_);
         zone_table = NULL;
         EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
     }
+    const RRClass zclass_;
     const Name zname1, zname2, zname3;
     TestMemorySegment mem_sgmt_;
     ZoneTable* zone_table;
@@ -80,41 +82,46 @@ TEST_F(ZoneTableTest, create) {
     // tests.  We only check exception safety by letting the test memory
     // segment throw.
     mem_sgmt_.setThrowCount(2);
-    EXPECT_THROW(ZoneTable::create(mem_sgmt_), std::bad_alloc);
+    EXPECT_THROW(ZoneTable::create(mem_sgmt_, zclass_), std::bad_alloc);
     // This shouldn't cause memory leak (that would be caught in TearDown()).
 }
 
 TEST_F(ZoneTableTest, addZone) {
     // Normal successful case.
     const ZoneTable::AddResult result1 =
-        zone_table->addZone(mem_sgmt_, zname1);
+        zone_table->addZone(mem_sgmt_, zclass_, zname1);
     EXPECT_EQ(result::SUCCESS, result1.code);
 
     // Duplicate add doesn't replace the existing data.
-    EXPECT_EQ(result::EXIST, zone_table->addZone(mem_sgmt_, zname1).code);
+    EXPECT_EQ(result::EXIST, zone_table->addZone(mem_sgmt_, zclass_,
+                                                 zname1).code);
     EXPECT_EQ(result1.zone_data,
-              zone_table->addZone(mem_sgmt_, zname1).zone_data);
+              zone_table->addZone(mem_sgmt_, zclass_, zname1).zone_data);
     // names are compared in a case insensitive manner.
-    EXPECT_EQ(result::EXIST, zone_table->addZone(mem_sgmt_,
+    EXPECT_EQ(result::EXIST, zone_table->addZone(mem_sgmt_, zclass_,
                                                  Name("EXAMPLE.COM")).code);
     // Add some more different ones.  Should just succeed.
-    EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zname2).code);
-    EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zname3).code);
+    EXPECT_EQ(result::SUCCESS,
+              zone_table->addZone(mem_sgmt_, zclass_, zname2).code);
+    EXPECT_EQ(result::SUCCESS,
+              zone_table->addZone(mem_sgmt_, zclass_, zname3).code);
 
     // Have the memory segment throw an exception in extending the internal
     // tree.  It still shouldn't cause memory leak (which would be detected
     // in TearDown()).
     mem_sgmt_.setThrowCount(2);
-    EXPECT_THROW(zone_table->addZone(mem_sgmt_, Name("example.org")),
+    EXPECT_THROW(zone_table->addZone(mem_sgmt_, zclass_, Name("example.org")),
                  std::bad_alloc);
 }
 
 TEST_F(ZoneTableTest, findZone) {
     const ZoneTable::AddResult add_result1 =
-        zone_table->addZone(mem_sgmt_, zname1);
+        zone_table->addZone(mem_sgmt_, zclass_, zname1);
     EXPECT_EQ(result::SUCCESS, add_result1.code);
-    EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zname2).code);
-    EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zname3).code);
+    EXPECT_EQ(result::SUCCESS,
+              zone_table->addZone(mem_sgmt_, zclass_, zname2).code);
+    EXPECT_EQ(result::SUCCESS,
+              zone_table->addZone(mem_sgmt_, zclass_, zname3).code);
 
     const ZoneTable::FindResult find_result1 =
         zone_table->findZone(Name("example.com"));
@@ -135,7 +142,7 @@ TEST_F(ZoneTableTest, findZone) {
 
     // make sure the partial match is indeed the longest match by adding
     // a zone with a shorter origin and query again.
-    EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_,
+    EXPECT_EQ(result::SUCCESS, zone_table->addZone(mem_sgmt_, zclass_,
                                                    Name("com")).code);
     EXPECT_EQ(add_result1.zone_data,
               zone_table->findZone(Name("www.example.com")).zone_data);

+ 170 - 0
src/lib/datasrc/memory/zone_data.cc

@@ -0,0 +1,170 @@
+// 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 <util/memory_segment.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rdataclass.h>
+
+#include "rdataset.h"
+#include "rdata_serialization.h"
+#include "zone_data.h"
+#include "segment_object_holder.h"
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+#include <cassert>
+#include <new>                  // for the placement new
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+namespace {
+void
+rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt,
+                RdataSet* rdataset_head)
+{
+    RdataSet* rdataset_next;
+    for (RdataSet* rdataset = rdataset_head;
+         rdataset != NULL;
+         rdataset = rdataset_next)
+    {
+        rdataset_next = rdataset->getNext();
+        RdataSet::destroy(*mem_sgmt, rrclass, rdataset);
+    }
+}
+
+void
+nullDeleter(RdataSet* rdataset_head) {
+    assert(rdataset_head == NULL);
+}
+}
+
+NSEC3Data*
+NSEC3Data::create(util::MemorySegment& mem_sgmt,
+                  const generic::NSEC3PARAM& rdata)
+{
+    return (NSEC3Data::create(mem_sgmt, rdata.getHashalg(), rdata.getFlags(),
+                              rdata.getIterations(), rdata.getSalt()));
+}
+
+NSEC3Data*
+NSEC3Data::create(util::MemorySegment& mem_sgmt, const generic::NSEC3& rdata) {
+    return (NSEC3Data::create(mem_sgmt, rdata.getHashalg(), rdata.getFlags(),
+                              rdata.getIterations(), rdata.getSalt()));
+}
+
+NSEC3Data*
+NSEC3Data::create(util::MemorySegment& mem_sgmt, uint8_t hashalg,
+                  uint8_t flags, uint16_t iterations,
+                  const std::vector<uint8_t>& salt)
+{
+    // NSEC3Data allocation can throw.  To avoid leaking the tree, we manage
+    // it in the holder.
+    // Note: we won't add any RdataSet, so we use the NO-OP deleter
+    // (with an assertion check for that).
+    typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
+    detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
+        mem_sgmt, ZoneTree::create(mem_sgmt, true),
+        boost::bind(nullDeleter, _1));
+
+    const size_t salt_len = salt.size();
+
+    void* p = mem_sgmt.allocate(sizeof(NSEC3Data) + 1 + salt_len);
+    NSEC3Data* const param_data =
+        new(p) NSEC3Data(holder.release(), hashalg, flags, iterations);
+    uint8_t* dp = param_data->getSaltBuf();
+    *dp++ = salt_len;
+    if (salt_len > 0) {
+        memcpy(dp, &salt.at(0), salt_len); // use at for safety
+    }
+
+    return (param_data);
+}
+
+void
+NSEC3Data::destroy(util::MemorySegment& mem_sgmt, NSEC3Data* data,
+                   RRClass nsec3_class)
+{
+    ZoneTree::destroy(mem_sgmt, data->nsec3_tree_.get(),
+                      boost::bind(rdataSetDeleter, nsec3_class, &mem_sgmt,
+                                  _1));
+    mem_sgmt.deallocate(data, sizeof(NSEC3Data) + 1 + data->getSaltLen());
+}
+
+void
+NSEC3Data::insertName(util::MemorySegment& mem_sgmt, const Name& name,
+                      ZoneNode** node)
+{
+    const ZoneTree::Result result = nsec3_tree_->insert(mem_sgmt, name, node);
+
+    // This should be ensured by the API:
+    assert((result == ZoneTree::SUCCESS ||
+            result == ZoneTree::ALREADYEXISTS) && node != NULL);
+}
+
+ZoneData*
+ZoneData::create(util::MemorySegment& mem_sgmt, const Name& zone_origin) {
+    // ZoneTree::insert() and ZoneData allocation can throw.  See also
+    // NSEC3Data::create().
+    typedef boost::function<void(RdataSet*)> RdataSetDeleterType;
+    detail::SegmentObjectHolder<ZoneTree, RdataSetDeleterType> holder(
+        mem_sgmt, ZoneTree::create(mem_sgmt, true),
+        boost::bind(nullDeleter, _1));
+
+    ZoneTree* tree = holder.get();
+    ZoneNode* origin_node = NULL;
+    const ZoneTree::Result result =
+        tree->insert(mem_sgmt, zone_origin, &origin_node);
+    assert(result == ZoneTree::SUCCESS);
+    void* p = mem_sgmt.allocate(sizeof(ZoneData));
+    ZoneData* zone_data = new(p) ZoneData(holder.release(), origin_node);
+
+    return (zone_data);
+}
+
+void
+ZoneData::destroy(util::MemorySegment& mem_sgmt, ZoneData* zone_data,
+                  RRClass zone_class)
+{
+    ZoneTree::destroy(mem_sgmt, zone_data->zone_tree_.get(),
+                      boost::bind(rdataSetDeleter, zone_class, &mem_sgmt,
+                                  _1));
+    if (zone_data->nsec3_data_) {
+        NSEC3Data::destroy(mem_sgmt, zone_data->nsec3_data_.get(), zone_class);
+    }
+    mem_sgmt.deallocate(zone_data, sizeof(ZoneData));
+}
+
+void
+ZoneData::insertName(util::MemorySegment& mem_sgmt, const Name& name,
+                     ZoneNode** node)
+{
+    const ZoneTree::Result result = zone_tree_->insert(mem_sgmt, name, node);
+
+    // This should be ensured by the API:
+    assert((result == ZoneTree::SUCCESS ||
+            result == ZoneTree::ALREADYEXISTS) && node != NULL);
+}
+
+} // namespace memory
+} // namespace datasrc
+} // datasrc isc

+ 527 - 14
src/lib/datasrc/memory/zone_data.h

@@ -17,29 +17,542 @@
 
 #include <util/memory_segment.h>
 
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/memory/domaintree.h>
+#include <datasrc/memory/rdataset.h>
+
+#include <boost/interprocess/offset_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <vector>
+
 namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+class NSEC3PARAM;
+class NSEC3;
+}
+}
+}
+
 namespace datasrc {
 namespace memory {
-/// \brief Data for a single zone.
+
+typedef DomainTree<RdataSet> ZoneTree;
+typedef DomainTreeNode<RdataSet> ZoneNode;
+
+/// \brief NSEC3 data for a DNS zone.
+///
+/// This class encapsulates a set of NSEC3 related data for a zone
+/// that is signed with NSEC3 RRs.  Specifically, it contains hash
+/// parameters as given in an NSEC3PARAM RDATA and all NSEC3 RRs of the zone.
 ///
-/// It's currently empty and is only provided for the implementation of
-/// ZoneTable.  The actual implementation of this class is the subject of
-/// Trac #2107.
-class ZoneData {
+/// The main concept of the class is generally the same as that of
+/// \c ZoneData (see its description for details), but the related data
+//// are encapsulated in a more straightforward way in this class.
+///
+/// The NSEC3 RRs (which should normally have RRSIGs) are stored in a
+/// \c DomainTree object whose data type is (a list of) \c RdataSet.
+/// This tree is expected to store NSEC3 RRs only, so the RR type of
+/// \c RdataSet should be NSEC3.  But this class itself doesn't guarantee
+/// this condition.  It's the caller's responsibility.
+///
+/// Read-only access to the tree is possible via the \c getNSEC3Tree() method.
+/// Modifying the tree must be done by a specific method (in the initial
+/// implementation, it's \c insertName().  There may be some more as we
+/// see the need); the application cannot directly change the content of the
+/// tree in an arbitrary way.  This class does not have a strong reason to be
+/// that strict, but is defined this way mainly to be consistent with the
+/// \c ZoneData class.
+///
+/// Most of the hash parameters are maintained in the form of straightforward
+/// member variables, which can be directly referenced by the application.
+/// The exception is the salt, which is encapsulated as opaque data
+/// immediately following the main class object, and should be accessible
+/// via the \c getSaltLen() and \c getSaltData() method.
+///
+/// \note The fact that the this class couples one set of hash parameters
+/// and the set of NSEC3 RRs implicitly means a zone is assumed to have
+/// only one set of NSEC3 parameters.  When we support multiple sets of
+/// parameters the design should be revised accordingly.
+class NSEC3Data : boost::noncopyable {
+public:
+    /// \brief Allocate and construct \c NSEC3Data from NSEC3PARAM Rdata.
+    ///
+    /// The NSEC3 parameters are extracted and stored within the created
+    /// \c NSEC3Data object.
+    ///
+    /// \throw std::bad_alloc Memory allocation fails.
+    ///
+    /// \param mem_sgmt A \c MemorySegment from which memory for the new
+    /// \c NSEC3Data is allocated.
+    /// \param rdata An NSEC3PARAM RDATA that specifies the NSEC3 parameters
+    /// to be stored.
+    static NSEC3Data* create(util::MemorySegment& mem_sgmt,
+                             const dns::rdata::generic::NSEC3PARAM& rdata);
+
+    /// \brief Allocate and construct \c NSEC3Data from NSEC3 Rdata.
+    ///
+    /// The NSEC3 hash parameters are extracted and stored within the created
+    /// \c NSEC3Data object.
+    ///
+    /// \throw std::bad_alloc Memory allocation fails.
+    ///
+    /// \param mem_sgmt A \c MemorySegment from which memory for the new
+    /// \c NSEC3Data is allocated.
+    /// \param rdata An NSEC3 RDATA that specifies the NSEC3 parameters
+    /// to be stored.
+    static NSEC3Data* create(util::MemorySegment& mem_sgmt,
+                             const dns::rdata::generic::NSEC3& rdata);
+
+    /// \brief Destruct and deallocate \c NSEC3Data.
+    ///
+    /// It releases all resources allocated for the internal NSEC3 name space
+    /// including NSEC3 RdataSet.  It assumes \c RdataSets objects stored
+    /// in the space were allocated using the same memory segment as
+    /// \c mem_sgmt.  The caller must ensure this assumption.
+    ///
+    /// Note that an \c RRClass object must be passed to this method.
+    /// It's necessary to destroy the stored \c RdataSet objects
+    /// (see its class description).  This class doesn't hold this information;
+    /// it's the caller's responsibility to associate an \c NSEC3Data
+    /// class object with its expected RR class, and pass it to
+    /// \c destroy().  (In practice, it will be passed via
+    /// \c ZoneData::destroy().)
+    ///
+    /// \throw none
+    ///
+    /// \param mem_sgmt The \c MemorySegment that allocated memory for
+    /// \c data.
+    /// \param data A non-NULL pointer to a valid NSEC3Data object
+    /// that was originally created by the \c create() method (the behavior
+    /// is undefined if this condition isn't met).
+    /// \param nsec3_class The RR class of the \c RdataSet stored in the NSEC3
+    /// name space to be destroyed.
+    static void destroy(util::MemorySegment& mem_sgmt, NSEC3Data* data,
+                        dns::RRClass nsec3_class);
+
 private:
-    ZoneData() {}
-    ~ZoneData() {}
+    // Domain tree for the Internal NSEC3 name space.  Access to it is
+    // limited only via public methods.
+    const boost::interprocess::offset_ptr<ZoneTree> nsec3_tree_;
 public:
-    static ZoneData* create(util::MemorySegment& mem_sgmt) {
-        void* p = mem_sgmt.allocate(sizeof(ZoneData));
-        ZoneData* zone_data = new(p) ZoneData();
-        return (zone_data);
+    const uint8_t hashalg;      ///< Hash algorithm
+    const uint8_t flags;        ///< NSEC3 parameter flags
+    const uint16_t iterations;  ///< Hash iterations
+    // For 64-bit machines there'll be padding space here, but since
+    // only at most one instance (or a few in very rare cases) will be
+    // created per zone, the overhead should be acceptable.
+
+    /// \brief Return \c ZoneTree for the NSEC3 name space.
+    ///
+    /// \throw none
+    const ZoneTree& getNSEC3Tree() const { return (*nsec3_tree_); }
+
+    /// \brief Return the size of NSEC3 salt.
+    ///
+    /// \throw none
+    ///
+    /// The return value must be in the range between 0 and 255 (inclusive).
+    size_t getSaltLen() const { return (*getSaltBuf()); }
+
+    /// \brief Return a pointer to the salt data.
+    ///
+    /// \throw none
+    ///
+    /// The valid range is up to the \c getSaltLen() bytes from the
+    /// returned value.  If \c getSaltLen() returns 0, the return value
+    /// of this method is invalid and must not be used.
+    const uint8_t* getSaltData() const { return (getSaltBuf() + 1); }
+
+    /// \brief Insert a name to the NSEC3 name space.
+    ///
+    /// It allocates resource for the given name in the internal NSEC3 name
+    /// space, and returns an access point to it in the form of \c ZoneNode
+    /// pointer via the given \c node variable.  If the name already exists
+    /// in the name space, it returns a pointer to the existing node.
+    ///
+    /// This method does not perform any semantics check on the given name
+    /// (e.g., whether the first label is a valid encoded string for an NSEC3
+    /// owner name).
+    ///
+    /// \throw std::bad_alloc Memory allocation fails
+    ///
+    /// \param mem_sgmt Memory segment in which resource for the new memory
+    /// is to be allocated.
+    /// \param name The name to be inserted.
+    /// \param node A pointer to \c ZoneNode pointer in which the created or
+    /// found node for the name is stored.  Must not be NULL (the method does
+    /// not check that condition).
+    void insertName(util::MemorySegment& mem_sgmt, const dns::Name& name,
+                    ZoneNode** node);
+
+private:
+    // Common subroutine for the public versions of create().
+    static NSEC3Data* create(util::MemorySegment& mem_sgmt, uint8_t hashalg,
+                             uint8_t flags, uint16_t iterations,
+                             const std::vector<uint8_t>& salt);
+
+    /// \brief The constructor.
+    ///
+    /// An object of this class is always expected to be created by the
+    /// allocator (\c create()), so the constructor is hidden as private.
+    ///
+    /// It never throws an exception.
+    NSEC3Data(ZoneTree* nsec3_tree_param, uint8_t hashalg_param,
+              uint8_t flags_param, uint16_t iterations_param) :
+        nsec3_tree_(nsec3_tree_param), hashalg(hashalg_param),
+        flags(flags_param), iterations(iterations_param)
+    {}
+
+    const uint8_t* getSaltBuf() const {
+        return (reinterpret_cast<const uint8_t*>(this + 1));
     }
-    static void destroy(util::MemorySegment& mem_sgmt, ZoneData* zone_data) {
-        zone_data->~ZoneData();
-        mem_sgmt.deallocate(zone_data, sizeof(ZoneData));
+    uint8_t* getSaltBuf() {
+        return (reinterpret_cast<uint8_t*>(this + 1));
     }
 };
+
+/// \brief DNS zone data.
+///
+/// This class encapsulates the content of a DNS zone (which is essentially a
+/// set of RRs) in a memory efficient way and provides accessor interfaces
+/// to it.
+///
+/// The primary goal of this class is to provide a packed structure of the
+/// data for memory efficiency.  Basically, this class should be considered
+/// a private part of some other classes within this module and should not
+/// be used directly from normal applications.  So it's not intended to hide
+/// much of the underlying implementation details; rather, it tries
+/// to keep the representation simple.
+///
+/// The RRs are stored in a \c DomainTree object whose data type is
+/// (a list of) \c RdataSet.  The tree nodes correspond to owner names,
+/// and the \c RdataSet objects (forming a linked list) set in the node
+/// represent the rest of the RR parameters except the RR class: type,
+/// TTL, and RDATA.  This class does not have any knowledge of the RR class
+/// of the zone; since it's quite likely that the application maintains
+/// a set of zones of the same RR class, and the number of such zones can be
+/// huge, it makes more sense to have the application maintain the class value
+/// in a unified way to minimize memory footprint.
+///
+/// The \c DomainTree object in this class is not expected to hold NSEC3
+/// RRs when the zone is signed with NSEC3; they should be maintained
+/// in an associated \c NSEC3Data object.  But this class does not prevent
+/// the unexpected usage of adding an NSEC3 RdataSet directly in the tree.
+/// It's the caller's responsibility to ensure this assumption.
+///
+/// This class maintains some other meta data and additional zone related
+/// content.  First, it automatically creates a \c DomainTree node for the
+/// zone's origin name on initialization and keeps a reference to it
+/// throughout its lifetime.  This is the case even if the zone doesn't have
+/// any RRs (such as in the case before initial loading).  Any valid zone
+/// to be served should have an RR at the origin node (at least SOA, for
+/// example), so this assumption should be reasonable.  But the application
+/// must ensure that any \c ZoneData object in actual use should have an
+/// RR at the origin; otherwise the inconsistency between the internal state
+/// and the actual zone content could lead to unexpected disruption.
+/// In particular, it must be careful when it supports dynamic updates
+/// to an existing zone so an update attempt doesn't result in deleting
+/// the origin node.
+///
+/// To ensure integrity regarding the reference to the origin, write
+/// access to the tree node can be done only by public methods; the member
+/// variable for the tree is hidden as private.  On the other hand, read-only
+/// access to the tree is allowed via the const version of \c getZoneTree()
+/// method for the convenience of the application.  So, it's intentional
+/// that there's no non-const version of this method.  Do not add one
+/// when this class is to be extended.
+///
+/// Another type of meta data is parameters and records of NSEC3 RRs
+/// when the zone is signed with NSEC3.  It's represented in the form of
+/// an \c NSEC3Data object, and a \c ZoneData object may be associated with
+/// 0 or 1 \c NSEC3Data objects using the \c setNSEC3Data() method, which
+/// can be retrieved by the \c getNSEC3Data() method.  If the \c ZoneData
+/// object is not associated with an \c NSEC3Data object, it's considered not
+/// signed with NSEC3 RRs; otherwise it's considered to be signed with
+/// NSEC3 RRs and with the parameters stored in the \c NSEC3Data object.
+///
+/// \note This interpretation may change in the future when we support migration
+/// from NSEC to NSEC3 or vice versa, support incremental signing, or support
+/// multiple sets of NSEC3 parameters.
+///
+/// One last type of meta data is the status of the zone in terms of DNSSEC
+/// signing.  This class supports the following concepts:
+/// - Whether the zone is signed or not, either with NSEC records or NSEC3
+///   records.
+/// - Whether the zone has a complete set of NSEC3 records.
+///
+/// The former status can be accessed via the \c isSigned() and \c setSigned()
+/// methods; the latter can be retrieved via the \c isNSEC3Signed() method.
+///
+/// This class does not actually relate the status of signed-or-not to
+/// any of its other attributes; it's up to the application how to set or
+/// use this status and maintain it in a reasonable way.  One possible
+/// definition is to set this status if and only if the zone has a
+/// DNSKEY RR at the zone origin (which is BIND 9's definition of signed
+/// zone).  When the application adopts this definition, it's the
+/// application's responsibility to keep the status consistent with the
+/// actual existence or non-existence of a DNSKEY RR.
+///
+/// In the current implementation, a zone is considered to have a complete
+/// set of NSEC3 records if and only if it's associated with an \c NSEC3Data
+/// object (as noted above, these concepts may be separated in future).
+/// For this reason there is no "set" method for the latter; setting
+/// an \c NSEC3Data effectively enables the latter status.  \c isNSEC3Signed()
+/// method is still provided (even though it's a kind of trivial wrapper to
+/// \c getNSEC3Data()) partly for a more intuitive shortcut, and partly
+/// because we won't have to change the application code when we implement
+/// the future separation.
+///
+/// The intended usage of these two status concepts is to implement the
+/// \c ZoneFinder::Context::isNSECSigned() and
+/// \c ZoneFinder::Context::isNSEC3Signed() methods.  A possible implementation
+/// is as follows:
+/// - \c ZoneFinder::Context::isNSECSigned() returns true iff \c isSigned()
+///   is true and \c isNSEC3Signed() is false.
+/// - \c ZoneFinder::Context::isNSEC3Signed() returns true iff \c isSigned()
+///   is true and \c isNSEC3Signed() is true.
+///
+/// Note that even though \c isNSEC3Signed() being true should indicate
+/// \c isSigned() is true too in practice, the interfaces do not
+/// automatically ensure that, so we'd need to check both conditions
+/// explicitly.  And, in fact, if we adopt the above definition of
+/// \c isSigned(), it's possible that a zone has a complete set of NSEC3
+/// RRs but no DNSKEY (although it's effectively a broken zone unless we
+/// support incremental signing).
+///
+/// This class is designed so an instance can be stored in a shared
+/// memory region.  So the pointer member variables (the initial
+/// implementation only contains pointer member variables) are defined
+/// as offset pointers.  When this class is extended these properties must
+/// be preserved, and must also meet other requirements so it can be stored
+/// in a shared memory region (see, for example, \c RdataSet description).
+/// Future extensions must also be conscious of placing the member variables
+/// so that they will not accidentally cause padding and increase memory
+/// footprint.
+class ZoneData : boost::noncopyable {
+private:
+    /// \brief The constructor.
+    ///
+    /// An object of this class is always expected to be created by the
+    /// allocator (\c create()), so the constructor is hidden as private.
+    ///
+    /// It never throws an exception.
+    ZoneData(ZoneTree* zone_tree, ZoneNode* origin_node) :
+        zone_tree_(zone_tree), origin_node_(origin_node)
+    {}
+
+    // Zone node flags.
+private:
+    // Set in the origin node (which always exists at the same address)
+    // to indicate whether the zone is signed or not.  Internal use,
+    // so defined as private.
+    static const ZoneNode::Flags DNSSEC_SIGNED = ZoneNode::FLAG_USER1;
+public:
+    /// \brief Node flag indicating it is at a "wildcard level"
+    ///
+    /// This means one of the node's immediate children is a wildcard.
+    static const ZoneNode::Flags WILDCARD_NODE = ZoneNode::FLAG_USER2;
+
+public:
+    /// \brief Allocate and construct \c ZoneData.
+    ///
+    /// \throw std::bad_alloc Memory allocation fails.
+    ///
+    /// \param mem_sgmt A \c MemorySegment from which memory for the new
+    /// \c ZoneData is allocated.
+    /// \param name The zone name.
+    static ZoneData* create(util::MemorySegment& mem_sgmt,
+                            const dns::Name& zone_name);
+
+    /// \brief Destruct and deallocate \c ZoneData.
+    ///
+    /// It releases all resource allocated in the internal storage NSEC3 for
+    /// zone names and RdataSet objects, and if associated, the \c NSEC3Data.
+    /// It assumes \c RdataSets objects stored in the space and the
+    /// associated \c NSEC3Data object were allocated using the same memory
+    /// segment as \c mem_sgmt.  The caller must ensure this assumption.
+    ///
+    /// Note that an \c RRClass object must be passed to this method.
+    /// It's used to destroy the stored \c RdataSet objects
+    /// (see its class description).  This class doesn't hold this information;
+    /// it's the caller's responsibility to associate a \c ZoneData class
+    /// object with its expected RR class, and pass it to \c destroy().
+    ///
+    /// \throw none
+    ///
+    /// \param mem_sgmt The \c MemorySegment that allocated memory for
+    /// \c zone_data.
+    /// \param zone_data A non-NULL pointer to a valid ZoneData object
+    /// that was originally created by the \c create() method (the behavior
+    /// is undefined if this condition isn't met).
+    /// \param zone_class The RR class of the \c RdataSet stored in the
+    /// internal tree.
+    static void destroy(util::MemorySegment& mem_sgmt, ZoneData* zone_data,
+                        dns::RRClass zone_class);
+
+    ///
+    /// \name Getter methods
+    ///
+    //@{
+    /// \brief Return zone's origin node.
+    ///
+    /// This is a convenience and efficient short cut to get access to the
+    /// zone origin in the form of \c ZoneNode object.
+    ///
+    /// The class encapsulation ensures that the origin node always exists at
+    /// the same address, so this method always returns a non-NULL valid
+    /// valid pointer.
+    ///
+    /// \throw none
+    const ZoneNode* getOriginNode() const {
+        return (origin_node_.get());
+    }
+
+    /// \brief Return the zone's name space in the form of \c ZoneTree
+    ///
+    /// \note It's intentional that non-const version of this method
+    /// isn't provided.  See the class description.
+    ///
+    /// \throw none
+    const ZoneTree& getZoneTree() const { return (*zone_tree_); }
+
+    /// \brief Return whether or not the zone is signed in terms of DNSSEC.
+    ///
+    /// Note that this class does not care about what "signed" means.
+    /// This method simply returns the last value set by \c setSigned()
+    /// (or the default, which is \c false).  The caller is expected to
+    /// use this method and \c setSigned() in a reasonable, consistent way.
+    ///
+    /// \throw none
+    bool isSigned() const { return (origin_node_->getFlag(DNSSEC_SIGNED)); }
+
+    /// \brief Return whether or not the zone is signed with NSEC3 RRs.
+    ///
+    /// In the current implementation, the zone is considered signed with
+    /// NSEC3 if and only if it has non-NULL NSEC3 data.
+    ///
+    /// This also means it's not considered NSEC3 signed by default.
+    ///
+    /// \throw none
+    bool isNSEC3Signed() const { return (nsec3_data_); }
+
+    /// \brief Return NSEC3Data of the zone.
+    ///
+    /// This method returns non-NULL valid pointer to \c NSEC3Data object
+    /// associated to the \c ZoneData if it was set by \c setNSEC3Data();
+    /// otherwise it returns NULL.
+    ///
+    /// \throw none
+    const NSEC3Data* getNSEC3Data() const { return (nsec3_data_.get()); }
+    //@}
+
+    ///
+    /// \name Methods for modifying the tree
+    ///
+    //@{
+    /// \brief Insert a name to the zone.
+    ///
+    /// It allocates resource for the given name in the internal storage
+    /// for zone data, and returns an access point to it in the form of
+    /// \c ZoneNode pointer via the given \c node variable.  If the name
+    /// already exists in the name space, it returns a pointer to the existing
+    /// node.
+    ///
+    /// The name to be inserted by this method is expected to belong to
+    /// zone's "normal" (i.e., non-NSEÇ3) name space.  If it's a name for
+    /// an NSEC3 RR, it must be set in the corresponding \c NSEC3Data for
+    /// this zone data (if it doesn't exist it must be created and set
+    /// by \c setNSEC3Data()).
+    ///
+    /// The name is also expected to be a subdomain of, or equal to the
+    /// zone's origin name (specified on creation in \c create()), but
+    /// this method does not check that condition.  The caller is responsible
+    /// for ensuring this assumption.
+    ///
+    /// Since this method doesn't perform any semantics check, it always
+    /// succeeds (except for the rare case where memory allocation
+    /// fails) and \c node will be set to a valid pointer.
+    ///
+    /// \note We may want to differentiate between the case where the name is
+    /// newly created and the case where it already existed.  Right now it's
+    /// unclear, so it doesn't return this information.  If we see the need
+    /// for it, this method can be extended that way.
+    ///
+    /// \throw std::bad_alloc Memory allocation fails
+    ///
+    /// \param mem_sgmt Memory segment in which resource for the new memory
+    /// is to be allocated.
+    /// \param name The name to be inserted.
+    /// \param node A pointer to \c ZoneNode pointer in which the created or
+    /// found node for the name is stored.  Must not be NULL (the method does
+    /// not check that condition).
+    void insertName(util::MemorySegment& mem_sgmt, const dns::Name& name,
+                    ZoneNode** node);
+
+    /// \brief Specify whether or not the zone is signed in terms of DNSSEC.
+    ///
+    /// The zone will be considered "signed" (in that subsequent calls to
+    /// \c isSigned() will return \c true) iff the parameter \c on is \c true.
+    ///
+    /// This class does not care what "signed" actually means; it does not
+    /// check any zone RRs to verify if the given state makes sense (e.g.
+    /// whether the zone has a DNSKEY RR at the origin).  The caller is
+    /// expected to use this method and \c isSigned() in a reasonable,
+    /// consistent way.
+    ///
+    /// \throw none
+    void setSigned(bool on) {
+        origin_node_->setFlag(DNSSEC_SIGNED, on);
+    }
+
+    /// \brief Return NSEC3Data of the zone, non-const version.
+    ///
+    /// This is similar to the const version, but return a non-const pointer
+    /// so the caller can modify the content.
+    ///
+    /// \throw none
+    NSEC3Data* getNSEC3Data() { return (nsec3_data_.get()); }
+
+    /// \brief Associate \c NSEC3Data to the zone.
+    ///
+    /// This method associates the given \c NSEC3Data object with the zone
+    /// data.  If there was already associated \c NSEC3Data object, it will
+    /// be returned.  If no \c NSEC3Data object was associated before,
+    /// a NULL pointer will be returned.  \c nsec3_data can be NULL, in which
+    /// case the zone will be disassociated with a \c NSEC3Data.
+    ///
+    /// In general, if a non-NULL pointer is passed, it's assumed that
+    /// the \c NSEC3Data object was allocated in the same \c MemorySegment
+    /// as that for the zone data, so the \c destroy() method can destroy
+    /// both with the same memory segment.  If this condition is not met,
+    /// the caller must extract the associated \c NSEC3Data by calling
+    /// this method with NULL and release any resource for it by itself
+    /// before destroying this zone data.
+    ///
+    /// \throw none
+    ///
+    /// \param nsec3_data A pointer to \c NSEC3Data object to be associated
+    /// with the zone.  Can be NULL.
+    /// \return Previously associated \c NSEC3Data object in the zone.  This
+    /// can be NULL.
+    NSEC3Data* setNSEC3Data(NSEC3Data* nsec3_data) {
+        NSEC3Data* old = nsec3_data_.get();
+        nsec3_data_ = nsec3_data;
+        return (old);
+    }
+    //@}
+
+private:
+    const boost::interprocess::offset_ptr<ZoneTree> zone_tree_;
+    const boost::interprocess::offset_ptr<ZoneNode> origin_node_;
+    boost::interprocess::offset_ptr<NSEC3Data> nsec3_data_;
+};
+
 } // namespace memory
 } // namespace datasrc
 } // namespace isc

+ 29 - 36
src/lib/datasrc/memory/zone_table.cc

@@ -19,6 +19,10 @@
 #include <datasrc/memory/zone_data.h>
 #include <datasrc/memory/zone_table.h>
 #include <datasrc/memory/domaintree.h>
+#include <datasrc/memory/segment_object_holder.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
 
 #include <cassert>
 
@@ -28,43 +32,25 @@ using namespace isc::dns;
 namespace isc {
 namespace datasrc {
 namespace memory {
-namespace {
-// A simple holder to create and use some objects in this implementation
-// in an exception safe manner.   It works like std::auto_ptr but much
-// more simplified.
-template <typename T>
-class Holder {
-public:
-    Holder(util::MemorySegment& mem_sgmt, T* obj) :
-        mem_sgmt_(mem_sgmt), obj_(obj)
-    {}
-    ~Holder() {
-        if (obj_ != NULL) {
-            T::destroy(mem_sgmt_, obj_);
-        }
-    }
-    T* get() { return (obj_); }
-    T* release() {
-        T* ret = obj_;
-        obj_ = NULL;
-        return (ret);
-    }
-private:
-    util::MemorySegment& mem_sgmt_;
-    T* obj_;
-};
-}
+using detail::SegmentObjectHolder;
 
+namespace {
 void
-ZoneTable::ZoneDataDeleter::operator()(util::MemorySegment& mem_sgmt,
-                                       ZoneData* zone_data) const
+deleteZoneData(util::MemorySegment* mem_sgmt, ZoneData* zone_data,
+               RRClass rrclass)
 {
-    ZoneData::destroy(mem_sgmt, zone_data);
+    if (zone_data != NULL) {
+        ZoneData::destroy(*mem_sgmt, zone_data, rrclass);
+    }
+}
+typedef boost::function<void(ZoneData*)> ZoneDataDeleterType;
 }
 
 ZoneTable*
-ZoneTable::create(util::MemorySegment& mem_sgmt) {
-    Holder<ZoneTableTree> holder(mem_sgmt, ZoneTableTree::create(mem_sgmt));
+ZoneTable::create(util::MemorySegment& mem_sgmt, RRClass zone_class) {
+    SegmentObjectHolder<ZoneTableTree, ZoneDataDeleterType> holder(
+        mem_sgmt, ZoneTableTree::create(mem_sgmt),
+        boost::bind(deleteZoneData, &mem_sgmt, _1, zone_class));
     void* p = mem_sgmt.allocate(sizeof(ZoneTable));
     ZoneTable* zone_table = new(p) ZoneTable(holder.get());
     holder.release();
@@ -72,20 +58,27 @@ ZoneTable::create(util::MemorySegment& mem_sgmt) {
 }
 
 void
-ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable) {
-    ZoneTableTree::destroy(mem_sgmt, ztable->zones_.get());
+ZoneTable::destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable,
+                   RRClass zone_class)
+{
+    ZoneTableTree::destroy(mem_sgmt, ztable->zones_.get(),
+                           boost::bind(deleteZoneData, &mem_sgmt, _1,
+                                       zone_class));
     mem_sgmt.deallocate(ztable, sizeof(ZoneTable));
 }
 
 ZoneTable::AddResult
-ZoneTable::addZone(util::MemorySegment& mem_sgmt, const Name& zone_name) {
+ZoneTable::addZone(util::MemorySegment& mem_sgmt, RRClass zone_class,
+                   const Name& zone_name)
+{
     // Create a new ZoneData instance first.  If the specified name already
     // exists in the table, the new data will soon be destroyed, but we want
     // to make sure if this allocation fails the tree won't be changed to
     // provide as strong guarantee as possible.  In practice, we generally
     // expect the caller tries to add a zone only when it's a new one, so
     // this should be a minor concern.
-    Holder<ZoneData> holder(mem_sgmt, ZoneData::create(mem_sgmt));
+    SegmentObjectHolder<ZoneData, RRClass> holder(
+        mem_sgmt, ZoneData::create(mem_sgmt, zone_name), zone_class);
 
     // Get the node where we put the zone
     ZoneTableNode* node(NULL);
@@ -103,7 +96,7 @@ ZoneTable::addZone(util::MemorySegment& mem_sgmt, const Name& zone_name) {
 
     // Is it empty? We either just created it or it might be nonterminal
     if (node->isEmpty()) {
-        node->setData(mem_sgmt, holder.get());
+        node->setData(holder.get());
         return (AddResult(result::SUCCESS, holder.release()));
     } else { // There's something there already
         return (AddResult(result::EXIST, node->getData()));

+ 26 - 6
src/lib/datasrc/memory/zone_table.h

@@ -17,6 +17,8 @@
 
 #include <util/memory_segment.h>
 
+#include <dns/rrclass.h>
+
 #include <datasrc/result.h>
 #include <datasrc/memory/domaintree.h>
 
@@ -68,8 +70,8 @@ private:
     };
 
     // Type aliases to make it shorter
-    typedef DomainTree<ZoneData, ZoneDataDeleter> ZoneTableTree;
-    typedef DomainTreeNode<ZoneData, ZoneDataDeleter> ZoneTableNode;
+    typedef DomainTree<ZoneData> ZoneTableTree;
+    typedef DomainTreeNode<ZoneData> ZoneTableNode;
 
 public:
     /// \brief Result data of addZone() method.
@@ -114,18 +116,27 @@ public:
     ///
     /// \param mem_sgmt A \c MemorySegment from which memory for the new
     /// \c ZoneTable is allocated.
-    static ZoneTable* create(util::MemorySegment& mem_sgmt);
+    /// \param zone_class The RR class of the zone.  It must be the RR class
+    /// that is supposed to be associated to the zone table.
+    static ZoneTable* create(util::MemorySegment& mem_sgmt,
+                             dns::RRClass zone_class);
 
     /// \brief Destruct and deallocate \c ZoneTable
     ///
+    /// This method releases all internal resources including all zone data
+    /// created via \c addZone() calls.
+    ///
     /// \throw none
     ///
     /// \param mem_sgmt The \c MemorySegment that allocated memory for
-    /// \c ztable.
+    /// \c ztable and used for prior calls to \c addZone().
+    /// \param zone_class The RR class of the zone.  It must be the RR class
+    /// that is supposed to be associated to the zone table.
     /// \param ztable A non NULL pointer to a valid \c ZoneTable object
     /// that was originally created by the \c create() method (the behavior
     /// is undefined if this condition isn't met).
-    static void destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable);
+    static void destroy(util::MemorySegment& mem_sgmt, ZoneTable* ztable,
+                        dns::RRClass zone_class);
 
     /// Add a new zone to the \c ZoneTable.
     ///
@@ -135,14 +146,23 @@ public:
     /// zone name already exists in the table, a new data object won't be
     /// created; instead, the existing corresponding data will be returned.
     ///
+    /// The zone table keeps the ownership of the created zone data; the
+    /// caller must not try to destroy it directly.  (We'll eventually
+    /// add an interface to delete specific zone data from the table).
+    ///
     /// \throw std::bad_alloc Internal resource allocation fails.
     ///
+    /// \param mem_sgmt The \c MemorySegment to allocate zone data to be
+    /// created.  It must be the same segment that was used to create
+    /// the zone table at the time of create().
     /// \param zone_name The name of the zone to be added.
+    /// \param zone_class The RR class of the zone.  It must be the RR class
+    /// that is supposed to be associated to the zone table.
     /// \return \c result::SUCCESS If the zone is successfully
     /// added to the zone table.
     /// \return \c result::EXIST The zone table already contains
     /// zone of the same origin.
-    AddResult addZone(util::MemorySegment& mem_sgmt,
+    AddResult addZone(util::MemorySegment& mem_sgmt, dns::RRClass zone_class,
                       const dns::Name& zone_name);
 
     /// Find a zone that best matches the given name in the \c ZoneTable.

+ 12 - 2
src/lib/dns/labelsequence.h

@@ -229,15 +229,25 @@ public:
 
     /// \brief Compares two label sequences for equality.
     ///
-    /// Performs a (optionally case-insensitive) comparison between this
+    /// Performs a (optionally case-sensitive) comparison between this
     /// LabelSequence and another LabelSequence for equality.
     ///
     /// \param other The LabelSequence to compare with
-    /// \param case_sensitive If true, comparison is case-insensitive
+    /// \param case_sensitive If true, comparison is case-sensitive
     /// \return true if The label sequences consist are the same length,
     ///         and contain the same data.
     bool equals(const LabelSequence& other, bool case_sensitive = false) const;
 
+    /// \brief Compares two label sequences for equality (case ignored).
+    ///
+    /// This is equivalent to <code>this->equals(other)</code>.
+    ///
+    /// The operator version is convenient some specific cases such as in
+    /// unit tests.
+    bool operator==(const LabelSequence& other) const {
+        return (equals(other));
+    }
+
     /// \brief Compares two label sequences.
     ///
     /// Performs a (optionally case-insensitive) comparison between this

+ 9 - 0
src/lib/dns/tests/labelsequence_unittest.cc

@@ -177,6 +177,15 @@ TEST_F(LabelSequenceTest, equals_insensitive) {
     EXPECT_TRUE(ls11.equals(ls12));
 }
 
+// operator==().  This is mostly trivial wrapper, so it should suffice to
+// check some basic cases.
+TEST_F(LabelSequenceTest, operatorEqual) {
+    EXPECT_TRUE(ls1 == ls1);      // self equivalence
+    EXPECT_TRUE(ls1 == LabelSequence(n1)); // equivalent two different objects
+    EXPECT_FALSE(ls1 == ls2);      // non equivalent objects
+    EXPECT_TRUE(ls1 == ls5);       // it's always case insensitive
+}
+
 // Compare tests
 TEST_F(LabelSequenceTest, compare) {
     // "example.org." and "example.org.", case sensitive