BIND 10 trac1604b, updated. 3071211d2c537150a691120b0a5ce2b18d010239 [1604b] Merge branch 'master' into trac1604b

BIND 10 source code commits bind10-changes at lists.isc.org
Fri Feb 3 11:59:18 UTC 2012


The branch, trac1604b has been updated
       via  3071211d2c537150a691120b0a5ce2b18d010239 (commit)
       via  6cba1c1241e8c23779d0dc29374ce8420fadc2c5 (commit)
       via  76f823d42af55ce3f30a0d741fc9297c211d8b38 (commit)
       via  ab457a83d9ab58e722b84a7f99bd83dd49d75483 (commit)
       via  ed110c7f595e6a761d9fdbebdcc552e39edbddb5 (commit)
       via  ea6a608404e2b8d78aee06d19cc173f8e9458142 (commit)
       via  03a80ffa461b3881b875b4a9bfe8d5023c53a59c (commit)
       via  943206cea937e858ac301eec764abd1069350783 (commit)
       via  548926e319f5ed75099667a063f5d8862aeeec3c (commit)
       via  ae0979752fd9062a5b184ec855c2f9eab1363aba (commit)
       via  4684af80eb76977c7bc0a62057fc324b8951381e (commit)
       via  721278917f81b75862a813a9acd7b45edc636737 (commit)
       via  4d3cf5eb83569ee90646cc070642d0075cf488cb (commit)
       via  5e05ed3052388f3bdd87e9d9e8f938b101014b98 (commit)
       via  7ee50a91c4e5d294e92e0472e3d05485fc63dd12 (commit)
       via  7074f97e8f9a6cc7b288f161fd5fea221c45f67a (commit)
       via  cb79b6fae1cd7c348eb2c2671f1a44e857e7a82d (commit)
       via  cccb06b7d9afb36217b873ae007f23eb5bffd0e1 (commit)
       via  b614d6fb5ce0950bd212f5b6b01daa634fa5bb22 (commit)
       via  e657a6ace7b2ef3935f34ba87b85f8c0989eb9e4 (commit)
       via  8080602fae702171f2f08301e3058956cffed881 (commit)
       via  d86d66e6d7c9a94356e76164e7675b3de448aeee (commit)
       via  1d74a54df8e93bc300e575bf1e3732d14c1432ec (commit)
       via  ceeb013ba37445e7728d82a850c69d8b2c3377b0 (commit)
       via  ec3e1e87637f0bb63c942758b1f5af4eb50708b7 (commit)
       via  e475cf69a701cb84b7261b3a73d67f8f8dbaf6c4 (commit)
       via  0b23de1d0bce5d32486fb6dc2de411047c325dec (commit)
       via  5883a489bd72fd9a7faa485d770cc74b99457e7f (commit)
       via  ee5cc69d9e5320e3d9c420c22b54d0ebae255268 (commit)
       via  85c2888f2fe2c9985ce89fe22fa518b91976398b (commit)
       via  d5a8a75cf9e5cd88b5e7471eb7be8255cd2da28d (commit)
       via  88b907df1f580e6241f2af12870df5c769c539fa (commit)
       via  13ef8a1a140dd572deeb4f971109e2fe4a8363ac (commit)
       via  c653a1bd0fa764039207f5ed1375acaa4170e8e5 (commit)
       via  8b51e48caf77ca64a06e1a3206445b1fe0b33261 (commit)
       via  f2ae6da3091138750b4bf51475c781e3d7ddc5f1 (commit)
       via  d69234b667207783c40d1f4b021ac50682fe2341 (commit)
       via  6b562b9f7c7df89081cd3e9c98625350a19cbbb7 (commit)
       via  bcb041edfbe5af5bae2818c2415b008772b97eee (commit)
       via  937690e47bc87c365e82467ab5c425eb255aa989 (commit)
       via  97d02ec04e4c0266ec3df0899ff5b6e2f0177746 (commit)
       via  d17500bf67cd807502793f1eb5525ed238b7e577 (commit)
       via  74fc8c25fff06acbe992a34f78f0756d06c78673 (commit)
       via  dc8a5a5b1022bb8624773ca9adda6025e683a3a9 (commit)
       via  95f2ccbb56b3dcb8f77e3def5beaade65a8891b2 (commit)
       via  7af06f97cddc0ee6734a5302096ebad4539d1d59 (commit)
       via  3602cd9d13d6add722665b25bea2ed5ba56fa28d (commit)
       via  a2943fb1b3f1416efcf0d3fc3b46293e34830ebf (commit)
       via  37c1b4a1d2fb5cfd630d43b016e514ce2d58fa59 (commit)
       via  52456f2f08fd688705f485540b5a65d178cbb810 (commit)
       via  a8c8f392aa951115313dc35159896fe506742079 (commit)
       via  1639806c250603b964f79a528285d3c3d20853cd (commit)
       via  a19c58f52726ab4f47f920b777209dee44c1e3b2 (commit)
       via  94cf54362f836eed349e87b153fe853d561e09bb (commit)
       via  84e105c7ffbda055f37bfa99324aae5c2189ff9c (commit)
       via  4cb88939cad52712bbfd4ae1903de9bb51465499 (commit)
       via  4164bd33ee8f465820a6d22aad0d2a63db7765b7 (commit)
       via  ef67582080db054694be522689b892c967bdba29 (commit)
      from  b9aed5e8c47dd0e44981e6a7c40b5b3c8e403e53 (commit)

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

- Log -----------------------------------------------------------------
commit 3071211d2c537150a691120b0a5ce2b18d010239
Merge: b9aed5e8c47dd0e44981e6a7c40b5b3c8e403e53 6cba1c1241e8c23779d0dc29374ce8420fadc2c5
Author: Stephen Morris <stephen at isc.org>
Date:   Fri Feb 3 11:39:28 2012 +0000

    [1604b] Merge branch 'master' into trac1604b
    
    Conflicts:
    	src/lib/datasrc/memory_datasrc.cc

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

