BIND 10 master, updated. 31c9326b6f87c7bc02fd8d37baab36f05c220f6f [master] Merge branch 'trac2110'

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Sep 18 14:05:56 UTC 2012


The branch, master has been updated
       via  31c9326b6f87c7bc02fd8d37baab36f05c220f6f (commit)
       via  9acb02fb275c88eff4ba8f62d25e0401f91abaa1 (commit)
       via  054304b9814bcda55e3c4b096376f14fb3f8c0ef (commit)
       via  2a62124ab5a419a572a75aa77a0561732d5b11a6 (commit)
       via  c47927ef079b47e10268c6677ea2494577fe462c (commit)
       via  95758d3d2aff365886a6b616889952a9f215813b (commit)
       via  1982543450f925e2f6b3b0a029b6c9425788854e (commit)
       via  6b7f1283d0de285cff60ecc25b6901715a6f104c (commit)
       via  6db917d5a6a1f209bf6381779b6673b0a64fd909 (commit)
       via  32f6a8736328b1d532d4ebdcbd15b10ca1815db0 (commit)
      from  b88349ce7d971514e9b6678d2c2eeb96c2a2a9b7 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 31c9326b6f87c7bc02fd8d37baab36f05c220f6f
Merge: 9acb02f 2a62124
Author: Jelte Jansen <jelte at isc.org>
Date:   Tue Sep 18 15:44:04 2012 +0200

    [master] Merge branch 'trac2110'

commit 9acb02fb275c88eff4ba8f62d25e0401f91abaa1
Merge: b88349c 054304b
Author: Jelte Jansen <jelte at isc.org>
Date:   Tue Sep 18 15:42:37 2012 +0200

    [master] Merge branch 'trac2109'
    
    Conflicts:
    	src/lib/datasrc/memory/Makefile.am
    	src/lib/datasrc/memory/benchmarks/Makefile.am
    	src/lib/datasrc/memory/zone_data.h

-----------------------------------------------------------------------

Summary of changes:
 src/lib/datasrc/Makefile.am                        |    3 +-
 src/lib/datasrc/memory/Makefile.am                 |    3 +-
 src/lib/datasrc/memory/tests/Makefile.am           |    3 +
 .../tests/zone_finder_unittest.cc}                 | 1234 ++++----------------
 src/lib/datasrc/memory/treenode_rrset.h            |    2 +
 src/lib/datasrc/memory/zone_finder.cc              |  600 ++++++++++
 src/lib/datasrc/memory/zone_finder.h               |  128 ++
 src/lib/testutils/dnsmessage_test.cc               |   12 +-
 8 files changed, 946 insertions(+), 1039 deletions(-)
 copy src/lib/datasrc/{tests/memory_datasrc_unittest.cc => memory/tests/zone_finder_unittest.cc} (50%)
 create mode 100644 src/lib/datasrc/memory/zone_finder.cc
 create mode 100644 src/lib/datasrc/memory/zone_finder.h

-----------------------------------------------------------------------
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index d1e113f..bd96838 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = memory . tests
+SUBDIRS = . memory tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
@@ -64,7 +64,6 @@ libb10_datasrc_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/log/libb10-log.la
 libb10_datasrc_la_LIBADD += $(top_builddir)/src/lib/cc/libb10-cc.la
-libb10_datasrc_la_LIBADD += memory/libdatasrc_memory.la # convenience library
 libb10_datasrc_la_LIBADD += $(SQLITE_LIBS)
 
 BUILT_SOURCES = datasrc_config.h datasrc_messages.h datasrc_messages.cc
diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am
index 168c2ab..eea9c0b 100644
--- a/src/lib/datasrc/memory/Makefile.am
+++ b/src/lib/datasrc/memory/Makefile.am
@@ -16,9 +16,10 @@ libdatasrc_memory_la_SOURCES += treenode_rrset.h treenode_rrset.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 += segment_object_holder.h
-libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
 libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
 libdatasrc_memory_la_SOURCES += logger.h logger.cc
+libdatasrc_memory_la_SOURCES += zone_table.h zone_table.cc
+libdatasrc_memory_la_SOURCES += zone_finder.h zone_finder.cc
 nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
 
 EXTRA_DIST  = rdata_serialization_priv.cc
diff --git a/src/lib/datasrc/memory/tests/Makefile.am b/src/lib/datasrc/memory/tests/Makefile.am
index 0895b5c..74b1a3d 100644
--- a/src/lib/datasrc/memory/tests/Makefile.am
+++ b/src/lib/datasrc/memory/tests/Makefile.am
@@ -27,6 +27,8 @@ run_unittests_SOURCES += domaintree_unittest.cc
 run_unittests_SOURCES += treenode_rrset_unittest.cc
 run_unittests_SOURCES += zone_table_unittest.cc
 run_unittests_SOURCES += zone_data_unittest.cc
+run_unittests_SOURCES += zone_finder_unittest.cc
+run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
 run_unittests_SOURCES += memory_segment_test.h
 run_unittests_SOURCES += segment_object_holder_unittest.cc
 run_unittests_SOURCES += memory_client_unittest.cc
@@ -35,6 +37,7 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
 
 run_unittests_LDADD = $(builddir)/../libdatasrc_memory.la
