BIND 10 master, updated. 4d266d80beb7e0d8b1f45a4a4bc7cc39a1657486 [master] Merge branch 'trac1576' with fixing conflicts.

BIND 10 source code commits bind10-changes at lists.isc.org
Mon Feb 13 22:00:42 UTC 2012


The branch, master has been updated
       via  4d266d80beb7e0d8b1f45a4a4bc7cc39a1657486 (commit)
       via  540d2a66787b2ff57e20a2c99a84d2875dfbcc54 (commit)
       via  6d675cbff066b5ee3f1c3bde7aab7dfd0ad1482c (commit)
       via  ecd4db4c298f4f1aae038bc0b42837478fe9910b (commit)
       via  0ad08d16ff5100ec01865bf2814481960c79358e (commit)
       via  ef979231a2925c3cb6c079b181c6747005d72baa (commit)
       via  5eaa333fe39ddc02af78ffceba8173497e1352c3 (commit)
       via  82f947e1bc6978f7bde731955b96dc5c68840a6f (commit)
       via  4f23a482e885f3fe3dd104fa43638352f9ff3e7b (commit)
       via  88aa930fd6189c121c784b2175aab519d72ed5ab (commit)
       via  46232cfc39be398a04cfbc4e6fe2e75b41d8eb7c (commit)
       via  a5fa9a0f01113a92f6136f6dfc1716f877cbf07e (commit)
      from  1c0df5ba07b95d2e2b1c678675be16a2a1059593 (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 4d266d80beb7e0d8b1f45a4a4bc7cc39a1657486
Merge: 1c0df5b 540d2a6
Author: JINMEI Tatuya <jinmei at isc.org>
Date:   Mon Feb 13 14:00:28 2012 -0800

    [master] Merge branch 'trac1576' with fixing conflicts.

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

Summary of changes:
 src/lib/datasrc/datasrc_messages.mes               |   24 ++
 src/lib/datasrc/memory_datasrc.cc                  |  109 ++++---
 src/lib/datasrc/memory_datasrc.h                   |   10 -
 src/lib/datasrc/tests/Makefile.am                  |    1 +
 src/lib/datasrc/tests/memory_datasrc_unittest.cc   |  351 +++++++++++++++++---
 .../tests/testdata/rfc5155-example.zone.signed     |   72 ++++
 src/lib/dns/nsec3hash.cc                           |   34 ++-
 src/lib/dns/nsec3hash.h                            |   90 +++++
 src/lib/dns/tests/nsec3hash_unittest.cc            |   79 +++++-
 9 files changed, 665 insertions(+), 105 deletions(-)
 create mode 100644 src/lib/datasrc/tests/testdata/rfc5155-example.zone.signed

-----------------------------------------------------------------------
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index f05ff21..d8ad07b 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -332,6 +332,30 @@ should be followed. The requested domain is an apex of some zone.
 % DATASRC_MEM_FIND find '%1/%2'
 Debug information. A search for the requested RRset is being started.
 
+% DATASRC_MEM_FINDNSEC3 finding NSEC3 for %1, mode %2
+Debug information. A search in an in-memory data source for NSEC3 that
+matches or covers the given name is being started.
+
+% DATASRC_MEM_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
+Debug information. An NSEC3 that covers the given name is found and
+being returned.  The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEM_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned.  When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_MEM_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEM_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space.  When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried).
+
 % DATASRC_MEM_FIND_ZONE looking for zone '%1'
 Debug information. A zone object for this zone is being searched for in the
 in-memory data source.
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 450c8b6..5137727 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -881,57 +881,80 @@ InMemoryZoneFinder::findAll(const Name& name,
 }
 
 ZoneFinder::FindNSEC3Result
-InMemoryZoneFinder::findNSEC3(const Name&, bool) {
-    isc_throw(NotImplemented, "findNSEC3 is not yet implemented for in memory "
-              "data source");
-}
+InMemoryZoneFinder::findNSEC3(const Name& name, bool recursive) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FINDNSEC3).arg(name).
+        arg(recursive ? "recursive" : "non-recursive");
 