Summary of changes:
 ChangeLog                                          |    9 +-
 doc/guide/bind10-guide.html                        |   16 +-
 doc/guide/bind10-guide.txt                         |   17 +-
 doc/guide/bind10-guide.xml                         |    4 +-
 doc/guide/bind10-messages.html                     |    2 +-
 doc/guide/bind10-messages.xml                      |    2 +-
 src/lib/datasrc/datasrc_messages.mes               |  116 ++--
 src/lib/datasrc/memory_datasrc.cc                  |  420 ++++++++++---
 src/lib/datasrc/memory_datasrc.h                   |   10 +
 src/lib/datasrc/tests/Makefile.am                  |    2 +
 src/lib/datasrc/tests/memory_datasrc_unittest.cc   |  688 ++++++++++++++++----
 .../tests/testdata/example.org.nsec3-signed        |   14 +
 .../testdata/example.org.nsec3-signed-noparam      |   15 +
 src/lib/dns/nsec3hash.cc                           |   44 ++-
 src/lib/dns/nsec3hash.h                            |   29 +
 src/lib/dns/python/nsec3hash_python.cc             |   67 ++-
 src/lib/dns/python/nsec3hash_python_inc.cc         |   29 +-
 src/lib/dns/python/tests/nsec3hash_python_test.py  |   73 ++-
 src/lib/dns/tests/nsec3hash_unittest.cc            |   82 +++-
 src/lib/server_common/tests/portconfig_unittest.cc |    5 +
 20 files changed, 1314 insertions(+), 330 deletions(-)
 create mode 100644 src/lib/datasrc/tests/testdata/example.org.nsec3-signed
 create mode 100644 src/lib/datasrc/tests/testdata/example.org.nsec3-signed-noparam

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 14eb4ab..5b81afc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,12 @@
+373.	[bug]		jinmei
+	libdatasrc: the in-memory data source incorrectly rejected loading
+	a zone containing a CNAME RR with RRSIG and/or NSEC.
+	(Trac #1551, git 76f823d42af55ce3f30a0d741fc9297c211d8b38)
+
 372.	[func]		vorner
 	When the allocation of a socket fails for a different reason than the
-	socket not being provided by the OS, the b10-auth and b10-resolver abort,
-	as the system might be in inconsistent state after such error.
+	socket not being provided by the OS, the b10-auth and b10-resolver
+	abort, as the system might be in inconsistent state after such error.
 	(Trac #1543, git 49ac4659f15c443e483922bf9c4f2de982bae25d)
 
 371.	[bug]		jelte
diff --git a/doc/guide/bind10-guide.html b/doc/guide/bind10-guide.html
index ef47c65..6abe287 100644
--- a/doc/guide/bind10-guide.html
+++ b/doc/guide/bind10-guide.html
@@ -1,12 +1,12 @@
-<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Guide</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a framework that features Domain Name System (DNS) suite and Dynamic Host Configuration Protocol (DHCP) servers managed by Internet Systems Consortium (ISC). It includes DNS libraries, modular components for controlling authoritative and recursive DNS servers, and experimental DHCPv4 and DHCPv6 servers. This is the reference guide for BIND 10 version 20111129. The most up-to-date version of this document (in PDF, HTML, and plain text formats), along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Guide"><div class="titlepage"><div><div><h1 class="title"><a name="id1168
 229451102"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the reference guide for BIND 10 version
-        20111129.</p></div><div><p class="copyright">Copyright © 2010-2011 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a framework that features Domain Name System
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Guide</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a framework that features Domain Name System (DNS) suite and Dynamic Host Configuration Protocol (DHCP) servers managed by Internet Systems Consortium (ISC). It includes DNS libraries, modular components for controlling authoritative and recursive DNS servers, and experimental DHCPv4 and DHCPv6 servers. This is the reference guide for BIND 10 version 20120127. The most up-to-date version of this document (in PDF, HTML, and plain text formats), along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Guide"><div class="titlepage"><div><div><h1 class="title"><a name="id1168
 229451102"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the reference guide for BIND 10 version
+        20120127.</p></div><div><p class="copyright">Copyright © 2010-2012 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a framework that features Domain Name System
       (DNS) suite and Dynamic Host Configuration Protocol (DHCP)
       servers managed by Internet Systems Consortium (ISC). It
       includes DNS libraries, modular components for controlling
       authoritative and recursive DNS servers, and experimental DHCPv4
       and DHCPv6 servers.
       </p><p>
-        This is the reference guide for BIND 10 version 20111129.
+        This is the reference guide for BIND 10 version 20120127.
         The most up-to-date version of this document (in PDF, HTML,
         and plain text formats), along with other documents for
         BIND 10, can be found at <a class="ulink" href="http://bind10.isc.org/docs" target="_top">http://bind10.isc.org/docs</a>.
@@ -22,7 +22,7 @@
       provides forwarding.
     </p><p>
       This guide covers the experimental prototype of
-      BIND 10 version 20111129.
+      BIND 10 version 20120127.
     </p><div class="section" title="1.1. Supported Platforms"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168229451269"></a>1.1. Supported Platforms</h2></div></div></div><p>
   BIND 10 builds have been tested on Debian GNU/Linux 5 and unstable,
   Ubuntu 9.10, NetBSD 5, Solaris 10, FreeBSD 7 and 8, CentOS
@@ -238,10 +238,10 @@
           The leading development is done in the <span class="quote">“<span class="quote">master</span>”</span>.
         </p><p>
           The code can be checked out from
-          <code class="filename">git://bind10.isc.org/bind10</code>;
+          <code class="filename">git://git.bind10.isc.org/bind10</code>;
           for example:
 
-        </p><pre class="screen">$ <strong class="userinput"><code>git clone git://bind10.isc.org/bind10</code></strong></pre><p>
+        </p><pre class="screen">$ <strong class="userinput"><code>git clone git://git.bind10.isc.org/bind10</code></strong></pre><p>
         </p><p>
           When checking out the code from
           the code version control system, it doesn't include the
@@ -1183,6 +1183,10 @@ eth0 fe80::21e:8cff:fe9b:7349
 > <strong class="userinput"><code>Stats show</code></strong>
 {
     "Auth": {
+        "opcode.iquery": 0,
+        "opcode.notify": 10,
+        "opcode.query": 869617,
+        ...
         "queries.tcp": 1749,
         "queries.udp": 867868
     },
diff --git a/doc/guide/bind10-guide.txt b/doc/guide/bind10-guide.txt
index 52e7b0f..7ec35e6 100644
--- a/doc/guide/bind10-guide.txt
+++ b/doc/guide/bind10-guide.txt
@@ -2,9 +2,9 @@
 
 Administrator Reference for BIND 10
 
-   This is the reference guide for BIND 10 version 20111129.
+   This is the reference guide for BIND 10 version 20120127.
 
-   Copyright (c) 2010-2011 Internet Systems Consortium, Inc.
+   Copyright (c) 2010-2012 Internet Systems Consortium, Inc.
 
    Abstract
 
@@ -14,7 +14,7 @@ Administrator Reference for BIND 10
    for controlling authoritative and recursive DNS servers, and experimental
    DHCPv4 and DHCPv6 servers.
 
-   This is the reference guide for BIND 10 version 20111129. The most
+   This is the reference guide for BIND 10 version 20120127. The most
    up-to-date version of this document (in PDF, HTML, and plain text
    formats), along with other documents for BIND 10, can be found at
    http://bind10.isc.org/docs.
@@ -172,7 +172,7 @@ Chapter 1. Introduction
    DNS. BIND 10 provides a EDNS0- and DNSSEC-capable authoritative DNS server
    and a caching recursive name server which also provides forwarding.
 
-   This guide covers the experimental prototype of BIND 10 version 20111129.
+   This guide covers the experimental prototype of BIND 10 version 20120127.
 
 1.1. Supported Platforms
 
@@ -389,9 +389,10 @@ Chapter 2. Installation
    system. This is powered by Git and all the BIND 10 development is public.
    The leading development is done in the "master".
 
-   The code can be checked out from git://bind10.isc.org/bind10; for example:
+   The code can be checked out from git://git.bind10.isc.org/bind10; for
+   example:
 
- $ git clone git://bind10.isc.org/bind10
+ $ git clone git://git.bind10.isc.org/bind10
 
    When checking out the code from the code version control system, it
    doesn't include the generated configure script, Makefile.in files, nor the
@@ -1376,6 +1377,10 @@ Chapter 15. Statistics
  > Stats show
  {
      "Auth": {
+         "opcode.iquery": 0,
+         "opcode.notify": 10,
+         "opcode.query": 869617,
+         ...
          "queries.tcp": 1749,
          "queries.udp": 867868
      },
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index c7c0bd8..c551c5f 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -526,10 +526,10 @@ as a dependency earlier -->
         </para>
         <para>
           The code can be checked out from
-          <filename>git://bind10.isc.org/bind10</filename>;
+          <filename>git://git.bind10.isc.org/bind10</filename>;
           for example:
 
-        <screen>$ <userinput>git clone git://bind10.isc.org/bind10</userinput></screen>
+        <screen>$ <userinput>git clone git://git.bind10.isc.org/bind10</userinput></screen>
         </para>
 
         <para>
diff --git a/doc/guide/bind10-messages.html b/doc/guide/bind10-messages.html
index 596cb53..b82b485 100644
--- a/doc/guide/bind10-messages.html
+++ b/doc/guide/bind10-messages.html
@@ -1,5 +1,5 @@
 <html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Messages Manual</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the messages manual for BIND 10 version 20111129. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Messages Manual"><div class="titlepage"><div><div><h1 class="title"><a name="id1168229451102"></a>BIND 10 Messages Manual</h1></div><div><p class="releaseinfo">This is the messages manual for BIND 10 version
-        20111129.</p></div><div><p class="copyright">Copyright © 2011 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
+        20111129.</p></div><div><p class="copyright">Copyright © 2011-2012 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
 	  Internet Systems Consortium (ISC). It includes DNS libraries
 	  and modular components for controlling authoritative and
 	  recursive DNS servers.
diff --git a/doc/guide/bind10-messages.xml b/doc/guide/bind10-messages.xml
index ba55de0..c085cb1 100644
--- a/doc/guide/bind10-messages.xml
+++ b/doc/guide/bind10-messages.xml
@@ -18,7 +18,7 @@
     <title>BIND 10 Messages Manual</title>
 
     <copyright>
-      <year>2011</year><holder>Internet Systems Consortium, Inc.</holder>
+      <year>2011-2012</year><holder>Internet Systems Consortium, Inc.</holder>
     </copyright>
 
     <abstract>
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index fd46896..f05ff21 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -16,6 +16,13 @@ $NAMESPACE isc::datasrc
 
 # \brief Messages for the data source library
 
+% DATASRC_BAD_NSEC3_NAME NSEC3 record has a bad owner name '%1'
+The software refuses to load NSEC3 records into a wildcard domain or
+the owner name has two or more labels below the zone origin.
+It isn't explicitly forbidden, but no sane zone wouldn have such names
+for NSEC3.  BIND 9 also refuses NSEC3 at wildcard, so this behavior is
+compatible with BIND 9.
+
 % DATASRC_CACHE_CREATE creating the hotspot cache
 This is a debug message issued during startup when the hotspot cache
 is created.
@@ -143,6 +150,34 @@ were found to be different. This isn't allowed on the wire and is considered
 an error, so we set it to the lowest value we found (but we don't modify the
 database). The data in database should be checked and fixed.
 
+% DATASRC_DATABASE_JOURNALREADER_END %1/%2 on %3 from %4 to %5
+This is a debug message indicating that the program (successfully)
+reaches the end of sequences of a zone's differences.  The zone's name
+and class, database name, and the start and end serials are shown in
+the message.
+
+% DATASRC_DATABASE_JOURNALREADER_NEXT %1/%2 in %3/%4 on %5
+This is a debug message indicating that the program retrieves one
+difference in difference sequences of a zone and successfully converts
+it to an RRset.  The zone's name and class, database name, and the
+name and RR type of the retrieved diff are shown in the message.
+
+% DATASRC_DATABASE_JOURNALREADER_START %1/%2 on %3 from %4 to %5
+This is a debug message indicating that the program starts reading
+a zone's difference sequences from a database-based data source.  The
+zone's name and class, database name, and the start and end serials
+are shown in the message.
+
+% DATASRC_DATABASE_JOURNALREADR_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
+This is an error message indicating that a zone's diff is broken and
+the data source library failed to convert it to a valid RRset.  The
+most likely cause of this is that someone has manually modified the
+zone's diff in the database and inserted invalid data as a result.
+The zone's name and class, database name, and the start and end
+serials, and an additional detail of the error are shown in the
+message.  The administrator should examine the diff in the database
+to find any invalid data and fix it.
+
 % DATASRC_DATABASE_NO_MATCH not match for %2/%3/%4 in %1
 No match (not even a wildcard) was found in the named data source for the given
 name/type/class in the data source.
@@ -307,6 +342,14 @@ Debug information. The content of master file is being loaded into the memory.
 % DATASRC_MEM_NOT_FOUND requested domain '%1' not found
 Debug information. The requested domain does not exist.
 
+% DATASRC_MEM_NO_NSEC3PARAM NSEC3PARAM is missing for NSEC3-signed zone %1/%2
+The in-memory data source has loaded a zone signed with NSEC3 RRs,
+but it doesn't have a NSEC3PARAM RR at the zone origin.  It's likely that
+the zone is somehow broken, but this RR is not necessarily needed for
+handling lookups with NSEC3 in this data source, so it accepts the given
+content of the zone.  Nevertheless the administrator should look into
+the integrity of the zone data.
+
 % DATASRC_MEM_NS_ENCOUNTERED encountered a NS
 Debug information. While searching for the requested domain, a NS was
 encountered on the way (a delegation). This may lead to stop of the search.
@@ -333,10 +376,12 @@ Some resource types are singletons -- only one is allowed in a domain
 % DATASRC_MEM_SUCCESS query for '%1/%2' successful
 Debug information. The requested record was found.
 
-% DATASRC_MEM_SUPER_STOP stopped at superdomain '%1', domain '%2' is empty
-Debug information. The search stopped at a superdomain of the requested
-domain. The domain is an empty nonterminal, therefore it is treated  as NXRRSET
-case (eg. the domain exists, but it doesn't have the requested record type).
+% DATASRC_MEM_SUPER_STOP stopped as '%1' is superdomain of a zone node, meaning it's empty
+Debug information. The search stopped because the requested domain was
+detected to be a superdomain of some existing node of zone (while there
+was no exact match).  This means that the domain is an empty nonterminal,
+therefore it is treated  as NXRRSET case (eg. the domain exists, but it
+doesn't have the requested record type).
 
 % DATASRC_MEM_SWAP swapping contents of two zone representations ('%1' and '%2')
 Debug information. The contents of two in-memory zones are being exchanged.
@@ -671,66 +716,3 @@ data source.
 % DATASRC_UNEXPECTED_QUERY_STATE unexpected query state
 This indicates a programming error. An internal task of unknown type was
 generated.
-
-% DATASRC_DATABASE_UPDATER_CREATED zone updater created for '%1/%2' on %3
-Debug information.  A zone updater object is created to make updates to
-the shown zone on the shown backend database.
-
-% DATASRC_DATABASE_UPDATER_DESTROYED zone updater destroyed for '%1/%2' on %3
-Debug information.  A zone updater object is destroyed, either successfully
-or after failure of, making updates to the shown zone on the shown backend
-database.
-
-%DATASRC_DATABASE_UPDATER_ROLLBACK zone updates roll-backed for '%1/%2' on %3
-A zone updater is being destroyed without committing the changes.
-This would typically mean the update attempt was aborted due to some
-error, but may also be a bug of the application that forgets committing
-the changes.  The intermediate changes made through the updater won't
-be applied to the underlying database.  The zone name, its class, and
-the underlying database name are shown in the log message.
-
-%DATASRC_DATABASE_UPDATER_ROLLBACKFAIL failed to roll back zone updates for '%1/%2' on %3: %4
-A zone updater is being destroyed without committing the changes to
-the database, and attempts to rollback incomplete updates, but it
-unexpectedly fails.  The higher level implementation does not expect
-it to fail, so this means either a serious operational error in the
-underlying data source (such as a system failure of a database) or
-software bug in the underlying data source implementation.  In either
-case if this message is logged the administrator should carefully
-examine the underlying data source to see what exactly happens and
-whether the data is still valid.  The zone name, its class, and the
-underlying database name as well as the error message thrown from the
-database module are shown in the log message.
-
-% DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3
-Debug information.  A set of updates to a zone has been successfully
-committed to the corresponding database backend.  The zone name,
-its class and the database name are printed.
-
-% DATASRC_DATABASE_JOURNALREADER_START %1/%2 on %3 from %4 to %5
-This is a debug message indicating that the program starts reading
-a zone's difference sequences from a database-based data source.  The
-zone's name and class, database name, and the start and end serials
-are shown in the message.
-
-% DATASRC_DATABASE_JOURNALREADER_NEXT %1/%2 in %3/%4 on %5
-This is a debug message indicating that the program retrieves one
-difference in difference sequences of a zone and successfully converts
-it to an RRset.  The zone's name and class, database name, and the
-name and RR type of the retrieved diff are shown in the message.
-
-% DATASRC_DATABASE_JOURNALREADER_END %1/%2 on %3 from %4 to %5
-This is a debug message indicating that the program (successfully)
-reaches the end of sequences of a zone's differences.  The zone's name
-and class, database name, and the start and end serials are shown in
-the message.
-
-% DATASRC_DATABASE_JOURNALREADR_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
-This is an error message indicating that a zone's diff is broken and
-the data source library failed to convert it to a valid RRset.  The
-most likely cause of this is that someone has manually modified the
-zone's diff in the database and inserted invalid data as a result.
-The zone's name and class, database name, and the start and end
-serials, and an additional detail of the error are shown in the
-message.  The administrator should examine the diff in the database
-to find any invalid data and fix it.
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index 285cee5..450c8b6 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -12,15 +12,21 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <algorithm>
 #include <map>
+#include <utility>
+#include <cctype>
 #include <cassert>
+
 #include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
 #include <boost/bind.hpp>
 #include <boost/foreach.hpp>
 
 #include <exceptions/exceptions.h>
 
 #include <dns/name.h>
+#include <dns/nsec3hash.h>
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
 #include <dns/rrsetlist.h>
@@ -39,6 +45,7 @@ using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 using namespace isc::data;
+using boost::scoped_ptr;
 
 namespace isc {
 namespace datasrc {
@@ -62,30 +69,63 @@ typedef boost::shared_ptr<Domain> DomainPtr;
 // The tree stores domains
 typedef RBTree<Domain> DomainTree;
 typedef RBNode<Domain> DomainNode;
-}
 
-// Private data and hidden methods of InMemoryZoneFinder
-struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
-    // Constructor
-    InMemoryZoneFinderImpl(const RRClass& zone_class, const Name& origin) :
-        zone_class_(zone_class), origin_(origin), origin_data_(NULL),
-        domains_(true)
-    {
+// Separate storage for NSEC3 RRs (and their RRSIGs).  It's an STL map
+// from string to the NSEC3 RRset.  The map key is the first label
+// (upper cased) of the owner name of the corresponding NSEC3 (i.e., map
+// value).  We can use  the standard string comparison (if the comparison
+// target is also upper cased) due to the nature of NSEC3 owner names.
+typedef map<string, ConstRRsetPtr> NSEC3Map;
+typedef NSEC3Map::value_type NSEC3Pair;
+
+// Actual zone data: Essentially a set of zone's RRs.  This is defined as
+// a separate structure so that it'll be replaceable on reload.
+struct ZoneData {
+    ZoneData(const Name& origin) : domains_(true), origin_data_(NULL) {
         // We create the node for origin (it needs to exist anyway in future)
         domains_.insert(origin, &origin_data_);
         DomainPtr origin_domain(new Domain);
         origin_data_->setData(origin_domain);
     }
+
+    // The main data (name + RRsets)
+    DomainTree domains_;
+
+    // Shortcut to the origin node, which should always exist
+    DomainNode* origin_data_;
+
+    // The optional NSEC3 related data
+    struct NSEC3Data {
+        NSEC3Data(const generic::NSEC3PARAM& nsec3param) :
+            hash_(NSEC3Hash::create(nsec3param))
+        {}
+        NSEC3Data(const generic::NSEC3& nsec3) :
+            hash_(NSEC3Hash::create(nsec3))
+        {}
+        NSEC3Map map_;    // Actual NSEC3 RRs
+        const scoped_ptr<NSEC3Hash> hash_; // hash parameter/calculator
+    };
+    scoped_ptr<NSEC3Data> nsec3_data_; // non NULL only when it's NSEC3 signed
+};
+}
+
+// Private data and hidden methods of InMemoryZoneFinder
+struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
+    // Constructor
+    InMemoryZoneFinderImpl(const RRClass& zone_class, const Name& origin) :
+        zone_class_(zone_class), origin_(origin),
+        zone_data_(new ZoneData(origin_))
+    {}
+
     static const DomainNode::Flags DOMAINFLAG_WILD = DomainNode::FLAG_USER1;
 
     // Information about the zone
     RRClass zone_class_;
     Name origin_;
-    DomainNode* origin_data_;
     string file_name_;
 
     // The actual zone data
-    DomainTree domains_;
+    scoped_ptr<ZoneData> zone_data_;
 
     // Add the necessary magic for any wildcard contained in 'name'
     // (including itself) to be found in the zone.
@@ -130,6 +170,12 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         }
     }
 
+    // A helper predicate used in contextCheck() to check if a given domain
+    // name has a RRset of type different than NSEC.
+    static bool isNotNSEC(const DomainPair& element) {
+        return (element.second->getType() != RRType::NSEC());
+    }
+
     /*
      * Does some checks in context of the data that are already in the zone.
      * Currently checks for forbidden combinations of RRsets in the same
@@ -137,24 +183,23 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
      *
      * If such condition is found, it throws AddError.
      */
-    void contextCheck(const ConstRRsetPtr& rrset,
-                      const DomainPtr& domain) const {
+    void contextCheck(const AbstractRRset& rrset, const Domain& domain) const {
         // Ensure CNAME and other type of RR don't coexist for the same
-        // owner name.
-        if (rrset->getType() == RRType::CNAME()) {
-            // TODO: this check will become incorrect when we support DNSSEC
-            // (depending on how we support DNSSEC).  We should revisit it
-            // at that point.
-            if (!domain->empty()) {
+        // owner name except with NSEC, which is the only RR that can coexist
+        // with CNAME (and also RRSIG, which is handled separately)
+        if (rrset.getType() == RRType::CNAME()) {
+            if (find_if(domain.begin(), domain.end(), isNotNSEC)
+                != domain.end()) {
                 LOG_ERROR(logger, DATASRC_MEM_CNAME_TO_NONEMPTY).
-                    arg(rrset->getName());
+                    arg(rrset.getName());
                 isc_throw(AddError, "CNAME can't be added with other data for "
-                          << rrset->getName());
+                          << rrset.getName());
             }
-        } else if (domain->find(RRType::CNAME()) != domain->end()) {
-            LOG_ERROR(logger, DATASRC_MEM_CNAME_COEXIST).arg(rrset->getName());
-            isc_throw(AddError, "CNAME and " << rrset->getType() <<
-                      " can't coexist for " << rrset->getName());
+        } else if (rrset.getType() != RRType::NSEC() &&
+                   domain.find(RRType::CNAME()) != domain.end()) {
+            LOG_ERROR(logger, DATASRC_MEM_CNAME_COEXIST).arg(rrset.getName());
+            isc_throw(AddError, "CNAME and " << rrset.getType() <<
+                      " can't coexist for " << rrset.getName());
         }
 
         /*
@@ -162,17 +207,17 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
          * non-apex domains.
          * RFC 2672 section 3 mentions that it is implied from it and RFC 2181
          */
-        if (rrset->getName() != origin_ &&
+        if (rrset.getName() != origin_ &&
             // Adding DNAME, NS already there
-            ((rrset->getType() == RRType::DNAME() &&
-            domain->find(RRType::NS()) != domain->end()) ||
+            ((rrset.getType() == RRType::DNAME() &&
+            domain.find(RRType::NS()) != domain.end()) ||
             // Adding NS, DNAME already there
-            (rrset->getType() == RRType::NS() &&
-            domain->find(RRType::DNAME()) != domain->end())))
+            (rrset.getType() == RRType::NS() &&
+            domain.find(RRType::DNAME()) != domain.end())))
         {
-            LOG_ERROR(logger, DATASRC_MEM_DNAME_NS).arg(rrset->getName());
+            LOG_ERROR(logger, DATASRC_MEM_DNAME_NS).arg(rrset.getName());
             isc_throw(AddError, "DNAME can't coexist with NS in non-apex "
-                "domain " << rrset->getName());
+                "domain " << rrset.getName());
         }
     }
 
@@ -201,6 +246,14 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
             isc_throw(AddError, "multiple RRs of singleton type for "
                       << rrset->getName());
         }
+        // NSEC3/NSEC3PARAM is not a "singleton" per protocol, but this
+        // implementation requests it be so at the moment.
+        if ((rrset->getType() == RRType::NSEC3() ||
+             rrset->getType() == RRType::NSEC3PARAM()) &&
+            rrset->getRdataCount() > 1) {
+            isc_throw(AddError, "Multiple NSEC3/NSEC3PARAM RDATA is given for "
+                      << rrset->getName() << " which isn't supported");
+        }
 
         NameComparisonResult compare(origin_.compare(rrset->getName()));
         if (compare.getRelation() != NameComparisonResult::SUPERDOMAIN &&
@@ -234,19 +287,25 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
                           rrset->getName());
             }
         }
-    }
 
-    result::Result addRRsig(const ConstRRsetPtr sig_rrset,
-                            DomainTree& domains)
-    {
-        DomainNode* node = NULL;
-        if (domains.find(sig_rrset->getName(), &node) !=
-            DomainTree::EXACTMATCH || node == NULL || !node->getData()) {
-            isc_throw(AddError,
-                      "RRSIG is being added, but no RR to be covered: "
-                      << sig_rrset->getName());
+        // Owner names of NSEC3 have special format as defined in RFC5155,
+        // and cannot be a wildcard name or must be one label longer than
+        // the zone origin.  While the RFC doesn't prohibit other forms of
+        // names, no sane zone would have such names for NSEC3.
+        // BIND 9 also refuses NSEC3 at wildcard.
+        if (rrset->getType() == RRType::NSEC3() &&
+            (rrset->getName().isWildcard() ||
+             rrset->getName().getLabelCount() !=
+             origin_.getLabelCount() + 1)) {
+            LOG_ERROR(logger, DATASRC_BAD_NSEC3_NAME).
+                arg(rrset->getName());
+            isc_throw(AddError, "Invalid NSEC3 owner name: " <<
+                      rrset->getName());
         }
+    }
 
+    result::Result addRRsig(const ConstRRsetPtr sig_rrset, ZoneData& zone_data)
+    {
         // Check consistency of the type covered.
         // We know the RRset isn't empty, so the following check is safe.
         RdataIteratorPtr rit = sig_rrset->getRdataIterator();
@@ -262,16 +321,50 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
 
         // Find the RRset to be covered; if not found, treat it as an error
         // for now.
-        const Domain::const_iterator it = node->getData()->find(covered);
-        if (it == node->getData()->end()) {
-            isc_throw(AddError,
-                      "RRSIG is being added, but no RR of covered type found: "
-                      << sig_rrset->toText());
+        ConstRRsetPtr covered_rrset;
+        if (covered != RRType::NSEC3()) {
+            DomainNode* node = NULL;
+            if (zone_data.domains_.find(sig_rrset->getName(), &node) !=
+                DomainTree::EXACTMATCH || node == NULL || !node->getData()) {
+                isc_throw(AddError,
+                          "RRSIG is being added, but no RR to be covered: "
+                          << sig_rrset->getName());
+            }
+            const Domain::const_iterator it = node->getData()->find(covered);
+            if (it != node->getData()->end()) {
+                covered_rrset = it->second;
+            }
+        } else {
+            // In case of NSEC3 if something is found it must be NSEC3 RRset
+            // under the assumption of our current implementation.
+            if (zone_data.nsec3_data_) {
+                // Convert the first label to upper-cased text.  Note that
+                // for a valid NSEC3 RR the label should only consist of
+                // positive 8-bit char values, so using toupper(int) should be
+                // safe (if it's a bogus label for NSEC3 the zone won't work
+                // anyway).  Also note the '::' below: g++'s STL implementation
+                // seems to require it to toupper to make this compile.
+                string fst_label =
+                    sig_rrset->getName().split(0, 1).toText(true);
+                transform(fst_label.begin(), fst_label.end(),
+                          fst_label.begin(), ::toupper);
+
+                NSEC3Map::const_iterator found =
+                    zone_data.nsec3_data_->map_.find(fst_label);
+                if (found != zone_data.nsec3_data_->map_.end()) {
+                    covered_rrset = found->second;
+                    assert(covered_rrset->getType() == covered);
+                }
+            }
+        }
+        if (!covered_rrset) {
+            isc_throw(AddError, "RRSIG is being added, but no RR of "
+                      "covered type found: " << sig_rrset->toText());
         }
 
         // The current implementation doesn't allow an existing RRSIG to be
         // overridden (or updated with additional ones).
-        if ((it->second)->getRRsig()) {
+        if (covered_rrset->getRRsig()) {
             isc_throw(AddError,
                       "RRSIG is being added to override an existing one: "
                       << sig_rrset->toText());
@@ -286,8 +379,38 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         // Note: there's a slight chance of getting an exception.
         // As noted in add(), we give up strong exception guarantee in such
         // cases.
-        boost::const_pointer_cast<AbstractRRset>(it->second)->addRRsig(sig_rrset);
+        boost::const_pointer_cast<AbstractRRset>(covered_rrset)->addRRsig(sig_rrset);
+
+        return (result::SUCCESS);
+    }
+
+    result::Result addNSEC3(const ConstRRsetPtr rrset, ZoneData& zone_data) {
+        // We know rrset has exactly one RDATA
+        const generic::NSEC3& nsec3_rdata =
+            dynamic_cast<const generic::NSEC3&>(
+                rrset->getRdataIterator()->getCurrent());
+
+        // If we've not done any NSEC3 setup for the zone, do it now;
+        // otherwise check parameter consistency.
+        if (!zone_data.nsec3_data_) {
+            zone_data.nsec3_data_.reset(new ZoneData::NSEC3Data(nsec3_rdata));
+        } else if (!zone_data.nsec3_data_->hash_->match(nsec3_rdata)) {
+            isc_throw(AddError, "NSEC3 with inconsistent parameters: " <<
+                      rrset->toText());
+        }
+
+        string fst_label = rrset->getName().split(0, 1).toText(true);
+        transform(fst_label.begin(), fst_label.end(), fst_label.begin(),
+                  ::toupper);
+
+        // Our current implementation doesn't allow an existing NSEC3 to be
+        // updated/overridden.
+        if (zone_data.nsec3_data_->map_.find(fst_label) !=
+            zone_data.nsec3_data_->map_.end()) {
+            return (result::EXIST);
+        }
 
+        zone_data.nsec3_data_->map_.insert(NSEC3Pair(fst_label, rrset));
         return (result::SUCCESS);
     }
 
@@ -296,7 +419,7 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
      * access is without the impl_-> and it will get inlined anyway.
      */
     // Implementation of InMemoryZoneFinder::add
-    result::Result add(const ConstRRsetPtr& rrset, DomainTree* domains) {
+    result::Result add(const ConstRRsetPtr& rrset, ZoneData& zone_data) {
         // Sanitize input.  This will cause an exception to be thrown
         // if the input RRset is empty.
         addValidation(rrset);
@@ -305,21 +428,26 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ADD_RRSET).
             arg(rrset->getName()).arg(rrset->getType()).arg(origin_);
 
+        if (rrset->getType() == RRType::NSEC3()) {
+            return (addNSEC3(rrset, zone_data));
+        }
+
         // RRSIGs are special in various points, so we handle it in a
         // separate dedicated method.
         if (rrset->getType() == RRType::RRSIG()) {
-            return (addRRsig(rrset, *domains));
+            return (addRRsig(rrset, zone_data));
         }
 
         // Add wildcards possibly contained in the owner name to the domain
         // tree.
         // Note: this can throw an exception, breaking strong exception
         // guarantee.  (see also the note for contextCheck() below).
-        addWildcards(*domains, rrset->getName());
+        addWildcards(zone_data.domains_, rrset->getName());
 
         // Get the node
         DomainNode* node;
-        DomainTree::Result result = domains->insert(rrset->getName(), &node);
+        DomainTree::Result result = zone_data.domains_.insert(rrset->getName(),
+                                                              &node);
         // Just check it returns reasonable results
         assert((result == DomainTree::SUCCESS ||
                 result == DomainTree::ALREADYEXISTS) && node!= NULL);
@@ -339,7 +467,7 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         // break strong exception guarantee.  At the moment we prefer
         // code simplicity and don't bother to introduce complicated
         // recovery code.
-        contextCheck(rrset, domain);
+        contextCheck(*rrset, *domain);
 
         // Try inserting the rrset there
         if (domain->insert(DomainPair(rrset->getType(), rrset)).second) {
@@ -355,6 +483,23 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
                 node->setFlag(DomainNode::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() == origin_) {
+                // We know rrset has exactly one RDATA
+                const generic::NSEC3PARAM& param =
+                    dynamic_cast<const generic::NSEC3PARAM&>(
+                        rrset->getRdataIterator()->getCurrent());
+
+                if (!zone_data.nsec3_data_) {
+                    zone_data.nsec3_data_.reset(
+                        new ZoneData::NSEC3Data(param));
+                } else if (!zone_data.nsec3_data_->hash_->match(param)) {
+                    isc_throw(AddError, "NSEC3PARAM with inconsistent "
+                              "parameters: " << rrset->toText());
+                }
+            }
             return (result::SUCCESS);
         } else {
             // The RRSet of given type was already there
@@ -366,18 +511,18 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
      * Same as above, but it checks the return value and if it already exists,
      * it throws.
      */
-    void addFromLoad(const ConstRRsetPtr& set, DomainTree* domains) {
-            switch (add(set, domains)) {
-                case result::EXIST:
-                    LOG_ERROR(logger, DATASRC_MEM_DUP_RRSET).
-                        arg(set->getName()).arg(set->getType());
-                    isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
-                        set->toText());
-                case result::SUCCESS:
-                    return;
-                default:
-                    assert(0);
-            }
+    void addFromLoad(const ConstRRsetPtr& set, ZoneData* zone_data) {
+        switch (add(set, *zone_data)) {
+        case result::EXIST:
+            LOG_ERROR(logger, DATASRC_MEM_DUP_RRSET).
+                arg(set->getName()).arg(set->getType());
+            isc_throw(dns::MasterLoadError, "Duplicate rrset: " <<
+                      set->toText());
+        case result::SUCCESS:
+            return;
+        default:
+            assert(0);
+        }
     }
 
     // Maintain intermediate data specific to the search context used in
@@ -487,6 +632,24 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         }
     }
 
+    // Set up FindResult object as a return value of find(), taking into
+    // account wildcard matches and DNSSEC information.  We set the NSEC/NSEC3
+    // flag when applicable regardless of the find option; the caller would
+    // simply ignore these when they didn't request DNSSEC related results.
+    FindResult createFindResult(Result code, ConstRRsetPtr rrset,
+                                bool wild) const
+    {
+        FindResultFlags flags = RESULT_DEFAULT;
+        if (wild) {
+            flags = flags | RESULT_WILDCARD;
+        }
+        if ((code == NXRRSET || code == NXDOMAIN || wild) &&
+            zone_data_->nsec3_data_) {
+            flags = flags | RESULT_NSEC3_SIGNED;
+        }
+        return (FindResult(code, rrset, flags));
+    }
+
     // Implementation of InMemoryZoneFinder::find
     FindResult find(const Name& name, RRType type,
                     std::vector<ConstRRsetPtr> *target,
@@ -499,7 +662,8 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         FindState state(options);
         RBTreeNodeChain<Domain> node_path;
         bool rename(false);
-        switch (domains_.find(name, &node, node_path, cutCallback, &state)) {
+        switch (zone_data_->domains_.find(name, &node, node_path, cutCallback,
+                                          &state)) {
             case DomainTree::PARTIALMATCH:
                 /*
                  * In fact, we could use a single variable instead of
@@ -525,13 +689,14 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
                     // We were traversing a DNAME node (and wanted to go
                     // lower below it), so return the DNAME
                     return (FindResult(DNAME, prepareRRset(name, state.rrset_,
-                        rename)));
+                                                           false)));
                 }
                 if (state.zonecut_node_ != NULL) {
                     LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DELEG_FOUND).
                         arg(state.rrset_->getName());
-                    return (FindResult(DELEGATION, prepareRRset(name,
-                        state.rrset_, rename)));
+                    return (FindResult(DELEGATION,
+                                       prepareRRset(name, state.rrset_,
+                                                    false)));
                 }
 
                 // If the RBTree search stopped at a node for a super domain
@@ -540,8 +705,8 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
                 if (node_path.getLastComparisonResult().getRelation() ==
                     NameComparisonResult::SUPERDOMAIN) {
                     LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUPER_STOP).
-                        arg(node_path.getAbsoluteName()).arg(name);
-                    return (FindResult(NXRRSET, ConstRRsetPtr()));
+                        arg(name);
+                    return (createFindResult(NXRRSET, ConstRRsetPtr(), false));
                 }
 
                 /*
@@ -580,11 +745,13 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
                         getLastComparisonResult().getCommonLabels() > 1) {
                         LOG_DEBUG(logger, DBG_TRACE_DATA,
                                      DATASRC_MEM_WILDCARD_CANCEL).arg(name);
-                        return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+                        return (createFindResult(NXDOMAIN, ConstRRsetPtr(),
+                                                 false));
                     }
-                    Name wildcard(Name("*").concatenate(
+                    const Name wildcard(Name("*").concatenate(
                         node_path.getAbsoluteName()));
-                    DomainTree::Result result(domains_.find(wildcard, &node));
+                    DomainTree::Result result =
+                        zone_data_->domains_.find(wildcard, &node);
                     /*
                      * Otherwise, why would the DOMAINFLAG_WILD be there if
                      * there was no wildcard under it?
@@ -604,7 +771,7 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
             case DomainTree::NOTFOUND:
                 LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NOT_FOUND).
                     arg(name);
-                return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+                return (createFindResult(NXDOMAIN, ConstRRsetPtr(), false));
             case DomainTree::EXACTMATCH: // This one is OK, handle it
                 break;
             default:
@@ -617,7 +784,7 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         if (node->isEmpty()) {
             LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_DOMAIN_EMPTY).
                 arg(name);
-            return (FindResult(NXRRSET, ConstRRsetPtr()));
+            return (createFindResult(NXRRSET, ConstRRsetPtr(), rename));
         }
 
         Domain::const_iterator found;
@@ -626,14 +793,14 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
         // has a NS RR, we should return a delegation, but not in the apex.
         // There is one exception: the case for DS query, which should always
         // be considered in-zone lookup.
-        if (node->getFlag(DomainNode::FLAG_CALLBACK) && node != origin_data_ &&
-            type != RRType::DS()) {
+        if (node->getFlag(DomainNode::FLAG_CALLBACK) &&
+            node != zone_data_->origin_data_ && type != RRType::DS()) {
             found = node->getData()->find(RRType::NS());
             if (found != node->getData()->end()) {
                 LOG_DEBUG(logger, DBG_TRACE_DATA,
                           DATASRC_MEM_EXACT_DELEGATION).arg(name);
-                return (FindResult(DELEGATION, prepareRRset(name,
-                    found->second, rename)));
+                return (FindResult(DELEGATION,
+                                   prepareRRset(name, found->second, rename)));
             }
         }
 
@@ -647,7 +814,7 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
             }
             LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_ANY_SUCCESS).
                 arg(name);
-            return (FindResult(SUCCESS, ConstRRsetPtr()));
+            return (createFindResult(SUCCESS, ConstRRsetPtr(), rename));
         }
 
         found = node->getData()->find(type);
@@ -655,21 +822,23 @@ struct InMemoryZoneFinder::InMemoryZoneFinderImpl {
             // Good, it is here
             LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_SUCCESS).arg(name).
                 arg(type);
-            return (FindResult(SUCCESS, prepareRRset(name, found->second,
-                rename)));
+            return (createFindResult(SUCCESS, prepareRRset(name,
+                                                           found->second,
+                                                           rename), rename));
         } else {
             // Next, try CNAME.
             found = node->getData()->find(RRType::CNAME());
             if (found != node->getData()->end()) {
                 LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_CNAME).arg(name);
-                return (FindResult(CNAME, prepareRRset(name, found->second,
-                    rename)));
+                return (createFindResult(CNAME,
+                                         prepareRRset(name, found->second,
+                                                      rename), rename));
             }
         }
         // No exact match or CNAME.  Return NXRRSET.
         LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEM_NXRRSET).arg(type).
             arg(name);
-        return (FindResult(NXRRSET, ConstRRsetPtr()));
+        return (createFindResult(NXRRSET, ConstRRsetPtr(), rename));
     }
 };
 
@@ -717,9 +886,57 @@ InMemoryZoneFinder::findNSEC3(const Name&, bool) {
               "data source");
 }
 
+ZoneFinder::FindNSEC3Result
+InMemoryZoneFinder::findNSEC3Tmp(const Name& name, bool recursive) {
+    if (!impl_->zone_data_->nsec3_data_) {
+        isc_throw(Unexpected, "findNSEC3 is called for non NSEC3 zone");
+    }
+    if (recursive) {
+        isc_throw(Unexpected, "recursive mode isn't expected in tests");
+    }
+
+    // A temporary workaround for testing: convert the original name to
+    // NSEC3-hashed name using hardcoded mapping.
+    string hname_text;
+    if (name == Name("example.org")) {
+        hname_text = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+    } else if (name == Name("www.example.org")) {
+        hname_text = "2S9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+    } else if (name == Name("xxx.example.org")) {
+        hname_text = "Q09MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+    } else if (name == Name("yyy.example.org")) {
+        hname_text = "0A9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+    } else {
+        isc_throw(Unexpected, "unexpected name for NSEC3 test: " << name);
+    }
+
+    // Below we assume the map is not empty for simplicity.
+    NSEC3Map::const_iterator found =
+        impl_->zone_data_->nsec3_data_->map_.lower_bound(hname_text);
+    if (found != impl_->zone_data_->nsec3_data_->map_.end() &&
+        found->first == hname_text) {
+        // exact match
+        return (FindNSEC3Result(true, 2, found->second, ConstRRsetPtr()));
+    } else if (found == impl_->zone_data_->nsec3_data_->map_.end() ||
+               found == impl_->zone_data_->nsec3_data_->map_.begin()) {
+        // the search key is "smaller" than the smallest or "larger" than
+        // largest.  In either case "previous" is the largest one.
+        return (FindNSEC3Result(false, 2,
+                                impl_->zone_data_->nsec3_data_->map_.
+                                rbegin()->second, ConstRRsetPtr()));
+    } else {
+        // Otherwise, H(found_domain-1) < given_hash < H(found_domain)
+        // The covering proof is the first one.
+        return (FindNSEC3Result(false, 2, (--found)->second, ConstRRsetPtr()));
+    }
+
+    // We should have covered all cases.
+    isc_throw(Unexpected, "Impossible NSEC3 search result for " << name);
+}
+
 result::Result
 InMemoryZoneFinder::add(const ConstRRsetPtr& rrset) {
-    return (impl_->add(rrset, &impl_->domains_));
+    return (impl_->add(rrset, *impl_->zone_data_));
 }
 
 
@@ -727,13 +944,29 @@ void
 InMemoryZoneFinder::load(const string& filename) {
     LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_LOAD).arg(getOrigin()).
         arg(filename);
-    // Load it into a temporary tree
-    DomainTree tmp;
+    // Load it into temporary zone data
+    scoped_ptr<ZoneData> tmp(new ZoneData(getOrigin()));
+
     masterLoad(filename.c_str(), getOrigin(), getClass(),
-        boost::bind(&InMemoryZoneFinderImpl::addFromLoad, impl_, _1, &tmp));
+               boost::bind(&InMemoryZoneFinderImpl::addFromLoad, impl_,
+                           _1, tmp.get()));
+
+    // If the zone is NSEC3-signed, check if it has NSEC3PARAM
+    if (tmp->nsec3_data_) {
+        // Note: origin_data_ is set on creation of ZoneData, and the load
+        // process only adds new nodes (and their data), so this assertion
+        // should hold.
+        assert(tmp->origin_data_ != NULL && !tmp->origin_data_->isEmpty());
+        if (tmp->origin_data_->getData()->find(RRType::NSEC3PARAM()) ==
+            tmp->origin_data_->getData()->end()) {
+            LOG_WARN(logger, DATASRC_MEM_NO_NSEC3PARAM).
+                arg(getOrigin()).arg(getClass());
+        }
+    }
+
     // If it went well, put it inside
     impl_->file_name_ = filename;
-    tmp.swap(impl_->domains_);
+    tmp.swap(impl_->zone_data_);
     // And let the old data die with tmp
 }
 
@@ -924,8 +1157,9 @@ InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
         isc_throw(Unexpected, "The zone at " + name.toText() +
                   " is not InMemoryZoneFinder");
     }
-    return (ZoneIteratorPtr(new MemoryIterator(zone->impl_->domains_, name,
-                                               separate_rrs)));
+    return (ZoneIteratorPtr(new MemoryIterator(
+                                zone->impl_->zone_data_->domains_, name,
+                                separate_rrs)));
 }
 
 ZoneUpdaterPtr
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index b960ab9..85fa5cf 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -89,6 +89,16 @@ public:
     virtual FindNSEC3Result
     findNSEC3(const isc::dns::Name& name, bool recursive);
 
+    // A temporary fake version of findNSEC3 for tests
+    //
+    // This method intentionally has the same interface as findNSEC3 but
+    // uses internally hardcoded hash values and offers a limited set
+    // of functionality for the convenience of tests.  This is a temporary
+    // workaround until #1577 is completed.  At that point this method
+    // should be removed.
+    FindNSEC3Result
+    findNSEC3Tmp(const isc::dns::Name& name, bool recursive);
+
     /// \brief Imelementation of the ZoneFinder::findPreviousName method
     ///
     /// This one throws NotImplemented exception, as InMemory doesn't
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 113d03c..69c7959 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -110,6 +110,8 @@ endif
 EXTRA_DIST =  testdata/brokendb.sqlite3
 EXTRA_DIST += testdata/example.com.signed
 EXTRA_DIST += testdata/example.org
+EXTRA_DIST += testdata/example.org.nsec3-signed
+EXTRA_DIST += testdata/example.org.nsec3-signed-noparam
 EXTRA_DIST += testdata/example.org.sqlite3
 EXTRA_DIST += testdata/example2.com
 EXTRA_DIST += testdata/example2.com.sqlite3
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index c841cb9..944ebbe 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -283,6 +283,25 @@ class InMemoryZoneFinderTest : public ::testing::Test {
         const char* const text; // textual representation of an RRset
         RRsetPtr* rrset;
     };
+protected:
+    // The following sub tests are shared by multiple test cases, changing
+    // the zone's DNSSEC status (unsigned, NSEC-signed or NSEC3-signed).
+    // expected_flags is set to either RESULT_NSEC_SIGNED or
+    // RESULT_NSEC3_SIGNED when it's NSEC/NSEC3 signed respectively and
+    // find() is expected to set the corresponding flags.
+    void findCheck(ZoneFinder::FindResultFlags expected_flags =
+                   ZoneFinder::RESULT_DEFAULT);
+    void emptyNodeCheck(ZoneFinder::FindResultFlags expected_flags =
+                        ZoneFinder::RESULT_DEFAULT);
+    void wildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+                       ZoneFinder::RESULT_DEFAULT);
+    void doCancelWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+                               ZoneFinder::RESULT_DEFAULT);
+    void anyWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+                          ZoneFinder::RESULT_DEFAULT);
+    void emptyWildcardCheck(ZoneFinder::FindResultFlags expected_flags =
+                            ZoneFinder::RESULT_DEFAULT);
+
 public:
     InMemoryZoneFinderTest() :
         class_(RRClass::IN()),
@@ -320,6 +339,8 @@ public:
              &rr_child_dname_},
             {"example.com. 300 IN A 192.0.2.10", &rr_out_},
             {"*.wild.example.org. 300 IN A 192.0.2.1", &rr_wild_},
+            {"*.cnamewild.example.org. 300 IN CNAME canonial.example.org.",
+             &rr_cnamewild_},
             {"foo.wild.example.org. 300 IN A 192.0.2.3", &rr_under_wild_},
             {"wild.*.foo.example.org. 300 IN A 192.0.2.1", &rr_emptywild_},
             {"wild.*.foo.*.bar.example.org. 300 IN A 192.0.2.1",
@@ -331,6 +352,9 @@ public:
             {"bar.foo.wild.example.org. 300 IN A 192.0.2.2", &rr_not_wild_},
             {"baz.foo.wild.example.org. 300 IN A 192.0.2.3",
              &rr_not_wild_another_},
+            {"0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM.example.org. 300 IN "
+             "NSEC3 1 1 12 aabbccdd 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG",
+             &rr_nsec3_},
             {NULL, NULL}
         };
 
@@ -379,7 +403,8 @@ public:
     RRsetPtr rr_grandchild_ns_; // NS below a zone cut (unusual)
     RRsetPtr rr_grandchild_glue_; // glue RR below a deeper zone cut
     RRsetPtr rr_child_dname_; // A DNAME under NS
-    RRsetPtr rr_wild_;
+    RRsetPtr rr_wild_;        // Wildcard record
+    RRsetPtr rr_cnamewild_;     // CNAME at a wildcard
     RRsetPtr rr_emptywild_;
     RRsetPtr rr_nested_emptywild_;
     RRsetPtr rr_nswild_, rr_dnamewild_;
@@ -387,6 +412,7 @@ public:
     RRsetPtr rr_under_wild_;
     RRsetPtr rr_not_wild_;
     RRsetPtr rr_not_wild_another_;
+    RRsetPtr rr_nsec3_;
 
     /**
      * \brief Test one find query to the zone finder.
@@ -401,6 +427,8 @@ public:
      * \param check_answer Should a check against equality of the answer be
      *     done?
      * \param answer The expected rrset, if any should be returned.
+     * \param expected_flags The expected result flags returned via find().
+     *     These can be tested using isWildcard() etc.
      * \param zone_finder Check different InMemoryZoneFinder object than
      *     zone_finder_ (if NULL, uses zone_finder_)
      * \param check_wild_answer Checks that the answer has the same RRs, type
@@ -412,6 +440,8 @@ public:
                   ZoneFinder::Result result,
                   bool check_answer = true,
                   const ConstRRsetPtr& answer = ConstRRsetPtr(),
+                  ZoneFinder::FindResultFlags expected_flags =
+                  ZoneFinder::RESULT_DEFAULT,
                   InMemoryZoneFinder* zone_finder = NULL,
                   ZoneFinder::FindOptions options = ZoneFinder::FIND_DEFAULT,
                   bool check_wild_answer = false)
@@ -423,71 +453,75 @@ public:
         // we can't assign to FindResult
         EXPECT_NO_THROW({
                 ZoneFinder::FindResult find_result(zone_finder->find(
-                                                       name, rrtype,
-                                                       options));
+                                                       name, rrtype, options));
                 // Check it returns correct answers
                 EXPECT_EQ(result, find_result.code);
+                EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+                          find_result.isWildcard());
+                EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED)
+                          != 0, find_result.isNSECSigned());
+                EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED)
+                          != 0, find_result.isNSEC3Signed());
                 if (check_answer) {
-                    EXPECT_EQ(answer, find_result.rrset);
+                    if (!answer) {
+                        ASSERT_FALSE(find_result.rrset);
+                    } else {
+                        ASSERT_TRUE(find_result.rrset);
+                        rrsetCheck(answer, find_result.rrset);
+                    }
                 } else if (check_wild_answer) {
                     ASSERT_NE(ConstRRsetPtr(), answer) <<
                         "Wrong test, don't check for wild names if you expect "
                         "empty answer";
                     ASSERT_NE(ConstRRsetPtr(), find_result.rrset) <<
                         "No answer found";
+                    // Build the expected answer using the given name and
+                    // other parameter of the base wildcard RRset.
+                    RRsetPtr wildanswer(new RRset(name, answer->getClass(),
+                                                  answer->getType(),
+                                                  answer->getTTL()));
                     RdataIteratorPtr expectedIt(answer->getRdataIterator());
-                    RdataIteratorPtr actualIt(
-                        find_result.rrset->getRdataIterator());
-                    while (!expectedIt->isLast() && !actualIt->isLast()) {
-                        EXPECT_EQ(0, expectedIt->getCurrent().compare(
-                            actualIt->getCurrent())) << "The RRs differ ('" <<
-                            expectedIt->getCurrent().toText() << "', '" <<
-                            actualIt->getCurrent().toText() << "')";
-                        expectedIt->next();
-                        actualIt->next();
+                    for (; !expectedIt->isLast(); expectedIt->next()) {
+                        wildanswer->addRdata(expectedIt->getCurrent());
                     }
-                    EXPECT_TRUE(expectedIt->isLast()) <<
-                        "Result has less RRs than expected";
-                    EXPECT_TRUE(actualIt->isLast()) <<
-                        "Result has more RRs than expected";
-                    EXPECT_EQ(answer->getClass(),
-                        find_result.rrset->getClass());
-                    EXPECT_EQ(answer->getType(),
-                        find_result.rrset->getType());
-                    EXPECT_EQ(answer->getTTL(),
-                        find_result.rrset->getTTL());
-                    EXPECT_EQ(name, find_result.rrset->getName());
+                    rrsetCheck(wildanswer, find_result.rrset);
                 }
             });
     }
     /**
      * \brief Calls the findAll on the finder and checks the result.
      */
-    std::vector<ConstRRsetPtr> findAllTest(const Name& name,
-                                           ZoneFinder::Result result,
-                                           size_t expected_size,
-                                           InMemoryZoneFinder* finder = NULL,
-                                           const ConstRRsetPtr &rrset_result =
-                                           ConstRRsetPtr(),
-                                           ZoneFinder::FindOptions options =
-                                           ZoneFinder::FIND_DEFAULT)
+    void findAllTest(const Name& name, ZoneFinder::Result result,
+                     const vector<ConstRRsetPtr>& expected_rrsets,
+                     ZoneFinder::FindResultFlags expected_flags =
+                     ZoneFinder::RESULT_DEFAULT,
+                     InMemoryZoneFinder* finder = NULL,
+                     const ConstRRsetPtr &rrset_result = ConstRRsetPtr(),
+                     ZoneFinder::FindOptions options =
+                     ZoneFinder::FIND_DEFAULT)
     {
         if (finder == NULL) {
             finder = &zone_finder_;
         }
         std::vector<ConstRRsetPtr> target;
-        ZoneFinder::FindResult findResult(finder->findAll(name, target,
-                                                          options));
-        EXPECT_EQ(result, findResult.code);
-        EXPECT_EQ(rrset_result, findResult.rrset);
-        BOOST_FOREACH(const ConstRRsetPtr& rrset, target) {
-            EXPECT_EQ(name, rrset->getName());
+        ZoneFinder::FindResult find_result(finder->findAll(name, target,
+                                                           options));
+        EXPECT_EQ(result, find_result.code);
+        if (!rrset_result) {
+            EXPECT_FALSE(find_result.rrset);
+        } else {
+            ASSERT_TRUE(find_result.rrset);
+            rrsetCheck(rrset_result, find_result.rrset);
         }
-        EXPECT_EQ(expected_size, target.size());
-        return (target);
+        EXPECT_EQ((expected_flags & ZoneFinder::RESULT_WILDCARD) != 0,
+                  find_result.isWildcard());
+        EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC_SIGNED)
+                  != 0, find_result.isNSECSigned());
+        EXPECT_EQ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED)
+                  != 0, find_result.isNSEC3Signed());
+        rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
+                    target.begin(), target.end());
     }
-    // Internal part of the cancelWildcard test that is multiple times
-    void doCancelWildcardTest();
 
     ConstRRsetPtr textToRRset(const string& text_rrset,
                               const RRClass& rrclass = RRClass::IN()) const
@@ -559,6 +593,37 @@ TEST_F(InMemoryZoneFinderTest, addOtherThenCNAME) {
     EXPECT_THROW(zone_finder_.add(rr_cname_), InMemoryZoneFinder::AddError);
 }
 
+TEST_F(InMemoryZoneFinderTest, addCNAMEAndDNSSECRecords) {
+    // CNAME and RRSIG can coexist
+    EXPECT_EQ(SUCCESS, zone_finder_.add(rr_cname_));
+    EXPECT_EQ(SUCCESS, zone_finder_.add(
+                  textToRRset("cname.example.org. 300 IN RRSIG CNAME 5 3 "
+                              "3600 20000101000000 20000201000000 12345 "
+                              "example.org. FAKEFAKEFAKE")));
+
+    // Same for NSEC
+    EXPECT_EQ(SUCCESS, zone_finder_.add(
+                  textToRRset("cname.example.org. 300 IN NSEC "
+                              "dname.example.org. CNAME RRSIG NSEC")));
+
+    // Same as above, but adding NSEC first.
+    EXPECT_EQ(SUCCESS, zone_finder_.add(
+                  textToRRset("cname2.example.org. 300 IN NSEC "
+                              "dname.example.org. CNAME RRSIG NSEC")));
+    EXPECT_EQ(SUCCESS, zone_finder_.add(
+                  textToRRset("cname2.example.org. 300 IN CNAME c.example.")));
+
+    // If there's another type of RRset with NSEC, it should still fail.
+    EXPECT_EQ(SUCCESS, zone_finder_.add(
+                  textToRRset("cname3.example.org. 300 IN A 192.0.2.1")));
+    EXPECT_EQ(SUCCESS, zone_finder_.add(
+                  textToRRset("cname3.example.org. 300 IN NSEC "
+                              "dname.example.org. CNAME RRSIG NSEC")));
+    EXPECT_THROW(zone_finder_.add(textToRRset("cname3.example.org. 300 "
+                                              "IN CNAME c.example.")),
+                 InMemoryZoneFinder::AddError);
+}
+
 TEST_F(InMemoryZoneFinderTest, findCNAME) {
     // install CNAME RR
     EXPECT_EQ(SUCCESS, zone_finder_.add(rr_cname_));
@@ -581,8 +646,8 @@ TEST_F(InMemoryZoneFinderTest, findCNAMEUnderZoneCut) {
         "cname.child.example.org. 300 IN CNAME target.child.example.org.");
     EXPECT_EQ(SUCCESS, zone_finder_.add(rr_cname_under_cut_));
     findTest(Name("cname.child.example.org"), RRType::AAAA(),
-             ZoneFinder::CNAME, true, rr_cname_under_cut_, NULL,
-             ZoneFinder::FIND_GLUE_OK);
+             ZoneFinder::CNAME, true, rr_cname_under_cut_,
+             ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
 }
 
 // Two DNAMEs at single domain are disallowed by RFC 2672, section 3)
@@ -657,7 +722,7 @@ TEST_F(InMemoryZoneFinderTest, DNAMEUnderNS) {
 
     findTest(lowName, RRType::A(), ZoneFinder::DELEGATION, true, rr_child_ns_);
     findTest(lowName, RRType::A(), ZoneFinder::DNAME, true, rr_child_dname_,
-             NULL, ZoneFinder::FIND_GLUE_OK);
+             ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
 }
 
 // Test adding child zones and zone cut handling
@@ -714,34 +779,33 @@ TEST_F(InMemoryZoneFinderTest, findAny) {
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_)));
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_child_glue_)));
 
