Browse Source

Merge #1577

Conflicts:
	src/lib/datasrc/tests/memory_datasrc_unittest.cc
Michal 'vorner' Vaner 13 years ago
parent
commit
56bd002f57

+ 137 - 9
src/lib/datasrc/database.cc

@@ -27,15 +27,18 @@
 #include <dns/rrset.h>
 #include <dns/rrset.h>
 #include <dns/rdata.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataclass.h>
+#include <dns/nsec3hash.h>
 
 
 #include <datasrc/data_source.h>
 #include <datasrc/data_source.h>
 #include <datasrc/logger.h>
 #include <datasrc/logger.h>
 
 
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
+#include <boost/scoped_ptr.hpp>
 
 
 using namespace isc::dns;
 using namespace isc::dns;
 using namespace std;
 using namespace std;
 using namespace isc::dns::rdata;
 using namespace isc::dns::rdata;
+using namespace boost;
 
 
 namespace isc {
 namespace isc {
 namespace datasrc {
 namespace datasrc {
@@ -177,15 +180,17 @@ private:
 DatabaseClient::Finder::FoundRRsets
 DatabaseClient::Finder::FoundRRsets
 DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
 DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
                                   bool check_ns, const string* construct_name,
                                   bool check_ns, const string* construct_name,
-                                  bool any)
+                                  bool any,
+                                  DatabaseAccessor::IteratorContextPtr context)
 {
 {
     RRsigStore sig_store;
     RRsigStore sig_store;
     bool records_found = false;
     bool records_found = false;
     std::map<RRType, RRsetPtr> result;
     std::map<RRType, RRsetPtr> result;
 
 
-    // Request the context
-    DatabaseAccessor::IteratorContextPtr
-        context(accessor_->getRecords(name, zone_id_));
+    // Request the context in case we didn't get one
+    if (!context) {
+        context = accessor_->getRecords(name, zone_id_);
+    }
     // It must not return NULL, that's a bug of the implementation
     // It must not return NULL, that's a bug of the implementation
     if (!context) {
     if (!context) {
         isc_throw(isc::Unexpected, "Iterator context null at " + name);
         isc_throw(isc::Unexpected, "Iterator context null at " + name);
@@ -316,12 +321,12 @@ namespace {
 typedef std::set<RRType> WantedTypes;
 typedef std::set<RRType> WantedTypes;
 
 
 const WantedTypes&
 const WantedTypes&
-NSEC_TYPES() {
+NSEC3_TYPES() {
     static bool initialized(false);
     static bool initialized(false);
     static WantedTypes result;
     static WantedTypes result;
 
 
     if (!initialized) {
     if (!initialized) {
-        result.insert(RRType::NSEC());
+        result.insert(RRType::NSEC3());
         initialized = true;
         initialized = true;
     }
     }
     return (result);
     return (result);
@@ -331,6 +336,7 @@ const WantedTypes&
 NSEC3PARAM_TYPES() {
 NSEC3PARAM_TYPES() {
     static bool initialized(false);
     static bool initialized(false);
     static WantedTypes result;
     static WantedTypes result;
+
     if (!initialized) {
     if (!initialized) {
         result.insert(RRType::NSEC3PARAM());
         result.insert(RRType::NSEC3PARAM());
         initialized = true;
         initialized = true;
@@ -339,6 +345,18 @@ NSEC3PARAM_TYPES() {
 }
 }
 
 
 const WantedTypes&
 const WantedTypes&
+NSEC_TYPES() {
+    static bool initialized(false);
+    static WantedTypes result;
+
+    if (!initialized) {
+        result.insert(RRType::NSEC());
+        initialized = true;
+    }
+    return (result);
+}
+
+const WantedTypes&
 DELEGATION_TYPES() {
 DELEGATION_TYPES() {
     static bool initialized(false);
     static bool initialized(false);
     static WantedTypes result;
     static WantedTypes result;
@@ -968,10 +986,120 @@ DatabaseClient::Finder::findInternal(const Name& name, const RRType& type,
     }
     }
 }
 }
 
 
+// The behaviour is inspired by the one in the in-memory implementation.
 ZoneFinder::FindNSEC3Result
 ZoneFinder::FindNSEC3Result
-DatabaseClient::Finder::findNSEC3(const Name&, bool) {
-    isc_throw(NotImplemented, "findNSEC3 is not yet implemented for database "
-              "data source");
+DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_DATABASE_FINDNSEC3).arg(name).
+        arg(recursive ? "recursive" : "non-recursive");
+
+    // First, validate the input
+    const NameComparisonResult cmp_result(name.compare(getOrigin()));
+    if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
+        cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
+        isc_throw(OutOfZone, "findNSEC3 attempt for out-of-zone name: " <<
+                  name << ", zone: " << getOrigin() << "/" << getClass());
+    }
+
+    // Now, we need to get the NSEC3 params from the apex and create the hash
+    // creator for it.
+    const FoundRRsets nsec3param(getRRsets(getOrigin().toText(),
+                                 NSEC3PARAM_TYPES(), false));
+    const FoundIterator param(nsec3param.second.find(RRType::NSEC3PARAM()));
+    if (!nsec3param.first || param == nsec3param.second.end()) {
+        // No NSEC3 params? :-(
+        isc_throw(DataSourceError, "findNSEC3 attempt for non NSEC3 signed " <<
+                  "zone: " << getOrigin() << "/" << getClass());
+    }
+    // This takes the RRset received from the find method, takes the first RR
+    // in it, casts it to NSEC3PARAM (as it should be that one) and then creates
+    // the hash calculator class from it.
+    const scoped_ptr<NSEC3Hash> calculator(NSEC3Hash::create(
+        dynamic_cast<const generic::NSEC3PARAM&>(
+            param->second->getRdataIterator()->getCurrent())));
+
+    // Few shortcut variables
+    const unsigned olabels(getOrigin().getLabelCount());
+    const unsigned qlabels(name.getLabelCount());
+    const string otext(getOrigin().toText());
+
+    // This will be set to the one covering the query name
+    ConstRRsetPtr covering_proof;
+
+    // We keep stripping the leftmost label until we find something.
+    // In case it is recursive, we'll exit the loop at the first iteration.
+    for (unsigned labels(qlabels); labels >= olabels; -- labels) {
+        const string hash(calculator->calculate(labels == qlabels ? name :
+                                                name.split(qlabels - labels,
+                                                           labels)));
+        // Get the exact match for the name.
+        LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_DATABASE_FINDNSEC3_TRYHASH).
+            arg(name).arg(labels).arg(hash);
+
+        DatabaseAccessor::IteratorContextPtr
+            context(accessor_->getNSEC3Records(hash, zone_id_));
+
+        if (!context) {
+            isc_throw(Unexpected, "Iterator context null for hash " + hash);
+        }
+
+        const FoundRRsets nsec3(getRRsets(hash + "." + otext, NSEC3_TYPES(),
+                                          false, NULL, false, context));
+
+        if (nsec3.first) {
+            // We found an exact match against the current label.
+            const FoundIterator it(nsec3.second.find(RRType::NSEC3()));
+            if (it == nsec3.second.end()) {
+                isc_throw(DataSourceError, "Hash " + hash +
+                          "exists, but no NSEC3 there");
+            }
+
+            LOG_DEBUG(logger, DBG_TRACE_BASIC,
+                      DATASRC_DATABASE_FINDNSEC3_MATCH).arg(name).arg(labels).
+                arg(*it->second);
+            // Yes, we win
+            return (FindNSEC3Result(true, labels, it->second, covering_proof));
+        } else {
+            // There's no exact match. We try a previous one. We must find it
+            // (if the zone is properly signed).
+            const string prevHash(accessor_->findPreviousNSEC3Hash(zone_id_,
+                                                                   hash));
+            LOG_DEBUG(logger, DBG_TRACE_BASIC,
+                      DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV).arg(name).
+                arg(labels).arg(prevHash);
+            context = accessor_->getNSEC3Records(prevHash, zone_id_);
+            const FoundRRsets prev_nsec3(getRRsets(prevHash + "." + otext,
+                                                   NSEC3_TYPES(), false, NULL,
+                                                   false, context));
+
+            if (!prev_nsec3.first) {
+                isc_throw(DataSourceError, "Hash " + prevHash + " returned "
+                          "from findPreviousNSEC3Hash, but it is empty");
+            }
+            const FoundIterator
+                prev_it(prev_nsec3.second.find(RRType::NSEC3()));
+            if (prev_it == prev_nsec3.second.end()) {
+                isc_throw(DataSourceError, "The previous hash " + prevHash +
+                          "exists, but does not contain the NSEC3");
+            }
+
+            covering_proof = prev_it->second;
+            // In case it is recursive, we try to get an exact match a level
+            // up. If it is not recursive, the caller is ok with a covering
+            // one, so we just return it.
+            if (!recursive) {
+                LOG_DEBUG(logger, DBG_TRACE_BASIC,
+                          DATASRC_DATABASE_FINDNSEC3_COVER).arg(name).
+                    arg(labels).arg(*covering_proof);
+                return (FindNSEC3Result(false, labels, covering_proof,
+                                        ConstRRsetPtr()));
+            }
+        }
+    }
+
+    // The zone must contain at least the apex and that one should match
+    // exactly. If that doesn't happen, we have a problem.
+    isc_throw(DataSourceError, "recursive findNSEC3 mode didn't stop, likely a "
+              "broken NSEC3 zone: " << otext << "/" << getClass());
 }
 }
 
 
 Name
 Name

+ 7 - 2
src/lib/datasrc/database.h

@@ -684,7 +684,7 @@ public:
     /// This is used to find previous NSEC3 hashes, to find covering NSEC3 in
     /// This is used to find previous NSEC3 hashes, to find covering NSEC3 in
     /// case none match exactly.
     /// case none match exactly.
     ///
     ///
-    /// In case a hash before before the lowest or the lowest is provided,
+    /// In case a hash before the lowest or the lowest is provided,
     /// this should return the largest one in the zone (NSEC3 needs a
     /// this should return the largest one in the zone (NSEC3 needs a
     /// wrap-around semantics).
     /// wrap-around semantics).
     ///
     ///
@@ -889,6 +889,9 @@ public:
         ///     ones requested by types. It also puts a NULL pointer under the
         ///     ones requested by types. It also puts a NULL pointer under the
         ///     ANY type into the result, if it finds any RRs at all, to easy the
         ///     ANY type into the result, if it finds any RRs at all, to easy the
         ///     identification of success.
         ///     identification of success.
+        /// \param srcContext This can be set to non-NULL value to override the
+        ///     iterator context used for obtaining the data. This can be used,
+        ///     for example, to get data from the NSEC3 namespace.
         /// \return A pair, where the first element indicates if the domain
         /// \return A pair, where the first element indicates if the domain
         ///     contains any RRs at all (not only the requested, it may happen
         ///     contains any RRs at all (not only the requested, it may happen
         ///     this is set to true, but the second part is empty). The second
         ///     this is set to true, but the second part is empty). The second
@@ -900,7 +903,9 @@ public:
         FoundRRsets getRRsets(const std::string& name,
         FoundRRsets getRRsets(const std::string& name,
                               const WantedTypes& types, bool check_ns,
                               const WantedTypes& types, bool check_ns,
                               const std::string* construct_name = NULL,
                               const std::string* construct_name = NULL,
-                              bool any = false);
+                              bool any = false,
+                              DatabaseAccessor::IteratorContextPtr srcContext =
+                              DatabaseAccessor::IteratorContextPtr());
 
 
         /// \brief DNSSEC related context for ZoneFinder::findInternal.
         /// \brief DNSSEC related context for ZoneFinder::findInternal.
         ///
         ///

+ 29 - 0
src/lib/datasrc/datasrc_messages.mes

@@ -75,6 +75,35 @@ The datasource tried to provide an NSEC proof that the named domain does not
 exist, but the database backend doesn't support DNSSEC. No proof is included
 exist, but the database backend doesn't support DNSSEC. No proof is included
 in the answer as a result.
 in the answer as a result.
 
 
+% DATASRC_DATABASE_FINDNSEC3 Looking for NSEC3 for %1 in %2 mode
+Debug information. A search in an database data source for NSEC3 that
+matches or covers the given name is being started.
+
+% DATASRC_DATABASE_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_DATABASE_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_DATABSE_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+
+% DATASRC_DATABASE_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, as "." is 1 label long).
+
+% DATASRC_DATABASE_FINDNSEC3_TRYHASH_PREV looking for previous NSEC3 for %1 at label count %2 (hash %3)
+Debug information. An exact match on hash (see
+DATASRC_DATABASE_FINDNSEC3_TRYHASH) was unsuccessful. We get the previous hash
+to that one instead.
+
 % DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3/%4
 % DATASRC_DATABASE_FIND_RECORDS looking in datasource %1 for record %2/%3/%4
 Debug information. The database data source is looking up records with the given
 Debug information. The database data source is looking up records with the given
 name and type in the database.
 name and type in the database.

+ 1 - 0
src/lib/datasrc/tests/Makefile.am

@@ -59,6 +59,7 @@ run_unittests_SOURCES += sqlite3_accessor_unittest.cc
 run_unittests_SOURCES += memory_datasrc_unittest.cc
 run_unittests_SOURCES += memory_datasrc_unittest.cc
 run_unittests_SOURCES += rbnode_rrset_unittest.cc
 run_unittests_SOURCES += rbnode_rrset_unittest.cc
 run_unittests_SOURCES += zone_finder_context_unittest.cc
 run_unittests_SOURCES += zone_finder_context_unittest.cc
+run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
 
 
 # We need the actual module implementation in the tests (they are not part
 # We need the actual module implementation in the tests (they are not part
 # of libdatasrc)
 # of libdatasrc)

+ 67 - 3
src/lib/datasrc/tests/database_unittest.cc

@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#include "faked_nsec3.h"
+
 #include <stdlib.h>
 #include <stdlib.h>
 
 
 #include <boost/shared_ptr.hpp>
 #include <boost/shared_ptr.hpp>
@@ -24,6 +26,7 @@
 #include <dns/name.h>
 #include <dns/name.h>
 #include <dns/rrttl.h>
 #include <dns/rrttl.h>
 #include <dns/rrset.h>
 #include <dns/rrset.h>
+#include <dns/nsec3hash.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <datasrc/database.h>
 #include <datasrc/database.h>
@@ -44,6 +47,7 @@ using namespace std;
 using boost::dynamic_pointer_cast;
 using boost::dynamic_pointer_cast;
 using boost::lexical_cast;
 using boost::lexical_cast;
 using namespace isc::dns;
 using namespace isc::dns;
+using namespace isc::datasrc::test;
 
 
 namespace {
 namespace {
 
 
@@ -210,8 +214,14 @@ const char* const TEST_RECORDS[][5] = {
 
 
 // FIXME: Taken from a different test. Fill with proper data when creating a test.
 // FIXME: Taken from a different test. Fill with proper data when creating a test.
 const char* TEST_NSEC3_RECORDS[][5] = {
 const char* TEST_NSEC3_RECORDS[][5] = {
-    {"1BB7SO0452U1QHL98UISNDD9218GELR5", "NSEC3", "3600", "", "1 0 10 FEEDABEE 4KLSVDE8KH8G95VU68R7AHBE1CPQN38J"},
-    {"1BB7SO0452U1QHL98UISNDD9218GELR5", "RRSIG", "3600", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+    {apex_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
+    {apex_hash, "RRSIG", "300", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+    {ns1_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
+    {ns1_hash, "RRSIG", "300", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+    {w_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
+    {w_hash, "RRSIG", "300", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
+    {zzz_hash, "NSEC3", "300", "", "1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"},
+    {zzz_hash, "RRSIG", "300", "", "NSEC3 5 4 7200 20100410172647 20100311172647 63192 example.org. gNIVj4T8t51fEU6kOPpvK7HOGBFZGbalN5ZK mInyrww6UWZsUNdw07ge6/U6HfG+/s61RZ/L is2M6yUWHyXbNbj/QqwqgadG5dhxTArfuR02 xP600x0fWX8LXzW4yLMdKVxGbzYT+vvGz71o 8gHSY5vYTtothcZQa4BMKhmGQEk="},
     {NULL, NULL, NULL, NULL, NULL}
     {NULL, NULL, NULL, NULL, NULL}
 };
 };
 
 
@@ -955,7 +965,7 @@ private:
              i = cur_name_.begin(); i != cur_name_.end(); ++ i) {
              i = cur_name_.begin(); i != cur_name_.end(); ++ i) {
             i->push_back(hash);
             i->push_back(hash);
         }
         }
-        (*readonly_records_)[hash] = cur_name_;
+        nsec3_namespace_[hash] = cur_name_;
         cur_name_.clear();
         cur_name_.clear();
     }
     }
 
 
@@ -993,6 +1003,31 @@ private:
         }
         }
         addCurHash(prev_name);
         addCurHash(prev_name);
     }
     }
+
+public:
+    // This adds the NSEC3PARAM into the apex, so we can perform some NSEC3
+    // tests. Note that the NSEC3 namespace is available in other tests, but
+    // it should not be accessed at that time.
+    void enableNSEC3() {
+        // We place the signature first, so it's in the block with the other
+        // signatures
+        vector<string> signature;
+        signature.push_back("RRSIG");
+        signature.push_back("3600");
+        signature.push_back("");
+        signature.push_back("NSEC3PARAM 5 3 3600 20000101000000 20000201000000 "
+                            "12345 example.org. FAKEFAKEFAKE");
+        signature.push_back("exmaple.org.");
+        (*readonly_records_)["example.org."].push_back(signature);
+        // Now the NSEC3 param itself
+        vector<string> param;
+        param.push_back("NSEC3PARAM");
+        param.push_back("3600");
+        param.push_back("");
+        param.push_back("1 0 12 aabbccdd");
+        param.push_back("example.org.");
+        (*readonly_records_)["example.org."].push_back(param);
+    }
 };
 };
 
 
 // This tests the default getRecords behaviour, throwing NotImplemented
 // This tests the default getRecords behaviour, throwing NotImplemented
@@ -1046,6 +1081,11 @@ public:
                                                "FAKEFAKEFAKE"));
                                                "FAKEFAKEFAKE"));
     }
     }
 
 
