Browse Source

[master] Merge branch 'trac2420'

JINMEI Tatuya 12 years ago
parent
commit
6744c10095

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

@@ -27,6 +27,7 @@ libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
 libdatasrc_memory_la_SOURCES += zone_writer.h
 libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
 libdatasrc_memory_la_SOURCES += load_action.h
+libdatasrc_memory_la_SOURCES += util_internal.h
 
 nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
 

+ 2 - 2
src/lib/datasrc/memory/rdataset.cc

@@ -122,8 +122,8 @@ RdataSet::create(util::MemorySegment& mem_sgmt, RdataEncoder& encoder,
 }
 
 void
-RdataSet::destroy(util::MemorySegment& mem_sgmt, RRClass rrclass,
-                  RdataSet* rdataset)
+RdataSet::destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
+                  RRClass rrclass)
 {
     const size_t data_len =
         RdataReader(rrclass, rdataset->type,

+ 22 - 9
src/lib/datasrc/memory/rdataset.h

@@ -187,12 +187,12 @@ public:
     ///
     /// \param mem_sgmt The \c MemorySegment that allocated memory for
     /// \c node.
-    /// \param rrclass The RR class of the \c RdataSet to be destroyed.
     /// \param rdataset A non NULL pointer to a valid \c RdataSet object
+    /// \param rrclass The RR class of the \c RdataSet to be destroyed.
     /// that was originally created by the \c create() method (the behavior
     /// is undefined if this condition isn't met).
-    static void destroy(util::MemorySegment& mem_sgmt, dns::RRClass rrclass,
-                        RdataSet* rdataset);
+    static void destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
+                        dns::RRClass rrclass);
 
     /// \brief Find \c RdataSet of given RR type from a list (const version).
     ///
@@ -205,6 +205,11 @@ public:
     /// if not found in the entire list, it returns NULL.  The head pointer
     /// can be NULL, in which case this function will simply return NULL.
     ///
+    /// By default, this method ignores an RdataSet that only contains an
+    /// RRSIG (i.e., missing the covered RdataSet); if the optional
+    /// sigonly_ok parameter is explicitly set to true, it matches such
+    /// RdataSet and returns it if found.
+    ///
     /// \note This function is defined as a (static) class method to
     /// clarify its an operation for \c RdataSet objects and to make the
     /// name shorter.  But its implementation does not depend on private
@@ -215,10 +220,14 @@ public:
     /// \param rdata_head A pointer to \c RdataSet from which the search
     /// starts.  It can be NULL.
     /// \param type The RRType of \c RdataSet to find.
+    /// \param sigonly_ok Whether it should find an RdataSet that only has
+    /// RRSIG
     /// \return A pointer to the found \c RdataSet or NULL if none found.
     static const RdataSet*
-    find(const RdataSet* rdataset_head, const dns::RRType& type) {
-        return (find<const RdataSet>(rdataset_head, type));
+    find(const RdataSet* rdataset_head, const dns::RRType& type,
+         bool sigonly_ok = false)
+    {
+        return (find<const RdataSet>(rdataset_head, type, sigonly_ok));
     }
 
     /// \brief Find \c RdataSet of given RR type from a list (non const
@@ -227,8 +236,10 @@ public:
     /// This is similar to the const version, except it takes and returns non
     /// const pointers.
     static RdataSet*
-    find(RdataSet* rdataset_head, const dns::RRType& type) {
-        return (find<RdataSet>(rdataset_head, type));
+    find(RdataSet* rdataset_head, const dns::RRType& type,
+         bool sigonly_ok = false)
+    {
+        return (find<RdataSet>(rdataset_head, type, sigonly_ok));
     }
 
     typedef boost::interprocess::offset_ptr<RdataSet> RdataSetPtr;
@@ -347,12 +358,14 @@ private:
     // Shared by both mutable and immutable versions of find()
     template <typename RdataSetType>
     static RdataSetType*
-    find(RdataSetType* rdataset_head, const dns::RRType& type) {
+    find(RdataSetType* rdataset_head, const dns::RRType& type, bool sigonly_ok)
+    {
         for (RdataSetType* rdataset = rdataset_head;
              rdataset != NULL;
              rdataset = rdataset->getNext()) // use getNext() for efficiency
         {
-            if (rdataset->type == type) {
+            if (rdataset->type == type &&
+                (rdataset->getRdataCount() > 0 || sigonly_ok)) {
                 return (rdataset);
             }
         }

+ 57 - 0
src/lib/datasrc/memory/util_internal.h

@@ -0,0 +1,57 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MEMORY_UTIL_INTERNAL_H
+#define DATASRC_MEMORY_UTIL_INTERNAL_H 1
+
+#include <dns/rdataclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace detail {
+
+/// \brief Return the covered RR type of an RRSIG RRset.
+///
+/// This is a commonly used helper to extract the type covered field of an
+/// RRSIG RRset and return it in the form of an RRType object.
+///
+/// Normally, an empty RRSIG shouldn't be passed to this function, whether
+/// it comes from a master file or another data source iterator, but it could
+/// still happen in some buggy situations.  This function catches and rejects
+/// such cases.
+inline dns::RRType
+getCoveredType(const dns::ConstRRsetPtr& sig_rrset) {
+    dns::RdataIteratorPtr it = sig_rrset->getRdataIterator();
+    if (it->isLast()) {
+        isc_throw(isc::Unexpected,
+                  "Empty RRset is passed in-memory loader, name: "
+                  << sig_rrset->getName());
+    }
+    return (dynamic_cast<const dns::rdata::generic::RRSIG&>(it->getCurrent()).
+            typeCovered());
+}
+
+} // namespace detail
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_UTIL_INTERNAL_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 1
src/lib/datasrc/memory/zone_data.cc

@@ -49,7 +49,7 @@ rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt,
          rdataset = rdataset_next)
     {
         rdataset_next = rdataset->getNext();
-        RdataSet::destroy(*mem_sgmt, rrclass, rdataset);
+        RdataSet::destroy(*mem_sgmt, rdataset, rrclass);
     }
 }
 

+ 7 - 24
src/lib/datasrc/memory/zone_data_loader.cc

@@ -16,6 +16,7 @@
 #include <datasrc/memory/zone_data_updater.h>
 #include <datasrc/memory/logger.h>
 #include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/memory/util_internal.h>
 
 #include <dns/rdataclass.h>
 #include <dns/rrset.h>
@@ -35,6 +36,7 @@ namespace datasrc {
 namespace memory {
 
 using detail::SegmentObjectHolder;
+using detail::getCoveredType;
 
 namespace { // unnamed namespace
 
@@ -75,8 +77,6 @@ private:
     typedef NodeRRsets::value_type NodeRRsetsVal;
 
     // A helper to identify the covered type of an RRSIG.
-    static isc::dns::RRType getCoveredType
-        (const isc::dns::ConstRRsetPtr& sig_rrset);
     const isc::dns::Name& getCurrentName() const;
 
 private:
@@ -126,34 +126,17 @@ ZoneDataLoader::flushNodeRRsets() {
         updater_.add(val.second, sig_rrset);
     }
 
-    // Right now, we don't accept RRSIG without covered RRsets (this
-    // should eventually allowed, but to do so we'll need to update the
-    // finder).
-    if (!node_rrsigsets_.empty()) {
-        isc_throw(ZoneDataUpdater::AddError,
-                  "RRSIG is added without covered RRset for "
-                  << getCurrentName());
+    // Normally rrsigsets map should be empty at this point, but it's still
+    // possible that an RRSIG that don't has covered RRset is added; they
+    // still remain in the map.  We add them to the zone separately.
+    BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) {
+        updater_.add(ConstRRsetPtr(), val.second);
     }
 
     node_rrsets_.clear();
     node_rrsigsets_.clear();
 }
 
-RRType
-ZoneDataLoader::getCoveredType(const ConstRRsetPtr& sig_rrset) {
-    RdataIteratorPtr it = sig_rrset->getRdataIterator();
-    // Empty RRSIG shouldn't be passed either via a master file or
-    // another data source iterator, but it could still happen if the
-    // iterator has a bug.  We catch and reject such cases.
-    if (it->isLast()) {
-        isc_throw(isc::Unexpected,
-                  "Empty RRset is passed in-memory loader, name: "
-                  << sig_rrset->getName());
-    }
-    return (dynamic_cast<const generic::RRSIG&>(it->getCurrent()).
-            typeCovered());
-}
-
 const Name&
 ZoneDataLoader::getCurrentName() const {
     if (!node_rrsets_.empty()) {

+ 64 - 30
src/lib/datasrc/memory/zone_data_updater.cc

@@ -12,12 +12,18 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <exceptions/exceptions.h>
+
 #include <datasrc/memory/zone_data_updater.h>
 #include <datasrc/memory/logger.h>
+#include <datasrc/memory/util_internal.h>
 #include <datasrc/zone.h>
 
 #include <dns/rdataclass.h>
 
+#include <cassert>
+#include <string>
+
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
@@ -25,6 +31,8 @@ namespace isc {
 namespace datasrc {
 namespace memory {
 
+using detail::getCoveredType;
+
 void
 ZoneDataUpdater::addWildcards(const Name& name) {
     Name wname(name);
@@ -99,9 +107,7 @@ ZoneDataUpdater::contextCheck(const AbstractRRset& rrset,
 
 void
 ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
-    if (!rrset) {
-        isc_throw(NullRRset, "The rrset provided is NULL");
-    }
+    assert(rrset);
 
     if (rrset->getRdataCount() == 0) {
         isc_throw(AddError,
@@ -241,31 +247,46 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
 }
 
 void
-ZoneDataUpdater::addNSEC3(const ConstRRsetPtr rrset, const ConstRRsetPtr rrsig)
+ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
+                          const ConstRRsetPtr rrsig)
 {
-    setupNSEC3<generic::NSEC3>(rrset);
+    if (rrset) {
+        setupNSEC3<generic::NSEC3>(rrset);
+    }
 
     NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+    if (nsec3_data == NULL) {
+        // This is some tricky case: an RRSIG for NSEC3 is given without the
+        // covered NSEC3, and we don't even know any NSEC3 related data.
+        // This situation is not necessarily broken, but in our current
+        // implementation it's very difficult to deal with.  So we reject it;
+        // hopefully this case shouldn't happen in practice, at least unless
+        // zone is really broken.
+        assert(!rrset);
+        isc_throw(NotImplemented,
+                  "RRSIG for NSEC3 cannot be added - no known NSEC3 data");
+    }
 
     ZoneNode* node;
-    nsec3_data->insertName(mem_sgmt_, rrset->getName(), &node);
+    nsec3_data->insertName(mem_sgmt_, name, &node);
 
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig);
     RdataSet* old_rdataset = node->setData(rdataset);
     if (old_rdataset != NULL) {
-        RdataSet::destroy(mem_sgmt_, rrclass_, old_rdataset);
+        RdataSet::destroy(mem_sgmt_, old_rdataset, rrclass_);
     }
 }
 
 void
-ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
+ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
+                             const ConstRRsetPtr rrset,
                              const ConstRRsetPtr rrsig)
 {
-    if (rrset->getType() == RRType::NSEC3()) {
-        addNSEC3(rrset, rrsig);
+    if (rrtype == RRType::NSEC3()) {
+        addNSEC3(name, rrset, rrsig);
     } else {
         ZoneNode* node;
-        zone_data_.insertName(mem_sgmt_, rrset->getName(), &node);
+        zone_data_.insertName(mem_sgmt_, name, &node);
 
         RdataSet* rdataset_head = node->getData();
 
@@ -273,13 +294,14 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
         // fails and the exception is thrown, it may break strong
         // exception guarantee.  At the moment we prefer code simplicity
         // and don't bother to introduce complicated recovery code.
-        contextCheck(*rrset, rdataset_head);
+        if (rrset) { // this check is only for covered RRset, not RRSIG
+            contextCheck(*rrset, rdataset_head);
+        }
 
-        if (RdataSet::find(rdataset_head, rrset->getType()) != NULL) {
+        if (RdataSet::find(rdataset_head, rrtype, true) != NULL) {
             isc_throw(AddError,
                       "RRset of the type already exists: "
-                      << rrset->getName() << " (type: "
-                      << rrset->getType() << ")");
+                      << name << " (type: " << rrtype << ")");
         }
 
         RdataSet* rdataset_new = RdataSet::create(mem_sgmt_, encoder_,
@@ -289,23 +311,25 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
 
         // Ok, we just put it in.
 
+        // Convenient (and more efficient) shortcut to check RRsets at origin
+        const bool is_origin = (node == zone_data_.getOriginNode());
+
         // If this RRset creates a zone cut at this node, mark the node
-        // indicating the need for callback in find().
-        if (rrset->getType() == RRType::NS() &&
-            rrset->getName() != zone_name_) {
+        // indicating the need for callback in find().  Note that we do this
+        // only when non RRSIG RRset of that type is added.
+        if (rrset && rrtype == RRType::NS() && !is_origin) {
             node->setFlag(ZoneNode::FLAG_CALLBACK);
             // If it is DNAME, we have a callback as well here
-        } else if (rrset->getType() == RRType::DNAME()) {
+        } else if (rrset && rrtype == RRType::DNAME()) {
             node->setFlag(ZoneNode::FLAG_CALLBACK);
         }
 
         // If we've added NSEC3PARAM at zone origin, set up NSEC3
         // specific data or check consistency with already set up
         // parameters.
-        if (rrset->getType() == RRType::NSEC3PARAM() &&
-            rrset->getName() == zone_name_) {
+        if (rrset && rrtype == RRType::NSEC3PARAM() && is_origin) {
             setupNSEC3<generic::NSEC3PARAM>(rrset);
-        } else if (rrset->getType() == RRType::NSEC()) {
+        } else if (rrset && rrtype == RRType::NSEC() && is_origin) {
             // If it is NSEC signed zone, we mark the zone as signed
             // (conceptually "signed" is a broader notion but our
             // current zone finder implementation regards "signed" as
@@ -319,27 +343,37 @@ void
 ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
                      const ConstRRsetPtr& sig_rrset)
 {
-    // Validate input.  This will cause an exception to be thrown if the
-    // input RRset is empty.
-    validate(rrset);
+    // Validate input.
+    if (!rrset && !sig_rrset) {
+        isc_throw(NullRRset,
+                  "ZoneDataUpdater::add is given 2 NULL pointers");
+    }
+    if (rrset) {
+        validate(rrset);
+    }
     if (sig_rrset) {
         validate(sig_rrset);
     }
 
+    const Name& name = rrset ? rrset->getName() : sig_rrset->getName();
+    const RRType& rrtype = rrset ? rrset->getType() :
+        getCoveredType(sig_rrset);
+
     // OK, can add the RRset.
-    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).
-        arg(rrset->getName()).arg(rrset->getType()).arg(zone_name_);
+    LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).arg(name).
+        arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")").
+        arg(zone_name_);
 
     // Add wildcards possibly contained in the owner name to the domain
     // tree.  This can only happen for the normal (non-NSEC3) tree.
     // Note: this can throw an exception, breaking strong exception
     // guarantee.  (see also the note for the call to contextCheck()
     // above).
-    if (rrset->getType() != RRType::NSEC3()) {
-        addWildcards(rrset->getName());
+    if (rrtype != RRType::NSEC3()) {
+        addWildcards(name);
     }
 
-    addRdataSet(rrset, sig_rrset);
+    addRdataSet(name, rrtype, rrset, sig_rrset);
 }
 
 } // namespace memory

+ 31 - 6
src/lib/datasrc/memory/zone_data_updater.h

@@ -110,10 +110,32 @@ public:
     /// populated with the record data and added to the ZoneData for the
     /// name in the RRset.
     ///
-    /// This method throws an \c NullRRset exception (see above) if
-    /// \c rrset is empty. It throws \c AddError if any of a variety of
-    /// validation checks fail for the \c rrset and its associated
-    /// \c sig_rrset.
+    /// At least one of \c rrset or \c sig_rrset must be non NULL.
+    /// \c sig_rrset can be reasonably NULL when \c rrset is not signed in
+    /// the zone; it's unusual that \c rrset is NULL, but is still possible
+    /// if these RRsets are given separately to the loader, or if even the
+    /// zone is half broken and really contains an RRSIG that doesn't have
+    /// any covered RRset.  This implementation supports these cases (but
+    /// see the note below).
+    ///
+    /// There is one tricky case: Due to a limitation of the current
+    /// implementation, it cannot accept an RRSIG for NSEC3 without the covered
+    /// NSEC3, unless at least one NSEC3 or NSEC3PARAM has been added.
+    /// In this case an isc::NotImplemented exception will be thrown.  It
+    /// should be very rare in practice, and hopefully wouldn't be a real
+    /// issue.
+    ///
+    /// \note Due to limitations of the current implementation, if a
+    /// (non RRSIG) RRset and its RRSIG are added separately in different
+    /// calls to this method, the second attempt will be rejected due to
+    /// an \c AddError exception.  This will be loosened in Trac
+    /// ticket #2441.
+    ///
+    /// \throw NullRRset Both \c rrset and sig_rrset is NULL
+    /// \throw AddError any of a variety of validation checks fail for the
+    /// \c rrset and its associated \c sig_rrset.
+    /// \throw NotImplemented RRSIG for NSEC3 cannot be added due to internal
+    /// restriction.
     ///
     /// \param rrset The RRset to be added.
     /// \param sig_rrset An associated RRSIG RRset for the \c rrset. It
@@ -152,9 +174,12 @@ private:
     const isc::dns::NSEC3Hash* getNSEC3Hash();
     template <typename T>
     void setupNSEC3(const isc::dns::ConstRRsetPtr rrset);
-    void addNSEC3(const isc::dns::ConstRRsetPtr rrset,
+    void addNSEC3(const isc::dns::Name& name,
+                  const isc::dns::ConstRRsetPtr rrset,
                   const isc::dns::ConstRRsetPtr rrsig);
-    void addRdataSet(const isc::dns::ConstRRsetPtr rrset,
+    void addRdataSet(const isc::dns::Name& name,
+                     const isc::dns::RRType& rrtype,
+                     const isc::dns::ConstRRsetPtr rrset,
                      const isc::dns::ConstRRsetPtr rrsig);
 
     util::MemorySegment& mem_sgmt_;

+ 12 - 1
src/lib/datasrc/memory/zone_finder.cc

@@ -216,6 +216,14 @@ createNSEC3RRset(const ZoneNode* node, const RRClass& rrclass) {
      assert(rdataset != NULL);
      assert(rdataset->type == RRType::NSEC3());
 
+     // Check for the rare case of RRSIG-only record; in theory it could exist
+     // but we simply consider it broken for NSEC3.
+     if (rdataset->getRdataCount() == 0) {
+         uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+         isc_throw(DataSourceError, "Broken zone: RRSIG-only NSEC3 record at "
+                   << node->getAbsoluteLabels(labels_buf) << "/" << rrclass);
+     }
+
     // Create the RRset.  Note the DNSSEC flag: NSEC3 implies DNSSEC.
     return (createTreeNodeRRset(node, rdataset, rrclass,
                                 ZoneFinder::FIND_DNSSEC));
@@ -627,7 +635,10 @@ private:
             // This can be a bit more optimized, but unless we have many
             // requested types the effect is probably marginal.  For now we
             // keep it simple.
-            if (std::find(type_beg, type_end, rdset->type) != type_end) {
+            // Check for getRdataCount is necessary not to include RRSIG-only
+            // records accidentally (should be rare, but possible).
+            if (std::find(type_beg, type_end, rdset->type) != type_end &&
+                rdset->getRdataCount() > 0) {
                 result->push_back(createTreeNodeRRset(node, rdset, rrclass_,
                                                       options, real_name));
             }

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

@@ -94,6 +94,7 @@ endif
 
 EXTRA_DIST =  testdata/brokendb.sqlite3
 EXTRA_DIST += testdata/contexttest.zone
+EXTRA_DIST += testdata/contexttest-almost-obsolete.zone
 EXTRA_DIST += testdata/diffs.sqlite3
 EXTRA_DIST += testdata/duplicate_rrset.zone
 EXTRA_DIST += testdata/example2.com

+ 2 - 0
src/lib/datasrc/tests/memory/Makefile.am

@@ -32,6 +32,8 @@ run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
 run_unittests_SOURCES += memory_segment_test.h
 run_unittests_SOURCES += segment_object_holder_unittest.cc
 run_unittests_SOURCES += memory_client_unittest.cc
+run_unittests_SOURCES += zone_data_loader_unittest.cc
+run_unittests_SOURCES += zone_data_updater_unittest.cc
 run_unittests_SOURCES += zone_table_segment_test.h
 run_unittests_SOURCES += zone_table_segment_unittest.cc
 run_unittests_SOURCES += zone_writer_unittest.cc

+ 0 - 10
src/lib/datasrc/tests/memory/memory_client_unittest.cc

@@ -576,16 +576,6 @@ TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
     // Teardown checks for memory segment leaks
 }
 
-TEST_F(MemoryClientTest, loadRRSIGFollowsNothing) {
-    // This causes the situation where an RRSIG is added without a covered
-    // RRset.  Such cases are currently rejected.
-    EXPECT_THROW(client_->load(Name("example.org"),
-                               TEST_DATA_DIR
-                               "/example.org-rrsig-follows-nothing.zone"),
-                 ZoneDataUpdater::AddError);
-    // Teardown checks for memory segment leaks
-}
-
 TEST_F(MemoryClientTest, loadRRSIGs) {
     client_->load(Name("example.org"),
                   TEST_DATA_DIR "/example.org-rrsigs.zone");

+ 66 - 9
src/lib/datasrc/tests/memory/rdataset_unittest.cc

@@ -24,6 +24,7 @@
 #include <dns/rrtype.h>
 #include <dns/rrttl.h>
 
+#include <datasrc/memory/segment_object_holder.h>
 #include <datasrc/memory/rdata_serialization.h>
 #include <datasrc/memory/rdataset.h>
 
@@ -39,6 +40,7 @@ using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::datasrc::memory;
 using namespace isc::testutils;
+using isc::datasrc::memory::detail::SegmentObjectHolder;
 using boost::lexical_cast;
 
 namespace {
@@ -112,7 +114,7 @@ TEST_F(RdataSetTest, create) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           ConstRRsetPtr());
     checkRdataSet(*rdataset, true, false);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 TEST_F(RdataSetTest, getNext) {
@@ -131,7 +133,62 @@ TEST_F(RdataSetTest, getNext) {
     rdataset->next = rdataset;
     EXPECT_EQ(rdataset, static_cast<const RdataSet*>(rdataset)->getNext());
 
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
+}
+
+TEST_F(RdataSetTest, find) {
+    // Create some RdataSets and make a chain of them.
+    SegmentObjectHolder<RdataSet, RRClass> holder1(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()),
+        RRClass::IN());
+    ConstRRsetPtr aaaa_rrset =
+        textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1");
+    SegmentObjectHolder<RdataSet, RRClass> holder2(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()),
+        RRClass::IN());
+    ConstRRsetPtr sigonly_rrset =
+        textToRRset("www.example.com. 1076895760 IN RRSIG "
+                    "TXT 5 2 3600 20120814220826 20120715220826 "
+                    "1234 example.com. FAKE");
+    SegmentObjectHolder<RdataSet, RRClass> holder3(
+        mem_sgmt_,
+        RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset),
+        RRClass::IN());
+
+    RdataSet* rdataset_a = holder1.get();
+    RdataSet* rdataset_aaaa = holder2.get();
+    RdataSet* rdataset_sigonly = holder3.get();
+    RdataSet* rdataset_null = NULL;
+    rdataset_a->next = rdataset_aaaa;
+    rdataset_aaaa->next = rdataset_sigonly;
+
+    // If a non-RRSIG part of rdataset exists for the given type, it will be
+    // returned regardless of the value of sigonly_ok.  If it's RRSIG-only
+    // rdataset, it returns non NULL iff sigonly_ok is explicitly set to true.
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA()));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), true));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), false));
+
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT()));
+    EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a, RRType::TXT(),
+                                               true));
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT(), false));
+
+    // Same tests for the const version of find().
+    const RdataSet* rdataset_a_const = holder1.get();
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA()));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
+                                            true));
+    EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
+                                            false));
+
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT()));
+    EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a_const, RRType::TXT(),
+                                               true));
+    EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT(),
+                                            false));
 }
 
 // A helper function to create an RRset containing the given number of
