Browse Source

[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
Jelte Jansen 12 years ago
parent
commit
9acb02fb27

+ 1 - 2
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

+ 2 - 1
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

+ 3 - 0
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

+ 954 - 0
src/lib/datasrc/memory/tests/zone_finder_unittest.cc

@@ -0,0 +1,954 @@
+// 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 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());
+        }
+    }
+
+    // simplified version of 'loading' data
+    void addZoneData(const ConstRRsetPtr rrset) {
+        ZoneNode* node = NULL;
+
+        if (rrset->getType() == RRType::NSEC3()) {
+            return (addZoneDataNSEC3(rrset));
+        }
+        zone_data_->insertName(mem_sgmt_, rrset->getName(), &node);
+        RdataSet* next_rds = node->getData();
+
+        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* rdataset =
+            RdataSet::create(mem_sgmt_, encoder_, rrset, ConstRRsetPtr());
+        rdataset->next = next_rds;
+        node->setData(rdataset);
+    }
+
+    // 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);
+                        rrsetCheck(answer, convertRRset(find_result->rrset));
+                        if (answer_sig) {
+                            ASSERT_TRUE(find_result->rrset->getRRsig());
+                            rrsetCheck(answer_sig,
+                                       find_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());
+                    }
+                    rrsetCheck(wildanswer, find_result->rrset);
+
+                    // Same for the RRSIG, if any.
+                    if (answer_sig) {
+                        ASSERT_TRUE(find_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, find_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_);
+        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, DISABLED_findNSECEmptyNonterminalWildcard) {
+    // Wildcard case, above actual wildcard
+    findNSECENTCheck(Name("foo.example.org"), rr_nsec_);
+}
+
+TEST_F(InMemoryZoneFinderTest,DISABLED_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);
+}
+
+// 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);
+}
+
+}

+ 2 - 0
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

+ 537 - 0
src/lib/datasrc/memory/zone_finder.cc

@@ -0,0 +1,537 @@
+// 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
+///
+/// Returns an empty TreeNodeRRsetPtr if node is NULL or if rdataset is NULL.
+TreeNodeRRsetPtr
+createTreeNodeRRset(const ZoneNode* node,
+                    const RdataSet* rdataset,
+                    const RRClass& rrclass)
+{
+    if (node != NULL && rdataset != NULL) {
+        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
+//
+isc::datasrc::memory::ZoneFinderResultContext
+createFindResult(const RRClass& rrclass,
+                 const ZoneData& zone_data,
+                 ZoneFinder::Result code,
+                 const RdataSet* rrset,
+                 const ZoneNode* node,
+                 bool wild = false) {
+    ZoneFinder::FindResultFlags flags = ZoneFinder::RESULT_DEFAULT;
+
+    if (wild) {
+        flags = flags | ZoneFinder::RESULT_WILDCARD;
+    }
+    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),
+                                    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.
+//
+// (TODO: check this part when doing #2110)
+// 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));
+        }
+        // TODO: wildcard (see memory_datasrc.cc:480 and ticket #2110)
+        // Nothing really matched.
+        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.
+    // TODO, ticket #2110
+    // 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));
+    }
+
+    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);
+            // TODO: rename argument (wildcards, see #2110)
+            return (createFindResult(rrclass_, zone_data_, DELEGATION,
+                                     found, node));
+        }
+    }
+
+    // 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_));
+            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));
+    }
+
+    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));
+    } 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));
+        }
+    }
+    // No exact match or CNAME.  Get NSEC if necessary and return NXRRSET.
+    return (createFindResult(rrclass_, zone_data_, NXRRSET,
+                             getNSECForNXRRSET(zone_data_, options, node),
+                             node));
+}
+
+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

+ 128 - 0
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