+    vector<ConstRRsetPtr> expected_sets;
+
     // origin
-    std::vector<ConstRRsetPtr> rrsets(findAllTest(origin_, ZoneFinder::SUCCESS,
-                                                  2));
-    EXPECT_FALSE(rrsets.end() == std::find(rrsets.begin(), rrsets.end(),
-                                           rr_a_));
-    EXPECT_FALSE(rrsets.end() == std::find(rrsets.begin(), rrsets.end(),
-                                           rr_ns_));
+    expected_sets.push_back(rr_a_);
+    expected_sets.push_back(rr_ns_);
+    findAllTest(origin_, ZoneFinder::SUCCESS, expected_sets);
 
     // out zone name
-    findAllTest(Name("example.com"), ZoneFinder::NXDOMAIN, 0);
-
-    rrsets = findAllTest(rr_child_glue_->getName(), ZoneFinder::SUCCESS, 1);
-    EXPECT_FALSE(rrsets.end() == std::find(rrsets.begin(), rrsets.end(),
-                                           rr_child_glue_));
+    findAllTest(Name("example.com"), ZoneFinder::NXDOMAIN,
+                vector<ConstRRsetPtr>());
 
-    // TODO: test NXRRSET case after rbtree non-terminal logic has
-    // been implemented
+    expected_sets.clear();
+    expected_sets.push_back(rr_child_glue_);
+    findAllTest(rr_child_glue_->getName(), ZoneFinder::SUCCESS, expected_sets);
 
     // add zone cut
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_child_ns_)));
 
     // zone cut