+run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libb10-datasrc.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
diff --git a/src/lib/datasrc/memory/tests/zone_finder_unittest.cc b/src/lib/datasrc/memory/tests/zone_finder_unittest.cc
new file mode 100644
index 0000000..94ebe5e
--- /dev/null
+++ b/src/lib/datasrc/memory/tests/zone_finder_unittest.cc
@@ -0,0 +1,1481 @@
+// 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"
+
+// NOTE: this faked_nsec3 inclusion (and all related code below)
+// was ported during #2109 for the convenience of implementing #2218
+// In #2218 the NSEC3 test code in this file is expected to be finalized.
+// In #2219 the original is expected to be removed, and this file should
+// probably be moved here (and any leftover code not handled in #2218 should
+// be cleaned up)
+#include "../../tests/faked_nsec3.h"
+
+#include <datasrc/memory/zone_finder.h>
+#include <datasrc/memory/rdata_serialization.h>
+#include <datasrc/data_source.h>
+#include <testutils/dnsmessage_test.h>
+
+#include <boost/foreach.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::datasrc;
+using namespace isc::testutils;
+using boost::shared_ptr;
+using namespace isc::datasrc::test;
+using namespace isc::datasrc::memory::test;
+using namespace isc::datasrc::memory;
+
+namespace {
+// Commonly used result codes (Who should write the prefix all the time)
+using result::SUCCESS;
+using result::EXIST;
+
+// Some faked NSEC3 hash values commonly used in tests and the faked NSEC3Hash
+// object.
+//
+// For apex (example.org)
+const char* const apex_hash = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+const char* const apex_hash_lower = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
+// For ns1.example.org
+const char* const ns1_hash = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR";
+// For w.example.org
+const char* const w_hash = "01UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
+// For x.y.w.example.org (lower-cased)
+const char* const xyw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
+// For zzz.example.org.
+const char* const zzz_hash = "R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN";
+
+// A simple faked NSEC3 hash calculator with a dedicated creator for it.
+//
+// This is used in some NSEC3-related tests below.
+// Also see NOTE at inclusion of "../../tests/faked_nsec3.h"
+class TestNSEC3HashCreator : public NSEC3HashCreator {
+    class TestNSEC3Hash : public NSEC3Hash {
+    private:
+        typedef map<Name, string> NSEC3HashMap;
+        typedef NSEC3HashMap::value_type NSEC3HashPair;
+        NSEC3HashMap map_;
+    public:
+        TestNSEC3Hash() {
+            // Build pre-defined hash
+            map_[Name("example.org")] = apex_hash;
+            map_[Name("www.example.org")] = "2S9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+            map_[Name("xxx.example.org")] = "Q09MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+            map_[Name("yyy.example.org")] = "0A9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+            map_[Name("x.y.w.example.org")] =
+                "2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S";
+            map_[Name("y.w.example.org")] = "K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
+            map_[Name("w.example.org")] = w_hash;
+            map_[Name("zzz.example.org")] = zzz_hash;
+            map_[Name("smallest.example.org")] =
+                "00000000000000000000000000000000";
+            map_[Name("largest.example.org")] =
+                "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU";
+        }
+        virtual string calculate(const Name& name) const {
+            const NSEC3HashMap::const_iterator found = map_.find(name);
+            if (found != map_.end()) {
+                return (found->second);
+            }
+            isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+                      << name);
+        }
+        virtual bool match(const generic::NSEC3PARAM&) const {
+            return (true);
+        }
+        virtual bool match(const generic::NSEC3&) const {
+            return (true);
+        }
+    };
+
+public:
+    virtual NSEC3Hash* create(const generic::NSEC3PARAM&) const {
+        return (new TestNSEC3Hash);
+    }
+    virtual NSEC3Hash* create(const generic::NSEC3&) const {
+        return (new TestNSEC3Hash);
+    }
+};
+
+
+/// \brief expensive rrset converter
+///
+/// converts any specialized rrset (which may not have implemented some
+/// methods for efficiency) into a 'full' RRsetPtr, for easy use in test
+/// checks.
+///
+/// Done very inefficiently through text representation, speed should not
+/// be a concern here.
+ConstRRsetPtr
+convertRRset(ConstRRsetPtr src) {
+    return (textToRRset(src->toText()));
+}
+
+/// \brief Test fixture for the InMemoryZoneFinder class
+class InMemoryZoneFinderTest : public ::testing::Test {
+    // A straightforward pair of textual RR(set) and a RRsetPtr variable
+    // to store the RRset.  Used to build test data below.
+    struct RRsetData {
+        const char* const text; // textual representation of an RRset
+        RRsetPtr* rrset;
+    };
+protected:
+    // The following sub tests are shared by multiple test cases, changing
+    // the zone's DNSSEC status (unsigned, NSEC-signed or NSEC3-signed).
+    // expected_flags is set to either RESULT_NSEC_SIGNED or
+    // RESULT_NSEC3_SIGNED when it's NSEC/NSEC3 signed respectively and
+    // find() is expected to set the corresponding flags.
+    // find_options should be set to FIND_DNSSEC for NSEC-signed case when
+    // NSEC is expected to be returned.
+    void findCheck(ZoneFinder::FindResultFlags expected_flags =
+                   ZoneFinder::RESULT_DEFAULT,
+                   ZoneFinder::FindOptions find_options =
+                   ZoneFinder::FIND_DEFAULT);
+    void emptyNodeCheck(ZoneFinder::FindResultFlags expected_flags =
+                        ZoneFinder::RESULT_DEFAULT);
+    void wildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+                       ZoneFinder::RESULT_DEFAULT,
+                       ZoneFinder::FindOptions find_options =
+                       ZoneFinder::FIND_DEFAULT);
+    void doCancelWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+                               ZoneFinder::RESULT_DEFAULT,
+                               ZoneFinder::FindOptions find_options =
+                               ZoneFinder::FIND_DEFAULT);
+    void anyWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+                          ZoneFinder::RESULT_DEFAULT);
+    void emptyWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+                            ZoneFinder::RESULT_DEFAULT);
+    void findNSECENTCheck(const Name& ent_name,
+                          ConstRRsetPtr expected_nsec,
+                          ZoneFinder::FindResultFlags expected_flags =
+                          ZoneFinder::RESULT_DEFAULT);
+
+public:
+    InMemoryZoneFinderTest() :
+        class_(RRClass::IN()),
+        origin_("example.org"),
+        zone_data_(ZoneData::create(mem_sgmt_, origin_)),
+        zone_finder_(*zone_data_, class_)
+    {
+        // Build test RRsets.  Below, we construct an RRset for
+        // each textual RR(s) of zone_data, and assign it to the corresponding
+        // rr_xxx.
+        // Note that this contains an out-of-zone RR, and due to the
+        // validation check of masterLoad() used below, we cannot add SOA.
+        const RRsetData zone_data[] = {
+            {"example.org. 300 IN NS ns.example.org.", &rr_ns_},
+            {"example.org. 300 IN A 192.0.2.1", &rr_a_},
+            {"ns.example.org. 300 IN A 192.0.2.2", &rr_ns_a_},
+            {"ns.example.org. 300 IN AAAA 2001:db8::2", &rr_ns_aaaa_},
+            {"cname.example.org. 300 IN CNAME canonical.example.org",
+             &rr_cname_},
+            {"cname.example.org. 300 IN A 192.0.2.3", &rr_cname_a_},
+            {"dname.example.org. 300 IN DNAME target.example.org.",
+             &rr_dname_},
+            {"dname.example.org. 300 IN A 192.0.2.39", &rr_dname_a_},
+            {"dname.example.org. 300 IN NS ns.dname.example.org.",
+             &rr_dname_ns_},
+            {"example.org. 300 IN DNAME example.com.", &rr_dname_apex_},
+            {"child.example.org. 300 IN NS ns.child.example.org.",
+             &rr_child_ns_},
+            {"child.example.org. 300 IN DS 12345 5 1 DEADBEEF",
+             &rr_child_ds_},
+            {"ns.child.example.org. 300 IN A 192.0.2.153",
+             &rr_child_glue_},
+            {"grand.child.example.org. 300 IN NS ns.grand.child.example.org.",
+             &rr_grandchild_ns_},
+            {"ns.grand.child.example.org. 300 IN AAAA 2001:db8::253",
+             &rr_grandchild_glue_},
+            {"dname.child.example.org. 300 IN DNAME example.com.",
+             &rr_child_dname_},
+            {"example.com. 300 IN A 192.0.2.10", &rr_out_},
+            {"*.wild.example.org. 300 IN A 192.0.2.1", &rr_wild_},
+            {"*.cnamewild.example.org. 300 IN CNAME canonial.example.org.",
+             &rr_cnamewild_},
+            {"foo.wild.example.org. 300 IN A 192.0.2.3", &rr_under_wild_},
+            {"wild.*.foo.example.org. 300 IN A 192.0.2.1", &rr_emptywild_},
+            {"wild.*.foo.*.bar.example.org. 300 IN A 192.0.2.1",
+             &rr_nested_emptywild_},
+            {"*.nswild.example.org. 300 IN NS nswild.example.", &rr_nswild_},
+            {"*.dnamewild.example.org. 300 IN DNAME dnamewild.example.",
+             &rr_dnamewild_},
+            {"*.child.example.org. 300 IN A 192.0.2.1", &rr_child_wild_},
+            {"bar.foo.wild.example.org. 300 IN A 192.0.2.2", &rr_not_wild_},
+            {"baz.foo.wild.example.org. 300 IN A 192.0.2.3",
+             &rr_not_wild_another_},
+            {"0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM.example.org. 300 IN "
+             "NSEC3 1 1 12 aabbccdd 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG",
+             &rr_nsec3_},
+            {"example.org. 300 IN NSEC wild.*.foo.example.org. "
+             "NS SOA RRSIG NSEC DNSKEY", &rr_nsec_},
+            // Together with the apex NSEC, these next NSECs make a complete
+            // chain in the case of the zone for the emptyNonterminal tests
+            // (We may want to clean up this generator code and/or masterLoad
+            // so that we can prepare conflicting datasets better)
+            {"wild.*.foo.example.org. 3600 IN NSEC ns.example.org. "
+             "A RRSIG NSEC", &rr_ent_nsec2_},
+            {"ns.example.org. 3600 IN NSEC foo.wild.example.org. A RRSIG NSEC",
+             &rr_ent_nsec3_},
+            {"foo.wild.example.org. 3600 IN NSEC example.org. A RRSIG NSEC",
+             &rr_ent_nsec4_},
+            // And these are NSECs used in different tests
+            {"ns.example.org. 300 IN NSEC *.nswild.example.org. A AAAA NSEC",
+             &rr_ns_nsec_},
+            {"*.wild.example.org. 300 IN NSEC foo.wild.example.org. A NSEC",
+             &rr_wild_nsec_},
+            {NULL, NULL}
+        };
+
+        for (unsigned int i = 0; zone_data[i].text != NULL; ++i) {
+            *zone_data[i].rrset = textToRRset(zone_data[i].text);
+        }
+
+    }
+
+    ~InMemoryZoneFinderTest() {
+        // Make sure we reset the hash creator to the default
+        setNSEC3HashCreator(NULL);
+        ZoneData::destroy(mem_sgmt_, zone_data_, RRClass::IN());
+    }
+
+    // NSEC3-specific call for 'loading' data
+    // This needs to be updated and checked when implementing #2118
+    void addZoneDataNSEC3(const ConstRRsetPtr rrset) {
+        assert(rrset->getType() == RRType::NSEC3());
+
+        const Rdata* rdata = &rrset->getRdataIterator()->getCurrent();
+        const generic::NSEC3* nsec3_rdata =
+            dynamic_cast<const generic::NSEC3*>(rdata);
+        NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt_, *nsec3_rdata);
+        // in case we happen to be replacing, destroy old
+        NSEC3Data* old_data = zone_data_->setNSEC3Data(nsec3_data);
+        if (old_data != NULL) {
+            NSEC3Data::destroy(mem_sgmt_, old_data, rrset->getClass());
+        }
+        zone_data_->setSigned(true);
+    }
+
+    // simplified version of 'loading' data
+    void addZoneData(const ConstRRsetPtr rrset) {
+        ZoneNode* node = NULL;
+
+        if (rrset->getType() == RRType::NSEC3()) {
+            return (addZoneDataNSEC3(rrset));
+        } else if (rrset->getType() == RRType::NSEC()) {
+            zone_data_->setSigned(true);
+        }
+
+        zone_data_->insertName(mem_sgmt_, rrset->getName(), &node);
+
+        if (rrset->getType() == RRType::NS() &&
+            rrset->getName() != zone_data_->getOriginNode()->getName()) {
+            node->setFlag(DomainTreeNode<RdataSet>::FLAG_CALLBACK);
+        } else if (rrset->getType() == RRType::DNAME()) {
+            node->setFlag(DomainTreeNode<RdataSet>::FLAG_CALLBACK);
+        }
+
+        RdataSet* next_rds = node->getData();
+        RdataSet* rdataset =
+            RdataSet::create(mem_sgmt_, encoder_, rrset, rrset->getRRsig());
+        rdataset->next = next_rds;
+        node->setData(rdataset);
+
+        // find wildcard nodes in name (go through all of them in case there
+        // is a nonterminal one)
+        // Note that this method is pretty much equal to the 'real' loader;
+        // but less efficient
+        Name name(rrset->getName());
+        while (name.getLabelCount() > 1) {
+            if (name.isWildcard()) {
+                ZoneNode* wnode = NULL;
+                // add Wild node
+                zone_data_->insertName(mem_sgmt_, name.split(1), &wnode);
+                wnode->setFlag(ZoneData::WILDCARD_NODE);
+                // add wildcard name itself too
+                zone_data_->insertName(mem_sgmt_, name, &wnode);
+            }
+            name = name.split(1);
+        }
+    }
+
+    // Some data to test with
+    const RRClass class_;
+    const Name origin_;
+    // The zone finder to torture by tests
+    MemorySegmentTest mem_sgmt_;
+    memory::ZoneData* zone_data_;
+    memory::InMemoryZoneFinder zone_finder_;
+    isc::datasrc::memory::RdataEncoder encoder_;
+
+    // Placeholder for storing RRsets to be checked with rrsetsCheck()
+    vector<ConstRRsetPtr> actual_rrsets_;
+
+    /*
+     * Some RRsets to put inside the zone.
+     */
+    RRsetPtr
+        // Out of zone RRset
+        rr_out_,
+        // NS of example.org
+        rr_ns_,
+        // A of ns.example.org
+        rr_ns_a_,
+        // AAAA of ns.example.org
+        rr_ns_aaaa_,
+        // A of example.org
+        rr_a_;
+    RRsetPtr rr_cname_;         // CNAME in example.org (RDATA will be added)
+    RRsetPtr rr_cname_a_; // for mixed CNAME + A case
+    RRsetPtr rr_dname_;         // DNAME in example.org (RDATA will be added)
+    RRsetPtr rr_dname_a_; // for mixed DNAME + A case
+    RRsetPtr rr_dname_ns_; // for mixed DNAME + NS case
+    RRsetPtr rr_dname_apex_; // for mixed DNAME + NS case in the apex
+    RRsetPtr rr_child_ns_; // NS of a child domain (for delegation)
+    RRsetPtr rr_child_ds_; // DS of a child domain (for delegation, auth data)
+    RRsetPtr rr_child_glue_; // glue RR of the child domain
+    RRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
+    RRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
+    RRsetPtr rr_child_dname_; // A DNAME under NS
+    RRsetPtr rr_wild_;        // Wildcard record
+    RRsetPtr rr_cnamewild_;     // CNAME at a wildcard
+    RRsetPtr rr_emptywild_;
+    RRsetPtr rr_nested_emptywild_;
+    RRsetPtr rr_nswild_, rr_dnamewild_;
+    RRsetPtr rr_child_wild_;
+    RRsetPtr rr_under_wild_;
+    RRsetPtr rr_not_wild_;
+    RRsetPtr rr_not_wild_another_;
+    RRsetPtr rr_nsec3_;
+    RRsetPtr rr_nsec_;
+    RRsetPtr rr_ent_nsec2_;
+    RRsetPtr rr_ent_nsec3_;
+    RRsetPtr rr_ent_nsec4_;
+    RRsetPtr rr_ns_nsec_;
+    RRsetPtr rr_wild_nsec_;
+
+    // A faked NSEC3 hash calculator for convenience.
+    // Tests that need to use the faked hashed values should call
+    // setNSEC3HashCreator() with a pointer to this variable at the beginning
+    // of the test (at least before adding any NSEC3/NSEC3PARAM RR).
+    TestNSEC3HashCreator nsec3_hash_creator_;
+
+    /**
+     * \brief Test one find query to the zone finder.
+     *
+     * Asks a query to the zone finder and checks it does not throw and returns
+     * expected results. It returns nothing, it just signals failures
+     * to GTEST.
+     *
+     * \param name The name to ask for.
+     * \param rrtype The RRType to ask of.
+     * \param result The expected code of the result.
+     * \param check_answer Should a check against equality of the answer be
+     *     done?
+     * \param answer The expected rrset, if any should be returned.
+     * \param expected_flags The expected result flags returned via find().
+     *     These can be tested using isWildcard() etc.
+     * \param zone_finder Check different InMemoryZoneFinder object than
+     *     zone_finder_ (if NULL, uses zone_finder_)
+     * \param check_wild_answer Checks that the answer has the same RRs, type
+     *     class and TTL as the eqxpected answer and that the name corresponds
+     *     to the one searched. It is meant for checking answers for wildcard
+     *     queries.
+     */
+    void findTest(const Name& name, const RRType& rrtype,
+                  ZoneFinder::Result result,
+                  bool check_answer = true,
+                  const ConstRRsetPtr& answer = ConstRRsetPtr(),
+                  ZoneFinder::FindResultFlags expected_flags =
+                  ZoneFinder::RESULT_DEFAULT,
+                  memory::InMemoryZoneFinder* zone_finder = NULL,
+                  ZoneFinder::FindOptions options = ZoneFinder::FIND_DEFAULT,
+                  bool check_wild_answer = false)
+    {
+        SCOPED_TRACE("findTest for " + name.toText() + "/" + rrtype.toText());
+
+        if (zone_finder == NULL) {
+            zone_finder = &zone_finder_;
+        }
+        const ConstRRsetPtr answer_sig = answer ? answer->getRRsig() :
+            RRsetPtr(); // note we use the same type as of retval of getRRsig()
+        // The whole block is inside, because we need to check the result and
+        // we can't assign to FindResult
+        EXPECT_NO_THROW({
+                ZoneFinderContextPtr find_result(zone_finder->find(
+                                                     name, rrtype, options));
+                // Check it returns correct answers
+                EXPECT_EQ(result, find_result->code);
+                EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+                          find_result->isWildcard());
+                EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED)
+                          != 0, find_result->isNSECSigned());
+                EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED)
+                          != 0, find_result->isNSEC3Signed());
+                if (check_answer) {
+                    if (!answer) {
+                        ASSERT_FALSE(find_result->rrset);
+                    } else {
+                        ASSERT_TRUE(find_result->rrset);
+                        ConstRRsetPtr result_rrset(
+                            convertRRset(find_result->rrset));
+                        rrsetCheck(answer, result_rrset);
+                        if (answer_sig) {
+                            ASSERT_TRUE(result_rrset->getRRsig());
+                            rrsetCheck(answer_sig, result_rrset->getRRsig());
+                        }
+                    }
+                } else if (check_wild_answer) {
+                    ASSERT_NE(ConstRRsetPtr(), answer) <<
+                        "Wrong test, don't check for wild names if you expect "
+                        "empty answer";
+                    ASSERT_NE(ConstRRsetPtr(), find_result->rrset) <<
+                        "No answer found";
+                    // Build the expected answer using the given name and
+                    // other parameter of the base wildcard RRset.
+                    RRsetPtr wildanswer(new RRset(name, answer->getClass(),
+                                                  answer->getType(),
+                                                  answer->getTTL()));
+                    RdataIteratorPtr expectedIt(answer->getRdataIterator());
+                    for (; !expectedIt->isLast(); expectedIt->next()) {
+                        wildanswer->addRdata(expectedIt->getCurrent());
+                    }
+
+                    ConstRRsetPtr result_rrset(
+                        convertRRset(find_result->rrset));
+                    rrsetCheck(wildanswer, result_rrset);
+
+                    // Same for the RRSIG, if any.
+                    if (answer_sig) {
+                        ASSERT_TRUE(result_rrset->getRRsig());
+
+                        RRsetPtr wildsig(new RRset(name,
+                                                   answer_sig->getClass(),
+                                                   RRType::RRSIG(),
+                                                   answer_sig->getTTL()));
+                        RdataIteratorPtr expectedIt(
+                            answer_sig->getRdataIterator());
+                        for (; !expectedIt->isLast(); expectedIt->next()) {
+                            wildsig->addRdata(expectedIt->getCurrent());
+                        }
+                        rrsetCheck(wildsig, result_rrset->getRRsig());
+                    }
+                }
+            });
+    }
+    /**
+     * \brief Calls the findAll on the finder and checks the result.
+     */
+    void findAllTest(const Name& name, ZoneFinder::Result result,
+                     const vector<ConstRRsetPtr>& expected_rrsets,
+                     ZoneFinder::FindResultFlags expected_flags =
+                     ZoneFinder::RESULT_DEFAULT,
+                     memory::InMemoryZoneFinder* finder = NULL,
+                     const ConstRRsetPtr &rrset_result = ConstRRsetPtr(),
+                     ZoneFinder::FindOptions options =
+                     ZoneFinder::FIND_DEFAULT)
+    {
+        if (finder == NULL) {
+            finder = &zone_finder_;
+        }
+        std::vector<ConstRRsetPtr> target;
+        ZoneFinderContextPtr find_result(finder->findAll(name, target,
+                                                         options));
+        EXPECT_EQ(result, find_result->code);
+        if (!rrset_result) {
+            EXPECT_FALSE(find_result->rrset);
+        } else {
+            ASSERT_TRUE(find_result->rrset);
+            rrsetCheck(rrset_result, convertRRset(find_result->rrset));
+        }
+        EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+                  find_result->isWildcard());
+        EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED)
+                  != 0, find_result->isNSECSigned());
+        EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED)
+                  != 0, find_result->isNSEC3Signed());
+        // Convert all rrsets to 'full' ones before checking
+        std::vector<ConstRRsetPtr> converted_rrsets;
+        BOOST_FOREACH(ConstRRsetPtr cur_rrset, target) {
+            converted_rrsets.push_back(convertRRset(cur_rrset));
+        }
+        rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
+                    converted_rrsets.begin(), converted_rrsets.end());
+    }
+};
+
+/**
+ * \brief Test InMemoryZoneFinder::InMemoryZoneFinder constructor.
+ *
+ * Takes the created zone finder and checks its properties they are the same
+ * as passed parameters.
+ */
+TEST_F(InMemoryZoneFinderTest, constructor) {
+    ASSERT_EQ(origin_, zone_finder_.getOrigin());
+}
+
+TEST_F(InMemoryZoneFinderTest, findCNAME) {
+    // install CNAME RR
+    addZoneData(rr_cname_);
+
+    // Find A RR of the same.  Should match the CNAME
+    findTest(rr_cname_->getName(), RRType::NS(), ZoneFinder::CNAME, true,
+             rr_cname_);
+
+    // Find the CNAME itself.  Should result in normal SUCCESS
+    findTest(rr_cname_->getName(), RRType::CNAME(), ZoneFinder::SUCCESS, true,
+             rr_cname_);
+}
+
+TEST_F(InMemoryZoneFinderTest, findCNAMEUnderZoneCut) {
+    // There's nothing special when we find a CNAME under a zone cut
+    // (with FIND_GLUE_OK).  The behavior is different from BIND 9,
+    // so we test this case explicitly.
+    addZoneData(rr_child_ns_);
+    ConstRRsetPtr rr_cname_under_cut_ = textToRRset(
+        "cname.child.example.org. 300 IN CNAME target.child.example.org.");
+    addZoneData(rr_cname_under_cut_);
+    findTest(Name("cname.child.example.org"), RRType::AAAA(),
+             ZoneFinder::CNAME, true, rr_cname_under_cut_,
+             ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
+}
+
+// Search under a DNAME record. It should return the DNAME
+TEST_F(InMemoryZoneFinderTest, findBelowDNAME) {
+    EXPECT_NO_THROW(addZoneData(rr_dname_));
+    findTest(Name("below.dname.example.org"), RRType::A(), ZoneFinder::DNAME,
+             true, rr_dname_);
+}
+
+// Search at the domain with DNAME. It should act as DNAME isn't there, DNAME
+// influences only the data below (see RFC 2672, section 3)
+TEST_F(InMemoryZoneFinderTest, findAtDNAME) {
+    EXPECT_NO_THROW(addZoneData(rr_dname_));
+    EXPECT_NO_THROW(addZoneData(rr_dname_a_));
+
+    const Name dname_name(rr_dname_->getName());
+    findTest(dname_name, RRType::A(), ZoneFinder::SUCCESS, true, rr_dname_a_);
+    findTest(dname_name, RRType::DNAME(), ZoneFinder::SUCCESS, true,
+             rr_dname_);
+    findTest(dname_name, RRType::TXT(), ZoneFinder::NXRRSET, true);
+}
+
+// Try searching something that is both under NS and DNAME, without and with
+// GLUE_OK mode (it should stop at the NS and DNAME respectively).
+TEST_F(InMemoryZoneFinderTest, DNAMEUnderNS) {
+    addZoneData(rr_child_ns_);
+    addZoneData(rr_child_dname_);
+
+    Name lowName("below.dname.child.example.org.");
+
+    findTest(lowName, RRType::A(), ZoneFinder::DELEGATION, true, rr_child_ns_);
+    findTest(lowName, RRType::A(), ZoneFinder::DNAME, true, rr_child_dname_,
+             ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
+}
+
+// Test adding child zones and zone cut handling
+TEST_F(InMemoryZoneFinderTest, delegationNS) {
+    // add in-zone data
+    EXPECT_NO_THROW(addZoneData(rr_ns_));
+
+    // install a zone cut
+    EXPECT_NO_THROW(addZoneData(rr_child_ns_));
+
+    // below the zone cut
+    findTest(Name("www.child.example.org"), RRType::A(),
+             ZoneFinder::DELEGATION, true, rr_child_ns_);
+
+    // at the zone cut
+    findTest(Name("child.example.org"), RRType::A(), ZoneFinder::DELEGATION,
+             true, rr_child_ns_);
+    findTest(Name("child.example.org"), RRType::NS(), ZoneFinder::DELEGATION,
+             true, rr_child_ns_);
+
+    // finding NS for the apex (origin) node.  This must not be confused
+    // with delegation due to the existence of an NS RR.
+    findTest(origin_, RRType::NS(), ZoneFinder::SUCCESS, true, rr_ns_);
+
+    // unusual case of "nested delegation": the highest cut should be used.
+    EXPECT_NO_THROW(addZoneData(rr_grandchild_ns_));
+    findTest(Name("www.grand.child.example.org"), RRType::A(),
+             // note: !rr_grandchild_ns_
+             ZoneFinder::DELEGATION, true, rr_child_ns_);
+}
+
+TEST_F(InMemoryZoneFinderTest, delegationWithDS) {
+    // Similar setup to the previous one, but with DS RR at the delegation
+    // point.
+    addZoneData(rr_ns_);
+    addZoneData(rr_child_ns_);
+    addZoneData(rr_child_ds_);
+
+    // Normal types of query should result in delegation, but DS query
+    // should be considered in-zone (but only exactly at the delegation point).
+    findTest(Name("child.example.org"), RRType::A(), ZoneFinder::DELEGATION,
+             true, rr_child_ns_);
+    findTest(Name("child.example.org"), RRType::DS(), ZoneFinder::SUCCESS,
+             true, rr_child_ds_);
+    findTest(Name("grand.child.example.org"), RRType::DS(),
+             ZoneFinder::DELEGATION, true, rr_child_ns_);
+
+    // There's nothing special for DS query at the zone apex.  It would
+    // normally result in NXRRSET.
+    findTest(Name("example.org"), RRType::DS(), ZoneFinder::NXRRSET,
+             true, ConstRRsetPtr());
+}
+
+TEST_F(InMemoryZoneFinderTest, findAny) {
+    EXPECT_NO_THROW(addZoneData(rr_a_));
+    EXPECT_NO_THROW(addZoneData(rr_ns_));
+    EXPECT_NO_THROW(addZoneData(rr_child_glue_));
+
+    vector<ConstRRsetPtr> expected_sets;
+
+    // origin
+    expected_sets.push_back(rr_a_);
+    expected_sets.push_back(rr_ns_);
+    findAllTest(origin_, ZoneFinder::SUCCESS, expected_sets);
+
+    // out zone name
+    EXPECT_THROW(findAllTest(Name("example.com"), ZoneFinder::NXDOMAIN,
+                             vector<ConstRRsetPtr>()),
+                 OutOfZone);
+
+    expected_sets.clear();
+    expected_sets.push_back(rr_child_glue_);
+    findAllTest(rr_child_glue_->getName(), ZoneFinder::SUCCESS, expected_sets);
+
+    // add zone cut
+    EXPECT_NO_THROW(addZoneData(rr_child_ns_));
+
+    // zone cut
+    findAllTest(rr_child_ns_->getName(), ZoneFinder::DELEGATION,
+                vector<ConstRRsetPtr>(), ZoneFinder::RESULT_DEFAULT,
+                NULL, rr_child_ns_);
+
+    // glue for this zone cut
+    findAllTest(rr_child_glue_->getName(),ZoneFinder::DELEGATION,
+                vector<ConstRRsetPtr>(), ZoneFinder::RESULT_DEFAULT,
+                NULL, rr_child_ns_);
+}
+
+TEST_F(InMemoryZoneFinderTest, glue) {
+    // install zone data:
+    // a zone cut
+    EXPECT_NO_THROW(addZoneData(rr_child_ns_));
+    // glue for this cut
+    EXPECT_NO_THROW(addZoneData(rr_child_glue_));
+    // a nested zone cut (unusual)
+    EXPECT_NO_THROW(addZoneData(rr_grandchild_ns_));
+    // glue under the deeper zone cut
+    EXPECT_NO_THROW(addZoneData(rr_grandchild_glue_));
+
+    // by default glue is hidden due to the zone cut
+    findTest(rr_child_glue_->getName(), RRType::A(), ZoneFinder::DELEGATION,
+             true, rr_child_ns_);
+
+
+    // If we do it in the "glue OK" mode, we should find the exact match.
+    findTest(rr_child_glue_->getName(), RRType::A(), ZoneFinder::SUCCESS, true,
+             rr_child_glue_, ZoneFinder::RESULT_DEFAULT, NULL,
+             ZoneFinder::FIND_GLUE_OK);
+
+    // glue OK + NXRRSET case
+    findTest(rr_child_glue_->getName(), RRType::AAAA(), ZoneFinder::NXRRSET,
+             true, ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, NULL,
+             ZoneFinder::FIND_GLUE_OK);
+
+    // glue OK + NXDOMAIN case
+    findTest(Name("www.child.example.org"), RRType::A(),
+             ZoneFinder::DELEGATION, true, rr_child_ns_,
+             ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
+
+    // nested cut case.  The glue should be found.
+    findTest(rr_grandchild_glue_->getName(), RRType::AAAA(),
+             ZoneFinder::SUCCESS,
+             true, rr_grandchild_glue_, ZoneFinder::RESULT_DEFAULT, NULL,
+             ZoneFinder::FIND_GLUE_OK);
+
+    // A non-existent name in nested cut.  This should result in delegation
+    // at the highest zone cut.
+    findTest(Name("www.grand.child.example.org"), RRType::TXT(),
+             ZoneFinder::DELEGATION, true, rr_child_ns_,
+             ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
+}
+
+/**
+ * \brief Test searching.
+ *
+ * Check it finds or does not find correctly and does not throw exceptions.
+ */
+void
+InMemoryZoneFinderTest::findCheck(ZoneFinder::FindResultFlags expected_flags,
+                                  ZoneFinder::FindOptions find_options)
+{
+    // Fill some data inside
+    // Now put all the data we have there. It should throw nothing
+    EXPECT_NO_THROW(addZoneData(rr_ns_));
+    EXPECT_NO_THROW(addZoneData(rr_ns_a_));
+    EXPECT_NO_THROW(addZoneData(rr_ns_aaaa_));
+    EXPECT_NO_THROW(addZoneData(rr_a_));
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        addZoneData(rr_nsec3_);
+        zone_data_->setSigned(true);
+    }
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+        addZoneData(rr_nsec_);
+        zone_data_->setSigned(true);
+    }
+
+    // These two should be successful
+    findTest(origin_, RRType::NS(), ZoneFinder::SUCCESS, true, rr_ns_);
+    findTest(rr_ns_a_->getName(), RRType::A(), ZoneFinder::SUCCESS, true,
+             rr_ns_a_);
+
+    // These domains don't exist. (and one is out of the zone).  In an
+    // NSEC-signed zone with DNSSEC records requested, it should return the
+    // covering NSEC for the query name (the actual NSEC in the test data may
+    // not really "cover" it, but for the purpose of this test it's okay).
+    ConstRRsetPtr expected_nsec; // by default it's NULL
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 &&
+        (find_options & ZoneFinder::FIND_DNSSEC) != 0) {
+        expected_nsec = rr_nsec_;
+    }
+
+    // There's no other name between this one and the origin, so when NSEC
+    // is to be returned it should be the origin NSEC.
+    findTest(Name("nothere.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+             NULL, find_options);
+
+    // The previous name in the zone is "ns.example.org", but it doesn't
+    // have an NSEC.  It should be skipped and the origin NSEC will be
+    // returned as the "closest NSEC".
+    findTest(Name("nxdomain.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+             NULL, find_options);
+    EXPECT_THROW(zone_finder_.find(Name("example.net"), RRType::A()),
+                 OutOfZone);
+
+    // These domain exist but don't have the provided RRType.  For the latter
+    // one we now add its NSEC (which was delayed so that it wouldn't break
+    // other cases above).
+    findTest(origin_, RRType::AAAA(), ZoneFinder::NXRRSET, true,
+             expected_nsec, expected_flags, NULL, find_options);
+
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+        addZoneData(rr_ns_nsec_);
+        zone_data_->setSigned(true);
+        if ((find_options & ZoneFinder::FIND_DNSSEC) != 0) {
+            expected_nsec = rr_ns_nsec_;
+        }
+    }
+    findTest(rr_ns_a_->getName(), RRType::NS(), ZoneFinder::NXRRSET, true,
+             expected_nsec, expected_flags, NULL, find_options);
+}
+
+TEST_F(InMemoryZoneFinderTest, find) {
+    findCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSEC3Signed) {
+    findCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSEC3SignedWithDNSSEC) {
+    // For NSEC3-signed zones, specifying the DNSSEC option shouldn't change
+    // anything (the NSEC3_SIGNED flag is always set, and no records are
+    // returned for negative cases regardless).
+    findCheck(ZoneFinder::RESULT_NSEC3_SIGNED, ZoneFinder::FIND_DNSSEC);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSECSigned) {
+    // NSEC-signed zone, without requesting DNSSEC (no NSEC should be provided)
+    findCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+}
+
+// Generalized test for Empty Nonterminal (ENT) cases with NSEC
+void
+InMemoryZoneFinderTest::findNSECENTCheck(const Name& ent_name,
+    ConstRRsetPtr expected_nsec,
+    ZoneFinder::FindResultFlags expected_flags)
+{
+    addZoneData(rr_emptywild_);
+    addZoneData(rr_under_wild_);
+
+    // Sanity check: Should result in NXRRSET
+    findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(), expected_flags);
+    // Sanity check: No NSEC added yet
+    findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(), expected_flags,
+             NULL, ZoneFinder::FIND_DNSSEC);
+
+    // Now add the NSEC rrs making it a 'complete' zone (in terms of NSEC,
+    // there are no sigs)
+    addZoneData(rr_nsec_);
+    addZoneData(rr_ent_nsec2_);
+    addZoneData(rr_ent_nsec3_);
+    addZoneData(rr_ent_nsec4_);
+    zone_data_->setSigned(true);
+
+    // Should result in NXRRSET, and RESULT_NSEC_SIGNED
+    findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(),
+             expected_flags | ZoneFinder::RESULT_NSEC_SIGNED);
+
+    // And check for the NSEC if DNSSEC_OK
+    findTest(ent_name, RRType::A(), ZoneFinder::NXRRSET, true,
+             expected_nsec, expected_flags | ZoneFinder::RESULT_NSEC_SIGNED,
+             NULL, ZoneFinder::FIND_DNSSEC);
+}
+
+TEST_F(InMemoryZoneFinderTest,findNSECEmptyNonterminal) {
+    // Non-wildcard case
+    findNSECENTCheck(Name("wild.example.org"), rr_ent_nsec3_);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSECEmptyNonterminalWildcard) {
+    // Wildcard case, above actual wildcard
+    findNSECENTCheck(Name("foo.example.org"), rr_nsec_);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSECEmptyNonterminalAtWildcard) {
+    // Wildcard case, at actual wildcard
+    findNSECENTCheck(Name("bar.foo.example.org"), rr_nsec_,
+                     ZoneFinder::RESULT_WILDCARD);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSECSignedWithDNSSEC) {
+    // NSEC-signed zone, requesting DNSSEC (NSEC should be provided)
+    findCheck(ZoneFinder::RESULT_NSEC_SIGNED, ZoneFinder::FIND_DNSSEC);
+}
+
+void
+InMemoryZoneFinderTest::emptyNodeCheck(
+    ZoneFinder::FindResultFlags expected_flags)
+{
+    /*
+     * The backend RBTree for this test should look like as follows:
+     *          example.org
+     *               |
+     *              baz (empty; easy case)
+     *            /  |  \
+     *          bar  |  x.foo ('foo' part is empty; a bit trickier)
+     *              bbb
+     *             /
+     *           aaa
+     */
+
+    // Construct the test zone
+    const char* const names[] = {
+        "bar.example.org.", "x.foo.example.org.", "aaa.baz.example.org.",
+        "bbb.baz.example.org.", NULL};
+    for (int i = 0; names[i] != NULL; ++i) {
+        ConstRRsetPtr rrset = textToRRset(string(names[i]) +
+                                          " 300 IN A 192.0.2.1");
+        addZoneData(rrset);
+    }
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        addZoneData(rr_nsec3_);
+        zone_data_->setSigned(true);
+    }
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+        addZoneData(rr_nsec_);
+        zone_data_->setSigned(true);
+    }
+
+    // empty node matching, easy case: the node for 'baz' exists with
+    // no data.
+    findTest(Name("baz.example.org"), RRType::A(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(), expected_flags);
+
+    // empty node matching, a trickier case: the node for 'foo' is part of
+    // "x.foo", which should be considered an empty node.
+    findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(), expected_flags);
+
+    // "org" is contained in "example.org", but it shouldn't be treated as
+    // NXRRSET because it's out of zone.
+    // Note: basically we don't expect such a query to be performed (the common
+    // operation is to identify the best matching zone first then perform
+    // search it), but we shouldn't be confused even in the unexpected case.
+    EXPECT_THROW(zone_finder_.find(Name("org"), RRType::A()), OutOfZone);
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyNode) {
+    emptyNodeCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyNodeNSEC3) {
+    emptyNodeCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyNodeNSEC) {
+    emptyNodeCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+}
+
+/*
+ * Test that puts a (simple) wildcard into the zone and checks we can
+ * correctly find the data.
+ */
+void
+InMemoryZoneFinderTest::wildcardCheck(
+    ZoneFinder::FindResultFlags expected_flags,
+    ZoneFinder::FindOptions find_options)
+{
+    /*
+     *            example.org.
+     *                 |
+     *             [cname]wild (not *.wild, should have wild mark)
+     *                 |
+     *                 *
+     */
+
+    // If the zone is "signed" (detecting it by the NSEC/NSEC3 signed flags),
+    // add RRSIGs to the records.
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 ||
+        (expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        // Convenience shortcut.  The RDATA is not really validatable, but
+        // it doesn't matter for our tests.
+        const char* const rrsig_common = "5 3 3600 "
+            "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
+
+        find_options = find_options | ZoneFinder::FIND_DNSSEC;
+        rr_wild_->addRRsig(textToRRset("*.wild.example.org. 300 IN RRSIG A " +
+                                       string(rrsig_common)));
+        rr_cnamewild_->addRRsig(textToRRset("*.cnamewild.example.org. 300 IN "
+                                            "RRSIG CNAME " +
+                                            string(rrsig_common)));
+    }
+    addZoneData(rr_wild_);
+    addZoneData(rr_cnamewild_);
+    // If the zone is expected to be "signed" with NSEC3, add an NSEC3.
+    // (the content of the NSEC3 shouldn't matter)
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        addZoneData(rr_nsec3_);
+    }
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+        addZoneData(rr_nsec_);
+    }
+
+    // Search at the parent. The parent will not have the A, but it will
+    // be in the wildcard (so check the wildcard isn't matched at the parent)
+    {
+        SCOPED_TRACE("Search at parent");
+        if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+            findTest(Name("wild.example.org"), RRType::A(),
+                     ZoneFinder::NXRRSET, true, rr_nsec_, expected_flags,
+                     NULL, find_options);
+        } else {
+            findTest(Name("wild.example.org"), RRType::A(),
+                     ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+                     expected_flags, NULL, find_options);
+        }
+    }
+
+    // For the test setup of "NSEC-signed" zone, we might expect it will
+    // be returned with a negative result, either because wildcard match is
+    // disabled by the search option or because wildcard match is canceled
+    // per protocol.
+    ConstRRsetPtr expected_nsec; // by default it's NULL
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 &&
+        (find_options & ZoneFinder::FIND_DNSSEC) != 0) {
+        expected_nsec = rr_nsec_;
+    }
+    // Explicitly converting the following to const pointers; some compilers
+    // would complain about mixed use of const and non const in ?: below.
+    const ConstRRsetPtr rr_wild = rr_wild_;
+    const ConstRRsetPtr rr_cnamewild = rr_cnamewild_;
+
+    // Search the original name of wildcard
+    {
+        SCOPED_TRACE("Search directly at *");
+        findTest(Name("*.wild.example.org"), RRType::A(), ZoneFinder::SUCCESS,
+                 true, rr_wild_, ZoneFinder::RESULT_DEFAULT, NULL,
+                 find_options);
+    }
+
+    // Below some of the test cases will normally result in a wildcard match;
+    // if NO_WILDCARD is specified, it should result in NXDOMAIN instead,
+    // and, when available and requested, the covering NSEC will be returned.
+    // The following are shortcut parameters to unify these cases.
+    const bool wild_ok = ((find_options & ZoneFinder::NO_WILDCARD) == 0);
+    const ZoneFinder::FindResultFlags wild_expected_flags =
+        wild_ok ? (ZoneFinder::RESULT_WILDCARD | expected_flags) :
+        expected_flags;
+
+    // Search "created" name.
+    {
+        SCOPED_TRACE("Search at created child");
+        findTest(Name("a.wild.example.org"), RRType::A(),
+                 wild_ok ? ZoneFinder::SUCCESS : ZoneFinder::NXDOMAIN, false,
+                 wild_ok ? rr_wild : expected_nsec,
+                 wild_expected_flags, NULL, find_options, wild_ok);
+    }
+
+    // Search name that has CNAME.
+    {
+        SCOPED_TRACE("Matching CNAME");
+        findTest(Name("a.cnamewild.example.org"), RRType::A(),
+                 wild_ok ? ZoneFinder::CNAME : ZoneFinder::NXDOMAIN, false,
+                 wild_ok ? rr_cnamewild : expected_nsec,
+                 wild_expected_flags, NULL, find_options, wild_ok);
+    }
+
+    // Search another created name, this time little bit lower
+    {
+        SCOPED_TRACE("Search at created grand-child");
+        findTest(Name("a.b.wild.example.org"), RRType::A(),
+                 wild_ok ? ZoneFinder::SUCCESS : ZoneFinder::NXDOMAIN, false,
+                 wild_ok ? rr_wild : expected_nsec,
+                 wild_expected_flags, NULL, find_options, wild_ok);
+    }
+
+    addZoneData(rr_under_wild_);
+    {
+        SCOPED_TRACE("Search under non-wildcard");
+        findTest(Name("bar.foo.wild.example.org"), RRType::A(),
+                 ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+                 NULL, find_options);
+    }
+
+    // Wildcard match, but no data.  We add the additional NSEC at the wildcard
+    // at this point so that it wouldn't break other tests above.  Note also
+    // that in the NO_WILDCARD case the resulting NSEC is the same.  Ideally
+    // we could use a more tricky setup so we can distinguish these cases,
+    // but for this purpose it's not bad; what we'd like to test here is that
+    // wildcard substitution doesn't happen for either case, and the
+    // NO_WILDCARD effect itself can be checked by the result code (NXDOMAIN).
+    ConstRRsetPtr expected_wild_nsec; // by default it's NULL
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+        addZoneData(rr_wild_nsec_);
+        expected_wild_nsec = rr_wild_nsec_;
+    }
+    {
+        SCOPED_TRACE("Search at wildcard, no data");
+        findTest(Name("a.wild.example.org"), RRType::AAAA(),
+                 wild_ok ? ZoneFinder::NXRRSET : ZoneFinder::NXDOMAIN, true,
+                 wild_ok ? expected_wild_nsec : expected_wild_nsec,
+                 wild_expected_flags, NULL, find_options);
+    }
+}
+
+TEST_F(InMemoryZoneFinderTest, wildcard) {
+    // Normal case
+    wildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, wildcardDisabledWithNSEC) {
+    // Wildcard is disabled.  In practice, this is used as part of query
+    // processing for an NSEC-signed zone, so we test that case specifically.
+    wildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED, ZoneFinder::NO_WILDCARD);
+}
+
+TEST_F(InMemoryZoneFinderTest, wildcardDisabledWithoutNSEC) {
+    // Similar to the previous once, but check the behavior for a non signed
+    // zone just in case.
+    wildcardCheck(ZoneFinder::RESULT_DEFAULT, ZoneFinder::NO_WILDCARD);
+}
+
+/*
+ * Test that we don't match a wildcard if we get under delegation.
+ * By 4.3.3 of RFC1034:
+ * "Wildcard RRs do not apply:
+ *   - When the query is in another zone.  That is, delegation cancels
+ *     the wildcard defaults."
+ */
+TEST_F(InMemoryZoneFinderTest, delegatedWildcard) {
+    addZoneData(rr_child_wild_);
+    addZoneData(rr_child_ns_);
+
+    {
+        SCOPED_TRACE("Looking under delegation point");
+        findTest(Name("a.child.example.org"), RRType::A(),
+                 ZoneFinder::DELEGATION, true, rr_child_ns_);
+    }
+
+    {
+        SCOPED_TRACE("Looking under delegation point in GLUE_OK mode");
+        findTest(Name("a.child.example.org"), RRType::A(),
+                 ZoneFinder::DELEGATION, true, rr_child_ns_,
+                 ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
+    }
+}
+
+// Tests combination of wildcard and ANY.
+void
+InMemoryZoneFinderTest::anyWildcardCheck(
+    ZoneFinder::FindResultFlags expected_flags)
+{
+    addZoneData(rr_wild_);
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        addZoneData(rr_nsec3_);
+    }
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+        addZoneData(rr_nsec_);
+    }
+
+    vector<ConstRRsetPtr> expected_sets;
+
+    // First try directly the name (normal match)
+    {
+        SCOPED_TRACE("Asking directly for *");
+        expected_sets.push_back(rr_wild_);
+        findAllTest(Name("*.wild.example.org"), ZoneFinder::SUCCESS,
+                    expected_sets);
+    }
+
+    // Then a wildcard match
+    {
+        SCOPED_TRACE("Asking in the wild way");
+        expected_sets.clear();
+        RRsetPtr expected(new RRset(Name("a.wild.example.org"),
+                                    rr_wild_->getClass(), rr_wild_->getType(),
+                                    rr_wild_->getTTL()));
+        expected->addRdata(rr_wild_->getRdataIterator()->getCurrent());
+        expected_sets.push_back(expected);
+        findAllTest(Name("a.wild.example.org"), ZoneFinder::SUCCESS,
+                    expected_sets,
+                    ZoneFinder::RESULT_WILDCARD | expected_flags);
+    }
+}
+
+TEST_F(InMemoryZoneFinderTest, anyWildcard) {
+    anyWildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, anyWildcardNSEC3) {
+    anyWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, anyWildcardNSEC) {
+    anyWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+}
+
+// Test there's nothing in the wildcard in the middle if we load
+// wild.*.foo.example.org.
+void
+InMemoryZoneFinderTest::emptyWildcardCheck(
+    ZoneFinder::FindResultFlags expected_flags)
+{
+    /*
+     *            example.org.
+     *                foo
+     *                 *
+     *               wild
+     */
+    addZoneData(rr_emptywild_);
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        addZoneData(rr_nsec3_);
+    }
+    if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0) {
+        addZoneData(rr_nsec_);
+    }
+
+    {
+        SCOPED_TRACE("Asking for the original record under wildcard");
+        findTest(Name("wild.*.foo.example.org"), RRType::A(),
+                 ZoneFinder::SUCCESS, true, rr_emptywild_);
+    }
+
+    {
+        SCOPED_TRACE("Asking for A record");
+        findTest(Name("a.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+                 true, ConstRRsetPtr(),
+                 ZoneFinder::RESULT_WILDCARD | expected_flags);
+        findTest(Name("*.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+                 true, ConstRRsetPtr(), expected_flags);
+        findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+                 true, ConstRRsetPtr(), expected_flags);
+    }
+
+    {
+        SCOPED_TRACE("Asking for ANY record");
+        findAllTest(Name("*.foo.example.org"), ZoneFinder::NXRRSET,
+                    vector<ConstRRsetPtr>(), expected_flags);
+
+        findAllTest(Name("a.foo.example.org"), ZoneFinder::NXRRSET,
+                    vector<ConstRRsetPtr>(),
+                    ZoneFinder::RESULT_WILDCARD | expected_flags);
+    }
+
+    {
+        SCOPED_TRACE("Asking on the non-terminal");
+        findTest(Name("wild.bar.foo.example.org"), RRType::A(),
+                 ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+                 ZoneFinder::RESULT_WILDCARD | expected_flags);
+    }
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
+    emptyWildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyWildcardNSEC3) {
+    emptyWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyWildcardNSEC) {
+    emptyWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+}
+
+// Same as emptyWildcard, but with multiple * in the path.
+TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
+    addZoneData(rr_nested_emptywild_);
+
+    {
+        SCOPED_TRACE("Asking for the original record under wildcards");
+        findTest(Name("wild.*.foo.*.bar.example.org"), RRType::A(),
+            ZoneFinder::SUCCESS, true, rr_nested_emptywild_);
+    }
+
+    {
+        SCOPED_TRACE("Matching wildcard against empty nonterminal");
+
+        const char* names[] = {
+            "baz.foo.*.bar.example.org",
+            "baz.foo.baz.bar.example.org",
+            "*.foo.baz.bar.example.org",
+            NULL
+        };
+
+        for (const char** name = names; *name != NULL; ++ name) {
+            SCOPED_TRACE(string("Node ") + *name);
+            findTest(Name(*name), RRType::A(), ZoneFinder::NXRRSET, true,
+                     ConstRRsetPtr(), ZoneFinder::RESULT_WILDCARD);
+        }
+    }
+
+    // Domains to test
+    const char* names[] = {
+        "*.foo.*.bar.example.org",
+        "foo.*.bar.example.org",
+        "*.bar.example.org",
+        "bar.example.org",
+        NULL
+    };
+
+    {
+        SCOPED_TRACE("Asking directly for A on parent nodes");
+
+        for (const char** name = names; *name != NULL; ++ name) {
+            SCOPED_TRACE(string("Node ") + *name);
+            findTest(Name(*name), RRType::A(), ZoneFinder::NXRRSET);
+        }
+    }
+
+    {
+        SCOPED_TRACE("Asking for ANY on parent nodes");
+
+        for (const char** name = names; *name != NULL; ++ name) {
+            SCOPED_TRACE(string("Node ") + *name);
+
+            findAllTest(Name(*name), ZoneFinder::NXRRSET,
+                        vector<ConstRRsetPtr>());
+        }
+    }
+}
+
+// We run this part twice from the below test, in two slightly different
+// situations
+void
+InMemoryZoneFinderTest::doCancelWildcardCheck(
+    ZoneFinder::FindResultFlags expected_flags,
+    ZoneFinder::FindOptions find_options)
+{
+    // These should be canceled
+    {
+        SCOPED_TRACE("Canceled under foo.wild.example.org");
+
+        // For an NSEC-signed zone with DNSSEC requested, the covering NSEC
+        // should be returned.  The expected NSEC is actually just the only
+        // NSEC in the test data, but in this context it doesn't matter;
+        // it's sufficient just to check any NSEC is returned (or not).
+        ConstRRsetPtr expected_nsec; // by default it's NULL
+        if ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED) != 0 &&
+            (find_options & ZoneFinder::FIND_DNSSEC)) {
+            expected_nsec = rr_nsec_;
+        }
+
+        findTest(Name("aaa.foo.wild.example.org"), RRType::A(),
+                 ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+                 NULL, find_options);
+        findTest(Name("zzz.foo.wild.example.org"), RRType::A(),
+                 ZoneFinder::NXDOMAIN, true, expected_nsec, expected_flags,
+                 NULL, find_options);
+    }
+
+    // This is existing, non-wildcard domain, shouldn't wildcard at all
+    {
+        SCOPED_TRACE("Existing domain under foo.wild.example.org");
+        findTest(Name("bar.foo.wild.example.org"), RRType::A(),
+                 ZoneFinder::SUCCESS, true, rr_not_wild_);
+    }
+
+    // These should be caught by the wildcard
+    {
+        SCOPED_TRACE("Neighbor wildcards to foo.wild.example.org");
+
+        const char* names[] = {
+            "aaa.bbb.wild.example.org",
+            "aaa.zzz.wild.example.org",
+            "zzz.wild.example.org",
+            NULL
+        };
+
+        for (const char** name = names; *name != NULL; ++ name) {
+            SCOPED_TRACE(string("Node ") + *name);
+
+            findTest(Name(*name), RRType::A(), ZoneFinder::SUCCESS, false,
+                     rr_wild_,
+                     ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+                     ZoneFinder::FIND_DEFAULT, true);
+        }
+    }
+
+    // This shouldn't be wildcarded, it's an existing domain
+    {
+        SCOPED_TRACE("The foo.wild.example.org itself");
+        findTest(Name("foo.wild.example.org"), RRType::A(),
+                 ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+    }
+}
+
+/*
+ * This tests that if there's a name between the wildcard domain and the
+ * searched one, it will not trigger wildcard, for example, if we have
+ * *.wild.example.org and bar.foo.wild.example.org, then we know
+ * foo.wild.example.org exists and is not wildcard. Therefore, search for
+ * aaa.foo.wild.example.org should return NXDOMAIN.
+ *
+ * Tests few cases "around" the canceled wildcard match, to see something that
+ * shouldn't be canceled isn't.
+ */
+TEST_F(InMemoryZoneFinderTest, cancelWildcard) {
+    addZoneData(rr_wild_);
+    addZoneData(rr_not_wild_);
+
+    {
+        SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
+        doCancelWildcardCheck();
+    }
+
+    // Try putting another one under foo.wild....
+    // The result should be the same but it will be done in another way in the
+    // code, because the foo.wild.example.org will exist in the tree.
+    addZoneData(rr_not_wild_another_);
+    {
+        SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
+        doCancelWildcardCheck();
+    }
+}
+
+// Same tests as cancelWildcard for NSEC3-signed zone
+TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC3) {
+    addZoneData(rr_wild_);
+    addZoneData(rr_not_wild_);
+    addZoneData(rr_nsec3_);
+
+    {
+        SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
+        doCancelWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+    }
+    addZoneData(rr_not_wild_another_);
+    {
+        SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
+        doCancelWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+    }
+}
+
+// Same tests as cancelWildcard for NSEC-signed zone.  Check both cases with
+// or without FIND_DNSSEC option.  NSEC should be returned only when the option
+// is given.
+TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC) {
+    addZoneData(rr_wild_);
+    addZoneData(rr_not_wild_);
+    addZoneData(rr_nsec_);
+
+    {
+        SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
+        doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED,
+                              ZoneFinder::FIND_DNSSEC);
+        doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+    }
+    addZoneData(rr_not_wild_another_);
+    {
+        SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
+        doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED,
+                              ZoneFinder::FIND_DNSSEC);
+        doCancelWildcardCheck(ZoneFinder::RESULT_NSEC_SIGNED);
+    }
+}
+
+
+// DISABLED: nsec3 will be re-added in #2118
+TEST_F(InMemoryZoneFinderTest, DISABLED_findNSEC3) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
+    // Add a few NSEC3 records:
+    // apex (example.org.): hash=0P..
+    // ns1.example.org:     hash=2T..
+    // w.example.org:       hash=01..
+    // zzz.example.org:     hash=R5..
+    const string apex_nsec3_text = string(apex_hash) + ".example.org." +
+        string(nsec3_common);
+    addZoneData(textToRRset(apex_nsec3_text));
+    const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+        string(nsec3_common);
+    addZoneData(textToRRset(ns1_nsec3_text));
+    const string w_nsec3_text = string(w_hash) + ".example.org." +
+        string(nsec3_common);
+    addZoneData(textToRRset(w_nsec3_text));
+    const string zzz_nsec3_text = string(zzz_hash) + ".example.org." +
+        string(nsec3_common);
+    addZoneData(textToRRset(zzz_nsec3_text));
+
+    performNSEC3Test(zone_finder_);
+}
+
+// DISABLED: NSEC3 will be re-added in #2218
+TEST_F(InMemoryZoneFinderTest, DISABLED_findNSEC3ForBadZone) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
+    // If the zone has nothing about NSEC3 (neither NSEC3 or NSEC3PARAM),
+    // findNSEC3() should be rejected.
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+                 DataSourceError);
+
+    // Only having NSEC3PARAM isn't enough
+    addZoneData(textToRRset("example.org. 300 IN NSEC3PARAM "
+                            "1 0 12 aabbccdd"));
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+                 DataSourceError);
+
+    // Unless NSEC3 for apex is added the result in the recursive mode
+    // is guaranteed.
+    const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+        string(nsec3_common);
+    addZoneData(textToRRset(ns1_nsec3_text));
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+                 DataSourceError);
+}
+
+}
diff --git a/src/lib/datasrc/memory/treenode_rrset.h b/src/lib/datasrc/memory/treenode_rrset.h
index 3f4d6d0..a5f48bf 100644
--- a/src/lib/datasrc/memory/treenode_rrset.h
+++ b/src/lib/datasrc/memory/treenode_rrset.h
@@ -260,6 +260,8 @@ private:
     mutable dns::RRTTL* ttl_;
 };
 