-ZoneFinder::FindNSEC3Result
-InMemoryZoneFinder::findNSEC3Tmp(const Name& name, bool recursive) {
     if (!impl_->zone_data_->nsec3_data_) {
-        isc_throw(Unexpected, "findNSEC3 is called for non NSEC3 zone");
+        isc_throw(DataSourceError,
+                  "findNSEC3 attempt for non NSEC3 signed zone: " <<
+                  impl_->origin_ << "/" << impl_->zone_class_);
     }
-    if (recursive) {
-        isc_throw(Unexpected, "recursive mode isn't expected in tests");
+    const NSEC3Map& map = impl_->zone_data_->nsec3_data_->map_;
+    if (map.empty()) {
+        isc_throw(DataSourceError,
+                  "findNSEC3 attempt but zone has no NSEC3 RR: " <<
+                  impl_->origin_ << "/" << impl_->zone_class_);
     }
-
-    // A temporary workaround for testing: convert the original name to
-    // NSEC3-hashed name using hardcoded mapping.
-    string hname_text;
-    if (name == Name("example.org")) {
-        hname_text = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-    } else if (name == Name("www.example.org")) {
-        hname_text = "2S9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-    } else if (name == Name("xxx.example.org")) {
-        hname_text = "Q09MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-    } else if (name == Name("yyy.example.org")) {
-        hname_text = "0A9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-    } else {
-        isc_throw(Unexpected, "unexpected name for NSEC3 test: " << name);
+    const NameComparisonResult cmp_result = name.compare(impl_->origin_);
+    if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
+        cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
+        isc_throw(InvalidParameter, "findNSEC3 attempt for out-of-zone name: "
+                  << name << ", zone: " << impl_->origin_ << "/"
+                  << impl_->zone_class_);
     }
 
-    // Below we assume the map is not empty for simplicity.
-    NSEC3Map::const_iterator found =
-        impl_->zone_data_->nsec3_data_->map_.lower_bound(hname_text);
-    if (found != impl_->zone_data_->nsec3_data_->map_.end() &&
-        found->first == hname_text) {
-        // exact match
-        return (FindNSEC3Result(true, 2, found->second, ConstRRsetPtr()));
-    } else if (found == impl_->zone_data_->nsec3_data_->map_.end() ||
-               found == impl_->zone_data_->nsec3_data_->map_.begin()) {
-        // the search key is "smaller" than the smallest or "larger" than
-        // largest.  In either case "previous" is the largest one.
-        return (FindNSEC3Result(false, 2,
-                                impl_->zone_data_->nsec3_data_->map_.
-                                rbegin()->second, ConstRRsetPtr()));
-    } else {
-        // Otherwise, H(found_domain-1) < given_hash < H(found_domain)
-        // The covering proof is the first one.
-        return (FindNSEC3Result(false, 2, (--found)->second, ConstRRsetPtr()));
+    // Convenient shortcuts
+    const NSEC3Hash& nsec3hash = *impl_->zone_data_->nsec3_data_->hash_;
+    const unsigned int olabels = impl_->origin_.getLabelCount();
+    const unsigned int qlabels = name.getLabelCount();
+
+    ConstRRsetPtr covering_proof; // placeholder of the next closer proof
+    // Examine all names from the query name to the origin name, stripping
+    // the deepest label one by one, until we find a name that has a matching
+    // NSEC3 hash.
+    for (unsigned int labels = qlabels; labels >= olabels; --labels) {
+        const string hlabel = nsec3hash.calculate(
+            labels == qlabels ? name : name.split(qlabels - labels, labels));
+        NSEC3Map::const_iterator found = map.lower_bound(hlabel);
+        LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FINDNSEC3_TRYHASH).
+            arg(name).arg(labels).arg(hlabel);
+
+        // If the given hash is larger than the largest stored hash or
+        // the first label doesn't match the target, identify the "previous"
+        // hash value and remember it as the candidate next closer proof.
+        if (found == map.end() || found->first != hlabel) {
+            // If the given hash is larger or smaller than everything,
+            // the covering proof is the NSEC3 that has the largest hash.
+            // Note that we know the map isn't empty, so rbegin() is
+            // safe.
+            if (found == map.end() || found == map.begin()) {
+                covering_proof = map.rbegin()->second;
+            } else {
+                // Otherwise, H(found_entry-1) < given_hash < H(found_entry).
+                // The covering proof is the first one (and it's valid
+                // because found is neither begin nor end)
+                covering_proof = (--found)->second;
+            }
+            if (!recursive) {   // in non recursive mode, we are done.
+                LOG_DEBUG(logger, DBG_TRACE_BASIC,
+                          DATASRC_MEM_FINDNSEC3_COVER).
+                    arg(name).arg(*covering_proof);
+                return (FindNSEC3Result(false, labels, covering_proof,
+                                        ConstRRsetPtr()));
+            }
+        } else {                // found an exact match.
+                LOG_DEBUG(logger, DBG_TRACE_BASIC,
+                          DATASRC_MEM_FINDNSEC3_MATCH).arg(name).arg(labels).
+                    arg(*found->second);
+            return (FindNSEC3Result(true, labels, found->second,
+                                    covering_proof));
+        }
     }
 
-    // We should have covered all cases.
-    isc_throw(Unexpected, "Impossible NSEC3 search result for " << name);
+    isc_throw(DataSourceError, "recursive findNSEC3 mode didn't stop, likely "
+              "a broken NSEC3 zone: " << impl_->origin_ << "/"
+              << impl_->zone_class_);
 }
 
 result::Result
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index 85fa5cf..b960ab9 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -89,16 +89,6 @@ public:
     virtual FindNSEC3Result
     findNSEC3(const isc::dns::Name& name, bool recursive);
 
-    // A temporary fake version of findNSEC3 for tests
-    //
-    // This method intentionally has the same interface as findNSEC3 but
-    // uses internally hardcoded hash values and offers a limited set
-    // of functionality for the convenience of tests.  This is a temporary
-    // workaround until #1577 is completed.  At that point this method
-    // should be removed.
-    FindNSEC3Result
-    findNSEC3Tmp(const isc::dns::Name& name, bool recursive);
-
     /// \brief Imelementation of the ZoneFinder::findPreviousName method
     ///
     /// This one throws NotImplemented exception, as InMemory doesn't
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 69c7959..163d3d7 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -124,3 +124,4 @@ EXTRA_DIST += testdata/test.sqlite3
 EXTRA_DIST += testdata/test.sqlite3.nodiffs
 EXTRA_DIST += testdata/rwtest.sqlite3
 EXTRA_DIST += testdata/diffs.sqlite3
+EXTRA_DIST += testdata/rfc5155-example.zone.signed
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index b68e9c0..bd5070c 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -22,6 +22,7 @@
 
 #include <dns/masterload.h>
 #include <dns/name.h>
+#include <dns/nsec3hash.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
@@ -275,6 +276,83 @@ setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
     ++it;
 }
 