+    ~ DatabaseClientTest() {
+        // Make sure we return the default creator no matter if we set it or not
+        setNSEC3HashCreator(NULL);
+    }
+
     /*
     /*
      * We initialize the client from a function, so we can call it multiple
      * We initialize the client from a function, so we can call it multiple
      * times per test.
      * times per test.
@@ -1206,6 +1246,9 @@ public:
     const std::vector<std::string> empty_rdatas_; // for NXRRSET/NXDOMAIN
     const std::vector<std::string> empty_rdatas_; // for NXRRSET/NXDOMAIN
     std::vector<std::string> expected_rdatas_;
     std::vector<std::string> expected_rdatas_;
     std::vector<std::string> expected_sig_rdatas_;
     std::vector<std::string> expected_sig_rdatas_;
+
+    // A creator for use in several NSEC3 related tests.
+    TestNSEC3HashCreator test_nsec3_hash_creator_;
 };
 };
 
 
 class TestSQLite3Accessor : public SQLite3Accessor {
 class TestSQLite3Accessor : public SQLite3Accessor {
@@ -3742,4 +3785,25 @@ TEST_F(MockDatabaseClientTest, journalWithBadData) {
                  second->getNextDiff(), DataSourceError);
                  second->getNextDiff(), DataSourceError);
 }
 }
 
 
+/// Let us test a little bit of NSEC3.
+TEST_F(MockDatabaseClientTest, findNSEC3) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&test_nsec3_hash_creator_);
+
+    DataSourceClient::FindResult
+        zone(this->client_->findZone(Name("example.org")));
+    ASSERT_EQ(result::SUCCESS, zone.code);
+    boost::shared_ptr<DatabaseClient::Finder> finder(
+        dynamic_pointer_cast<DatabaseClient::Finder>(zone.zone_finder));
+
+    // It'll complain if there is no NSEC3PARAM yet
+    EXPECT_THROW(finder->findNSEC3(Name("example.org"), false),
+                 DataSourceError);
+    // And enable NSEC3 in the zone.
+    this->current_accessor_->enableNSEC3();
+
+    // The rest is in the function, it is shared with in-memory tests
+    performNSEC3Test(*finder);
+}
+
 }
 }

+ 196 - 0
src/lib/datasrc/tests/faked_nsec3.cc

@@ -0,0 +1,196 @@
+// Copyright (C) 2011  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 "faked_nsec3.h"
+
+#include <dns/name.h>
+#include <testutils/dnsmessage_test.h>
+
+#include <map>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::testutils;
+
+namespace isc {
+namespace datasrc {
+namespace test {
+
+class TestNSEC3HashCreator::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 rdata::generic::NSEC3PARAM&) const {
+        return (true);
+    }
+    virtual bool match(const rdata::generic::NSEC3&) const {
+        return (true);
+    }
+};
+
+NSEC3Hash* TestNSEC3HashCreator::create(const rdata::generic::NSEC3PARAM&)
+    const
+{
+    return (new TestNSEC3Hash);
+}
+
+NSEC3Hash* TestNSEC3HashCreator::create(const rdata::generic::NSEC3&) const {
+    return (new TestNSEC3Hash);
+}
+
+void
+findNSEC3Check(bool expected_matched, uint8_t expected_labels,
+               const string& expected_closest,
+               const string& expected_next,
+               const ZoneFinder::FindNSEC3Result& result,
+               bool expected_sig)
+{
+    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);
+    actual_rrsets.push_back(result.closest_proof);
+    if (expected_sig) {
+        actual_rrsets.push_back(result.closest_proof->getRRsig());
+    }
+    rrsetsCheck(expected_closest, actual_rrsets.begin(),
+                actual_rrsets.end());
+
+    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());
+    }
+}
+
+void
+performNSEC3Test(ZoneFinder &finder) {
+    // Parameter validation: the query name must be in or below the zone
+    EXPECT_THROW(finder.findNSEC3(Name("example.com"), false), OutOfZone);
+    EXPECT_THROW(finder.findNSEC3(Name("org"), true), OutOfZone);
+
+    Name origin("example.org");
+    const string apex_nsec3_text = string(apex_hash) + ".example.org." +
+        string(nsec3_common);
+    const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+        string(nsec3_common);
+    const string w_nsec3_text = string(w_hash) + ".example.org." +
+        string(nsec3_common);
+    const string zzz_nsec3_text = string(zzz_hash) + ".example.org." +
+        string(nsec3_common);
+
+    // Apex name.  It should have a matching NSEC3.
+    {
+        SCOPED_TRACE("apex, non recursive mode");
+        findNSEC3Check(true, origin.getLabelCount(), apex_nsec3_text, "",
+                       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, "",
+                       finder.findNSEC3(origin, true));
+    }
+
+    // Non existent name (in the NSEC3 namespace -- the findNSEC3 does
+    // not look into the normal data).  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, "",
+                       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,
+                       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,
+                       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, "",
+                       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, "",
+                       finder.findNSEC3(largest_name, false));
+    }
+}
+
+}
+}
+}

+ 86 - 0
src/lib/datasrc/tests/faked_nsec3.h

@@ -0,0 +1,86 @@
+// Copyright (C) 2011  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 FAKED_NSEC3_H
+#define FAKED_NSEC3_H
+
+#include <datasrc/zone.h>
+
+#include <dns/nsec3hash.h>
+
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace datasrc {
+namespace test {
+
+//
+// (Faked) NSEC3 hash data.  Arbitrarily borrowed from RFC515 examples.
+//
+// Commonly used NSEC3 suffix.  It's incorrect to use it for all NSEC3s, but
+// doesn't matter for the purpose of our tests.
+const char* const nsec3_common = " 300 IN NSEC3 1 1 12 aabbccdd "
+    "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
+// Likewise, common RRSIG suffix for NSEC3s.
+const char* const nsec3_rrsig_common = " 300 IN RRSIG NSEC3 5 3 3600 "
+    "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
+
+// 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 isc::dns::NSEC3HashCreator {
+private:
+    class TestNSEC3Hash;
+public:
+    virtual isc::dns::NSEC3Hash* create(const
+                                        isc::dns::rdata::generic::NSEC3PARAM&)
+        const;
+    virtual isc::dns::NSEC3Hash* create(const isc::dns::rdata::generic::NSEC3&)
+        const;
+};
+
+// Check the result against expected values. It directly calls EXPECT_ macros
+void
+findNSEC3Check(bool expected_matched, uint8_t expected_labels,
+               const std::string& expected_closest,
+               const std::string& expected_next,
+               const isc::datasrc::ZoneFinder::FindNSEC3Result& result,
+               bool expected_sig = false);
+
+// Perform the shared part of NSEC3 test (shared between in-memory and database
+// tests).
+void
+performNSEC3Test(ZoneFinder &finder);
+
+}
+}
+}
+
+#endif

+ 4 - 177
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
+#include "faked_nsec3.h"
+
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
 #include <dns/masterload.h>
 #include <dns/masterload.h>
@@ -48,6 +50,7 @@ using namespace isc::dns::rdata;
 using namespace isc::datasrc;
 using namespace isc::datasrc;
 using namespace isc::testutils;
 using namespace isc::testutils;
 using boost::shared_ptr;
 using boost::shared_ptr;
+using namespace isc::datasrc::test;
 
 
 namespace {
 namespace {
 // Commonly used result codes (Who should write the prefix all the time)
 // Commonly used result codes (Who should write the prefix all the time)
@@ -302,72 +305,6 @@ textToRRset(const string& text_rrset, const RRClass& rrclass = RRClass::IN(),
     return (rrset);
     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
 /// \brief Test fixture for the InMemoryZoneFinder class
 class InMemoryZoneFinderTest : public ::testing::Test {
 class InMemoryZoneFinderTest : public ::testing::Test {
     // A straightforward pair of textual RR(set) and a RRsetPtr variable
     // A straightforward pair of textual RR(set) and a RRsetPtr variable
@@ -1707,52 +1644,6 @@ TEST_F(InMemoryZoneFinderTest, addbadRRsig) {
                  InMemoryZoneFinder::AddError);
                  InMemoryZoneFinder::AddError);
 }
 }
 
 
-//
-// (Faked) NSEC3 hash data.  Arbitrarily borrowed from RFC515 examples.
-//
-// Commonly used NSEC3 suffix.  It's incorrect to use it for all NSEC3s, but
-// doesn't matter for the purpose of our tests.
-const char* const nsec3_common = " 300 IN NSEC3 1 1 12 aabbccdd "
-    "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
-// Likewise, common RRSIG suffix for NSEC3s.
-const char* const nsec3_rrsig_common = " 300 IN RRSIG NSEC3 5 3 3600 "
-    "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
-
-void
-findNSEC3Check(bool expected_matched, uint8_t expected_labels,
-               const string& expected_closest,
-               const string& expected_next,
-               const ZoneFinder::FindNSEC3Result& result,
-               bool expected_sig = false)
-{
-    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);
-    actual_rrsets.push_back(result.closest_proof);
-    if (expected_sig) {
-        actual_rrsets.push_back(result.closest_proof->getRRsig());
-    }
-    rrsetsCheck(expected_closest, actual_rrsets.begin(),
-                actual_rrsets.end());
-
-    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) {
 TEST_F(InMemoryZoneFinderTest, addNSEC3) {
     // Set up the faked hash calculator.
     // Set up the faked hash calculator.
     setNSEC3HashCreator(&nsec3_hash_creator_);
     setNSEC3HashCreator(&nsec3_hash_creator_);
@@ -2041,71 +1932,7 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3) {
         string(nsec3_common);
         string(nsec3_common);
     EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(zzz_nsec3_text)));
     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), OutOfZone);
-    EXPECT_THROW(zone_finder_.findNSEC3(Name("org"), true), OutOfZone);
-
-    // 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));
-    }
+    performNSEC3Test(zone_finder_);
 }
 }
 
 
 TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {
 TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {