Browse 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 12 years ago
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

File diff suppressed because it is too large
+ 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