+ConstRRsetPtr
+textToRRset(const string& text_rrset, const RRClass& rrclass = RRClass::IN()) {
+    stringstream ss(text_rrset);
+    RRsetPtr rrset;
+    vector<RRsetPtr*> rrsets;
+    rrsets.push_back(&rrset);
+    masterLoad(ss, Name::ROOT_NAME(), rrclass,
+               boost::bind(setRRset, _1, rrsets.begin()));
+    return (rrset);
+}
+
+// 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.
+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 Test fixture for the InMemoryZoneFinder class
 class InMemoryZoneFinderTest : public ::testing::Test {
     // A straightforward pair of textual RR(set) and a RRsetPtr variable
@@ -368,6 +446,12 @@ public:
         masterLoad(zone_data_stream, Name::ROOT_NAME(), class_,
                    boost::bind(setRRset, _1, rrsets.begin()));
     }
+
+    ~InMemoryZoneFinderTest() {
+        // Make sure we reset the hash creator to the default
+        setNSEC3HashCreator(NULL);
+    }
+
     // Some data to test with
     const RRClass class_;
     const Name origin_;
@@ -414,6 +498,12 @@ public:
     RRsetPtr rr_not_wild_another_;
     RRsetPtr rr_nsec3_;
 
+    // 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.
      *
@@ -522,18 +612,6 @@ public:
         rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
                     target.begin(), target.end());
     }