-    findAllTest(rr_child_ns_->getName(), ZoneFinder::DELEGATION, 0, NULL,
-                rr_child_ns_);
+    findAllTest(rr_child_ns_->getName(), ZoneFinder::DELEGATION,
+                vector<ConstRRsetPtr>(), ZoneFinder::RESULT_DEFAULT,
+                NULL, rr_child_ns_);
 
     // glue for this zone cut
-    findAllTest(rr_child_glue_->getName(),ZoneFinder::DELEGATION, 0, NULL,
-                rr_child_ns_);
+    findAllTest(rr_child_glue_->getName(),ZoneFinder::DELEGATION,
+                vector<ConstRRsetPtr>(), ZoneFinder::RESULT_DEFAULT,
+                NULL, rr_child_ns_);
 }
 
 TEST_F(InMemoryZoneFinderTest, glue) {
@@ -762,27 +826,30 @@ TEST_F(InMemoryZoneFinderTest, glue) {
 
     // If we do it in the "glue OK" mode, we should find the exact match.
     findTest(rr_child_glue_->getName(), RRType::A(), ZoneFinder::SUCCESS, true,
-             rr_child_glue_, NULL, ZoneFinder::FIND_GLUE_OK);
+             rr_child_glue_, ZoneFinder::RESULT_DEFAULT, NULL,
+             ZoneFinder::FIND_GLUE_OK);
 
     // glue OK + NXRRSET case
     findTest(rr_child_glue_->getName(), RRType::AAAA(), ZoneFinder::NXRRSET,
-             true, ConstRRsetPtr(), NULL, ZoneFinder::FIND_GLUE_OK);
+             true, ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, NULL,
+             ZoneFinder::FIND_GLUE_OK);
 
     // glue OK + NXDOMAIN case
     findTest(Name("www.child.example.org"), RRType::A(),
-             ZoneFinder::DELEGATION, true, rr_child_ns_, NULL,
-             ZoneFinder::FIND_GLUE_OK);
+             ZoneFinder::DELEGATION, true, rr_child_ns_,
+             ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
 
     // nested cut case.  The glue should be found.
     findTest(rr_grandchild_glue_->getName(), RRType::AAAA(),
              ZoneFinder::SUCCESS,
-             true, rr_grandchild_glue_, NULL, ZoneFinder::FIND_GLUE_OK);
+             true, rr_grandchild_glue_, ZoneFinder::RESULT_DEFAULT, NULL,
+             ZoneFinder::FIND_GLUE_OK);
 
     // A non-existent name in nested cut.  This should result in delegation
     // at the highest zone cut.
     findTest(Name("www.grand.child.example.org"), RRType::TXT(),
-             ZoneFinder::DELEGATION, true, rr_child_ns_, NULL,
-             ZoneFinder::FIND_GLUE_OK);
+             ZoneFinder::DELEGATION, true, rr_child_ns_,
+             ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
 }
 
 /**
@@ -792,13 +859,17 @@ TEST_F(InMemoryZoneFinderTest, glue) {
  * \todo This doesn't do any kind of CNAME and so on. If it isn't
  *     directly there, it just tells it doesn't exist.
  */