@@ -154,7 +211,7 @@ TEST_F(RdataSetTest, createManyRRs) {
                                           ConstRRsetPtr());
     EXPECT_EQ(8191, rdataset->getRdataCount());
     EXPECT_EQ(0, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Exceeding that will result in an exception.
     EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_,
@@ -173,7 +230,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           rrsig_rrset_);
     checkRdataSet(*rdataset, true, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Unusual case: TTL doesn't match.  This implementation accepts that,
     // using the TTL of the covered RRset.
@@ -183,7 +240,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
                                    "20120715220826 1234 example.com. FAKE"));
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_badttl);
     checkRdataSet(*rdataset, true, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 // A helper function to create an RRSIG RRset containing the given number of
@@ -218,21 +275,21 @@ TEST_F(RdataSetTest, createManyRRSIGs) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                           getRRSIGWithRdataCount(7));
     EXPECT_EQ(7, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // 8 would cause overflow in the normal 3-bit field if there were no extra
     // count field.
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                 getRRSIGWithRdataCount(8));
     EXPECT_EQ(8, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Up to 2^16-1 RRSIGs are allowed (although that would be useless
     // in practice)
     rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
                                 getRRSIGWithRdataCount(65535));
     EXPECT_EQ(65535, rdataset->getSigRdataCount());
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 
     // Exceeding this limit will result in an exception.
     EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
@@ -250,7 +307,7 @@ TEST_F(RdataSetTest, createWithRRSIGOnly) {
     RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(),
                                           rrsig_rrset_);
     checkRdataSet(*rdataset, false, true);
-    RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+    RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
 }
 
 TEST_F(RdataSetTest, badCeate) {

+ 65 - 0
src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc

@@ -0,0 +1,65 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/memory/zone_data_loader.h>
+
+#include "memory_segment_test.h"
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class ZoneDataLoaderTest : public ::testing::Test {
+protected:
+    ZoneDataLoaderTest() : zclass_(RRClass::IN()), zone_data_(NULL) {}
+    void TearDown() {
+        if (zone_data_ != NULL) {
+            ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        }
+        EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
+    }
+    const RRClass zclass_;
+    test::MemorySegmentTest mem_sgmt_;
+    ZoneData* zone_data_;
+};
+
+TEST_F(ZoneDataLoaderTest, loadRRSIGFollowsNothing) {
+    // This causes the situation where an RRSIG is added without a covered
+    // RRset.  It will be accepted, and corresponding "sig-only" rdata will
+    // be created.
+    zone_data_ = loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+                              TEST_DATA_DIR
+                              "/example.org-rrsig-follows-nothing.zone");
+    ZoneNode* node = NULL;
+    zone_data_->insertName(mem_sgmt_, Name("ns1.example.org"), &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    EXPECT_EQ(RRType::A(), rdset->type); // there should be only 1 data here
+    EXPECT_EQ(0, rdset->getRdataCount()); // no RDATA
+    EXPECT_EQ(1, rdset->getSigRdataCount()); // but 1 SIG
+
+    // Teardown checks for memory segment leaks
+}
+
+}