-
-    ConstRRsetPtr textToRRset(const string& text_rrset,
-                              const RRClass& rrclass = RRClass::IN()) const
-    {
-        stringstream ss(text_rrset);
-        RRsetPtr rrset;
-        vector<RRsetPtr*> rrsets;
-        rrsets.push_back(&rrset);
-        masterLoad(ss, Name::ROOT_NAME(), rrclass,
-                   boost::bind(setRRset, _1, rrsets.begin()));
-        return (rrset);
-    }
 };
 
 /**
@@ -895,7 +973,7 @@ TEST_F(InMemoryZoneFinderTest, find) {
     findCheck();
 }
 
-TEST_F(InMemoryZoneFinderTest, findNSEC3) {
+TEST_F(InMemoryZoneFinderTest, findNSEC3Signed) {
     findCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
 }
 
@@ -1518,40 +1596,45 @@ const char* const nsec3_common = " 300 IN NSEC3 1 1 12 aabbccdd "
 const char* const nsec3_rrsig_common = " 300 IN RRSIG NSEC3 5 3 3600 "
     "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
 
-// 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 x.y.w.example.org (lower-cased)
-const char* const xrw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
-
 void
-nsec3Check(bool expected_matched, const string& expected_rrsets_txt,
-           const ZoneFinder::FindNSEC3Result& result,
-           bool expected_sig = false)
+findNSEC3Check(bool expected_matched, uint8_t expected_labels,
+               const string& expected_closest,
+               const string& expected_next,
+               const ZoneFinder::FindNSEC3Result& result,
+               bool expected_sig = false)
 {
-    vector<ConstRRsetPtr> actual_rrsets;
     EXPECT_EQ(expected_matched, result.matched);
+    // Convert to int so the error messages would be more readable:
+    EXPECT_EQ(static_cast<int>(expected_labels),
+              static_cast<int>(result.closest_labels));
+
+    vector<ConstRRsetPtr> actual_rrsets;
     ASSERT_TRUE(result.closest_proof);
-    if (expected_sig) {
-        ASSERT_TRUE(result.closest_proof->getRRsig());
-    }
     actual_rrsets.push_back(result.closest_proof);
     if (expected_sig) {
         actual_rrsets.push_back(result.closest_proof->getRRsig());
     }
-    rrsetsCheck(expected_rrsets_txt, actual_rrsets.begin(),
+    rrsetsCheck(expected_closest, actual_rrsets.begin(),
                 actual_rrsets.end());
-}
 
-// In the following tests we use a temporary faked version of findNSEC3
-// as the real version isn't implemented yet (it's a task for #1577).
-// When #1577 is done the tests should be updated using the real version.
-// If we can use fake hash calculator (see #1575), we should be able to
-// just replace findNSEC3Tmp with findNSEC3.
+    actual_rrsets.clear();
+    if (expected_next.empty()) {
+        EXPECT_FALSE(result.next_proof);
+    } else {
+        ASSERT_TRUE(result.next_proof);
+        actual_rrsets.push_back(result.next_proof);
+        if (expected_sig) {
+            actual_rrsets.push_back(result.next_proof->getRRsig());
+        }
+        rrsetsCheck(expected_next, actual_rrsets.begin(),
+                    actual_rrsets.end());
+    }
+}
 
 TEST_F(InMemoryZoneFinderTest, addNSEC3) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
     const string nsec3_text = string(apex_hash) + ".example.org." +
         string(nsec3_common);
     // This name shouldn't be found in the normal domain tree.
@@ -1560,8 +1643,8 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3) {
               zone_finder_.find(Name(string(apex_hash) + ".example.org"),
                                 RRType::NSEC3()).code);
     // Dedicated NSEC3 find should be able to find it.
-    nsec3Check(true, nsec3_text,
-               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+    findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+                   zone_finder_.findNSEC3(Name("example.org"), false));
 
     // This implementation rejects duplicate/update add of the same hash name
     EXPECT_EQ(result::EXIST,
@@ -1569,8 +1652,8 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3) {
                                    string(apex_hash) + ".example.org." +
                                    string(nsec3_common) + " AAAA")));
     // The original NSEC3 should be intact
-    nsec3Check(true, nsec3_text,
-               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+    findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+                   zone_finder_.findNSEC3(Name("example.org"), false));
 
     // NSEC3-like name but of ordinary RR type should go to normal tree.
     const string nonsec3_text = string(apex_hash) + ".example.org. " +
@@ -1582,15 +1665,21 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3) {
 }
 
 TEST_F(InMemoryZoneFinderTest, addNSEC3Lower) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
     // Similar to the previous case, but NSEC3 owner name is lower-cased.
     const string nsec3_text = string(apex_hash_lower) + ".example.org." +
         string(nsec3_common);
     EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_text)));
-    nsec3Check(true, nsec3_text,
-               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+    findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+                   zone_finder_.findNSEC3(Name("example.org"), false));
 }
 
 TEST_F(InMemoryZoneFinderTest, addNSEC3Ordering) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
     // Check that the internal storage ensures comparison based on the NSEC3
     // semantics, regardless of the add order or the letter-case of hash.
 
@@ -1599,7 +1688,7 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3Ordering) {
         string(nsec3_common);
     const string middle = string(ns1_hash) + ".example.org." +
         string(nsec3_common);
-    const string largest = string(xrw_hash) + ".example.org." +
+    const string largest = string(xyw_hash) + ".example.org." +
         string(nsec3_common);
     zone_finder_.add(textToRRset(smallest));
     zone_finder_.add(textToRRset(largest));
@@ -1607,15 +1696,15 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3Ordering) {
 
     // Then look for NSEC3 that covers a name whose hash is "2S.."
     // The covering NSEC3 should be "0P.."
-    nsec3Check(false, smallest,
-               zone_finder_.findNSEC3Tmp(Name("www.example.org"), false));
+    findNSEC3Check(false, 4, smallest, "",
+                   zone_finder_.findNSEC3(Name("www.example.org"), false));
 
     // Look for NSEC3 that covers names whose hash are "Q0.." and "0A.."
     // The covering NSEC3 should be "2v.." in both cases
-    nsec3Check(false, largest,
-               zone_finder_.findNSEC3Tmp(Name("xxx.example.org"), false));
-    nsec3Check(false, largest,
-               zone_finder_.findNSEC3Tmp(Name("yyy.example.org"), false));
+    findNSEC3Check(false, 4, largest, "",
+                   zone_finder_.findNSEC3(Name("xxx.example.org"), false));
+    findNSEC3Check(false, 4, largest, "",
+                   zone_finder_.findNSEC3(Name("yyy.example.org"), false));
 }
 
 TEST_F(InMemoryZoneFinderTest, badNSEC3Name) {
@@ -1643,6 +1732,9 @@ TEST_F(InMemoryZoneFinderTest, addMultiNSEC3) {
 }
 
 TEST_F(InMemoryZoneFinderTest, addNSEC3WithRRSIG) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
     // Adding NSEC3 and its RRSIG
     const string nsec3_text = string(apex_hash) + ".example.org." +
         string(nsec3_common);
@@ -1652,8 +1744,9 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3WithRRSIG) {
     EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_rrsig_text)));
 
     // Then look for it.  The NSEC3 should have the RRSIG that was just added.
-    nsec3Check(true, nsec3_text + "\n" + nsec3_rrsig_text,
-               zone_finder_.findNSEC3Tmp(Name("example.org"), false), true);
+    findNSEC3Check(true, origin_.getLabelCount(),
+                   nsec3_text + "\n" + nsec3_rrsig_text, "",
+                   zone_finder_.findNSEC3(Name("example.org"), false), true);
 
     // Duplicate add of RRSIG for the same NSEC3 is prohibited.
     EXPECT_THROW(zone_finder_.add(textToRRset(nsec3_rrsig_text)),
@@ -1804,4 +1897,162 @@ TEST_F(InMemoryZoneFinderTest, queryToNSEC3NameNonterminal) {
              ZoneFinder::FIND_DNSSEC);
 }
 
+TEST_F(InMemoryZoneFinderTest, 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);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(apex_nsec3_text)));
+    const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(ns1_nsec3_text)));
+    const string w_nsec3_text = string(w_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(w_nsec3_text)));
+    const string zzz_nsec3_text = string(zzz_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(zzz_nsec3_text)));
+
+    // Parameter validation: the query name must be in or below the zone
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("example.com"), false),
+                 isc::InvalidParameter);
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("org"), true),
+                 isc::InvalidParameter);
+
+    // Apex name.  It should have a matching NSEC3.
+    {
+        SCOPED_TRACE("apex, non recursive mode");
+        findNSEC3Check(true, origin_.getLabelCount(), apex_nsec3_text, "",
+                       zone_finder_.findNSEC3(origin_, false));
+    }
+
+    // Recursive mode doesn't change the result in this case.
+    {
+        SCOPED_TRACE("apex, recursive mode");
+        findNSEC3Check(true, origin_.getLabelCount(), apex_nsec3_text, "",
+                       zone_finder_.findNSEC3(origin_, true));
+    }
+
+    // Non existent name.  Disabling recursion, a covering NSEC3 should be
+    // returned.
+    const Name www_name("www.example.org");
+    {
+        SCOPED_TRACE("non existent name, non recursive mode");
+        findNSEC3Check(false, www_name.getLabelCount(), apex_nsec3_text, "",
+                       zone_finder_.findNSEC3(www_name, false));
+    }
+
+    // Non existent name.  The closest provable encloser is the apex,
+    // and next closer is the query name itself (which NSEC3 for ns1
+    // covers)
+    // H(ns1) = 2T... < H(xxx) = Q0... < H(zzz) = R5...
+    {
+        SCOPED_TRACE("non existent name, recursive mode");
+        findNSEC3Check(true, origin_.getLabelCount(), apex_nsec3_text,
+                       ns1_nsec3_text,
+                       zone_finder_.findNSEC3(Name("xxx.example.org"), true));
+    }
+
+    // Similar to the previous case, but next closer name is different
+    // from the query name.  The closet encloser is w.example.org, and
+    // next closer is y.w.example.org.
+    // H(ns1) = 2T.. < H(y.w) = K8.. < H(zzz) = R5
+    {
+        SCOPED_TRACE("non existent name, non qname next closer");
+        findNSEC3Check(true, Name("w.example.org").getLabelCount(),
+                       w_nsec3_text, ns1_nsec3_text,
+                       zone_finder_.findNSEC3(Name("x.y.w.example.org"),
+                                              true));
+    }
+
+    // In the rest of test we check hash comparison for wrap around cases.
+    {
+        SCOPED_TRACE("very small hash");
+        const Name smallest_name("smallest.example.org");
+        findNSEC3Check(false, smallest_name.getLabelCount(),
+                       zzz_nsec3_text, "",
+                       zone_finder_.findNSEC3(smallest_name, false));
+    }
+    {
+        SCOPED_TRACE("very large hash");
+        const Name largest_name("largest.example.org");
+        findNSEC3Check(false, largest_name.getLabelCount(),
+                       zzz_nsec3_text, "",
+                       zone_finder_.findNSEC3(largest_name, false));
+    }
+}
+
+TEST_F(InMemoryZoneFinderTest, 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
+    EXPECT_EQ(result::SUCCESS,
+              zone_finder_.add(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);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(ns1_nsec3_text)));
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+                 DataSourceError);
+}
+
+TEST_F(InMemoryZoneFinderTest, loadAndFindNSEC3) {
+    // Using more realistic example, borrowed from RFC5155, with compliant
+    // hash calculator.  We only confirm the data source can load it
+    // successfully and find correct NSEC3 RRs for some selected cases
+    // (detailed tests have been done above).
+
+    InMemoryZoneFinder finder(class_, Name("example"));
+    finder.load(TEST_DATA_DIR "/rfc5155-example.zone.signed");
+
+    // See RFC5155 B.1
+    ZoneFinder::FindNSEC3Result result1 =
+        finder.findNSEC3(Name("c.x.w.example"), true);
+    ASSERT_TRUE(result1.closest_proof);
+    // We compare closest_labels as int so the error report will be more
+    // readable in case it fails.
+    EXPECT_EQ(4, static_cast<int>(result1.closest_labels));
+    EXPECT_EQ(Name("b4um86eghhds6nea196smvmlo4ors995.example"),
+              result1.closest_proof->getName());
+    ASSERT_TRUE(result1.next_proof);
+    EXPECT_EQ(Name("0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example"),
+              result1.next_proof->getName());
+
+    // See RFC5155 B.2.
+    ZoneFinder::FindNSEC3Result result2 =
+        finder.findNSEC3(Name("ns1.example"), true);
+    ASSERT_TRUE(result2.closest_proof);
+    EXPECT_EQ(3, static_cast<int>(result2.closest_labels));
+    EXPECT_EQ(Name("2t7b4g4vsa5smi47k61mv5bv1a22bojr.example"),
+              result2.closest_proof->getName());
+    ASSERT_FALSE(result2.next_proof);
+
+    // See RFC5155 B.5.
+    ZoneFinder::FindNSEC3Result result3 =
+        finder.findNSEC3(Name("a.z.w.example"), true);
+    ASSERT_TRUE(result3.closest_proof);
+    EXPECT_EQ(3, static_cast<int>(result3.closest_labels));
+    EXPECT_EQ(Name("k8udemvp1j2f7eg6jebps17vp3n8i58h.example"),
+              result3.closest_proof->getName());
+    ASSERT_TRUE(result3.next_proof);
+    EXPECT_EQ(Name("q04jkcevqvmu85r014c7dkba38o0ji5r.example"),
+              result3.next_proof->getName());
+}
 }
diff --git a/src/lib/datasrc/tests/testdata/rfc5155-example.zone.signed b/src/lib/datasrc/tests/testdata/rfc5155-example.zone.signed
new file mode 100644
index 0000000..4120224
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/rfc5155-example.zone.signed
@@ -0,0 +1,72 @@
+;; The example NSEC3-signed zone used in RFC5155.
+
+example.				      3600 IN SOA	ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+example.				      3600 IN RRSIG	SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+example.				      3600 IN NS	ns1.example.
+example.				      3600 IN NS	ns2.example.
+example.				      3600 IN RRSIG	NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+example.				      3600 IN MX	1 xx.example.
+example.				      3600 IN RRSIG	MX 7 1 3600 20150420235959 20051021000000 40430 example. GgQ1A9xs47k42VPvpL/a1BWUz/6XsnHkjotw9So8MQtZtl2wJBsnOQsa oHrRCrRbyriEl/GZn9Mto/Kx+wBo+w==
+example.				      3600 IN DNSKEY	256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=
+example.				      3600 IN DNSKEY	257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJj7IommWSpJABVfW8Q0rO vXdM6kzt+TAu92L9AbsUdblMFin8CVF3n4s=
+example.				      3600 IN RRSIG	DNSKEY 7 1 3600 20150420235959 20051021000000 12708 example. AuU4juU9RaxescSmStrQks3Gh9FblGBlVU31uzMZ/U/FpsUb8aC6QZS+ sTsJXnLnz7flGOsmMGQZf3bH+QsCtg==
+example.				      3600 IN NSEC3PARAM 1 0 12 AABBCCDD
+example.				      3600 IN RRSIG	NSEC3PARAM 7 1 3600 20150420235959 20051021000000 40430 example. C1Gl8tPZNtnjlrYWDeeUV/sGLCyy/IHie2rerN05XSA3Pq0U3+4VvGWY WdUMfflOdxqnXHwJTLQsjlkynhG6Cg==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.     3600 IN A		192.0.2.127
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.     3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. h6c++bzhRuWWt2bykN6mjaTNBcXNq5UuL5EdK+iDP4eY8I0kSiKaCjg3 tC1SQkeloMeub2GWk8p6xHMPZumXlw==
+a.example.				      3600 IN NS	ns1.a.example.
+a.example.				      3600 IN NS	ns2.a.example.
+a.example.				      3600 IN DS	58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C
+a.example.				      3600 IN RRSIG	DS 7 2 3600 20150420235959 20051021000000 40430 example. XacFcQVHLVzdoc45EJhN616zQ4mEXtE8FzUhM2KWjfy1VfRKD9r1MeVG wwoukOKgJxBPFsWoo722vZ4UZ2dIdA==
+ns1.a.example.				      3600 IN A		192.0.2.5
+ns2.a.example.				      3600 IN A		192.0.2.6
+ai.example.				      3600 IN A		192.0.2.9
+ai.example.				      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ai.example.				      3600 IN HINFO	"KLH-10" "ITS"
+ai.example.				      3600 IN RRSIG	HINFO 7 2 3600 20150420235959 20051021000000 40430 example. Yi42uOq43eyO6qXHNvwwfFnIustWgV5urFcxenkLvs6pKRh00VBjODmf 3Z4nMO7IOl6nHSQ1v0wLHpEZG7Xj2w==
+ai.example.				      3600 IN AAAA	2001:db8::f00:baa9
+ai.example.				      3600 IN RRSIG	AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+c.example.				      3600 IN NS	ns1.c.example.
+c.example.				      3600 IN NS	ns2.c.example.
+ns1.c.example.				      3600 IN A		192.0.2.7
+ns2.c.example.				      3600 IN A		192.0.2.8
+ns1.example.				      3600 IN A		192.0.2.1
+ns1.example.				      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ns2.example.				      3600 IN A		192.0.2.2
+ns2.example.				      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+*.w.example.				      3600 IN MX	1 ai.example.
+*.w.example.				      3600 IN RRSIG	MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+x.w.example.				      3600 IN MX	1 xx.example.
+x.w.example.				      3600 IN RRSIG	MX 7 3 3600 20150420235959 20051021000000 40430 example. IrK3tq/tHFIBF0scHiE/1IwMAvckS/55hAVvQyxTFbkAdDloP3NbZzu+ yoSsr3b3OX6qbBpY7WCtwwekLKRAwQ==
+x.y.w.example.				      3600 IN MX	1 xx.example.
+x.y.w.example.				      3600 IN RRSIG	MX 7 4 3600 20150420235959 20051021000000 40430 example. MqSt5HqJIN8+SLlzTOImrh5h9Xa6gDvAW/GnnbdPc6Z7nXvCpLPJj/5l Cwx3VuzVOjkbvXze8/8Ccl2Zn2hbug==
+xx.example.				      3600 IN A		192.0.2.10
+xx.example.				      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. T35hBWEZ017VC5u2c4OriKyVn/pu+fVK4AlXYOxJ6iQylfV2HQIKjv6b 7DzINB3aF/wjJqgXpQvhq+Ac6+ZiFg==
+xx.example.				      3600 IN HINFO	"KLH-10" "TOPS-20"
+xx.example.				      3600 IN RRSIG	HINFO 7 2 3600 20150420235959 20051021000000 40430 example. KimG+rDd+7VA1zRsu0ITNAQUTRlpnsmqWrihFRnU+bRa93v2e5oFNFYC s3Rqgv62K93N7AhW6Jfqj/8NzWjvKg==
+xx.example.				      3600 IN AAAA	2001:db8::f00:baaa
+xx.example.				      3600 IN RRSIG	AAAA 7 2 3600 20150420235959 20051021000000 40430 example. IXBcXORITNwd8h3gNwyxtYFvAupS/CYWufVeuBUX0O25ivBCULjZjpDx FSxfohb/KA7YRdxENzYfMItpILl/Xw==
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.     3600 IN NSEC3	1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.     3600 IN NSEC3	1 1 12 AABBCCDD 2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S A RRSIG
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OmBvJ1Vgg1hCKMXHFiNeIYHK9XVW0iLDLwJN4TFoNxZuP03gAXEI634Y wOc4YBNITrj413iqNI6mRk/r1dOSUw==
+2vptu5timamqttgl4luu9kg21e0aor3s.example.     3600 IN NSEC3	1 1 12 AABBCCDD 35MTHGPGCU1QG68FAB165KLNSNK3DPVL MX RRSIG
+2vptu5timamqttgl4luu9kg21e0aor3s.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. KL1V2oFYghNV0Hm7Tf2vpJjM6l+0g1JCcVYGVfI0lKrhPmTsOA96cLEA Cgo1x8I7kApJX+obTuktZ+sdsZPY1w==
+35mthgpgcu1qg68fab165klnsnk3dpvl.example.     3600 IN NSEC3	1 1 12 AABBCCDD B4UM86EGHHDS6NEA196SMVMLO4ORS995 NS DS RRSIG
+35mthgpgcu1qg68fab165klnsnk3dpvl.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+b4um86eghhds6nea196smvmlo4ors995.example.     3600 IN NSEC3	1 1 12 AABBCCDD GJEQE526PLBF1G8MKLP59ENFD789NJGI MX RRSIG
+b4um86eghhds6nea196smvmlo4ors995.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+gjeqe526plbf1g8mklp59enfd789njgi.example.     3600 IN NSEC3	1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+gjeqe526plbf1g8mklp59enfd789njgi.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.     3600 IN NSEC3	1 1 12 AABBCCDD K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. gPkFp1s2QDQ6wQzcg1uSebZ61W33rUBDcTj72F3kQ490fEdp7k1BUIfb cZtPbX3YCpE+sIt0MpzVSKfTwx4uYA==
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example.     3600 IN NSEC3	1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example.     3600 IN NSEC3	1 1 12 AABBCCDD Q04JKCEVQVMU85R014C7DKBA38O0JI5R A RRSIG
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. VrDXs2uVW21N08SyQIz88zml+y4ZCInTwgDr6zz43yAg+LFERjOrj3Oj ct51ac7Dp4eZbf9FQJazmASFKGxGXg==
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.     3600 IN NSEC3	1 1 12 AABBCCDD R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN A RRSIG
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example.     3600 IN NSEC3	1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+t644ebqk9bibcna874givr6joj62mlhv.example.     3600 IN NSEC3	1 1 12 AABBCCDD 0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM A HINFO AAAA RRSIG
+t644ebqk9bibcna874givr6joj62mlhv.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. RAjGECB8P7O+F4Pa4Dx3tC0M+Z3KmlLKImcafb9XWwx+NWUNz7NBEDBQ HivIyKPVDkChcePIX1xPl1ATNa+8Dw==
diff --git a/src/lib/dns/nsec3hash.cc b/src/lib/dns/nsec3hash.cc
index 8755959..159dff3 100644
--- a/src/lib/dns/nsec3hash.cc
+++ b/src/lib/dns/nsec3hash.cc
@@ -142,6 +142,23 @@ NSEC3HashRFC5155::match(const generic::NSEC3PARAM& nsec3param) const {
     return (match(nsec3param.getHashalg(), nsec3param.getIterations(),
                   nsec3param.getSalt()));
 }
+
+// A static pointer that refers to the currently usable creator.
+// Only get/setNSEC3HashCreator are expected to get access to this variable
+// directly.
+const NSEC3HashCreator* creator;
+
+// The accessor to the current creator.  If it's not explicitly set or has
+// been reset from a customized one, the default creator will be used.
+const NSEC3HashCreator*
+getNSEC3HashCreator() {
+    static DefaultNSEC3HashCreator default_creator;
+    if (creator == NULL) {
+        creator = &default_creator;
+    }
+    return (creator);
+}
+
 } // end of unnamed namespace
 
 namespace isc {
@@ -149,15 +166,30 @@ namespace dns {
 
 NSEC3Hash*
 NSEC3Hash::create(const generic::NSEC3PARAM& param) {
+    return (getNSEC3HashCreator()->create(param));
+}
+
+NSEC3Hash*
+NSEC3Hash::create(const generic::NSEC3& nsec3) {
+    return (getNSEC3HashCreator()->create(nsec3));
+}
+
+NSEC3Hash*
+DefaultNSEC3HashCreator::create(const generic::NSEC3PARAM& param) const {
     return (new NSEC3HashRFC5155(param.getHashalg(), param.getIterations(),
                                  param.getSalt()));
 }
 
 NSEC3Hash*
-NSEC3Hash::create(const generic::NSEC3& nsec3) {
+DefaultNSEC3HashCreator::create(const generic::NSEC3& nsec3) const {
     return (new NSEC3HashRFC5155(nsec3.getHashalg(), nsec3.getIterations(),
                                  nsec3.getSalt()));
 }
 
+void
+setNSEC3HashCreator(const NSEC3HashCreator* new_creator) {
+    creator = new_creator;
+}
+
 } // namespace dns
 } // namespace isc
diff --git a/src/lib/dns/nsec3hash.h b/src/lib/dns/nsec3hash.h
index 7937e9d..2056708 100644
--- a/src/lib/dns/nsec3hash.h
+++ b/src/lib/dns/nsec3hash.h
@@ -154,6 +154,96 @@ public:
     virtual bool match(const rdata::generic::NSEC3PARAM& nsec3param) const = 0;
 };
 
+/// \brief Factory class of NSEC3Hash.
+///
+/// This class is an abstract base class that provides the creation interfaces
+/// of \c NSEC3Hash objects.  By defining a specific derived class of the
+/// creator, normally with a different specific class of \c NSEC3Hash,
+/// the application can use a customized implementation of \c NSEC3Hash
+/// without changing the library itself.  The intended primary application of
+/// such customization is tests (it would be convenient for a test to produce
+/// a faked hash value regardless of the input so it doesn't have to identify
+/// a specific input value to produce a particular hash).  Another possibility
+/// would be an experimental extension for a newer hash algorithm or
+/// implementation.
+///
+/// The two main methods named \c create() correspond to the static factory
+/// methods of \c NSEC3Hash of the same name.
+///
+/// By default, the library uses the \c DefaultNSEC3HashCreator creator.
+/// The \c setNSEC3HashCreator() function can be used to replace it with a
+/// user defined version.  For such customization purposes as implementing
+/// experimental new hash algorithms, the application may internally want to
+/// use the \c DefaultNSEC3HashCreator in general cases while creating a
+/// customized type of \c NSEC3Hash object for that particular hash algorithm.
+///
+/// The creator objects are generally expected to be stateless; they will
+/// only encapsulate the factory logic.  The \c create() methods are declared
+/// as const member functions for this reason.  But if we see the need for
+/// having a customized creator that benefits from its own state in future,
+/// this condition can be loosened.
+class NSEC3HashCreator {
+protected:
+    /// \brief The default constructor.
+    ///
+    /// Make very sure this isn't directly instantiated by making it protected
+    /// even if this class is modified to lose all pure virtual methods.
+    NSEC3HashCreator() {}
+
+public:
+    /// \brief The destructor.
+    ///
+    /// This does nothing; defined only for allowing derived classes to
+    /// specialize its behavior.
+    virtual ~NSEC3HashCreator() {}
+
+    /// \brief Factory method of NSECHash from NSEC3PARAM RDATA.
+    ///
+    /// See
+    /// <code>NSEC3Hash::create(const rdata::generic::NSEC3PARAM& param)</code>
+    virtual NSEC3Hash* create(const rdata::generic::NSEC3PARAM& nsec3param)
+        const = 0;
+
+    /// \brief Factory method of NSECHash from NSEC3 RDATA.
+    ///
+    /// See
+    /// <code>NSEC3Hash::create(const rdata::generic::NSEC3& param)</code>
+    virtual NSEC3Hash* create(const rdata::generic::NSEC3& nsec3)
+        const = 0;
+};
+
+/// \brief The default NSEC3Hash creator.
+///
+/// This derived class implements the \c NSEC3HashCreator interfaces for
+/// the standard NSEC3 hash calculator as defined in RFC5155.  The library
+/// will use this creator by default, so normal applications don't have to
+/// be aware of this class at all.  This class is publicly visible for the
+/// convenience of special applications that want to customize the creator
+/// behavior for a particular type of parameters while preserving the default
+/// behavior for others.
+class DefaultNSEC3HashCreator : public NSEC3HashCreator {
+public:
+    virtual NSEC3Hash* create(const rdata::generic::NSEC3PARAM& param) const;
+    virtual NSEC3Hash* create(const rdata::generic::NSEC3& nsec3) const;
+};
+
+/// \brief The registrar of \c NSEC3HashCreator.
+///
+/// This function sets or resets the system-wide \c NSEC3HashCreator that
+/// is used by \c NSEC3Hash::create().
+///
+/// If \c new_creator is non NULL, the given creator object will replace
+/// any existing creator.  If it's NULL, the default builtin creator will be
+/// used again from that point.
+///
+/// When \c new_creator is non NULL, the caller is responsible for keeping
+/// the referenced object valid as long as it can be used via
+/// \c NSEC3Hash::create().
+///
+/// \exception None
+/// \param new_creator A pointer to the new creator object or NULL.
+void setNSEC3HashCreator(const NSEC3HashCreator* new_creator);
+
 }
 }
 #endif  // __NSEC3HASH_H
diff --git a/src/lib/dns/tests/nsec3hash_unittest.cc b/src/lib/dns/tests/nsec3hash_unittest.cc
index 36a1d4d..e607c74 100644
--- a/src/lib/dns/tests/nsec3hash_unittest.cc
+++ b/src/lib/dns/tests/nsec3hash_unittest.cc
@@ -29,7 +29,7 @@ using namespace isc::dns::rdata;
 namespace {
 typedef scoped_ptr<NSEC3Hash> NSEC3HashPtr;
 
-// Commonly used NSEC3 suffix, defined to reduce amount of type
+// Commonly used NSEC3 suffix, defined to reduce the amount of typing
 const char* const nsec3_common = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
 
 class NSEC3HashTest : public ::testing::Test {
@@ -41,6 +41,11 @@ protected:
                                            string(nsec3_common))))
     {}
 
+    ~NSEC3HashTest() {
+        // Make sure we reset the hash creator to the default
+        setNSEC3HashCreator(NULL);
+    }
+
     // An NSEC3Hash object commonly used in tests.  Parameters are borrowed
     // from the RFC5155 example.  Construction of this object implicitly
     // checks a successful case of the creation.
@@ -142,4 +147,76 @@ TEST_F(NSEC3HashTest, matchWithNSEC3PARAM) {
     }
 }
 
+// A simple faked hash calculator and a dedicated creator for it.
+class TestNSEC3Hash : public NSEC3Hash {
+    virtual string calculate(const Name&) const {
+        return ("00000000000000000000000000000000");
+    }
+    virtual bool match(const generic::NSEC3PARAM&) const {
+        return (true);
+    }
+    virtual bool match(const generic::NSEC3&) const {
+        return (true);
+    }
+};
+
+// This faked creator basically creates the faked calculator regardless of
+// the passed NSEC3PARAM or NSEC3.  But if the most significant bit of flags
+// is set, it will behave like the default creator.
+class TestNSEC3HashCreator : public NSEC3HashCreator {
+public:
+    virtual NSEC3Hash* create(const generic::NSEC3PARAM& param) const {
+        if ((param.getFlags() & 0x80) != 0) {
+            return (default_creator_.create(param));
+        }
+        return (new TestNSEC3Hash);
+    }
+    virtual NSEC3Hash* create(const generic::NSEC3& nsec3) const {
+        if ((nsec3.getFlags() & 0x80) != 0) {
+            return (default_creator_.create(nsec3));
+        }
+        return (new TestNSEC3Hash);
+    }
+private:
+    DefaultNSEC3HashCreator default_creator_;
+};
+
+TEST_F(NSEC3HashTest, setCreator) {
+    // Re-check an existing case using the default creator/hash implementation
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+
+    // Replace the creator, and confirm the hash values are faked
+    TestNSEC3HashCreator test_creator;
+    setNSEC3HashCreator(&test_creator);
+    // Re-create the hash object with the new creator
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
+    EXPECT_EQ("00000000000000000000000000000000",
+              test_hash->calculate(Name("example")));
+    // Same for hash from NSEC3 RDATA
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3
+                                      ("1 0 12 aabbccdd " +
+                                       string(nsec3_common))));
+    EXPECT_EQ("00000000000000000000000000000000",
+              test_hash->calculate(Name("example")));
+
+    // If we set a special flag big (0x80) on creation, it will act like the
+    // default creator.
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM(
+                                          "1 128 12 aabbccdd")));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3
+                                      ("1 128 12 aabbccdd " +
+                                       string(nsec3_common))));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+
+    // Reset the creator to default, and confirm that
+    setNSEC3HashCreator(NULL);
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+}
+
 } // end namespace




More information about the bind10-changes mailing list