-TEST_F(InMemoryZoneFinderTest, find) {
+void
+InMemoryZoneFinderTest::findCheck(ZoneFinder::FindResultFlags expected_flags) {
     // Fill some data inside
     // Now put all the data we have there. It should throw nothing
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_)));
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_a_)));
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_ns_aaaa_)));
     EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_finder_.add(rr_a_)));
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+    }
 
     // These two should be successful
     findTest(origin_, RRType::NS(), ZoneFinder::SUCCESS, true, rr_ns_);
@@ -806,15 +877,30 @@ TEST_F(InMemoryZoneFinderTest, find) {
              rr_ns_a_);
 
     // These domain exist but don't have the provided RRType
-    findTest(origin_, RRType::AAAA(), ZoneFinder::NXRRSET);
-    findTest(rr_ns_a_->getName(), RRType::NS(), ZoneFinder::NXRRSET);
+    findTest(origin_, RRType::AAAA(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(), expected_flags);
+    findTest(rr_ns_a_->getName(), RRType::NS(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(), expected_flags);
 
     // These domains don't exist (and one is out of the zone)
-    findTest(Name("nothere.example.org"), RRType::A(), ZoneFinder::NXDOMAIN);
-    findTest(Name("example.net"), RRType::A(), ZoneFinder::NXDOMAIN);
+    findTest(Name("nothere.example.org"), RRType::A(), ZoneFinder::NXDOMAIN,
+             true, ConstRRsetPtr(), expected_flags);
+    findTest(Name("example.net"), RRType::A(), ZoneFinder::NXDOMAIN, true,
+             ConstRRsetPtr(), expected_flags);
 }
 
-TEST_F(InMemoryZoneFinderTest, emptyNode) {
+TEST_F(InMemoryZoneFinderTest, find) {
+    findCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSEC3) {
+    findCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
+void
+InMemoryZoneFinderTest::emptyNodeCheck(
+    ZoneFinder::FindResultFlags expected_flags)
+{
     /*
      * The backend RBTree for this test should look like as follows:
      *          example.org
@@ -836,21 +922,35 @@ TEST_F(InMemoryZoneFinderTest, emptyNode) {
                                           " 300 IN A 192.0.2.1");
         EXPECT_EQ(SUCCESS, zone_finder_.add(rrset));
     }
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+    }
 
     // empty node matching, easy case: the node for 'baz' exists with
     // no data.
-    findTest(Name("baz.example.org"), RRType::A(), ZoneFinder::NXRRSET);
+    findTest(Name("baz.example.org"), RRType::A(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(), expected_flags);
 
     // empty node matching, a trickier case: the node for 'foo' is part of
     // "x.foo", which should be considered an empty node.
-    findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET);
+    findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET, true,
+             ConstRRsetPtr(), expected_flags);
 
     // "org" is contained in "example.org", but it shouldn't be treated as
     // NXRRSET because it's out of zone.
     // Note: basically we don't expect such a query to be performed (the common
     // operation is to identify the best matching zone first then perform
     // search it), but we shouldn't be confused even in the unexpected case.
-    findTest(Name("org"), RRType::A(), ZoneFinder::NXDOMAIN);
+    findTest(Name("org"), RRType::A(), ZoneFinder::NXDOMAIN, true,
+             ConstRRsetPtr(), expected_flags);
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyNode) {
+    emptyNodeCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyNodeNSEC3) {
+    emptyNodeCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
 }
 
 TEST_F(InMemoryZoneFinderTest, load) {
@@ -870,14 +970,14 @@ TEST_F(InMemoryZoneFinderTest, load) {
 
     // Now see there are some rrsets (we don't look inside, though)
     findTest(Name("."), RRType::SOA(), ZoneFinder::SUCCESS, false,
-             ConstRRsetPtr(), &rootzone);
+             ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &rootzone);
     findTest(Name("."), RRType::NS(), ZoneFinder::SUCCESS, false,
-             ConstRRsetPtr(), &rootzone);
+             ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &rootzone);
     findTest(Name("a.root-servers.net."), RRType::A(), ZoneFinder::SUCCESS,
-             false, ConstRRsetPtr(), &rootzone);
+             false, ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &rootzone);
     // But this should no longer be here
     findTest(rr_ns_a_->getName(), RRType::AAAA(), ZoneFinder::NXDOMAIN, true,
-             ConstRRsetPtr(), &rootzone);
+             ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &rootzone);
 
     // Try loading zone that is wrong in a different way
     EXPECT_THROW(zone_finder_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
@@ -888,21 +988,31 @@ TEST_F(InMemoryZoneFinderTest, load) {
  * Test that puts a (simple) wildcard into the zone and checks we can
  * correctly find the data.
  */
-TEST_F(InMemoryZoneFinderTest, wildcard) {
+void
+InMemoryZoneFinderTest::wildcardCheck(
+    ZoneFinder::FindResultFlags expected_flags)
+{
     /*
      *            example.org.
      *                 |
-     *                wild (not *.wild, should have wild mark)
+     *             [cname]wild (not *.wild, should have wild mark)
      *                 |
      *                 *
      */
     EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_));
+    EXPECT_EQ(SUCCESS, zone_finder_.add(rr_cnamewild_));
+    // If the zone is expected to be "signed" with NSEC3, add an NSEC3.
+    // (the content of the NSEC3 shouldn't matter)
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+    }
 
     // Search at the parent. The parent will not have the A, but it will
     // be in the wildcard (so check the wildcard isn't matched at the parent)
     {
-        SCOPED_TRACE("Search at parrent");
-        findTest(Name("wild.example.org"), RRType::A(), ZoneFinder::NXRRSET);
+        SCOPED_TRACE("Search at parent");
+        findTest(Name("wild.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+                 true, ConstRRsetPtr(), expected_flags);
     }
 
     // Search the original name of wildcard
@@ -915,14 +1025,30 @@ TEST_F(InMemoryZoneFinderTest, wildcard) {
     {
         SCOPED_TRACE("Search at created child");
         findTest(Name("a.wild.example.org"), RRType::A(), ZoneFinder::SUCCESS,
-                 false, rr_wild_, NULL, ZoneFinder::FIND_DEFAULT, true);
+                 false, rr_wild_,
+                 ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+                 ZoneFinder::FIND_DEFAULT, true);
+        // Wildcard match, but no data
+        findTest(Name("a.wild.example.org"), RRType::AAAA(),
+                 ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+                 ZoneFinder::RESULT_WILDCARD | expected_flags);
+    }
+
+    // Search name that has CNAME.
+    {
+        SCOPED_TRACE("Matching CNAME");
+        findTest(Name("a.cnamewild.example.org"), RRType::A(),
+                 ZoneFinder::CNAME, false, rr_cnamewild_,
+                 ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+                 ZoneFinder::FIND_DEFAULT, true);
     }
 
     // Search another created name, this time little bit lower
     {
         SCOPED_TRACE("Search at created grand-child");
         findTest(Name("a.b.wild.example.org"), RRType::A(),
-                 ZoneFinder::SUCCESS, false, rr_wild_, NULL,
+                 ZoneFinder::SUCCESS, false, rr_wild_,
+                 ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
                  ZoneFinder::FIND_DEFAULT, true);
     }
 
@@ -930,10 +1056,20 @@ TEST_F(InMemoryZoneFinderTest, wildcard) {
     {
         SCOPED_TRACE("Search under non-wildcard");
         findTest(Name("bar.foo.wild.example.org"), RRType::A(),
-            ZoneFinder::NXDOMAIN);
+                 ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
     }
 }
 
+TEST_F(InMemoryZoneFinderTest, wildcard) {
+    // Normal case
+    wildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, wildcardNSEC3) {
+    // Similar to the previous one, but the zone signed with NSEC3
+    wildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
 /*
  * Test that we don't match a wildcard if we get under delegation.
  * By 4.3.3 of RFC1034:
@@ -954,40 +1090,60 @@ TEST_F(InMemoryZoneFinderTest, delegatedWildcard) {
     {
         SCOPED_TRACE("Looking under delegation point in GLUE_OK mode");
         findTest(Name("a.child.example.org"), RRType::A(),
-                 ZoneFinder::DELEGATION, true, rr_child_ns_, NULL,
-                 ZoneFinder::FIND_GLUE_OK);
+                 ZoneFinder::DELEGATION, true, rr_child_ns_,
+                 ZoneFinder::RESULT_DEFAULT, NULL, ZoneFinder::FIND_GLUE_OK);
     }
 }
 
 // Tests combination of wildcard and ANY.
-TEST_F(InMemoryZoneFinderTest, anyWildcard) {
+void
+InMemoryZoneFinderTest::anyWildcardCheck(
+    ZoneFinder::FindResultFlags expected_flags)
+{
     EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_));
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+    }
+
+    vector<ConstRRsetPtr> expected_sets;
 
     // First try directly the name (normal match)
     {
         SCOPED_TRACE("Asking direcly for *");
-        const std::vector<ConstRRsetPtr>
-            target(findAllTest(Name("*.wild.example.org"), ZoneFinder::SUCCESS,
-                               1));
-        ASSERT_EQ(1, target.size());
-        EXPECT_EQ(RRType::A(), (*target.begin())->getType());
-        EXPECT_EQ(Name("*.wild.example.org"), (*target.begin())->getName());
+        expected_sets.push_back(rr_wild_);
+        findAllTest(Name("*.wild.example.org"), ZoneFinder::SUCCESS,
+                    expected_sets);
     }
 
     // Then a wildcard match
     {
         SCOPED_TRACE("Asking in the wild way");
-        const std::vector<ConstRRsetPtr>
-            target(findAllTest(Name("a.wild.example.org"), ZoneFinder::SUCCESS,
-                               1));
-        EXPECT_EQ(RRType::A(), (*target.begin())->getType());
-        EXPECT_EQ(Name("a.wild.example.org"), (*target.begin())->getName());
+        expected_sets.clear();
+        RRsetPtr expected(new RRset(Name("a.wild.example.org"),
+                                    rr_wild_->getClass(), rr_wild_->getType(),
+                                    rr_wild_->getTTL()));
+        expected->addRdata(rr_wild_->getRdataIterator()->getCurrent());
+        expected_sets.push_back(expected);
+        findAllTest(Name("a.wild.example.org"), ZoneFinder::SUCCESS,
+                    expected_sets,
+                    ZoneFinder::RESULT_WILDCARD | expected_flags);
     }
 }
 
+TEST_F(InMemoryZoneFinderTest, anyWildcard) {
+    anyWildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, anyWildcardNSEC3) {
+    anyWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
 // Test there's nothing in the wildcard in the middle if we load
 // wild.*.foo.example.org.
-TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
+void
+InMemoryZoneFinderTest::emptyWildcardCheck(
+    ZoneFinder::FindResultFlags expected_flags)
+{
     /*
      *            example.org.
      *                foo
@@ -995,6 +1151,9 @@ TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
      *               wild
      */
     EXPECT_EQ(SUCCESS, zone_finder_.add(rr_emptywild_));
+    if ((expected_flags & ZoneFinder::RESULT_NSEC3_SIGNED) != 0) {
+        EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+    }
 
     {
         SCOPED_TRACE("Asking for the original record under wildcard");
@@ -1004,25 +1163,41 @@ TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
 
     {
         SCOPED_TRACE("Asking for A record");
-        findTest(Name("a.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET);
-        findTest(Name("*.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET);
-        findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET);
+        findTest(Name("a.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+                 true, ConstRRsetPtr(),
+                 ZoneFinder::RESULT_WILDCARD | expected_flags);
+        findTest(Name("*.foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+                 true, ConstRRsetPtr(), expected_flags);
+        findTest(Name("foo.example.org"), RRType::A(), ZoneFinder::NXRRSET,
+                 true, ConstRRsetPtr(), expected_flags);
     }
 
     {
         SCOPED_TRACE("Asking for ANY record");
-        findAllTest(Name("*.foo.example.org"), ZoneFinder::NXRRSET, 0);
+        findAllTest(Name("*.foo.example.org"), ZoneFinder::NXRRSET,
+                    vector<ConstRRsetPtr>(), expected_flags);
 
-        findAllTest(Name("a.foo.example.org"), ZoneFinder::NXRRSET, 0);
+        findAllTest(Name("a.foo.example.org"), ZoneFinder::NXRRSET,
+                    vector<ConstRRsetPtr>(),
+                    ZoneFinder::RESULT_WILDCARD | expected_flags);
     }
 
     {
         SCOPED_TRACE("Asking on the non-terminal");
         findTest(Name("wild.bar.foo.example.org"), RRType::A(),
-            ZoneFinder::NXRRSET);
+                 ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+                 ZoneFinder::RESULT_WILDCARD | expected_flags);
     }
 }
 
+TEST_F(InMemoryZoneFinderTest, emptyWildcard) {
+    emptyWildcardCheck();
+}
+
+TEST_F(InMemoryZoneFinderTest, emptyWildcardNSEC3) {
+    emptyWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+}
+
 // Same as emptyWildcard, but with multiple * in the path.
 TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
     EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nested_emptywild_));
@@ -1045,7 +1220,8 @@ TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
 
         for (const char** name = names; *name != NULL; ++ name) {
             SCOPED_TRACE(string("Node ") + *name);
-            findTest(Name(*name), RRType::A(), ZoneFinder::NXRRSET);
+            findTest(Name(*name), RRType::A(), ZoneFinder::NXRRSET, true,
+                     ConstRRsetPtr(), ZoneFinder::RESULT_WILDCARD);
         }
     }
 
@@ -1073,7 +1249,8 @@ TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
         for (const char** name = names; *name != NULL; ++ name) {
             SCOPED_TRACE(string("Node ") + *name);
 
-            findAllTest(Name(*name), ZoneFinder::NXRRSET, 0);
+            findAllTest(Name(*name), ZoneFinder::NXRRSET,
+                        vector<ConstRRsetPtr>());
         }
     }
 }
@@ -1081,14 +1258,16 @@ TEST_F(InMemoryZoneFinderTest, nestedEmptyWildcard) {
 // We run this part twice from the below test, in two slightly different
 // situations
 void
-InMemoryZoneFinderTest::doCancelWildcardTest() {
+InMemoryZoneFinderTest::doCancelWildcardCheck(
+    ZoneFinder::FindResultFlags expected_flags)
+{
     // These should be canceled
     {
         SCOPED_TRACE("Canceled under foo.wild.example.org");
         findTest(Name("aaa.foo.wild.example.org"), RRType::A(),
-            ZoneFinder::NXDOMAIN);
+                 ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
         findTest(Name("zzz.foo.wild.example.org"), RRType::A(),
-            ZoneFinder::NXDOMAIN);
+                 ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
     }
 
     // This is existing, non-wildcard domain, shouldn't wildcard at all
@@ -1113,7 +1292,9 @@ InMemoryZoneFinderTest::doCancelWildcardTest() {
             SCOPED_TRACE(string("Node ") + *name);
 
             findTest(Name(*name), RRType::A(), ZoneFinder::SUCCESS, false,
-                     rr_wild_, NULL, ZoneFinder::FIND_DEFAULT, true);
+                     rr_wild_,
+                     ZoneFinder::RESULT_WILDCARD | expected_flags, NULL,
+                     ZoneFinder::FIND_DEFAULT, true);
         }
     }
 
@@ -1121,7 +1302,7 @@ InMemoryZoneFinderTest::doCancelWildcardTest() {
     {
         SCOPED_TRACE("The foo.wild.example.org itself");
         findTest(Name("foo.wild.example.org"), RRType::A(),
-                 ZoneFinder::NXRRSET);
+                 ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
     }
 }
 
@@ -1141,7 +1322,7 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcard) {
 
     {
         SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
-        doCancelWildcardTest();
+        doCancelWildcardCheck();
     }
 
     // Try putting another one under foo.wild....
@@ -1150,7 +1331,23 @@ TEST_F(InMemoryZoneFinderTest, cancelWildcard) {
     EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_another_));
     {
         SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
-        doCancelWildcardTest();
+        doCancelWildcardCheck();
+    }
+}
+
+TEST_F(InMemoryZoneFinderTest, cancelWildcardNSEC3) {
+    EXPECT_EQ(SUCCESS, zone_finder_.add(rr_wild_));
+    EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_));
+    EXPECT_EQ(SUCCESS, zone_finder_.add(rr_nsec3_));
+
+    {
+        SCOPED_TRACE("Runnig with single entry under foo.wild.example.org");
+        doCancelWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
+    }
+    EXPECT_EQ(SUCCESS, zone_finder_.add(rr_not_wild_another_));
+    {
+        SCOPED_TRACE("Runnig with two entries under foo.wild.example.org");
+        doCancelWildcardCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
     }
 }
 
@@ -1183,13 +1380,13 @@ TEST_F(InMemoryZoneFinderTest, swap) {
     EXPECT_EQ(RRClass::IN(), finder2.getClass());
     // make sure the zone data is swapped, too
     findTest(origin_, RRType::NS(), ZoneFinder::NXDOMAIN, false,
-             ConstRRsetPtr(), &finder1);
+             ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &finder1);
     findTest(other_origin, RRType::TXT(), ZoneFinder::SUCCESS, false,
-             ConstRRsetPtr(), &finder1);
+             ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &finder1);
     findTest(origin_, RRType::NS(), ZoneFinder::SUCCESS, false,
-             ConstRRsetPtr(), &finder2);
+             ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &finder2);
     findTest(other_origin, RRType::TXT(), ZoneFinder::NXDOMAIN, false,
-             ConstRRsetPtr(), &finder2);
+             ConstRRsetPtr(), ZoneFinder::RESULT_DEFAULT, &finder2);
 }
 
 TEST_F(InMemoryZoneFinderTest, getFileName) {
@@ -1307,4 +1504,249 @@ TEST_F(InMemoryZoneFinderTest, addbadRRsig) {
     EXPECT_THROW(zone_finder_.add(textToRRset(rrsig_a_txt)),
                  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";
+
+// For apex (example.org)
+const char* const apex_hash = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+const char* const apex_hash_lower = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
+// For ns1.example.org
+const char* const ns1_hash = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR";
+// For x.y.w.example.org (lower-cased)
+const char* const xrw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
+
+void
+nsec3Check(bool expected_matched, const string& expected_rrsets_txt,
+           const ZoneFinder::FindNSEC3Result& result,
+           bool expected_sig = false)
+{
+    vector<ConstRRsetPtr> actual_rrsets;
+    EXPECT_EQ(expected_matched, result.matched);
+    ASSERT_TRUE(result.closest_proof);
+    if (expected_sig) {
+        ASSERT_TRUE(result.closest_proof->getRRsig());
+    }
+    actual_rrsets.push_back(result.closest_proof);
+    if (expected_sig) {
+        actual_rrsets.push_back(result.closest_proof->getRRsig());
+    }
+    rrsetsCheck(expected_rrsets_txt, actual_rrsets.begin(),
+                actual_rrsets.end());
+}
+
+// In the following tests we use a temporary faked version of findNSEC3
+// as the real version isn't implemented yet (it's a task for #1577).
+// When #1577 is done the tests should be updated using the real version.
+// If we can use fake hash calculator (see #1575), we should be able to
+// just replace findNSEC3Tmp with findNSEC3.
+
+TEST_F(InMemoryZoneFinderTest, addNSEC3) {
+    const string nsec3_text = string(apex_hash) + ".example.org." +
+        string(nsec3_common);
+    // This name shouldn't be found in the normal domain tree.
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_text)));
+    EXPECT_EQ(ZoneFinder::NXDOMAIN,
+              zone_finder_.find(Name(string(apex_hash) + ".example.org"),
+                                RRType::NSEC3()).code);
+    // Dedicated NSEC3 find should be able to find it.
+    nsec3Check(true, nsec3_text,
+               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+
+    // This implementation rejects duplicate/update add of the same hash name
+    EXPECT_EQ(result::EXIST,
+              zone_finder_.add(textToRRset(
+                                   string(apex_hash) + ".example.org." +
+                                   string(nsec3_common) + " AAAA")));
+    // The original NSEC3 should be intact
+    nsec3Check(true, nsec3_text,
+               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+
+    // NSEC3-like name but of ordinary RR type should go to normal tree.
+    const string nonsec3_text = string(apex_hash) + ".example.org. " +
+        "300 IN A 192.0.2.1";
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nonsec3_text)));
+    EXPECT_EQ(ZoneFinder::SUCCESS,
+              zone_finder_.find(Name(string(apex_hash) + ".example.org"),
+                                RRType::A()).code);
+}
+
+TEST_F(InMemoryZoneFinderTest, addNSEC3Lower) {
+    // Similar to the previous case, but NSEC3 owner name is lower-cased.
+    const string nsec3_text = string(apex_hash_lower) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_text)));
+    nsec3Check(true, nsec3_text,
+               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+}
+
+TEST_F(InMemoryZoneFinderTest, addNSEC3Ordering) {
+    // Check that the internal storage ensures comparison based on the NSEC3
+    // semantics, regardless of the add order or the letter-case of hash.
+
+    // Adding "0P..", "2v..", then "2T..".
+    const string smallest = string(apex_hash) + ".example.org." +
+        string(nsec3_common);
+    const string middle = string(ns1_hash) + ".example.org." +
+        string(nsec3_common);
+    const string largest = string(xrw_hash) + ".example.org." +
+        string(nsec3_common);
+    zone_finder_.add(textToRRset(smallest));
+    zone_finder_.add(textToRRset(largest));
+    zone_finder_.add(textToRRset(middle));
+
+    // Then look for NSEC3 that covers a name whose hash is "2S.."
+    // The covering NSEC3 should be "0P.."
+    nsec3Check(false, smallest,
+               zone_finder_.findNSEC3Tmp(Name("www.example.org"), false));
+
+    // Look for NSEC3 that covers names whose hash are "Q0.." and "0A.."
+    // The covering NSEC3 should be "2v.." in both cases
+    nsec3Check(false, largest,
+               zone_finder_.findNSEC3Tmp(Name("xxx.example.org"), false));
+    nsec3Check(false, largest,
+               zone_finder_.findNSEC3Tmp(Name("yyy.example.org"), false));
+}
+
+TEST_F(InMemoryZoneFinderTest, badNSEC3Name) {
+    // Our implementation refuses to load NSEC3 at a wildcard name
+    EXPECT_THROW(zone_finder_.add(textToRRset("*.example.org." +
+                                              string(nsec3_common))),
+                 InMemoryZoneFinder::AddError);
+
+    // Likewise, if the owner name of NSEC3 has too many labels, it's refused.
+    EXPECT_THROW(zone_finder_.add(textToRRset("a." + string(apex_hash) +
+                                              ".example.org." +
+                                              string(nsec3_common))),
+                 InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, addMultiNSEC3) {
+    // In this current implementation multiple NSEC3 RDATA isn't supported.
+    RRsetPtr nsec3(new RRset(Name(string(apex_hash) + ".example.org"),
+                             RRClass::IN(), RRType::NSEC3(), RRTTL(300)));
+    nsec3->addRdata(
+        generic::NSEC3("1 0 12 aabbccdd 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A"));
+    nsec3->addRdata(
+        generic::NSEC3("1 1 1 ddccbbaa 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A"));
+    EXPECT_THROW(zone_finder_.add(nsec3), InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, addNSEC3WithRRSIG) {
+    // Adding NSEC3 and its RRSIG
+    const string nsec3_text = string(apex_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_text)));
+    const string nsec3_rrsig_text = string(apex_hash) + ".example.org." +
+        string(nsec3_rrsig_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_rrsig_text)));
+
+    // Then look for it.  The NSEC3 should have the RRSIG that was just added.
+    nsec3Check(true, nsec3_text + "\n" + nsec3_rrsig_text,
+               zone_finder_.findNSEC3Tmp(Name("example.org"), false), true);
+
+    // Duplicate add of RRSIG for the same NSEC3 is prohibited.
+    EXPECT_THROW(zone_finder_.add(textToRRset(nsec3_rrsig_text)),
+                 InMemoryZoneFinder::AddError);
+
+    // Same check using the lower-cased name.  This also confirms matching
+    // is case-insensitive.
+    EXPECT_THROW(zone_finder_.add(textToRRset(string(apex_hash_lower) +
+                                              ".example.org."
+                                              + string(nsec3_rrsig_common))),
+                 InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, badRRsigForNSEC3) {
+    // adding RRSIG for NSEC3 even before adding any NSEC3 (internally,
+    // a space for NSEC3 namespace isn't yet allocated)
+    EXPECT_THROW(zone_finder_.add(textToRRset(string(apex_hash) +
+                                              ".example.org." +
+                                              string(nsec3_rrsig_common))),
+                 InMemoryZoneFinder::AddError);
+
+    // Add an NSEC3
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(
+                  textToRRset(string(apex_hash) + ".example.org." +
+                              string(nsec3_common))));
+
+    // Then add an NSEC3 for a non existent NSEC3.  It should fail in the
+    // current implementation.
+    EXPECT_THROW(zone_finder_.add(textToRRset(string(ns1_hash) +
+                                              ".example.org." +
+                                              string(nsec3_rrsig_common))),
+                 InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, paramConsistencyWithNSEC3PARAM) {
+    // First, add an NSEC3PARAM RR
+    EXPECT_EQ(result::SUCCESS,
+              zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+                                           "1 0 12 aabbccdd")));
+    // Adding an NSEC3 that has matching parameters is okay.
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(
+                  textToRRset(string(apex_hash) + ".example.org." +
+                              string(nsec3_common))));
+    // NSEC3 with inconsistent parameter will be rejected
+    EXPECT_THROW(zone_finder_.add(
+                     textToRRset("a.example.org. 300 IN NSEC3 1 0 1 aabbccdd "
+                                 "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG")),
+                 InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, paramConsistencyWithNSEC3) {
+    // Add an NSEC3 without adding NSEC3PARAM
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(
+                  textToRRset(string(apex_hash) + ".example.org." +
+                              string(nsec3_common))));
+    // Adding an NSEC3 with inconsistent parameter will be rejected at this pt.
+    EXPECT_THROW(zone_finder_.add(
+                     textToRRset("a.example.org. 300 IN NSEC3 1 0 1 aabbccdd "
+                                 "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG")),
+                 InMemoryZoneFinder::AddError);
+
+    // Likewise, NSEC3PARAM with inconsistent parameter will be rejected.
+    EXPECT_THROW(zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+                                              "1 0 1 aabbccdd")),
+                 InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, multiNSEC3PARAM) {
+    // In this current implementation multiple NSEC3PARAM isn't supported.
+    RRsetPtr nsec3param(new RRset(Name("example.org"), RRClass::IN(),
+                                  RRType::NSEC3PARAM(), RRTTL(300)));
+    nsec3param->addRdata(generic::NSEC3PARAM("1 0 12 aabbccdd"));
+    nsec3param->addRdata(generic::NSEC3PARAM("1 1 1 ddccbbaa"));
+    EXPECT_THROW(zone_finder_.add(nsec3param), InMemoryZoneFinder::AddError);
+}
+
+TEST_F(InMemoryZoneFinderTest, nonOriginNSEC3PARAM) {
+    // This is a normal NSEC3PARAM at the zone origin
+    EXPECT_EQ(result::SUCCESS,
+              zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+                                           "1 0 12 aabbccdd")));
+    // Add another (with different param) at a non origin node.  This is
+    // awkward, but the implementation accepts it as an ordinary RR.
+    EXPECT_EQ(result::SUCCESS,
+              zone_finder_.add(textToRRset("a.example.org. 300 IN NSEC3PARAM "
+                                           "1 1 1 aabbccdd")));
+}
+
+TEST_F(InMemoryZoneFinderTest, loadNSEC3Zone) {
+    // Check if it can load validly NSEC3-signed zone.  At this moment
+    // it's sufficient to see it doesn't crash
+    zone_finder_.load(TEST_DATA_DIR "/example.org.nsec3-signed");
+
+    // Reload the zone with a version that doesn't have NSEC3PARAM.
+    // This is an abnormal case, but the implementation accepts it.
+    zone_finder_.load(TEST_DATA_DIR "/example.org.nsec3-signed-noparam");
+}
 }