+ 208 - 0
src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc

@@ -0,0 +1,208 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <testutils/dnsmessage_test.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_updater.h>
+
+#include "memory_segment_test.h"
+
+#include <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cassert>
+
+using isc::testutils::textToRRset;
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class ZoneDataUpdaterTest : public ::testing::Test {
+protected:
+    ZoneDataUpdaterTest() :
+        zname_("example.org"), zclass_(RRClass::IN()),
+        zone_data_(ZoneData::create(mem_sgmt_, zname_)),
+        updater_(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, *zone_data_))
+    {}
+
+    ~ZoneDataUpdaterTest() {
+        if (zone_data_ != NULL) {
+            ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        }
+        if (!mem_sgmt_.allMemoryDeallocated()) {
+            ADD_FAILURE() << "Memory leak detected";
+        }
+    }
+
+    void clearZoneData() {
+        assert(zone_data_ != NULL);
+        ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+        zone_data_ = ZoneData::create(mem_sgmt_, zname_);
+        updater_.reset(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_,
+                                           *zone_data_));
+    }
+
+    const Name zname_;
+    const RRClass zclass_;
+    test::MemorySegmentTest mem_sgmt_;
+    ZoneData* zone_data_;
+    boost::scoped_ptr<ZoneDataUpdater> updater_;
+};
+
+TEST_F(ZoneDataUpdaterTest, bothNull) {
+    // At least either covered RRset or RRSIG must be non NULL.
+    EXPECT_THROW(updater_->add(ConstRRsetPtr(), ConstRRsetPtr()),
+                 ZoneDataUpdater::NullRRset);
+}
+
+ZoneNode*
+getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
+        ZoneData* zone_data)
+{
+    ZoneNode* node = NULL;
+    zone_data->insertName(mem_sgmt, name, &node);
+    EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
+    return (node);
+}
+
+TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
+    // RRSIG that doesn't have covered RRset can be added.  The resulting
+    // rdataset won't have "normal" RDATA but sig RDATA.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "www.example.org. 3600 IN RRSIG A 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    ZoneNode* node = getNode(mem_sgmt_, Name("www.example.org"), zone_data_);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    rdset = RdataSet::find(rdset, RRType::A(), true);
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    EXPECT_EQ(0, rdset->getRdataCount());
+    EXPECT_EQ(1, rdset->getSigRdataCount());
+
+    // The RRSIG covering A prohibits an actual A RRset from being added.
+    // This should be loosened in future version, but we check the current
+    // behavior.
+    EXPECT_THROW(updater_->add(
+                     textToRRset("www.example.org. 3600 IN A 192.0.2.1"),
+                     ConstRRsetPtr()), ZoneDataUpdater::AddError);
+
+    // The special "wildcarding" node mark should be added for the RRSIG-only
+    // case, too.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "*.wild.example.org. 3600 IN RRSIG A 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("wild.example.org"), zone_data_);
+    EXPECT_TRUE(node->getFlag(ZoneData::WILDCARD_NODE));
+
+    // Simply adding RRSIG covering (delegating NS) shouldn't enable callback
+    // in search.
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "child.example.org. 3600 IN RRSIG NS 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("child.example.org"), zone_data_);
+    EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
+
+    // Same for DNAME
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "dname.example.org. 3600 IN RRSIG DNAME 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    node = getNode(mem_sgmt_, Name("dname.example.org"), zone_data_);
+    EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
+
+    // Likewise, RRSIG for NSEC3PARAM alone shouldn't make the zone
+    // "NSEC3-signed".
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_FALSE(zone_data_->isNSEC3Signed());
+
+    // And same for (RRSIG for) NSEC and "is signed".
+    updater_->add(ConstRRsetPtr(), textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_FALSE(zone_data_->isSigned());
+}
+
+// Commonly used checks for rrsigForNSEC3Only
+void
+checkNSEC3Rdata(isc::util::MemorySegment& mem_sgmt, const Name& name,
+                ZoneData* zone_data)
+{
+    ZoneNode* node = NULL;
+    zone_data->getNSEC3Data()->insertName(mem_sgmt, name, &node);
+    ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+    const RdataSet* rdset = node->getData();
+    ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+    ASSERT_EQ(RRType::NSEC3(), rdset->type);
+    EXPECT_EQ(0, rdset->getRdataCount());
+    EXPECT_EQ(1, rdset->getSigRdataCount());
+}
+
+TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
+    // Adding only RRSIG covering NSEC3 is tricky.  It should go to the
+    // separate NSEC3 tree, but the separate space is only created when
+    // NSEC3 or NSEC3PARAM is added.  So, in many cases RRSIG-only is allowed,
+    // but if no NSEC3 or NSEC3PARAM has been added it will be rejected.
+
+    // Below we use abnormal owner names and RDATA for NSEC3s for brevity,
+    // but that doesn't matter for this test.
+
+    // Add NSEC3PARAM, then RRSIG-only, which is okay.
+    updater_->add(textToRRset(
+                      "example.org. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD"),
+                  textToRRset(
+                      "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    EXPECT_TRUE(zone_data_->isNSEC3Signed());
+    updater_->add(ConstRRsetPtr(),
+                  textToRRset(
+                      "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+
+    // Clear the current content of zone, then add NSEC3
+    clearZoneData();
+    updater_->add(textToRRset(
+                      "AABB.example.org. 3600 IN NSEC3 1 0 10 AA 00000000 A"),
+                  textToRRset(
+                      "AABB.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    updater_->add(ConstRRsetPtr(),
+                  textToRRset(
+                      "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                      "20150420235959 20051021000000 1 example.org. FAKE"));
+    checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+
+    // If we add only RRSIG without any NSEC3 related data beforehand,
+    // it will be rejected; it's a limitation of the current implementation.
+    clearZoneData();
+    EXPECT_THROW(updater_->add(
+                     ConstRRsetPtr(),
+                     textToRRset(
+                         "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+                         "20150420235959 20051021000000 1 example.org. FAKE")),
+                 isc::NotImplemented);
+}
+
+}

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

@@ -1358,6 +1358,74 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {
                  DataSourceError);
 }
 
+TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
+    // Make the zone "NSEC signed"
+    addToZoneData(rr_nsec_);
+    const ZoneFinder::FindResultFlags expected_flags =
+        ZoneFinder::RESULT_NSEC_SIGNED;
+
+    // Add A for ns.example.org, and RRSIG-only covering TXT for the same name.
+    // query for the TXT should result in NXRRSET.
+    addToZoneData(rr_ns_a_);
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
+                     "20120715220826 1234 example.com. FAKE"));
+    findTest(Name("ns.example.org"), RRType::TXT(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+
+    // Add RRSIG-only covering NSEC.  This shouldn't be returned when NSEC is
+    // requested, whether it's for NXRRSET or NXDOMAIN
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    // The added RRSIG for NSEC could be used for NXRRSET but shouldn't
+    findTest(Name("ns.example.org"), RRType::TXT(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+             expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
+    // The added RRSIG for NSEC could be used for NXDOMAIN but shouldn't
+    findTest(Name("nz.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, rr_nsec_,
+             expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
+
+    // RRSIG-only CNAME shouldn't be accidentally confused with real CNAME.
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("nocname.example.org"), RRType::A(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+
+    // RRSIG-only for NS wouldn't invoke delegation anyway, but we check this
+    // case explicitly.
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("nodelegation.example.org"), RRType::A(),
+             ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+    findTest(Name("www.nodelegation.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+
+    // Same for RRSIG-only for DNAME
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    findTest(Name("www.nodname.example.org"), RRType::A(),
+             ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+    // If we have a delegation NS at this node, it will be a bit trickier,
+    // because the zonecut processing actually takes place at the node.
+    // But the RRSIG-only for DNAME shouldn't confuse the process and the NS
+    // should win.
+    ConstRRsetPtr ns_rrset =
+        textToRRset("nodname.example.org. 300 IN NS ns.nodname.example.org.");
+    addToZoneData(ns_rrset);
+    findTest(Name("www.nodname.example.org"), RRType::A(),
+             ZoneFinder::DELEGATION, true, ns_rrset);
+}
+
 /// \brief NSEC3 specific tests fixture for the InMemoryZoneFinder class
 class InMemoryZoneFinderNSEC3Test : public InMemoryZoneFinderTest {
 public:
@@ -1481,4 +1549,17 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3Walk) {
         }
     }
 }
+
+TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
+    // add an RRSIG-only NSEC3 to the NSEC3 space, and try to find it; it
+    // should result in an exception.
+    const string n8_hash = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
+    updater_.add(ConstRRsetPtr(),
+                 textToRRset(
+                     n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
+                     "20120814220826 20120715220826 1234 example.com. FAKE"));
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("n8.example.org"), false),
+                 DataSourceError);
+}
+
 }

+ 1 - 1
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -1219,7 +1219,7 @@ TEST_F(InMemoryZoneFinderTest, loadFromIterator) {
     // purpose of this test, so it should just succeed.
     db_client = unittest::createSQLite3Client(
         class_, origin_, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied",
-        TEST_DATA_DIR "/contexttest.zone");
+        TEST_DATA_DIR "/contexttest-almost-obsolete.zone");
     zone_finder_.load(*db_client->getIterator(origin_));
 
     // just checking a couple of RRs in the new version of zone.

+ 85 - 0
src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone

@@ -0,0 +1,85 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA	ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+example.org.			      3600 IN NS	ns1.example.org.
+example.org.			      3600 IN NS	ns2.example.org.
+example.org.			      3600 IN RRSIG	NS 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+example.org.			      3600 IN MX	1 mx1.example.org.
+example.org.			      3600 IN MX	2 mx2.example.org.
+example.org.			      3600 IN MX	3 mx.a.example.org.
+
+ns1.example.org.		      3600 IN A		192.0.2.1
+ns1.example.org.		      3600 IN RRSIG	A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+ns1.example.org.		      3600 IN AAAA	2001:db8::1
+ns1.example.org.		      3600 IN RRSIG	AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+ns2.example.org.		      3600 IN A		192.0.2.2
+ns2.example.org.		      3600 IN TXT	"text data"
+
+mx1.example.org.		      3600 IN A		192.0.2.10
+mx2.example.org.		      3600 IN AAAA	2001:db8::10
+
+;; delegation
+a.example.org.			      3600 IN NS	ns1.a.example.org.
+a.example.org.			      3600 IN NS	ns2.a.example.org.
+a.example.org.			      3600 IN NS	ns.example.com.
+
+ns1.a.example.org.		      3600 IN A		192.0.2.5
+ns2.a.example.org.		      3600 IN A		192.0.2.6
+ns2.a.example.org.		      3600 IN AAAA	2001:db8::6
+mx.a.example.org.		      3600 IN A		192.0.2.7
+
+;; delegation, one of its NS names is at zone cut.
+b.example.org.			      3600 IN NS	ns.b.example.org.
+b.example.org.			      3600 IN NS	b.example.org.
+b.example.org.			      3600 IN AAAA	2001:db8::8
+
+ns.b.example.org.		      3600 IN A		192.0.2.9
+
+;; The MX name is at a zone cut.  shouldn't be included in the
+;; additional section.
+mxatcut.example.org.		      3600 IN MX	1 b.example.org.
+
+;; delegation, one of its NS names is under a DNAME delegation point;
+;; another is at that point; and yet another is under DNAME below a
+;; zone cut.
+c.example.org. 	      	      3600 IN NS	ns.dname.example.org.
+c.example.org. 	      	      3600 IN NS	dname.example.org.
+c.example.org.      	      3600 IN NS	ns.deepdname.example.org.
+ns.dname.example.org.		      3600 IN A		192.0.2.11
+dname.example.org.		      3600 IN A		192.0.2.12
+ns.deepdname.example.org.	      3600 IN AAAA	2001:db8::9
+
+;; delegation, one of its NS name is at an empty non terminal.
+d.example.org. 	      	      3600 IN NS	ns.empty.example.org.
+d.example.org. 	      	      3600 IN NS	ns1.example.org.
+;; by adding these two we can create an empty RB node for
+;; ns.empty.example.org in the in-memory zone
+foo.ns.empty.example.org.     3600 IN A		192.0.2.13
+bar.ns.empty.example.org.     3600 IN A		192.0.2.14
+
+;; delegation; the NS name matches a wildcard (and there's no exact
+;; match).  One of the NS names matches an empty wildcard node, for
+;; which no additional record should be provided (or any other
+;; disruption should happen).
+e.example.org. 	      	      3600 IN NS	ns.wild.example.org.
+e.example.org. 	      	      3600 IN NS	ns.emptywild.example.org.
+e.example.org. 	      	      3600 IN NS	ns2.example.org.
+*.wild.example.org.	      3600 IN A		192.0.2.15
+a.*.emptywild.example.org.    3600 IN AAAA	2001:db8::2
+
+;; additional for an answer RRset (MX) as a result of wildcard
+;; expansion
+*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
+
+;; the owner name of additional for an answer RRset (MX) has DNAME
+dnamemx.example.org. 3600 IN MX 1 dname.example.org.
+
+;; CNAME
+alias.example.org. 3600 IN CNAME cname.example.org.
+
+;; DNAME
+dname.example.org. 3600 IN DNAME dname.example.com.
+
+;; DNAME under a NS (strange one)
+deepdname.c.example.org. 3600 IN DNAME deepdname.example.com.

+ 7 - 0
src/lib/datasrc/tests/testdata/contexttest.zone

@@ -68,6 +68,13 @@ e.example.org. 	      	      3600 IN NS	ns2.example.org.
 *.wild.example.org.	      3600 IN A		192.0.2.15
 a.*.emptywild.example.org.    3600 IN AAAA	2001:db8::2
 
+;; One of the additional records actually only has RRSIG, which should
+;; be ignored.
+f.example.org. 	      	      3600 IN MX 5 mx1.f.example.org.
+f.example.org. 	      	      3600 IN MX 10 mx2.f.example.org.
+mx1.f.example.org.     	      3600 IN RRSIG A 5 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+mx2.f.example.org.     	      3600 IN A 192.0.2.16
+
 ;; additional for an answer RRset (MX) as a result of wildcard
 ;; expansion
 *.wildmx.example.org. 3600 IN MX 1 mx1.example.org.

+ 12 - 0
src/lib/datasrc/tests/zone_finder_context_unittest.cc

@@ -418,4 +418,16 @@ TEST_P(ZoneFinderContextTest, getAdditionalForAny) {
                 result_sets_.begin(), result_sets_.end());
 }
 
+TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
+    // This has two MX records, but type-A for one of them only has RRSIG.
+    // It shouldn't be contained in the result.
+    ZoneFinderContextPtr ctx = finder_->find(Name("f.example.org"),
+                                             RRType::MX(),
+                                             ZoneFinder::FIND_DNSSEC);
+    EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+    ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+    rrsetsCheck("mx2.f.example.org. 3600 IN A 192.0.2.16\n",
+                result_sets_.begin(), result_sets_.end());
+}
+
 }