+typedef boost::shared_ptr<TreeNodeRRset> TreeNodeRRsetPtr;
+
 } // namespace memory
 } // namespace datasrc
 } // namespace isc
diff --git a/src/lib/datasrc/memory/zone_finder.cc b/src/lib/datasrc/memory/zone_finder.cc
new file mode 100644
index 0000000..75d3187
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_finder.cc
@@ -0,0 +1,600 @@
+// 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 <datasrc/memory/zone_finder.h>
+#include <datasrc/memory/domaintree.h>
+#include <datasrc/memory/treenode_rrset.h>
+
+#include <datasrc/zone.h>
+#include <datasrc/data_source.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+#include <datasrc/logger.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+using namespace isc::datasrc;
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+namespace {
+/// Creates a TreeNodeRRsetPtr for the given RdataSet at the given Node, for
+/// the given RRClass
+///
+/// We should probably have some pool so these  do not need to be allocated
+/// dynamically.
+///
+/// \param node The ZoneNode found by the find() calls
+/// \param rdataset The RdataSet to create the RRsetPtr for
+/// \param rrclass The RRClass as passed by the client
+/// \param realname If given, the TreeNodeRRset is created with this name
+///                 (e.g. for wildcard substitution)
+///
+/// Returns an empty TreeNodeRRsetPtr if node is NULL or if rdataset is NULL.
+TreeNodeRRsetPtr
+createTreeNodeRRset(const ZoneNode* node,
+                    const RdataSet* rdataset,
+                    const RRClass& rrclass,
+                    const Name* realname = NULL)
+{
+    if (node != NULL && rdataset != NULL) {
+        if (realname != NULL) {
+            return TreeNodeRRsetPtr(new TreeNodeRRset(*realname, rrclass, node,
+                                                      rdataset, true));
+        } else {
+            return TreeNodeRRsetPtr(new TreeNodeRRset(rrclass, node,
+                                                      rdataset, true));
+        }
+    } else {
+        return TreeNodeRRsetPtr();
+    }
+}
+
+/// Maintain intermediate data specific to the search context used in
+/// \c find().
+///
+/// It will be passed to \c cutCallback() (see below) and record a possible
+/// zone cut node and related RRset (normally NS or DNAME).
+struct FindState {
+    FindState(bool glue_ok) :
+        zonecut_node_(NULL),
+        dname_node_(NULL),
+        rrset_(NULL),
+        glue_ok_(glue_ok)
+    {}
+
+    // These will be set to a domain node of the highest delegation point,
+    // if any.  In fact, we could use a single variable instead of both.
+    // But then we would need to distinquish these two cases by something
+    // else and it seemed little more confusing when this was written.
+    const ZoneNode* zonecut_node_;
+    const ZoneNode* dname_node_;
+
+    // Delegation RRset (NS or DNAME), if found.
+    const RdataSet* rrset_;
+
+    // Whether to continue search below a delegation point.
+    // Set at construction time.
+    const bool glue_ok_;
+};
+
+// A callback called from possible zone cut nodes and nodes with DNAME.
+// This will be passed from findNode() to \c RBTree::find().
+bool cutCallback(const ZoneNode& node, FindState* state) {
+    // We need to look for DNAME first, there's allowed case where
+    // DNAME and NS coexist in the apex. DNAME is the one to notice,
+    // the NS is authoritative, not delegation (corner case explicitly
+    // allowed by section 3 of 2672)
+    const RdataSet* found_dname = RdataSet::find(node.getData(),
+                                                 RRType::DNAME());
+
+    if (found_dname != NULL) {
+        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_DNAME_ENCOUNTERED);
+        state->dname_node_ = &node;
+        state->rrset_ = found_dname;
+        return (true);
+    }
+
+    // Look for NS
+    const RdataSet* found_ns = RdataSet::find(node.getData(), RRType::NS());
+    if (found_ns != NULL) {
+        // We perform callback check only for the highest zone cut in the
+        // rare case of nested zone cuts.
+        if (state->zonecut_node_ != NULL) {
+            return (false);
+        }
+
+        LOG_DEBUG(logger, DBG_TRACE_DETAILED, DATASRC_MEM_NS_ENCOUNTERED);
+
+        // BIND 9 checks if this node is not the origin.  That's probably
+        // because it can support multiple versions for dynamic updates
+        // and IXFR, and it's possible that the callback is called at
+        // the apex and the DNAME doesn't exist for a particular version.
+        // It cannot happen for us (at least for now), so we don't do
+        // that check.
+        state->zonecut_node_ = &node;
+        state->rrset_ = found_ns;
+
+        // Unless glue is allowed the search stops here, so we return
+        // false; otherwise return true to continue the search.
+        return (!state->glue_ok_);
+    }
+
+    // This case should not happen because we enable callback only
+    // when we add an RR searched for above.
+    assert(0);
+    // This is here to avoid warning (therefore compilation error)
+    // in case assert is turned off. Otherwise we could get "Control
+    // reached end of non-void function".
+    return (false);
+}
+
+// convenience function to fill in the final details
+//
+// Set up ZoneFinderResultContext object as a return value of find(),
+// taking into account wildcard matches and DNSSEC information.  We set
+// the NSEC/NSEC3 flag when applicable regardless of the find option; the
+// caller would simply ignore these when they didn't request DNSSEC
+// related results.
+//
+// Also performs the conversion of node + RdataSet into a TreeNodeRRsetPtr
+//
+// if wild is true, the RESULT_WILDCARD flag will be set.
+// If qname is not NULL, this is the query name, to be used in wildcard
+// substitution instead of the Node's name).
+isc::datasrc::memory::ZoneFinderResultContext
+createFindResult(const RRClass& rrclass,
+                 const ZoneData& zone_data,
+                 ZoneFinder::Result code,
+                 const RdataSet* rrset,
+                 const ZoneNode* node,
+                 bool wild = false,
+                 const Name* qname = NULL) {
+    ZoneFinder::FindResultFlags flags = ZoneFinder::RESULT_DEFAULT;
+    const Name* rename = NULL;
+
+    if (wild) {
+        flags = flags | ZoneFinder::RESULT_WILDCARD;
+        // only use the rename qname if wild is true
+        rename = qname;
+    }
+    if (code == ZoneFinder::NXRRSET || code == ZoneFinder::NXDOMAIN || wild) {
+        if (zone_data.isNSEC3Signed()) {
+            flags = flags | ZoneFinder::RESULT_NSEC3_SIGNED;
+        } else if (zone_data.isSigned()) {
+            flags = flags | ZoneFinder::RESULT_NSEC_SIGNED;
+        }
+    }
+
+    return (ZoneFinderResultContext(code, createTreeNodeRRset(node, rrset,
+                                                              rrclass, rename),
+                                    flags, node));
+}
+
+// A helper function for NSEC-signed zones.  It searches the zone for
+// the "closest" NSEC corresponding to the search context stored in
+// node_path (it should contain sufficient information to identify the
+// previous name of the query name in the zone).  In some cases the
+// immediate closest name may not have NSEC (when it's under a zone cut
+// for glue records, or even when the zone is partly broken), so this
+// method continues the search until it finds a name that has NSEC,
+// and returns the one found first.  Due to the prerequisite (see below),
+// it should always succeed.
+//
+// node_path must store valid search context (in practice, it's expected
+// to be set by findNode()); otherwise the underlying RBTree implementation
+// throws.
+//
+// If the zone is not considered NSEC-signed or DNSSEC records were not
+// required in the original search context (specified in options), this
+// method doesn't bother to find NSEC, and simply returns NULL.  So, by
+// definition of "NSEC-signed", when it really tries to find an NSEC it
+// should succeed; there should be one at least at the zone origin.
+const RdataSet*
+getClosestNSEC(const ZoneData& zone_data,
+               ZoneChain& node_path,
+               const ZoneNode** nsec_node,
+               ZoneFinder::FindOptions options)
+{
+    if (!zone_data.isSigned() ||
+        (options & ZoneFinder::FIND_DNSSEC) == 0 ||
+        zone_data.isNSEC3Signed()) {
+        return (NULL);
+    }
+
+    const ZoneNode* prev_node;
+    while ((prev_node = zone_data.getZoneTree().previousNode(node_path))
+           != NULL) {
+        if (!prev_node->isEmpty()) {
+            const RdataSet* found =
+                RdataSet::find(prev_node->getData(), RRType::NSEC());
+            if (found != NULL) {
+                *nsec_node = prev_node;
+                return (found);
+            }
+        }
+    }
+    // This must be impossible and should be an internal bug.
+    // See the description at the method declaration.
+    assert(false);
+    // Even though there is an assert here, strict compilers
+    // will still need some return value.
+    return (NULL);
+}
+
+// A helper function for the NXRRSET case in find().  If the zone is
+// NSEC-signed and DNSSEC records are requested, try to find NSEC
+// on the given node, and return it if found; return NULL for all other
+// cases.
+const RdataSet*
+getNSECForNXRRSET(const ZoneData& zone_data,
+                  ZoneFinder::FindOptions options,
+                  const ZoneNode* node)
+{
+    if (zone_data.isSigned() &&
+        !zone_data.isNSEC3Signed() &&
+        (options & ZoneFinder::FIND_DNSSEC) != 0) {
+        const RdataSet* found = RdataSet::find(node->getData(),
+                                               RRType::NSEC());
+        if (found != NULL) {
+            return (found);
+        }
+    }
+    return (NULL);
+}
+
+// Structure to hold result data of the findNode() call
+class FindNodeResult {
+public:
+    // Bitwise flags to represent supplemental information of the
+    // search result:
+    // Search resulted in a wildcard match.
+    static const unsigned int FIND_WILDCARD = 1;
+    // Search encountered a zone cut due to NS but continued to look for
+    // a glue.
+    static const unsigned int FIND_ZONECUT = 2;
+
+    FindNodeResult(ZoneFinder::Result code_param,
+                   const ZoneNode* node_param,
+                   const RdataSet* rrset_param,
+                   unsigned int flags_param = 0) :
+        code(code_param),
+        node(node_param),
+        rrset(rrset_param),
+        flags(flags_param)
+    {}
+    const ZoneFinder::Result code;
+    const ZoneNode* node;
+    const RdataSet* rrset;
+    const unsigned int flags;
+};
+
+// Implementation notes: this method identifies an ZoneNode that best matches
+// the give name in terms of DNS query handling.  In many cases,
+// DomainTree::find() will result in EXACTMATCH or PARTIALMATCH (note that
+// the given name is generally expected to be contained in the zone, so
+// even if it doesn't exist, it should at least match the zone origin).
+// If it finds an exact match, that's obviously the best one.  The partial
+// match case is more complicated.
+//
+// We first need to consider the case where search hits a delegation point,
+// either due to NS or DNAME.  They are indicated as either dname_node_ or
+// zonecut_node_ being non NULL.  Usually at most one of them will be
+// something else than NULL (it might happen both are NULL, in which case we
+// consider it NOT FOUND). There's one corner case when both might be
+// something else than NULL and it is in case there's a DNAME under a zone
+// cut and we search in glue OK mode ‒ in that case we don't stop on the
+// domain with NS and ignore it for the answer, but it gets set anyway. Then
+// we find the DNAME and we need to act by it, therefore we first check for
+// DNAME and then for NS. In all other cases it doesn't matter, as at least
+// one of them is NULL.
+//
+// Next, we need to check if the ZoneTree search stopped at a node for a
+// subdomain of the search name (so the comparison result that stopped the
+// search is "SUPERDOMAIN"), it means the stopping node is an empty
+// non-terminal node.  In this case the search name is considered to exist
+// but no data should be found there.
+//
+// If none of above is the case, we then consider whether there's a matching
+// wildcard.  DomainTree::find() records the node if it encounters a
+// "wildcarding" node, i.e., the immediate ancestor of a wildcard name
+// (e.g., wild.example.com for *.wild.example.com), and returns it if it
+// doesn't find any node that better matches the query name.  In this case
+// we'll check if there's indeed a wildcard below the wildcarding node.
+//
+// Note, first, that the wildcard is checked after the empty
+// non-terminal domain case above, because if that one triggers, it
+// means we should not match according to 4.3.3 of RFC 1034 (the query
+// name is known to exist).
+//
+// Before we try to find a wildcard, we should check whether there's
+// an existing node that would cancel the wildcard match.  If
+// DomainTree::find() stopped at a node which has a common ancestor
+// with the query name, it might mean we are comparing with a
+// non-wildcard node. In that case, we check which part is common. If
+// we have something in common that lives below the node we got (the
+// one above *), then we should cancel the match according to section
+// 4.3.3 of RFC 1034 (as the name between the wildcard domain and the
+// query name is known to exist).
+//
+// If there's no node below the wildcarding node that shares a common ancestor
+// of the query name, we can conclude the wildcard is the best match.
+// We'll then identify the wildcard node via an incremental search.  Note that
+// there's no possibility that the query name is at an empty non terminal
+// node below the wildcarding node at this stage; that case should have been
+// caught above.
+//
+// If none of the above succeeds, we conclude the name doesn't exist in
+// the zone, and throw an OutOfZone exception.
+FindNodeResult findNode(const ZoneData& zone_data,
+                        const Name& name,
+                        ZoneChain& node_path,
+                        ZoneFinder::FindOptions options)
+{
+    ZoneNode* node = NULL;
+    FindState state((options & ZoneFinder::FIND_GLUE_OK) != 0);
+
+    const ZoneTree& tree(zone_data.getZoneTree());
+    ZoneTree::Result result = tree.find(isc::dns::LabelSequence(name),
+                                        &node, node_path, cutCallback,
+                                        &state);
+    const unsigned int zonecut_flag =
+        (state.zonecut_node_ != NULL) ? FindNodeResult::FIND_ZONECUT : 0;
+    if (result == ZoneTree::EXACTMATCH) {
+        return (FindNodeResult(ZoneFinder::SUCCESS, node, state.rrset_,
+                               zonecut_flag));
+    } else if (result == ZoneTree::PARTIALMATCH) {
+        assert(node != NULL);
+        if (state.dname_node_ != NULL) { // DNAME
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DNAME_FOUND).
+                arg(state.dname_node_->getName());
+            return (FindNodeResult(ZoneFinder::DNAME, state.dname_node_,
+                                   state.rrset_));
+        }
+        if (state.zonecut_node_ != NULL) { // DELEGATION due to NS
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
+                arg(state.zonecut_node_->getName());
+            return (FindNodeResult(ZoneFinder::DELEGATION,
+                                   state.zonecut_node_,
+                                   state.rrset_));
+        }
+        if (node_path.getLastComparisonResult().getRelation() ==
+            NameComparisonResult::SUPERDOMAIN) { // empty node, so NXRRSET
+            LOG_DEBUG(logger, DBG_TRACE_DATA,
+                      DATASRC_MEM_SUPER_STOP).arg(name);
+            const ZoneNode* nsec_node;
+            const RdataSet* nsec_rds = getClosestNSEC(zone_data,
+                                                      node_path,
+                                                      &nsec_node,
+                                                      options);
+            return (FindNodeResult(ZoneFinder::NXRRSET, nsec_node,
+                                   nsec_rds));
+        }
+        // Nothing really matched.
+
+        // May be a wildcard, but check only if not disabled
+        if (node->getFlag(ZoneData::WILDCARD_NODE) &&
+            (options & ZoneFinder::NO_WILDCARD) == 0) {
+            if (node_path.getLastComparisonResult().getRelation() ==
+                NameComparisonResult::COMMONANCESTOR) {
+                // This means, e.g., we have *.wild.example and
+                // bar.foo.wild.example and are looking for
+                // baz.foo.wild.example. The common ancestor, foo.wild.example,
+                // should cancel wildcard.  Treat it as NXDOMAIN.
+                LOG_DEBUG(logger, DBG_TRACE_DATA,
+                          DATASRC_MEM_WILDCARD_CANCEL).arg(name);
+                    const ZoneNode* nsec_node;
+                    const RdataSet* nsec_rds = getClosestNSEC(zone_data,
+                                                              node_path,
+                                                              &nsec_node,
+                                                              options);
+                    return (FindNodeResult(ZoneFinder::NXDOMAIN, nsec_node,
+                                           nsec_rds));
+            }
+            uint8_t ls_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+
+            // Create the wildcard name (i.e. take "*" and extend it
+            // with all node labels down to the wildcard node
+            LabelSequence wildcard_ls(LabelSequence::WILDCARD(), ls_buf);
+            const ZoneNode* extend_with = node;
+            while (extend_with != NULL) {
+                wildcard_ls.extend(extend_with->getLabels(), ls_buf);
+                extend_with = extend_with->getUpperNode();
+            }
+
+            // Clear the node_path so that we don't keep incorrect (NSEC)
+            // context
+            node_path.clear();
+            ZoneTree::Result result = tree.find(LabelSequence(wildcard_ls),
+                                                &node, node_path, cutCallback,
+                                                &state);
+            // Otherwise, why would the domain_flag::WILD be there if
+            // there was no wildcard under it?
+            assert(result == ZoneTree::EXACTMATCH);
+            return (FindNodeResult(ZoneFinder::SUCCESS, node, state.rrset_,
+                        FindNodeResult::FIND_WILDCARD | zonecut_flag));
+        }
+
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).arg(name);
+        const ZoneNode* nsec_node;
+        const RdataSet* nsec_rds = getClosestNSEC(zone_data, node_path,
+                                                  &nsec_node, options);
+        return (FindNodeResult(ZoneFinder::NXDOMAIN, nsec_node, nsec_rds));
+    } else {
+        // If the name is neither an exact or partial match, it is
+        // out of bailiwick, which is considered an error.
+        isc_throw(OutOfZone, name.toText() << " not in " <<
+                             zone_data.getOriginNode()->getName());
+    }
+}
+
+} // end anonymous namespace
+
+// Specialization of the ZoneFinder::Context for the in-memory finder.
+class InMemoryZoneFinder::Context : public ZoneFinder::Context {
+public:
+    /// \brief Constructor.
+    ///
+    /// Note that we don't have a specific constructor for the findAll() case.
+    /// For (successful) type ANY query, found_node points to the
+    /// corresponding RB node, which is recorded within this specialized
+    /// context.
+    Context(ZoneFinder& finder, ZoneFinder::FindOptions options,
+            ZoneFinderResultContext result) :
+        ZoneFinder::Context(finder, options,
+                            ResultContext(result.code, result.rrset,
+                                          result.flags)),
+        rrset_(result.rrset), found_node_(result.found_node)
+    {}
+
+private:
+    const TreeNodeRRsetPtr rrset_;
+    const ZoneNode* const found_node_;
+};
+
+boost::shared_ptr<ZoneFinder::Context>
+InMemoryZoneFinder::find(const isc::dns::Name& name,
+                const isc::dns::RRType& type,
+                const FindOptions options)
+{
+    return ZoneFinderContextPtr(new Context(*this, options,
+                                            find_internal(name,
+                                                          type,
+                                                          NULL,
+                                                          options)));
+}
+
+boost::shared_ptr<ZoneFinder::Context>
+InMemoryZoneFinder::findAll(const isc::dns::Name& name,
+        std::vector<isc::dns::ConstRRsetPtr>& target,
+        const FindOptions options)
+{
+    return ZoneFinderContextPtr(new Context(*this, options,
+                                            find_internal(name,
+                                                          RRType::ANY(),
+                                                          &target,
+                                                          options)));
+}
+
+ZoneFinderResultContext
+InMemoryZoneFinder::find_internal(const isc::dns::Name& name,
+                                  const isc::dns::RRType& type,
+                                  std::vector<ConstRRsetPtr>* target,
+                                  const FindOptions options)
+{
+    // Get the node.  All other cases than an exact match are handled
+    // in findNode().  We simply construct a result structure and return.
+    ZoneChain node_path;
+    const FindNodeResult node_result =
+        findNode(zone_data_, name, node_path, options);
+    if (node_result.code != SUCCESS) {
+        return (createFindResult(rrclass_, zone_data_, node_result.code,
+                                 node_result.rrset, node_result.node));
+    }
+
+    const ZoneNode* node = node_result.node;
+    assert(node != NULL);
+
+    // We've found an exact match, may or may not be a result of wildcard.
+    const bool wild = ((node_result.flags &
+                        FindNodeResult::FIND_WILDCARD) != 0);
+
+    // If there is an exact match but the node is empty, it's equivalent
+    // to NXRRSET.
+    if (node->isEmpty()) {
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
+            arg(name);
+        const ZoneNode* nsec_node;
+        const RdataSet* nsec_rds = getClosestNSEC(zone_data_, node_path,
+                                                  &nsec_node, options);
+        return (createFindResult(rrclass_, zone_data_, NXRRSET,
+                                 nsec_rds,
+                                 nsec_node,
+                                 wild));
+    }
+
+    const RdataSet* found;
+
+    // If the node callback is enabled, this may be a zone cut.  If it
+    // has a NS RR, we should return a delegation, but not in the apex.
+    // There is one exception: the case for DS query, which should always
+    // be considered in-zone lookup.
+    if (node->getFlag(ZoneNode::FLAG_CALLBACK) &&
+            node != zone_data_.getOriginNode() && type != RRType::DS()) {
+        found = RdataSet::find(node->getData(), RRType::NS());
+        if (found != NULL) {
+            LOG_DEBUG(logger, DBG_TRACE_DATA,
+                      DATASRC_MEM_EXACT_DELEGATION).arg(name);
+            return (createFindResult(rrclass_, zone_data_, DELEGATION,
+                                     found, node, wild, &name));
+        }
+    }
+
+    // Handle type any query
+    if (target != NULL && node->getData() != NULL) {
+        // Empty domain will be handled as NXRRSET by normal processing
+        const RdataSet* cur_rds = node->getData();
+        while (cur_rds != NULL) {
+            target->push_back(createTreeNodeRRset(node, cur_rds, rrclass_,
+                                                  &name));
+            cur_rds = cur_rds->getNext();
+        }
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ANY_SUCCESS).
+            arg(name);
+        return (createFindResult(rrclass_, zone_data_, SUCCESS, NULL, node,
+                                 wild, &name));
+    }
+
+    const RdataSet* currds = node->getData();
+    while (currds != NULL) {
+        currds = currds->getNext();
+    }
+    found = RdataSet::find(node->getData(), type);
+    if (found != NULL) {
+        // Good, it is here
+        LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUCCESS).arg(name).
+            arg(type);
+        return (createFindResult(rrclass_, zone_data_, SUCCESS, found, node,
+                                 wild, &name));
+    } else {
+        // Next, try CNAME.
+        found = RdataSet::find(node->getData(), RRType::CNAME());
+        if (found != NULL) {
+
+            LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_CNAME).arg(name);
+            return (createFindResult(rrclass_, zone_data_, CNAME, found, node,
+                                     wild, &name));
+        }
+    }
+    // No exact match or CNAME.  Get NSEC if necessary and return NXRRSET.
+    return (createFindResult(rrclass_, zone_data_, NXRRSET,
+                             getNSECForNXRRSET(zone_data_, options, node),
+                             node, wild, &name));
+}
+
+isc::datasrc::ZoneFinder::FindNSEC3Result
+InMemoryZoneFinder::findNSEC3(const isc::dns::Name& name, bool recursive) {
+    (void)name;
+    (void)recursive;
+    isc_throw(isc::NotImplemented, "not completed yet! please implement me");
+}
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
diff --git a/src/lib/datasrc/memory/zone_finder.h b/src/lib/datasrc/memory/zone_finder.h
new file mode 100644
index 0000000..8f2c687
--- /dev/null
+++ b/src/lib/datasrc/memory/zone_finder.h
@@ -0,0 +1,128 @@
+// 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_ZONE_FINDER_H
+#define DATASRC_MEMORY_ZONE_FINDER_H 1
+
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/treenode_rrset.h>
+
+#include <datasrc/zone.h>
+#include <dns/name.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+
+class ZoneFinderResultContext {
+public:
+    /// \brief Constructor
+    ///
+    /// The first three parameters correspond to those of
+    /// ZoneFinder::ResultContext.  If node is non NULL, it specifies the
+    /// found RBNode in the search.
+    ZoneFinderResultContext(ZoneFinder::Result code_param,
+                            TreeNodeRRsetPtr rrset_param,
+                            ZoneFinder::FindResultFlags flags_param,
+                            const ZoneNode* node) :
+        code(code_param), rrset(rrset_param), flags(flags_param),
+        found_node(node)
+    {}
+
+    const ZoneFinder::Result code;
+    const TreeNodeRRsetPtr rrset;
+    const ZoneFinder::FindResultFlags flags;
+    const ZoneNode* const found_node;
+};
+
+/// A derived zone finder class intended to be used with the memory data
+/// source, using ZoneData for its contents.
+class InMemoryZoneFinder : boost::noncopyable, public ZoneFinder {
+public:
+    /// \brief Constructor.
+    ///
+    /// Since ZoneData does not keep RRClass information, but this
+    /// information is needed in order to construct actual RRsets,
+    /// this needs to be passed here (the datasource client should
+    /// have this information). In the future, this may be replaced
+    /// by some construction to pull TreeNodeRRsets from a pool, but
+    /// currently, these are created dynamically with the given RRclass
+    ///
+    /// \param zone_data The ZoneData containing the zone.
+    /// \param rrclass The RR class of the zone
+    InMemoryZoneFinder(const ZoneData& zone_data,
+                       const isc::dns::RRClass& rrclass) :
+        zone_data_(zone_data),
+        rrclass_(rrclass)
+    {}
+
+    /// \brief Find an RRset in the datasource
+    virtual boost::shared_ptr<ZoneFinder::Context> find(
+        const isc::dns::Name& name,
+        const isc::dns::RRType& type,
+        const FindOptions options = FIND_DEFAULT);
+
+    /// \brief Version of find that returns all types at once
+    ///
+    /// It acts the same as find, just that when the correct node is found,
+    /// all the RRsets are filled into the target parameter instead of being
+    /// returned by the result.
+    virtual boost::shared_ptr<ZoneFinder::Context> findAll(
+        const isc::dns::Name& name,
+        std::vector<isc::dns::ConstRRsetPtr>& target,
+        const FindOptions options = FIND_DEFAULT);
+
+    /// Look for NSEC3 for proving (non)existence of given name.
+    ///
+    /// See documentation in \c Zone.
+    virtual FindNSEC3Result
+    findNSEC3(const isc::dns::Name& name, bool recursive);
+
+    /// \brief Returns the origin of the zone.
+    virtual isc::dns::Name getOrigin() const {
+        return zone_data_.getOriginNode()->getName();
+    }
+
+    /// \brief Returns the RR class of the zone.
+    virtual isc::dns::RRClass getClass() const {
+        return rrclass_;
+    }
+
+
+private:
+    /// \brief In-memory version of finder context.
+    ///
+    /// The implementation (and any specialized interface) is completely local
+    /// to the InMemoryZoneFinder class, so it's defined as private
+    class Context;
+
+    /// Actual implementation for both find() and findAll()
+    ZoneFinderResultContext find_internal(
+        const isc::dns::Name& name,
+        const isc::dns::RRType& type,
+        std::vector<isc::dns::ConstRRsetPtr>* target,
+        const FindOptions options =
+        FIND_DEFAULT);
+
+    const ZoneData& zone_data_;
+    const isc::dns::RRClass& rrclass_;
+};
+
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_ZONE_FINDER_H
diff --git a/src/lib/testutils/dnsmessage_test.cc b/src/lib/testutils/dnsmessage_test.cc
index ec6914d..b166bb6 100644
--- a/src/lib/testutils/dnsmessage_test.cc
+++ b/src/lib/testutils/dnsmessage_test.cc
@@ -91,10 +91,16 @@ matchRdata(const char*, const char*,
 void
 setRRset(RRsetPtr rrset, RRsetPtr* rrsetp) {
     if (*rrsetp) {
-        isc_throw(isc::Unexpected,
-                  "multiple RRsets are given to textToRRset");
+        // may be a sig
+        if (rrset->getType() == RRType::RRSIG()) {
+            (*rrsetp)->addRRsig(rrset);
+        } else {
+            isc_throw(isc::Unexpected,
+                      "multiple RRsets are given to textToRRset");
+        }
+    } else {
+        *rrsetp = rrset;
     }
-    *rrsetp = rrset;
 }
 }
 



More information about the bind10-changes mailing list