diff --git a/src/lib/datasrc/tests/testdata/example.org.nsec3-signed b/src/lib/datasrc/tests/testdata/example.org.nsec3-signed
new file mode 100644
index 0000000..9c1195f
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/example.org.nsec3-signed
@@ -0,0 +1,14 @@
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012013000 7200 3600 2592000 1200
+example.org.				      86400 IN RRSIG	SOA 7 2 86400 20120301040838 20120131040838 19562 example.org. Jt9wCRLS5TQxZH0IBqrM9uMGD453rIoxYopfM9AjjRZfEx+HGlBpOZeR pGN7yLcN+URnicOD0ydLHiakaBODiZyNoYCKYG5d2ZOhL+026REnDKNM 0m5T3X3sczP+l55An/GITheTdrKt3Y1Ouc2yKI8ro8JjOxV/a4nGDWjK x9A=
+example.org.				      86400 IN NS	ns.example.org.
+example.org.				      86400 IN RRSIG	NS 7 2 86400 20120301040838 20120131040838 19562 example.org. gYXL3xK4IFdJU6TtiVuzqDBb2MeA8xB3AKtHlJGFTfTRNHyuej0ZGovx TeUYsLYmoiGYaJG66iD1tYYFq0qdj0xWq+LEa53ACtKvYf9IIwK4ijJs k0g6xCNavc6/qPppymDhN7MvoFVkW59uJa0HPWlsIIuRlEAr7xyt85vq yoA=
+example.org.				      86400 IN DNSKEY	256 3 7 AwEAAbrBkKf2gmGtG4aogZY4svIZCrOLLZlQzVHwz7WxJdTR8iEnvz/x Q/jReDroS5CBZWvzwLlhPIpsJAojx0oj0RvfJNsz3+6LN8q7x9u6+86B 85CYjTk3dcFOebgkF4fXr7/kkOX+ZY94Zk0Z1+pUC3eY4gkKcyME/Uxm O18PBTeB
+example.org.				      86400 IN RRSIG	DNSKEY 7 2 86400 20120301040838 20120131040838 19562 example.org. d0eLF8JqNHaGuBSX0ashU5c1O/wyWU43UUsKGrMQIoBDiJ588MWQOnas rwvW6vdkLNqRqCsP/B4epV/EtLL0tBsk5SHkTYbNo80gGrBufQ6YrWRr Ile8Z+h+MR4y9DybbjmuNKqaO4uQMg/X6+4HqRAKx1lmZMTcrcVeOwDM ZA4=
+example.org.				      0	IN NSEC3PARAM	1 0 10 AABBCCDD
+example.org.				      0	IN RRSIG	NSEC3PARAM 7 2 0 20120301040838 20120131040838 19562 example.org. Ggs5MiQDlXXt22Fz9DNg3Ujc0T6MBfumlRkd8/enBbJwLmqw2QXAzDEk pjUeGstCEHKzxJDJstavGoCpTDJgoV4Fd9szooMx69rzPrq9tdoM46jG xZHqw+Pv2fkRGC6aP7ZX1r3Qnpwpk47AQgATftbO4G6KcMcO8JoKE47x XLM=
+ns.example.org.				      86400 IN A	192.0.2.1
+ns.example.org.				      86400 IN RRSIG	A 7 3 86400 20120301040838 20120131040838 19562 example.org. dOH+Dxib8VcGnjLrKILsqDhS1wki6BWk1dZwpOGUGHyLWcLNW8ygWY2o r29jPhHtaFCNWpn46JebgnXDPRiQjaY3dQqL8rcf2QX1e3+Cicw1OSrs S0sUDE5DmMNEac+ZCUQ0quCStZTCldl05hlspV2RS92TpApnoOK0nXMp Uak=
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG	NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKO yfZc8wKRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVv cD3dFksPyiKHf/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3 CTM=
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN RRSIG	NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. j7d8GL4YqX035FBcPPsEcSWHjWcKdlQMHLL4TB67xVNFnl4SEFQCp4OO AtPap5tkKakwgWxoQVN9XjnqrBz+oQhofDkB3aTatAjIIkcwcnrm3AYQ rTI3E03ySiRwuCPKVmHOLUV2cG6O4xzcmP+MYZcvPTS8V3F5LlaU22i7 A3E=
diff --git a/src/lib/datasrc/tests/testdata/example.org.nsec3-signed-noparam b/src/lib/datasrc/tests/testdata/example.org.nsec3-signed-noparam
new file mode 100644
index 0000000..5caa308
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/example.org.nsec3-signed-noparam
@@ -0,0 +1,15 @@
+;; This file intentionally removes NSEC3PARAM from example.org.nsec3-signed
+example.org.				      86400 IN SOA	ns.example.org. ns.example.org. 2012013000 7200 3600 2592000 1200
+example.org.				      86400 IN RRSIG	SOA 7 2 86400 20120301040838 20120131040838 19562 example.org. Jt9wCRLS5TQxZH0IBqrM9uMGD453rIoxYopfM9AjjRZfEx+HGlBpOZeR pGN7yLcN+URnicOD0ydLHiakaBODiZyNoYCKYG5d2ZOhL+026REnDKNM 0m5T3X3sczP+l55An/GITheTdrKt3Y1Ouc2yKI8ro8JjOxV/a4nGDWjK x9A=
+example.org.				      86400 IN NS	ns.example.org.
+example.org.				      86400 IN RRSIG	NS 7 2 86400 20120301040838 20120131040838 19562 example.org. gYXL3xK4IFdJU6TtiVuzqDBb2MeA8xB3AKtHlJGFTfTRNHyuej0ZGovx TeUYsLYmoiGYaJG66iD1tYYFq0qdj0xWq+LEa53ACtKvYf9IIwK4ijJs k0g6xCNavc6/qPppymDhN7MvoFVkW59uJa0HPWlsIIuRlEAr7xyt85vq yoA=
+example.org.				      86400 IN DNSKEY	256 3 7 AwEAAbrBkKf2gmGtG4aogZY4svIZCrOLLZlQzVHwz7WxJdTR8iEnvz/x Q/jReDroS5CBZWvzwLlhPIpsJAojx0oj0RvfJNsz3+6LN8q7x9u6+86B 85CYjTk3dcFOebgkF4fXr7/kkOX+ZY94Zk0Z1+pUC3eY4gkKcyME/Uxm O18PBTeB
+example.org.				      86400 IN RRSIG	DNSKEY 7 2 86400 20120301040838 20120131040838 19562 example.org. d0eLF8JqNHaGuBSX0ashU5c1O/wyWU43UUsKGrMQIoBDiJ588MWQOnas rwvW6vdkLNqRqCsP/B4epV/EtLL0tBsk5SHkTYbNo80gGrBufQ6YrWRr Ile8Z+h+MR4y9DybbjmuNKqaO4uQMg/X6+4HqRAKx1lmZMTcrcVeOwDM ZA4=
+;; example.org.				      0	IN NSEC3PARAM	1 0 10 AABBCCDD
+;; example.org.				      0	IN RRSIG	NSEC3PARAM 7 2 0 20120301040838 20120131040838 19562 example.org. Ggs5MiQDlXXt22Fz9DNg3Ujc0T6MBfumlRkd8/enBbJwLmqw2QXAzDEk pjUeGstCEHKzxJDJstavGoCpTDJgoV4Fd9szooMx69rzPrq9tdoM46jG xZHqw+Pv2fkRGC6aP7ZX1r3Qnpwpk47AQgATftbO4G6KcMcO8JoKE47x XLM=
+ns.example.org.				      86400 IN A	192.0.2.1
+ns.example.org.				      86400 IN RRSIG	A 7 3 86400 20120301040838 20120131040838 19562 example.org. dOH+Dxib8VcGnjLrKILsqDhS1wki6BWk1dZwpOGUGHyLWcLNW8ygWY2o r29jPhHtaFCNWpn46JebgnXDPRiQjaY3dQqL8rcf2QX1e3+Cicw1OSrs S0sUDE5DmMNEac+ZCUQ0quCStZTCldl05hlspV2RS92TpApnoOK0nXMp Uak=
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG
+09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG	NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKO yfZc8wKRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVv cD3dFksPyiKHf/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3 CTM=
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN NSEC3	1 0 10 AABBCCDD 09GM5T42SMIMT7R8DF6RTG80SFMS1NLU NS SOA RRSIG DNSKEY NSEC3PARAM
+RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD.example.org. 1200 IN RRSIG	NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org. j7d8GL4YqX035FBcPPsEcSWHjWcKdlQMHLL4TB67xVNFnl4SEFQCp4OO AtPap5tkKakwgWxoQVN9XjnqrBz+oQhofDkB3aTatAjIIkcwcnrm3AYQ rTI3E03ySiRwuCPKVmHOLUV2cG6O4xzcmP+MYZcvPTS8V3F5LlaU22i7 A3E=
diff --git a/src/lib/dns/nsec3hash.cc b/src/lib/dns/nsec3hash.cc
index 3217c67..8755959 100644
--- a/src/lib/dns/nsec3hash.cc
+++ b/src/lib/dns/nsec3hash.cc
@@ -15,6 +15,7 @@
 #include <stdint.h>
 
 #include <cassert>
+#include <cstring>
 #include <string>
 #include <vector>
 
@@ -55,10 +56,10 @@ private:
     static const uint8_t NSEC3_HASH_SHA1 = 1;
 
 public:
-    NSEC3HashRFC5155(const generic::NSEC3PARAM& param) :
-        algorithm_(param.getHashalg()),
-        iterations_(param.getIterations()),
-        salt_(param.getSalt()), digest_(SHA1_HASHSIZE), obuf_(Name::MAX_WIRE)
+    NSEC3HashRFC5155(uint8_t algorithm, uint16_t iterations,
+                     const vector<uint8_t>& salt) :
+        algorithm_(algorithm), iterations_(iterations),
+        salt_(salt), digest_(SHA1_HASHSIZE), obuf_(Name::MAX_WIRE)
     {
         if (algorithm_ != NSEC3_HASH_SHA1) {
             isc_throw(UnknownNSEC3HashAlgorithm, "Unknown NSEC3 algorithm: " <<
@@ -69,6 +70,11 @@ public:
 
     virtual std::string calculate(const Name& name) const;
 
+    virtual bool match(const generic::NSEC3& nsec3) const;
+    virtual bool match(const generic::NSEC3PARAM& nsec3param) const;
+    bool match(uint8_t algorithm, uint16_t iterations,
+               const vector<uint8_t>& salt) const;
+
 private:
     const uint8_t algorithm_;
     const uint16_t iterations_;
@@ -115,6 +121,27 @@ NSEC3HashRFC5155::calculate(const Name& name) const {
 
     return (encodeBase32Hex(digest_));
 }
+
+bool
+NSEC3HashRFC5155::match(uint8_t algorithm, uint16_t iterations,
+                        const vector<uint8_t>& salt) const
+{
+    return (algorithm_ == algorithm && iterations_ == iterations &&
+            salt_.size() == salt.size() &&
+            (salt_.empty() || memcmp(&salt_[0], &salt[0], salt_.size()) == 0));
+}
+
+bool
+NSEC3HashRFC5155::match(const generic::NSEC3& nsec3) const {
+    return (match(nsec3.getHashalg(), nsec3.getIterations(),
+                  nsec3.getSalt()));
+}
+
+bool
+NSEC3HashRFC5155::match(const generic::NSEC3PARAM& nsec3param) const {
+    return (match(nsec3param.getHashalg(), nsec3param.getIterations(),
+                  nsec3param.getSalt()));
+}
 } // end of unnamed namespace
 
 namespace isc {
@@ -122,7 +149,14 @@ namespace dns {
 
 NSEC3Hash*
 NSEC3Hash::create(const generic::NSEC3PARAM& param) {
-    return (new NSEC3HashRFC5155(param));
+    return (new NSEC3HashRFC5155(param.getHashalg(), param.getIterations(),
+                                 param.getSalt()));
+}
+
+NSEC3Hash*
+NSEC3Hash::create(const generic::NSEC3& nsec3) {
+    return (new NSEC3HashRFC5155(nsec3.getHashalg(), nsec3.getIterations(),
+                                 nsec3.getSalt()));
 }
 
 } // namespace dns
diff --git a/src/lib/dns/nsec3hash.h b/src/lib/dns/nsec3hash.h
index 9f39172..7937e9d 100644
--- a/src/lib/dns/nsec3hash.h
+++ b/src/lib/dns/nsec3hash.h
@@ -25,6 +25,7 @@ class Name;
 
 namespace rdata {
 namespace generic {
+class NSEC3;
 class NSEC3PARAM;
 }
 }
@@ -108,6 +109,12 @@ public:
     /// \return A pointer to a concrete derived object of \c NSEC3Hash.
     static NSEC3Hash* create(const rdata::generic::NSEC3PARAM& param);
 
+    /// \brief Factory method of NSECHash from NSEC3 RDATA.
+    ///
+    /// This is similar to the other version, but extracts the parameters
+    /// for hash calculation from an NSEC3 RDATA object.
+    static NSEC3Hash* create(const rdata::generic::NSEC3& nsec3);
+
     /// \brief The destructor.
     virtual ~NSEC3Hash() {}
 
@@ -123,6 +130,28 @@ public:
     /// calculated.
     /// \return Base32hex-encoded string of the hash value.
     virtual std::string calculate(const Name& name) const = 0;
+
+    /// \brief Match given NSEC3 parameters with that of the hash.
+    ///
+    /// This method compares NSEC3 parameters used for hash calculation
+    /// in the object with those in the given NSEC3 RDATA, and return
+    /// true iff they completely match.  In the current implementation
+    /// only the algorithm, iterations and salt are compared; the flags
+    /// are ignored (as they don't affect hash calculation per RFC5155).
+    ///
+    /// \throw None
+    ///
+    /// \param nsec3 An NSEC3 RDATA object whose hash parameters are to be
+    /// matched
+    /// \return true If the given parameters match the local ones; false
+    /// otherwise.
+    virtual bool match(const rdata::generic::NSEC3& nsec3) const = 0;
+
+    /// \brief Match given NSEC3PARAM parameters with that of the hash.
+    ///
+    /// This is similar to the other version, but extracts the parameters
+    /// to compare from an NSEC3PARAM RDATA object.
+    virtual bool match(const rdata::generic::NSEC3PARAM& nsec3param) const = 0;
 };
 
 }
diff --git a/src/lib/dns/python/nsec3hash_python.cc b/src/lib/dns/python/nsec3hash_python.cc
index cd97a80..01e8ae5 100644
--- a/src/lib/dns/python/nsec3hash_python.cc
+++ b/src/lib/dns/python/nsec3hash_python.cc
@@ -55,13 +55,24 @@ NSEC3Hash_init(PyObject* po_self, PyObject* args, PyObject*) {
         if (PyArg_ParseTuple(args, "O", &po_rdata)) {
             if (!PyRdata_Check(po_rdata)) {
                 PyErr_Format(PyExc_TypeError,
-                             "param must be an Rdata of type NSEC3HASH, "
-                             "not %.200s", po_rdata->ob_type->tp_name);
+                             "param must be an Rdata of type NSEC3/NSEC3PARAM,"
+                             " not %.200s", po_rdata->ob_type->tp_name);
+                return (-1);
+            }
+            const Rdata& rdata = PyRdata_ToRdata(po_rdata);
+            const generic::NSEC3PARAM* nsec3param =
+                dynamic_cast<const generic::NSEC3PARAM*>(&rdata);
+            const generic::NSEC3* nsec3 =
+                dynamic_cast<const generic::NSEC3*>(&rdata);
+            if (nsec3param != NULL) {
+                self->cppobj = NSEC3Hash::create(*nsec3param);
+            } else if (nsec3 != NULL) {
+                self->cppobj = NSEC3Hash::create(*nsec3);
+            } else {
+                PyErr_Format(PyExc_TypeError,
+                             "param must be an Rdata of type NSEC3/NSEC3HASH");
                 return (-1);
             }
-            self->cppobj = NSEC3Hash::create(
-                dynamic_cast<const generic::NSEC3PARAM&>(
-                    PyRdata_ToRdata(po_rdata)));
             return (0);
         }
     } catch (const UnknownNSEC3HashAlgorithm& ex) {
@@ -118,6 +129,51 @@ NSEC3Hash_calculate(PyObject* po_self, PyObject* args) {
     return (NULL);
 }
 
+PyObject*
+NSEC3Hash_match(PyObject* po_self, PyObject* args) {
+    s_NSEC3Hash* const self = static_cast<s_NSEC3Hash*>(po_self);
+
+    try {
+        PyObject* po_rdata;
+        if (PyArg_ParseTuple(args, "O", &po_rdata)) {
+            if (!PyRdata_Check(po_rdata)) {
+                PyErr_Format(PyExc_TypeError,
+                             "param must be an Rdata of type NSEC3/NSEC3PARAM,"
+                             " not %.200s", po_rdata->ob_type->tp_name);
+                return (NULL);
+            }
+            const Rdata& rdata = PyRdata_ToRdata(po_rdata);
+            const generic::NSEC3PARAM* nsec3param =
+                dynamic_cast<const generic::NSEC3PARAM*>(&rdata);
+            const generic::NSEC3* nsec3 =
+                dynamic_cast<const generic::NSEC3*>(&rdata);
+            bool matched;
+            if (nsec3param != NULL) {
+                matched = self->cppobj->match(*nsec3param);
+            } else if (nsec3 != NULL) {
+                matched = self->cppobj->match(*nsec3);
+            } else {
+                PyErr_Format(PyExc_TypeError,
+                             "param must be an Rdata of type NSEC3/NSEC3HASH");
+                return (NULL);
+            }
+            PyObject* ret = matched ? Py_True : Py_False;
+            Py_INCREF(ret);
+            return (ret);
+        }
+    } catch (const exception& ex) {
+        const string ex_what = "Unexpected failure in NSEC3Hash.match: " +
+            string(ex.what());
+        PyErr_SetString(po_IscException, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+
+    return (NULL);
+}
+
 // This list contains the actual set of functions we have in
 // python. Each entry has
 // 1. Python method name
@@ -126,6 +182,7 @@ NSEC3Hash_calculate(PyObject* po_self, PyObject* args) {
 // 4. Documentation
 PyMethodDef NSEC3Hash_methods[] = {
     { "calculate", NSEC3Hash_calculate, METH_VARARGS, NSEC3Hash_calculate_doc },
+    { "match", NSEC3Hash_match, METH_VARARGS, NSEC3Hash_match_doc },
     { NULL, NULL, 0, NULL }
 };
 } // end of unnamed namespace
diff --git a/src/lib/dns/python/nsec3hash_python_inc.cc b/src/lib/dns/python/nsec3hash_python_inc.cc
index 9e8640f..7d0dfd4 100644
--- a/src/lib/dns/python/nsec3hash_python_inc.cc
+++ b/src/lib/dns/python/nsec3hash_python_inc.cc
@@ -10,7 +10,7 @@ NSEC3 hash values as defined in RFC5155.\n\
 \n\
 NSEC3Hash(param)\n\
 \n\
-    Constructor from NSEC3PARAM RDATA.\n\
+    Constructor.\n\
 \n\
     The hash algorithm given via param must be known to the\n\
     implementation. Otherwise UnknownNSEC3HashAlgorithm exception will\n\
@@ -21,12 +21,13 @@ NSEC3Hash(param)\n\
                  unknown.\n\
 \n\
     Parameters:\n\
-      param      NSEC3 parameters used for subsequent calculation.\n\
+      param      NSEC3PARAM or NSEC3 Rdata object whose parameters are\n\
+                 to be used for subsequent calculation.\n\
 \n\
 ";
 
 const char* const NSEC3Hash_calculate_doc = "\
-calculate(Name) -> string\n\
+calculate(name) -> string\n\
 \n\
 Calculate the NSEC3 hash.\n\
 \n\
@@ -42,4 +43,26 @@ Parameters:\n\
 \n\
 Return Value(s): Base32hex-encoded string of the hash value.\n\
 ";
+
+const char* const NSEC3Hash_match_doc = "\
+match(rdata) -> bool\n                   \
+\n\
+Match given NSEC3 or NSEC3PARAM parameters with that of the hash.\n\
+\n\
+This method compares NSEC3 parameters used for hash calculation in the\n\
+object with those in the given RDATA, and return true iff they\n\
+completely match. In the current implementation only the algorithm,\n\
+iterations and salt are compared; the flags are ignored (as they don't\n\
+affect hash calculation per RFC5155).\n\
+\n\
+Exceptions:\n\
+  None\n\
+\n\
+Parameters:\n\
+  rdata      An NSEC3 or NSEC3PARAM Rdata object whose hash parameters\n\
+             are to be matched\n\
+\n\
+Return Value(s): true If the given parameters match the local ones;\n\
+false otherwise.\n\
+";
 } // unnamed namespace
diff --git a/src/lib/dns/python/tests/nsec3hash_python_test.py b/src/lib/dns/python/tests/nsec3hash_python_test.py
index 41b163e..1a247d0 100644
--- a/src/lib/dns/python/tests/nsec3hash_python_test.py
+++ b/src/lib/dns/python/tests/nsec3hash_python_test.py
@@ -23,9 +23,12 @@ class NSEC3HashTest(unittest.TestCase):
     '''
 
     def setUp(self):
+        self.nsec3_common = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG"
         self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(), RRClass.IN(),
                                          "1 0 12 aabbccdd"))
-
+        self.test_hash_nsec3 = NSEC3Hash(Rdata(RRType.NSEC3(), RRClass.IN(),
+                                               "1 0 12 aabbccdd " +
+                                               self.nsec3_common))
     def test_bad_construct(self):
         # missing parameter
         self.assertRaises(TypeError, NSEC3Hash)
@@ -38,28 +41,33 @@ class NSEC3HashTest(unittest.TestCase):
                                                       RRClass.IN(),
                                                       "1 0 12 aabbccdd"), 1)
 
+        # Invaid type of RDATA
+        self.assertRaises(TypeError, NSEC3Hash, Rdata(RRType.A(), RRClass.IN(),
+                                                      "192.0.2.1"))
+
     def test_unknown_algorithm(self):
         self.assertRaises(UnknownNSEC3HashAlgorithm, NSEC3Hash,
                           Rdata(RRType.NSEC3PARAM(), RRClass.IN(),
                                 "2 0 12 aabbccdd"))
+        self.assertRaises(UnknownNSEC3HashAlgorithm, NSEC3Hash,
+                          Rdata(RRType.NSEC3(), RRClass.IN(),
+                                "2 0 12 aabbccdd " + self.nsec3_common))
 
-    def test_calculate(self):
+    def calculate_check(self, hash):
         # A couple of normal cases from the RFC5155 example.
         self.assertEqual("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
-                         self.test_hash.calculate(Name("example")))
+                         hash.calculate(Name("example")))
         self.assertEqual("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
-                         self.test_hash.calculate(Name("a.example")))
+                         hash.calculate(Name("a.example")))
 
         # Check case-insensitiveness
         self.assertEqual("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
-                         self.test_hash.calculate(Name("EXAMPLE")))
+                         hash.calculate(Name("EXAMPLE")))
 
-        # Some boundary cases: 0-iteration and empty salt.  Borrowed from the
-        # .com zone data.
-        self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(),
-                                         RRClass.IN(),"1 0 0 -"))
-        self.assertEqual("CK0POJMG874LJREF7EFN8430QVIT8BSM",
-                         self.test_hash.calculate(Name("com")))
+
+    def test_calculate(self):
+        self.calculate_check(self.test_hash)
+        self.calculate_check(self.test_hash_nsec3)
 
         # Using unusually large iterations, something larger than the 8-bit
         #range.  (expected hash value generated by BIND 9's dnssec-signzone)
@@ -68,10 +76,53 @@ class NSEC3HashTest(unittest.TestCase):
         self.assertEqual("COG6A52MJ96MNMV3QUCAGGCO0RHCC2Q3",
                          self.test_hash.calculate(Name("example.org")))
 
+        # Some boundary cases: 0-iteration and empty salt.  Borrowed from the
+        # .com zone data.
+        self.test_hash = NSEC3Hash(Rdata(RRType.NSEC3PARAM(),
+                                         RRClass.IN(),"1 0 0 -"))
+        self.assertEqual("CK0POJMG874LJREF7EFN8430QVIT8BSM",
+                         self.test_hash.calculate(Name("com")))
+
     def test_calculate_badparam(self):
         self.assertRaises(TypeError, self.test_hash.calculate, "example")
         self.assertRaises(TypeError, self.test_hash.calculate)
         self.assertRaises(TypeError, self.test_hash.calculate, Name("."), 1)
 
+    def check_match(self, hash, rrtype, postfix):
+        # If all parameters match, it's considered to be matched.
+        self.assertTrue(hash.match(Rdata(rrtype, RRClass.IN(),
+                                         "1 0 12 aabbccdd" + postfix)))
+        # Algorithm doesn't match
+        self.assertFalse(hash.match(Rdata(rrtype, RRClass.IN(),
+                                          "2 0 12 aabbccdd" + postfix)))
+        # Iterations doesn't match
+        self.assertFalse(hash.match(Rdata(rrtype, RRClass.IN(),
+                                          "1 0 1 aabbccdd" + postfix)))
+        # Salt doesn't match
+        self.assertFalse(hash.match(Rdata(rrtype, RRClass.IN(),
+                                          "1 0 12 aabbccde" + postfix)))
+        # Salt doesn't match: the other has an empty salt
+        self.assertFalse(hash.match(Rdata(rrtype, RRClass.IN(),
+                                          "1 0 12 -" + postfix)))
+        # Flag doesn't matter
+        self.assertTrue(hash.match(Rdata(rrtype, RRClass.IN(),
+                                         "1 1 12 aabbccdd" + postfix)))
+
+    def test_match(self):
+        self.check_match(self.test_hash, RRType.NSEC3(),
+                         " " + self.nsec3_common)
+        self.check_match(self.test_hash_nsec3, RRType.NSEC3(),
+                         " " + self.nsec3_common)
+        self.check_match(self.test_hash, RRType.NSEC3PARAM(), "")
+        self.check_match(self.test_hash_nsec3, RRType.NSEC3PARAM(), "")
+
+        # bad parameter checks
+        self.assertRaises(TypeError, self.test_hash.match, 1)
+        self.assertRaises(TypeError, self.test_hash.match,
+                          Rdata(RRType.NSEC3(), RRClass.IN(),
+                                "1 0 12 aabbccdd " + self.nsec3_common), 1)
+        self.assertRaises(TypeError, self.test_hash.match,
+                          Rdata(RRType.A(), RRClass.IN(), "192.0.2.1"))
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/src/lib/dns/tests/nsec3hash_unittest.cc b/src/lib/dns/tests/nsec3hash_unittest.cc
index c0fc9d9..36a1d4d 100644
--- a/src/lib/dns/tests/nsec3hash_unittest.cc
+++ b/src/lib/dns/tests/nsec3hash_unittest.cc
@@ -12,6 +12,8 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <string>
+
 #include <gtest/gtest.h>
 
 #include <boost/scoped_ptr.hpp>
@@ -20,22 +22,32 @@
 #include <dns/rdataclass.h>
 
 using boost::scoped_ptr;
+using namespace std;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
 namespace {
 typedef scoped_ptr<NSEC3Hash> NSEC3HashPtr;
 
+// Commonly used NSEC3 suffix, defined to reduce amount of type
+const char* const nsec3_common = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
+
 class NSEC3HashTest : public ::testing::Test {
 protected:
     NSEC3HashTest() :
-        test_hash(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")))
+        test_hash(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd"))),
+        test_hash_nsec3(NSEC3Hash::create(generic::NSEC3
+                                          ("1 0 12 aabbccdd " +
+                                           string(nsec3_common))))
     {}
 
     // An NSEC3Hash object commonly used in tests.  Parameters are borrowed
     // from the RFC5155 example.  Construction of this object implicitly
     // checks a successful case of the creation.
     NSEC3HashPtr test_hash;
+
+    // Similar to test_hash, but created from NSEC3 RR.
+    NSEC3HashPtr test_hash_nsec3;
 };
 
 TEST_F(NSEC3HashTest, unknownAlgorithm) {
@@ -43,18 +55,36 @@ TEST_F(NSEC3HashTest, unknownAlgorithm) {
                      NSEC3Hash::create(
                          generic::NSEC3PARAM("2 0 12 aabbccdd"))),
                      UnknownNSEC3HashAlgorithm);
+    EXPECT_THROW(NSEC3HashPtr(
+                     NSEC3Hash::create(
+                         generic::NSEC3("2 0 12 aabbccdd " +
+                                        string(nsec3_common)))),
+                     UnknownNSEC3HashAlgorithm);
 }
 
-TEST_F(NSEC3HashTest, calculate) {
+// Common checks for NSEC3 hash calculation
+void
+calculateCheck(NSEC3Hash& hash) {
     // A couple of normal cases from the RFC5155 example.
     EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
-              test_hash->calculate(Name("example")));
+              hash.calculate(Name("example")));
     EXPECT_EQ("35MTHGPGCU1QG68FAB165KLNSNK3DPVL",
-              test_hash->calculate(Name("a.example")));
+              hash.calculate(Name("a.example")));
 
     // Check case-insensitiveness
     EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
-              test_hash->calculate(Name("EXAMPLE")));
+              hash.calculate(Name("EXAMPLE")));
+}
+
+TEST_F(NSEC3HashTest, calculate) {
+    {
+        SCOPED_TRACE("calculate check with NSEC3PARAM based hash");
+        calculateCheck(*test_hash);
+    }
+    {
+        SCOPED_TRACE("calculate check with NSEC3 based hash");
+        calculateCheck(*test_hash_nsec3);
+    }
 
     // Some boundary cases: 0-iteration and empty salt.  Borrowed from the
     // .com zone data.
@@ -70,4 +100,46 @@ TEST_F(NSEC3HashTest, calculate) {
               ->calculate(Name("example.org")));
 }
 
+// Common checks for match cases
+template <typename RDATAType>
+void
+matchCheck(NSEC3Hash& hash, const string& postfix) {
+    // If all parameters match, it's considered to be matched.
+    EXPECT_TRUE(hash.match(RDATAType("1 0 12 aabbccdd" + postfix)));
+
+    // Algorithm doesn't match
+    EXPECT_FALSE(hash.match(RDATAType("2 0 12 aabbccdd" + postfix)));
+    // Iterations doesn't match
+    EXPECT_FALSE(hash.match(RDATAType("1 0 1 aabbccdd" + postfix)));
+    // Salt doesn't match
+    EXPECT_FALSE(hash.match(RDATAType("1 0 12 aabbccde" + postfix)));
+    // Salt doesn't match: the other has an empty salt
+    EXPECT_FALSE(hash.match(RDATAType("1 0 12 -" + postfix)));
+    // Flags don't matter
+    EXPECT_TRUE(hash.match(RDATAType("1 1 12 aabbccdd" + postfix)));
+}
+
+TEST_F(NSEC3HashTest, matchWithNSEC3) {
+    {
+        SCOPED_TRACE("match NSEC3PARAM based hash against NSEC3 parameters");
+        matchCheck<generic::NSEC3>(*test_hash, " " + string(nsec3_common));
+    }
+    {
+        SCOPED_TRACE("match NSEC3 based hash against NSEC3 parameters");
+        matchCheck<generic::NSEC3>(*test_hash_nsec3,
+                                   " " + string(nsec3_common));
+    }
+}
+
+TEST_F(NSEC3HashTest, matchWithNSEC3PARAM) {
+    {
+        SCOPED_TRACE("match NSEC3PARAM based hash against NSEC3 parameters");
+        matchCheck<generic::NSEC3PARAM>(*test_hash, "");
+    }
+    {
+        SCOPED_TRACE("match NSEC3 based hash against NSEC3 parameters");
+        matchCheck<generic::NSEC3PARAM>(*test_hash_nsec3, "");
+    }
+}
+
 } // end namespace
diff --git a/src/lib/server_common/tests/portconfig_unittest.cc b/src/lib/server_common/tests/portconfig_unittest.cc
index 7605bc6..ffeb322 100644
--- a/src/lib/server_common/tests/portconfig_unittest.cc
+++ b/src/lib/server_common/tests/portconfig_unittest.cc
@@ -298,6 +298,10 @@ TEST_F(InstallListenAddresses, brokenRollback) {
 // Make sure the death tests are filterable away.
 typedef InstallListenAddresses InstallListenAddressesDeathTest;
 
+// There are systems which don't have EXPECT_DEATH. We skip the tests there.
+// We're lucky, EXPECT_DEATH is a macro, so we can test for its existence this
+// easily.
+#ifdef EXPECT_DEATH
 // We make the socket requestor throw a "fatal" exception, one where we can't be
 // sure the state between processes is consistent. So we abort in that case.
 TEST_F(InstallListenAddressesDeathTest, inconsistent) {
@@ -336,5 +340,6 @@ TEST_F(InstallListenAddressesDeathTest, cantClose) {
     // And reset it back, so it can safely clean up itself.
     sock_requestor_.break_release_ = false;
 }
+#endif // EXPECT_DEATH
 
 }




More information about the bind10-changes mailing list