BIND 10 trac1596, updated. 578f4a5e28f06ff84d75502d5484d995904947dd [1596] Update spec files for everybody

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Feb 14 10:11:19 UTC 2012


The branch, trac1596 has been updated
       via  578f4a5e28f06ff84d75502d5484d995904947dd (commit)
       via  a9ade7fc8f8db95124c33bf475a59fd522914e49 (commit)
       via  4c8164b131661552f3bc8e74806d4cbbe1cda1d1 (commit)
       via  f15c13fb80fd6512f31df035555392494c6dfbd2 (commit)
       via  da724029ca02951b51d63333394bac0d62dbb0a2 (commit)
       via  4d266d80beb7e0d8b1f45a4a4bc7cc39a1657486 (commit)
       via  1c0df5ba07b95d2e2b1c678675be16a2a1059593 (commit)
       via  28ba8bd71ae4d100cb250fd8d99d80a17a6323a2 (commit)
       via  0aef7c479e7d127d0c219d6b3a5a4d0f3f701161 (commit)
       via  185cb45b7ab1a164a7bb0a989c582a74ce50f2f3 (commit)
       via  047380ffbbf6e876282cc269ef16d8d6b168d6d2 (commit)
       via  eaff1f5816bd2ed57a011f658e0b2dd762800079 (commit)
       via  2a29f6b8725bf106dce2f45f2705bac555022c5d (commit)
       via  9ff58bb76e632a8f7a28a4f86f94dd1d77690796 (commit)
       via  a9eadb451bea1a4813a7b3942a7ca2eea97618a5 (commit)
       via  ddaaee4b050468048409170f1bb56530f33567d6 (commit)
       via  540d2a66787b2ff57e20a2c99a84d2875dfbcc54 (commit)
       via  6d675cbff066b5ee3f1c3bde7aab7dfd0ad1482c (commit)
       via  72b239285aa03f9afa5685a4665451d89a941143 (commit)
       via  1b43a3adbb58860353dba59f48b23e2efc936041 (commit)
       via  ad2bcdd85d6e40cbfd9eb99acf817b626083962c (commit)
       via  9e3c7d57f3144b6992d8967af5a4583aa3d55c62 (commit)
       via  6e12d95c6a1fac2d4a182e58072934d2ab4b237e (commit)
       via  78785509428db0da450a2be7e89a342d1200cf0a (commit)
       via  8e98da42d48e5e847cd12debab6738c585db4740 (commit)
       via  f10604eb75f876d77af25b30b0709d5c554a6b26 (commit)
       via  d9640f6f30078a6b86d7b096ad0563bf45ffeb0f (commit)
       via  5c4967145b426d190fb6086307ade06e8ad093e1 (commit)
       via  4a20cc2eef19a7ada8e2c594724ca44e40be5cf8 (commit)
       via  d72e05e550f443209afbfd0b173d0a1e96dff749 (commit)
       via  e9647bb3c689941916508a663b72df63be7d3963 (commit)
       via  b86f7c330eba5b252adbea0594c2225fbc31e2e2 (commit)
       via  ecd4db4c298f4f1aae038bc0b42837478fe9910b (commit)
       via  e6532b8bb0bca5d9b2a64c721f99063371055add (commit)
       via  56596cd21e95779796a1e69dcd759ce77c6f3ce0 (commit)
       via  64b4c44c2d67130c304cf720bff62723d2fc2293 (commit)
       via  c41cda65e06b6776c7111e65b04c72d5bc84c54f (commit)
       via  17e78fa1bb1227340aa9815e91ed5c50d174425d (commit)
       via  2da5a3de561484f902aa7fb8475551217b9535b9 (commit)
       via  db3d9fdfd8da2d9ced567ca43d9be8d9885e0cff (commit)
       via  6bb658482335f1904c3b210ef4380719170677eb (commit)
       via  ac686701c48b92a98507e1533ccd4e3c851bf3ec (commit)
       via  2858b2098a10a8cc2d34bf87463ace0629d3670e (commit)
       via  f328341c1cfed8e5ca0479619e9d12d3b4907c86 (commit)
       via  373435e91924553439f4881a1fbf71aacd5fb4a1 (commit)
       via  5a3853394c09ef698795c3b7fe98661e752e0426 (commit)
       via  0ad08d16ff5100ec01865bf2814481960c79358e (commit)
       via  63f7b1ab7c81110633ed2e381e5fdfe05e143d72 (commit)
       via  61203697e8fc714835ef504a7e3eda925dfcda42 (commit)
       via  5da80cb570399e277dd02d6d45bc4efb3f949d61 (commit)
       via  5d11f8513af16aca9eb298db3226791f1f4e584a (commit)
       via  871e4c22effffd79ff1d0a44d06a102b27b24ac7 (commit)
       via  cb4049864f51f5dae26d7e985568e06e3ee1f9bc (commit)
       via  66a1a1ac3cec4b9b5f453370c85546ddafb1a9a4 (commit)
       via  18828275414b6a7fa529282e2e53d3ffb2b55082 (commit)
       via  e8580780dacffa82089d030c5b9950eb31c693f6 (commit)
       via  959f4cebed4f1182f4c9f4c3058364e83d553344 (commit)
       via  ef979231a2925c3cb6c079b181c6747005d72baa (commit)
       via  5eaa333fe39ddc02af78ffceba8173497e1352c3 (commit)
       via  82f947e1bc6978f7bde731955b96dc5c68840a6f (commit)
       via  4f23a482e885f3fe3dd104fa43638352f9ff3e7b (commit)
       via  88aa930fd6189c121c784b2175aab519d72ed5ab (commit)
       via  46232cfc39be398a04cfbc4e6fe2e75b41d8eb7c (commit)
       via  a5fa9a0f01113a92f6136f6dfc1716f877cbf07e (commit)
       via  90f98423f675dec3fc8335f17bbf761662536cce (commit)
       via  cb2af9730fdf9f0a9488c1197f29dec90b31c19d (commit)
       via  bf8c9216523617f5834b4acf9f876587995f5078 (commit)
       via  013e14c7d9c84f00510aa8f7a99955aa47c018ad (commit)
       via  82caa5734136ca29981a5107c9b0e4f644a573cf (commit)
       via  6b45467a71b534657a435dbb455f0ec3da99d16a (commit)
       via  170556d806125d1556fa570070b9f876c97df30c (commit)
       via  42b7d6a5c8f04659a4463d932b95e5305d6de3f7 (commit)
       via  b82c78512d63424b805e1ab15e6b781e4d923dbd (commit)
       via  0ad8fea24b58d05e8c57337c7b77f1b76be12015 (commit)
       via  c6aec4cdb46dd8825248437bc9c45d6a54b81121 (commit)
       via  287e17100b60fbfceacc7337cd6ac9bab36185be (commit)
       via  deadd3b30b20cad6964bebf38dba417c42b78289 (commit)
       via  58b7b149b8b3afbdbd8c960fc716f4e85f1803ee (commit)
       via  3071211d2c537150a691120b0a5ce2b18d010239 (commit)
       via  33ff861b4cd62e8d2e6063452cc14e980d35bbaf (commit)
       via  b341b3ca37749a29e1f1f286d103298d3c3fa568 (commit)
       via  4a8fa93781cce0dbd33f55e604d87db511fb5903 (commit)
       via  78e535f5a0e65658f7de0c3b777f9e0b296ad53e (commit)
       via  53cde03994472c09094721f94307f46599c0d1d5 (commit)
       via  a092343db9338c7e6acfb238d8c432291ad31e58 (commit)
       via  f364574080cd8f9d1a2acb27ddb3318157393ebc (commit)
       via  efd87c8cddb380d78a0cef4ee7fcc91559f7352b (commit)
       via  3007ba9ef2cffdf4903b43a14ccf52a0c1d3ec65 (commit)
       via  afc721fe5f2964c9d03bdf0738852556c69ad41b (commit)
       via  6cba1c1241e8c23779d0dc29374ce8420fadc2c5 (commit)
       via  76f823d42af55ce3f30a0d741fc9297c211d8b38 (commit)
       via  ab457a83d9ab58e722b84a7f99bd83dd49d75483 (commit)
       via  b9aed5e8c47dd0e44981e6a7c40b5b3c8e403e53 (commit)
       via  f5a196c2aaa02ab50f8bb50f9e23cdff7e945559 (commit)
       via  158f5113fe1effcf74cd0e09b5b072508e8462ed (commit)
       via  d1c5effaacd00d4ae5c97a48f820c5e281964cdb (commit)
       via  caf03348be996e4fe58efa70927b119b4fdf3d84 (commit)
       via  f45a95082d8db85dff8d7b0bf0e0817e7175103e (commit)
       via  2c40ed8bbbab2e095be163a6f144eb45be465f80 (commit)
       via  e3f86da049a7af0ddb0369db2e9db3fa0903d3c5 (commit)
       via  0e7c11723dcde11c0d9d789d072801d383e9ec83 (commit)
       via  9bbda62a5e88e9f1e4c8aeb7924308e37ef65a7c (commit)
       via  0d1406f71c9cd9439017b56957fa9d37ba7729cb (commit)
       via  32d9c527a98f1e417d21799d982361892ca87c6c (commit)
       via  47bca52baed667a6788450e1a0912c7a06363c8c (commit)
       via  00de2fb5e636d527a7d1a7bf97a6e4552d84b1f0 (commit)
       via  5fbff1bd2b79b417955e53dba3e3788ff32ebd6a (commit)
       via  df25eedae7f4e65ed0a8ecd2f95722061335d498 (commit)
       via  e16deb4665c50c5232cec96ae2bd69348610c926 (commit)
       via  ddbf6fe0f244d0c808408e94b502bc462c5d2f41 (commit)
       via  ae0979752fd9062a5b184ec855c2f9eab1363aba (commit)
       via  4e8a429913d5ecf888ec95d5d337f4ebc5c1976a (commit)
       via  5883a489bd72fd9a7faa485d770cc74b99457e7f (commit)
       via  ee5cc69d9e5320e3d9c420c22b54d0ebae255268 (commit)
       via  85c2888f2fe2c9985ce89fe22fa518b91976398b (commit)
       via  d5a8a75cf9e5cd88b5e7471eb7be8255cd2da28d (commit)
       via  88b907df1f580e6241f2af12870df5c769c539fa (commit)
       via  6afe3a0422647e7e6973857bed9c657366f10674 (commit)
       via  848e9d9759c84f822a6d866477016b5a95415dfc (commit)
      from  0a78488462cb3d7998f8af45fc830c18648dd45e (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 578f4a5e28f06ff84d75502d5484d995904947dd
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Feb 14 11:05:52 2012 +0100

    [1596] Update spec files for everybody
    
    Or, everybody who accepts a shutdown command with pid now. Stats needed
    to be updated because it did call functions based on the name and spec
    file somehow.

commit a9ade7fc8f8db95124c33bf475a59fd522914e49
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Feb 14 10:41:37 2012 +0100

    [1596] Fix obvious typo in makefile

commit 4c8164b131661552f3bc8e74806d4cbbe1cda1d1
Merge: 4d266d80beb7e0d8b1f45a4a4bc7cc39a1657486 f15c13fb80fd6512f31df035555392494c6dfbd2
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Feb 14 10:40:05 2012 +0100

    Merge branch #1596
    
    Conflicts:
    	src/bin/resolver/main.cc

commit f15c13fb80fd6512f31df035555392494c6dfbd2
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Feb 14 09:56:55 2012 +0100

    [1596] Remove unneeded checks
    
    They'd get caught by an exception anyway.

commit da724029ca02951b51d63333394bac0d62dbb0a2
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Feb 14 09:47:59 2012 +0100

    [1596] Initialize in constructor

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

Summary of changes:
 ChangeLog                                          |   39 ++-
 src/bin/auth/command.cc                            |    3 +-
 src/bin/auth/common.cc                             |    2 +
 src/bin/auth/common.h                              |    5 +
 src/bin/auth/main.cc                               |    4 +-
 src/bin/auth/query.cc                              |  119 ++++-
 src/bin/auth/query.h                               |   30 +-
 src/bin/auth/tests/auth_srv_unittest.cc            |    5 +-
 src/bin/auth/tests/command_unittest.cc             |    2 +-
 src/bin/auth/tests/config_unittest.cc              |    5 +-
 src/bin/auth/tests/query_unittest.cc               |  550 +++++++++++++++-----
 src/bin/bind10/bind10_src.py.in                    |    8 +-
 src/bin/bind10/tests/bind10_test.py.in             |    5 +
 src/bin/cmdctl/cmdctl.py.in                        |   15 +-
 src/bin/cmdctl/tests/cmdctl_test.py                |   34 ++
 src/bin/ddns/ddns.py.in                            |    5 +-
 src/bin/ddns/ddns.spec                             |    8 +-
 src/bin/ddns/tests/ddns_test.py                    |    6 +
 src/bin/resolver/Makefile.am                       |    1 +
 .../asiodns/logger.cc => bin/resolver/common.cc}   |   12 +-
 .../asiodns/logger.cc => bin/resolver/common.h}    |   16 +-
 src/bin/resolver/main.cc                           |    9 +-
 src/bin/resolver/tests/resolver_config_unittest.cc |    5 +-
 src/bin/stats/stats-httpd.spec                     |    8 +-
 src/bin/stats/stats.py.in                          |   11 +-
 src/bin/stats/stats.spec                           |    8 +-
 src/bin/stats/stats_httpd.py.in                    |    1 +
 src/bin/stats/tests/b10-stats-httpd_test.py        |   11 +-
 src/bin/stats/tests/b10-stats_test.py              |   14 +-
 src/bin/stats/tests/test_utils.py                  |   14 +-
 src/bin/xfrin/tests/xfrin_test.py                  |    5 +-
 src/bin/xfrin/xfrin.py.in                          |    1 +
 src/bin/xfrin/xfrin.spec                           |    8 +-
 src/bin/xfrout/tests/xfrout_test.py.in             |   27 +
 src/bin/xfrout/xfrout.py.in                        |    8 +-
 src/bin/xfrout/xfrout.spec.pre.in                  |    8 +-
 src/bin/zonemgr/tests/zonemgr_test.py              |   10 +-
 src/bin/zonemgr/zonemgr.py.in                      |    7 +-
 src/bin/zonemgr/zonemgr.spec.pre.in                |    8 +-
 src/lib/cache/local_zone_data.cc                   |    2 +-
 src/lib/cache/local_zone_data.h                    |    2 +-
 src/lib/cache/rrset_cache.cc                       |    2 +-
 src/lib/cache/rrset_cache.h                        |    2 +-
 src/lib/cache/rrset_copy.cc                        |    2 +-
 src/lib/cache/rrset_copy.h                         |    2 +-
 src/lib/cache/rrset_entry.cc                       |    3 +-
 src/lib/cache/rrset_entry.h                        |    3 +-
 src/lib/config/ccsession.cc                        |   23 +
 src/lib/config/ccsession.h                         |    9 +
 src/lib/config/config_messages.mes                 |   22 +-
 src/lib/config/tests/ccsession_unittests.cc        |   63 +++
 src/lib/config/tests/fake_session.cc               |    9 +-
 src/lib/config/tests/fake_session.h                |    9 +
 src/lib/datasrc/database.cc                        |   13 +-
 src/lib/datasrc/datasrc_messages.mes               |   24 +
 src/lib/datasrc/memory_datasrc.cc                  |  161 ++++---
 src/lib/datasrc/memory_datasrc.h                   |   10 -
 src/lib/datasrc/static_datasrc.cc                  |    1 +
 src/lib/datasrc/tests/Makefile.am                  |    1 +
 src/lib/datasrc/tests/memory_datasrc_unittest.cc   |  439 ++++++++++++++--
 src/lib/datasrc/tests/static_unittest.cc           |    3 +-
 .../tests/testdata/rfc5155-example.zone.signed     |   72 +++
 src/lib/datasrc/zone.h                             |    4 +-
 src/lib/dns/nsec3hash.cc                           |   34 ++-
 src/lib/dns/nsec3hash.h                            |   90 ++++
 src/lib/dns/python/message_python.cc               |    2 +-
 src/lib/dns/python/rrset_python.cc                 |    4 +-
 src/lib/dns/python/rrset_python.h                  |    4 +-
 src/lib/dns/rdata/generic/detail/nsec_bitmap.cc    |   84 +++-
 src/lib/dns/rdata/generic/detail/nsec_bitmap.h     |   63 ++-
 src/lib/dns/rdata/generic/nsec3_50.cc              |  186 +++----
 src/lib/dns/rdata/generic/nsec_47.cc               |   62 +--
 src/lib/dns/rrset.h                                |  145 +++++-
 src/lib/dns/tests/nsec3hash_unittest.cc            |   79 +++-
 src/lib/dns/tests/rdata_nsec3_unittest.cc          |   94 +++-
 src/lib/dns/tests/rdata_nsec_unittest.cc           |   29 +-
 src/lib/dns/tests/rdata_nsecbitmap_unittest.cc     |  253 ++++++++--
 src/lib/dns/tests/testdata/Makefile.am             |    5 +
 .../dns/tests/testdata/rdata_nsec3_fromWire16.spec |    8 +
 .../dns/tests/testdata/rdata_nsec3_fromWire17.spec |    8 +
 .../dns/tests/testdata/rdata_nsec_fromWire16.spec  |    8 +
 .../tests/nameserver_address_store_unittest.cc     |    2 +-
 src/lib/python/isc/config/ccsession.py             |   52 ++-
 src/lib/python/isc/config/cfgmgr.py                |   65 ++-
 src/lib/python/isc/config/config_data.py           |    4 +
 src/lib/python/isc/config/config_messages.mes      |    6 +
 src/lib/python/isc/config/tests/ccsession_test.py  |   44 ++
 src/lib/python/isc/config/tests/cfgmgr_test.py     |  255 ++++++---
 .../python/isc/config/tests/config_data_test.py    |   10 +
 src/lib/python/isc/testutils/Makefile.am           |    3 +-
 src/lib/python/isc/testutils/ccsession_mock.py     |   34 ++
 src/lib/server_common/portconfig.cc                |   14 +-
 src/lib/server_common/server_common_messages.mes   |    2 +-
 src/lib/server_common/socket_request.cc            |   20 +-
 src/lib/server_common/socket_request.h             |   35 ++-
 src/lib/server_common/tests/portconfig_unittest.cc |    5 +-
 .../server_common/tests/socket_requestor_test.cc   |   97 ++--
 src/lib/testutils/socket_request.h                 |   25 +-
 tests/lettuce/features/bindctl_commands.feature    |   38 ++
 tests/lettuce/features/terrain/bind10_control.py   |  121 ++++-
 100 files changed, 3083 insertions(+), 825 deletions(-)
 copy src/{lib/asiodns/logger.cc => bin/resolver/common.cc} (85%)
 copy src/{lib/asiodns/logger.cc => bin/resolver/common.h} (78%)
 create mode 100644 src/lib/datasrc/tests/testdata/rfc5155-example.zone.signed
 create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec
 create mode 100644 src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec
 create mode 100644 src/lib/python/isc/testutils/ccsession_mock.py
 create mode 100644 tests/lettuce/features/bindctl_commands.feature

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 14eb4ab..2c47d46 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,42 @@
+377.	[bug]		jinmei
+	libdns++: miscellaneous bug fixes for the NSEC and NSEC3 RDATA
+	implementation, including a crash in NSEC3::toText() for some RR
+	types, incorrect handling of empty NSEC3 salt, and incorrect
+	comparison logic in NSEC3::compare().
+	(Trac #1641, git 28ba8bd71ae4d100cb250fd8d99d80a17a6323a2)
+
+376.	[bug]		jinmei, vorner
+	The new query handling module of b10-auth did not handle type DS
+	query correctly: It didn't look for it in the parent zone, and
+	it incorrectly returned a DS from the child zone if it
+	happened to exist there.  Both were corrected, and it now also
+	handles the case of having authority for the child and a grand
+	ancestor.
+	(Trac #1570, git 2858b2098a10a8cc2d34bf87463ace0629d3670e)
+
+375.	[func]		jelte
+	Modules now inform the system when they are stopping. As a result, they
+	are removed from the 'active modules' list in bindctl, which can then
+	inform the user directly when it tries to send them a command or
+	configuration update. Previously this would result in a 'not
+	responding' error instead of 'not running'.
+	(Trac #640, git 17e78fa1bb1227340aa9815e91ed5c50d174425d)
+
+374.	[func]*		stephen
+	Alter RRsetPtr and ConstRRsetPtr to point to AbstractRRset (instead
+	of RRset) to allow for specialised implementations of RRsets in
+	data sources.
+	(Trac #1604, git 3071211d2c537150a691120b0a5ce2b18d010239)
+
+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/src/bin/auth/command.cc b/src/bin/auth/command.cc
index 1b440db..ad0d134 100644
--- a/src/bin/auth/command.cc
+++ b/src/bin/auth/command.cc
@@ -109,8 +109,7 @@ class ShutdownCommand : public AuthCommand {
 public:
     virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
         // Is the pid argument provided?
-        if (args && args->getType() == isc::data::Element::map &&
-            args->contains("pid")) {
+        if (args && args->contains("pid")) {
             // If it is, we check it is the same as our PID
 
             // This might throw in case the type is not an int, but that's
diff --git a/src/bin/auth/common.cc b/src/bin/auth/common.cc
index a7031f3..1602a1a 100644
--- a/src/bin/auth/common.cc
+++ b/src/bin/auth/common.cc
@@ -37,3 +37,5 @@ getXfroutSocketPath() {
         }
     }
 }
+
+const char* const AUTH_NAME = "b10-auth";
diff --git a/src/bin/auth/common.h b/src/bin/auth/common.h
index b913593..cf71214 100644
--- a/src/bin/auth/common.h
+++ b/src/bin/auth/common.h
@@ -38,6 +38,11 @@ public:
 /// The logic should be the same as in b10-xfrout, so they find each other.
 std::string getXfroutSocketPath();
 
+/// \brief The name used when identifieng the process
+///
+/// This is currently b10-auth, but it can be changed easily in one place.
+extern const char* const AUTH_NAME;
+
 #endif // __COMMON_H
 
 // Local Variables:
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index 4a425e5..fc2f751 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -116,7 +116,7 @@ main(int argc, char* argv[]) {
     }
 
     // Initialize logging.  If verbose, we'll use maximum verbosity.
-    isc::log::initLogger("b10-auth",
+    isc::log::initLogger(AUTH_NAME,
                          (verbose ? isc::log::DEBUG : isc::log::INFO),
                          isc::log::MAX_DEBUG_LEVEL, NULL);
 
@@ -154,7 +154,7 @@ main(int argc, char* argv[]) {
         cc_session = new Session(io_service.get_io_service());
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_CONFIG_CHANNEL_CREATED);
         // Initialize the Socket Requestor
-        isc::server_common::initSocketRequestor(*cc_session);
+        isc::server_common::initSocketRequestor(*cc_session, AUTH_NAME);
 
         // We delay starting listening to new commands/config just before we
         // go into the main loop to avoid confusion due to mixture of
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index 88624b8..f8f59c4 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -34,7 +34,7 @@ namespace isc {
 namespace auth {
 
 void
-Query::addAdditional(ZoneFinder& zone, const RRset& rrset) {
+Query::addAdditional(ZoneFinder& zone, const AbstractRRset& rrset) {
     RdataIteratorPtr rdata_iterator(rrset.getRdataIterator());
     for (; !rdata_iterator->isLast(); rdata_iterator->next()) {
         const Rdata& rdata(rdata_iterator->getCurrent());
@@ -73,7 +73,7 @@ Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
                                                     options | dnssec_opt_);
         if (a_result.code == ZoneFinder::SUCCESS) {
             response_.addRRset(Message::SECTION_ADDITIONAL,
-                    boost::const_pointer_cast<RRset>(a_result.rrset), dnssec_);
+                    boost::const_pointer_cast<AbstractRRset>(a_result.rrset), dnssec_);
         }
     }
 
@@ -83,7 +83,7 @@ Query::addAdditionalAddrs(ZoneFinder& zone, const Name& qname,
                                                        options | dnssec_opt_);
         if (aaaa_result.code == ZoneFinder::SUCCESS) {
             response_.addRRset(Message::SECTION_ADDITIONAL,
-                    boost::const_pointer_cast<RRset>(aaaa_result.rrset),
+                    boost::const_pointer_cast<AbstractRRset>(aaaa_result.rrset),
                     dnssec_);
         }
     }
@@ -104,7 +104,7 @@ Query::addSOA(ZoneFinder& finder) {
          * to insist.
          */
         response_.addRRset(Message::SECTION_AUTHORITY,
-            boost::const_pointer_cast<RRset>(soa_result.rrset), dnssec_);
+            boost::const_pointer_cast<AbstractRRset>(soa_result.rrset), dnssec_);
     }
 }
 
@@ -124,7 +124,7 @@ Query::addNXDOMAINProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
 
     // Add the NSEC proving NXDOMAIN to the authority section.
     response_.addRRset(Message::SECTION_AUTHORITY,
-                       boost::const_pointer_cast<RRset>(nsec), dnssec_);
+                       boost::const_pointer_cast<AbstractRRset>(nsec), dnssec_);
 
     // Next, identify the best possible wildcard name that would match
     // the query name.  It's the longer common suffix with the qname
@@ -162,7 +162,7 @@ Query::addNXDOMAINProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
     // for some optimized data source implementations.
     if (nsec->getName() != fresult.rrset->getName()) {
         response_.addRRset(Message::SECTION_AUTHORITY,
-                           boost::const_pointer_cast<RRset>(fresult.rrset),
+                           boost::const_pointer_cast<AbstractRRset>(fresult.rrset),
                            dnssec_);
     }
 }
@@ -180,7 +180,7 @@ Query::addWildcardProof(ZoneFinder& finder) {
         isc_throw(BadNSEC, "Unexpected result for wildcard proof");
     }
     response_.addRRset(Message::SECTION_AUTHORITY,
-                       boost::const_pointer_cast<RRset>(fresult.rrset),
+                       boost::const_pointer_cast<AbstractRRset>(fresult.rrset),
                        dnssec_);
 }
 
@@ -203,7 +203,7 @@ Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
     if (nsec->getName() != fresult.rrset->getName()) {
         // one NSEC RR proves wildcard_nxrrset that no matched QNAME.
         response_.addRRset(Message::SECTION_AUTHORITY,
-                           boost::const_pointer_cast<RRset>(fresult.rrset),
+                           boost::const_pointer_cast<AbstractRRset>(fresult.rrset),
                            dnssec_);
     }
 }
@@ -214,7 +214,7 @@ Query::addDS(ZoneFinder& finder, const Name& dname) {
         finder.find(dname, RRType::DS(), dnssec_opt_);
     if (ds_result.code == ZoneFinder::SUCCESS) {
         response_.addRRset(Message::SECTION_AUTHORITY,
-                           boost::const_pointer_cast<RRset>(ds_result.rrset),
+                           boost::const_pointer_cast<AbstractRRset>(ds_result.rrset),
                            dnssec_);
     } else if (ds_result.code == ZoneFinder::NXRRSET) {
         addNXRRsetProof(finder, ds_result);
@@ -230,12 +230,23 @@ Query::addNXRRsetProof(ZoneFinder& finder,
 {
     if (db_result.isNSECSigned() && db_result.rrset) {
         response_.addRRset(Message::SECTION_AUTHORITY,
-                           boost::const_pointer_cast<RRset>(
+                           boost::const_pointer_cast<AbstractRRset>(
                                db_result.rrset),
                            dnssec_);
         if (db_result.isWildcard()) {
             addWildcardNXRRSETProof(finder, db_result.rrset);
         }
+    } else if (db_result.isNSEC3Signed()) {
+        ZoneFinder::FindNSEC3Result result(finder.findNSEC3(qname_, false));
+        if (result.matched) {
+            response_.addRRset(Message::SECTION_AUTHORITY,
+                               boost::const_pointer_cast<AbstractRRset>(
+                                   result.closest_proof), dnssec_);
+
+        } else {
+            isc_throw(BadNSEC3, "No NSEC3 found for existing domain " <<
+                      qname_.toText());
+        }
     }
 }
 
@@ -251,19 +262,33 @@ Query::addAuthAdditional(ZoneFinder& finder) {
                 finder.getOrigin().toText());
     } else {
         response_.addRRset(Message::SECTION_AUTHORITY,
-            boost::const_pointer_cast<RRset>(ns_result.rrset), dnssec_);
+            boost::const_pointer_cast<AbstractRRset>(ns_result.rrset), dnssec_);
         // Handle additional for authority section
         addAdditional(finder, *ns_result.rrset);
     }
 }
 
+namespace {
+// A simple wrapper for DataSourceClient::findZone().  Normally we can simply
+// check the closest zone to the qname, but for type DS query we need to
+// look into the parent zone.  Nevertheless, if there is no "parent" (i.e.,
+// the qname consists of a single label, which also means it's the root name),
+// we should search the deepest zone we have (which should be the root zone;
+// otherwise it's a query error).
+DataSourceClient::FindResult
+findZone(const DataSourceClient& client, const Name& qname, RRType qtype) {
+    if (qtype != RRType::DS() || qname.getLabelCount() == 1) {
+        return (client.findZone(qname));
+    }
+    return (client.findZone(qname.split(1)));
+}
+}
+
 void
 Query::process() {
-    const bool qtype_is_any = (qtype_ == RRType::ANY());
-
-    response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
-    const DataSourceClient::FindResult result =
-        datasrc_client_.findZone(qname_);
+    // Found a zone which is the nearest ancestor to QNAME
+    const DataSourceClient::FindResult result = findZone(datasrc_client_,
+                                                         qname_, qtype_);
 
     // If we have no matching authoritative zone for the query name, return
     // REFUSED.  In short, this is to be compatible with BIND 9, but the
@@ -272,16 +297,26 @@ Query::process() {
     // https://lists.isc.org/mailman/htdig/bind10-dev/2010-December/001633.html
     if (result.code != result::SUCCESS &&
         result.code != result::PARTIALMATCH) {
+        // If we tried to find a "parent zone" for a DS query and failed,
+        // we may still have authority at the child side.  If we do, the query
+        // has to be handled there.
+        if (qtype_ == RRType::DS() && qname_.getLabelCount() > 1 &&
+            processDSAtChild()) {
+            return;
+        }
+        response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
         response_.setRcode(Rcode::REFUSED());
         return;
     }
     ZoneFinder& zfinder = *result.zone_finder;
 
-    // Found a zone which is the nearest ancestor to QNAME, set the AA bit
+    // We have authority for a zone that contain the query name (possibly
+    // indirectly via delegation).  Look into the zone.
     response_.setHeaderFlag(Message::HEADERFLAG_AA);
     response_.setRcode(Rcode::NOERROR());
     std::vector<ConstRRsetPtr> target;
     boost::function0<ZoneFinder::FindResult> find;
+    const bool qtype_is_any = (qtype_ == RRType::ANY());
     if (qtype_is_any) {
         find = boost::bind(&ZoneFinder::findAll, &zfinder, qname_,
                            boost::ref(target), dnssec_opt_);
@@ -294,7 +329,7 @@ Query::process() {
         case ZoneFinder::DNAME: {
             // First, put the dname into the answer
             response_.addRRset(Message::SECTION_ANSWER,
-                boost::const_pointer_cast<RRset>(db_result.rrset),
+                boost::const_pointer_cast<AbstractRRset>(db_result.rrset),
                 dnssec_);
             /*
              * Empty DNAME should never get in, as it is impossible to
@@ -345,7 +380,7 @@ Query::process() {
              * So, just put it there.
              */
             response_.addRRset(Message::SECTION_ANSWER,
-                boost::const_pointer_cast<RRset>(db_result.rrset),
+                boost::const_pointer_cast<AbstractRRset>(db_result.rrset),
                 dnssec_);
 
             // If the answer is a result of wildcard substitution,
@@ -360,13 +395,13 @@ Query::process() {
                 // into answer section.
                 BOOST_FOREACH(ConstRRsetPtr rrset, target) {
                     response_.addRRset(Message::SECTION_ANSWER,
-                        boost::const_pointer_cast<RRset>(rrset), dnssec_);
+                        boost::const_pointer_cast<AbstractRRset>(rrset), dnssec_);
                     // Handle additional for answer section
                     addAdditional(*result.zone_finder, *rrset.get());
                 }
             } else {
                 response_.addRRset(Message::SECTION_ANSWER,
-                    boost::const_pointer_cast<RRset>(db_result.rrset),
+                    boost::const_pointer_cast<AbstractRRset>(db_result.rrset),
                     dnssec_);
                 // Handle additional for answer section
                 addAdditional(*result.zone_finder, *db_result.rrset);
@@ -389,9 +424,17 @@ Query::process() {
             }
             break;
         case ZoneFinder::DELEGATION:
+            // If a DS query resulted in delegation, we also need to check
+            // if we are an authority of the child, too.  If so, we need to
+            // complete the process in the child as specified in Section
+            // 2.2.1.2. of RFC3658.
+            if (qtype_ == RRType::DS() && processDSAtChild()) {
+                return;
+            }
+
             response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
             response_.addRRset(Message::SECTION_AUTHORITY,
-                boost::const_pointer_cast<RRset>(db_result.rrset),
+                boost::const_pointer_cast<AbstractRRset>(db_result.rrset),
                 dnssec_);
             // If DNSSEC is requested, see whether there is a DS
             // record for this delegation.
@@ -422,5 +465,37 @@ Query::process() {
     }
 }
 
+bool
+Query::processDSAtChild() {
+    const DataSourceClient::FindResult zresult =
+        datasrc_client_.findZone(qname_);
+
+    if (zresult.code != result::SUCCESS) {
+        return (false);
+    }
+
+    // We are receiving a DS query at the child side of the owner name,
+    // where the DS isn't supposed to belong.  We should return a "no data"
+    // response as described in Section 3.1.4.1 of RFC4035 and Section
+    // 2.2.1.1 of RFC 3658.  find(DS) should result in NXRRSET, in which
+    // case (and if DNSSEC is required) we also add the proof for that,
+    // but even if find() returns an unexpected result, we don't bother.
+    // The important point in this case is to return SOA so that the resolver
+    // that happens to contact us can hunt for the appropriate parent zone
+    // by seeing the SOA.
+    response_.setHeaderFlag(Message::HEADERFLAG_AA);
+    response_.setRcode(Rcode::NOERROR());
+    addSOA(*zresult.zone_finder);
+    const ZoneFinder::FindResult ds_result =
+        zresult.zone_finder->find(qname_, RRType::DS(), dnssec_opt_);
+    if (ds_result.code == ZoneFinder::NXRRSET) {
+        if (dnssec_) {
+            addNXRRsetProof(*zresult.zone_finder, ds_result);
+        }
+    }
+
+    return (true);
+}
+
 }
 }
diff --git a/src/bin/auth/query.h b/src/bin/auth/query.h
index 886ff93..4cc4c5d 100644
--- a/src/bin/auth/query.h
+++ b/src/bin/auth/query.h
@@ -135,7 +135,7 @@ private:
     /// \param rrset The RRset (i.e., NS or MX rrset) which require additional
     /// processing.
     void addAdditional(isc::datasrc::ZoneFinder& zone,
-                       const isc::dns::RRset& rrset);
+                       const isc::dns::AbstractRRset& rrset);
 
     /// \brief Find address records for a specified name.
     ///
@@ -178,6 +178,23 @@ private:
     /// data for the query are to be found.
     void addAuthAdditional(isc::datasrc::ZoneFinder& finder);
 
+    /// \brief Process a DS query possible at the child side of zone cut.
+    ///
+    /// This private method is a subroutine of process(), and is called if
+    /// there's a possibility that this server has authority for the child
+    /// side of the DS's owner name (and it's detected that the server at
+    /// least doesn't have authority at the parent side).  This method
+    /// first checks if it has authority for the child, and if does,
+    /// just build a "no data" response with SOA for the zone origin
+    /// (possibly with a proof for the no data) as specified in Section
+    /// 2.2.1.1 of RFC3658.
+    ///
+    /// It returns true if this server has authority of the child; otherwise
+    /// it returns false.  In the former case, the caller is expected to
+    /// terminate the query processing, because it should have been completed
+    /// within this method.
+    bool processDSAtChild();
+
 public:
     /// Constructor from query parameters.
     ///
@@ -270,6 +287,17 @@ public:
         {}
     };
 
+    /// An invalid result is given when a valid NSEC3 is expected
+    ///
+    /// This can only happen when the underlying data source implementation or
+    /// the zone is broken.  By throwing an exception we treat such cases
+    /// as SERVFAIL.
+    struct BadNSEC3 : public BadZone {
+        BadNSEC3(const char* file, size_t line, const char* what) :
+            BadZone(file, line, what)
+        {}
+    };
+
     /// An invalid result is given when a valid DS records (or NXRRSET) is
     /// expected
     ///
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 2fccccf..d9c3de6 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -71,7 +71,10 @@ protected:
         dnss_(ios_, NULL, NULL, NULL),
         server(true, xfrout),
         rrclass(RRClass::IN()),
-        sock_requestor_(dnss_, address_store_, 53210)
+        // The empty string is expected value of the parameter of
+        // requestSocket, not the app_name (there's no fallback, it checks
+        // the empty string is passed).
+        sock_requestor_(dnss_, address_store_, 53210, "")
     {
         server.setDNSService(dnss_);
         server.setXfrinSession(&notify_session);
diff --git a/src/bin/auth/tests/command_unittest.cc b/src/bin/auth/tests/command_unittest.cc
index fe99327..087ce81 100644
--- a/src/bin/auth/tests/command_unittest.cc
+++ b/src/bin/auth/tests/command_unittest.cc
@@ -56,6 +56,7 @@ protected:
     AuthCommandTest() :
         server_(false, xfrout_),
         rcode_(-1),
+        expect_rcode_(0),
         itimer_(server_.getIOService())
     {
         server_.setStatisticsSession(&statistics_session_);
@@ -162,7 +163,6 @@ TEST_F(AuthCommandTest, shutdownIncorrectPID) {
     // The PID = 0 should be taken by init, so we are not init and the
     // PID should be different
     param_ = Element::fromJSON("{\"pid\": 0}");
-    expect_rcode_ = 0;
     itimer_.setup(boost::bind(&AuthCommandTest::dontStopServer, this), 1);
     server_.getIOService().run();
     EXPECT_EQ(0, rcode_);
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index 18092c1..973ab31 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -46,7 +46,10 @@ protected:
         dnss_(ios_, NULL, NULL, NULL),
         rrclass(RRClass::IN()),
         server(true, xfrout),
-        sock_requestor_(dnss_, address_store_, 53210)
+        // The empty string is expected value of the parameter of
+        // requestSocket, not the app_name (there's no fallback, it checks
+        // the empty string is passed).
+        sock_requestor_(dnss_, address_store_, 53210, "")
     {
         server.setDNSService(dnss_);
     }
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 0f330f5..8658ef4 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -52,11 +52,23 @@ namespace {
 // dns::masterLoad().  Some of the RRs are also used as the expected
 // data in specific tests, in which case they are referenced via specific
 // local variables (such as soa_txt).
-const char* const soa_txt = "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
+//
+// For readability consistency, all strings are placed in a separate line,
+// even if they are very short and can reasonably fit in a single line with
+// the corresponding variable.  For example, we write
+// const char* const foo_txt =
+//  "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
+// instead of
+// const char* const foo_txt = "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
+const char* const soa_txt =
+    "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
 const char* const zone_ns_txt =
     "example.com. 3600 IN NS glue.delegation.example.com.\n"
     "example.com. 3600 IN NS noglue.example.com.\n"
     "example.com. 3600 IN NS example.net.\n";
+const char* const zone_ds_txt =
+    "example.com. 3600 IN DS 57855 5 1 "
+        "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
 const char* const ns_addrs_txt =
     "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
     "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
@@ -66,11 +78,16 @@ const char* const delegation_txt =
     "delegation.example.com. 3600 IN NS noglue.example.com.\n"
     "delegation.example.com. 3600 IN NS cname.example.com.\n"
     "delegation.example.com. 3600 IN NS example.org.\n";
+// Borrowed from the RFC4035
+const char* const delegation_ds_txt =
+    "delegation.example.com. 3600 IN DS 57855 5 1 "
+        "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
 const char* const mx_txt =
     "mx.example.com. 3600 IN MX 10 www.example.com.\n"
     "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
     "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
-const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
+const char* const www_a_txt =
+    "www.example.com. 3600 IN A 192.0.2.80\n";
 const char* const cname_txt =
     "cname.example.com. 3600 IN CNAME www.example.com.\n";
 const char* const cname_nxdom_txt =
@@ -95,13 +112,15 @@ const char* const other_zone_rrs =
     "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
     "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
 // Wildcards
-const char* const wild_txt = "*.wild.example.com. 3600 IN A 192.0.2.7\n";
+const char* const wild_txt =
+    "*.wild.example.com. 3600 IN A 192.0.2.7\n";
 const char* const nsec_wild_txt =
     "*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG\n";
 const char* const cnamewild_txt =
     "*.cnamewild.example.com. 3600 IN CNAME www.example.org.\n";
-const char* const nsec_cnamewild_txt = "*.cnamewild.example.com. "
-    "3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG\n";
+const char* const nsec_cnamewild_txt =
+    "*.cnamewild.example.com. 3600 IN NSEC "
+    "delegation.example.com. CNAME NSEC RRSIG\n";
 // Wildcard_nxrrset
 const char* const wild_txt_nxrrset =
     "*.uwild.example.com. 3600 IN A 192.0.2.9\n";
@@ -112,10 +131,12 @@ const char* const wild_txt_next =
 const char* const nsec_wild_txt_next =
     "www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG\n";
 // Wildcard empty
-const char* const empty_txt = "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
+const char* const empty_txt =
+    "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
 const char* const nsec_empty_txt =
     "b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG\n";
-const char* const empty_prev_txt = "t.example.com. 3600 IN A 192.0.2.15\n";
+const char* const empty_prev_txt =
+    "t.example.com. 3600 IN A 192.0.2.15\n";
 const char* const nsec_empty_prev_txt =
     "t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG\n";
 // Used in NXDOMAIN proof test.  We are going to test some unusual case where
@@ -153,7 +174,8 @@ const char* const nsec_www_txt =
     "www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG\n";
 
 // Authoritative data without NSEC
-const char* const nonsec_a_txt = "nonsec.example.com. 3600 IN A 192.0.2.0\n";
+const char* const nonsec_a_txt =
+    "nonsec.example.com. 3600 IN A 192.0.2.0\n";
 
 // NSEC3 RRs.  You may also need to add mapping to MockZoneFinder::hash_map_.
 const char* const nsec3_apex_txt =
@@ -195,13 +217,34 @@ getCommonRRSIGText(const string& type) {
                    "example.com. FAKEFAKEFAKE"));
 }
 
+// A helper callback of masterLoad() used in InMemoryZoneFinderTest.
+void
+setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
+    *(*it) = rrset;
+    ++it;
+}
+
+// A helper function that converts a textual form of a single RR into a
+// RRsetPtr object.  If it's SOA, origin must be set to its owner name;
+// otherwise masterLoad() will reject it.
+RRsetPtr
+textToRRset(const string& text_rrset, const Name& origin = Name::ROOT_NAME()) {
+    stringstream ss(text_rrset);
+    RRsetPtr rrset;
+    vector<RRsetPtr*> rrsets;
+    rrsets.push_back(&rrset);
+    masterLoad(ss, origin, RRClass::IN(),
+               boost::bind(setRRset, _1, rrsets.begin()));
+    return (rrset);
+}
+
 // This is a mock Zone Finder class for testing.
 // It is a derived class of ZoneFinder for the convenient of tests.
 // Its find() method emulates the common behavior of protocol compliant
 // ZoneFinder classes, but simplifies some minor cases and also supports broken
 // behavior.
-// For simplicity, most names are assumed to be "in zone"; there's only
-// one zone cut at the point of name "delegation.example.com".
+// For simplicity, most names are assumed to be "in zone"; delegations
+// to child zones are identified by the existence of non origin NS records.
 // Another special name is "dname.example.com".  Query names under this name
 // will result in DNAME.
 // This mock zone doesn't handle empty non terminal nodes (if we need to test
@@ -210,22 +253,20 @@ class MockZoneFinder : public ZoneFinder {
 public:
     MockZoneFinder() :
         origin_(Name("example.com")),
-        delegation_name_("delegation.example.com"),
-        signed_delegation_name_("signed-delegation.example.com"),
         bad_signed_delegation_name_("bad-delegation.example.com"),
-        unsigned_delegation_name_("unsigned-delegation.example.com"),
         dname_name_("dname.example.com"),
         has_SOA_(true),
         has_apex_NS_(true),
         rrclass_(RRClass::IN()),
         include_rrsig_anyway_(false),
         use_nsec3_(false),
-        nsec_name_(origin_)
+        nsec_name_(origin_),
+        nsec3_fake_(NULL)
     {
         stringstream zone_stream;
         zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
-            delegation_txt << mx_txt << www_a_txt << cname_txt <<
-            cname_nxdom_txt << cname_out_txt << dname_txt <<
+            delegation_txt << delegation_ds_txt << mx_txt << www_a_txt <<
+            cname_txt << cname_nxdom_txt << cname_out_txt << dname_txt <<
             dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
             nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
             nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
@@ -248,8 +289,12 @@ public:
 
         // (Faked) NSEC3 hash map.  For convenience we use hardcoded built-in
         // map instead of calculating and using actual hash.
-        // The used hash values are borrowed from RFC5155 examples.
+        // The used hash values are borrowed from RFC5155 examples (they are
+        // based on the query name, not that they would correspond directly
+        // to the name).
         hash_map_[Name("example.com")] = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
+        hash_map_[Name("www.example.com")] =
+            "q04jkcevqvmu85r014c7dkba38o0ji5r";
         hash_map_[Name("nxdomain.example.com")] =
             "v644ebqk9bibcna874givr6joj62mlhv";
         hash_map_[Name("nx.domain.example.com")] =
@@ -293,28 +338,59 @@ public:
         nsec_result_.reset(new ZoneFinder::FindResult(code, rrset));
     }
 
+    // Once called, the findNSEC3 will return the provided result for the next
+    // query. After that, it'll return to operate normally.
+    // NULL disables. Does not take ownership of the pointer (it is generally
+    // expected to be a local variable in the test function).
+    void setNSEC3Result(const FindNSEC3Result* result) {
+        nsec3_fake_ = result;
+    }
+
     // If true is passed return an empty NSEC3 RRset for some negative
     // answers when DNSSEC is required.
     void setNSEC3Flag(bool on) { use_nsec3_ = on; }
 
-    Name findPreviousName(const Name&) const {
+    virtual Name findPreviousName(const Name&) const {
         isc_throw(isc::NotImplemented, "Mock doesn't support previous name");
     }
 
+    // This method allows tests to insert new record in the middle of the test.
+    //
+    // \param record_txt textual RR representation of RR (such as soa_txt, etc)
+    void addRecord(const string& record_txt) {
+        stringstream record_stream;
+        record_stream << record_txt;
+        masterLoad(record_stream, origin_, rrclass_,
+                   boost::bind(&MockZoneFinder::loadRRset, this, _1));
+    }
+
 public:
     // We allow the tests to use these for convenience
-    ConstRRsetPtr delegation_rrset_;
-    ConstRRsetPtr signed_delegation_rrset_;
-    ConstRRsetPtr signed_delegation_ds_rrset_;
-    ConstRRsetPtr bad_signed_delegation_rrset_;
-    ConstRRsetPtr unsigned_delegation_rrset_;
+    ConstRRsetPtr dname_rrset_; // could be used as an arbitrary bogus RRset
     ConstRRsetPtr empty_nsec_rrset_;
 
 private:
     typedef map<RRType, ConstRRsetPtr> RRsetStore;
     typedef map<Name, RRsetStore> Domains;
     Domains domains_;
+    Domains delegations_;
     Domains nsec3_domains_;
+
+    // This is used to identify delegation to a child zone, and used to
+    // find a matching entry in delegations_.  Note that first found entry
+    // is returned, so it's not a longest match.  Test data must be set up
+    // to ensure the first match is always the longest match.
+    struct SubdomainMatch {
+        SubdomainMatch(const Name& name) : name_(name) {}
+        bool operator()(const pair<Name, RRsetStore>& domain_elem) const {
+            return (name_ == domain_elem.first ||
+                    name_.compare(domain_elem.first).getRelation() ==
+                    NameComparisonResult::SUBDOMAIN);
+        }
+    private:
+        const Name& name_;
+    };
+
     void loadRRset(RRsetPtr rrset) {
         if (rrset->getType() == RRType::NSEC3()) {
             // NSEC3 should go to the dedicated table
@@ -328,39 +404,24 @@ private:
             return;
         }
         domains_[rrset->getName()][rrset->getType()] = rrset;
-        if (rrset->getName() == delegation_name_ &&
-            rrset->getType() == RRType::NS()) {
-            delegation_rrset_ = rrset;
-        } else if (rrset->getName() == signed_delegation_name_ &&
-                   rrset->getType() == RRType::NS()) {
-            signed_delegation_rrset_ = rrset;
-        } else if (rrset->getName() == bad_signed_delegation_name_ &&
-                   rrset->getType() == RRType::NS()) {
-            bad_signed_delegation_rrset_ = rrset;
-        } else if (rrset->getName() == unsigned_delegation_name_ &&
-                   rrset->getType() == RRType::NS()) {
-            unsigned_delegation_rrset_ = rrset;
-        } else if (rrset->getName() == signed_delegation_name_ &&
-                   rrset->getType() == RRType::DS()) {
-            signed_delegation_ds_rrset_ = rrset;
-            // Like NSEC(3), by nature it should have an RRSIG.
-            rrset->addRRsig(RdataPtr(new generic::RRSIG(
-                                         getCommonRRSIGText(rrset->getType().
-                                                            toText()))));
+
+        // Remember delegation (NS/DNAME) related RRsets separately.
+        if (rrset->getType() == RRType::NS() && rrset->getName() != origin_) {
+            delegations_[rrset->getName()][rrset->getType()] = rrset;
         } else if (rrset->getName() == dname_name_ &&
-            rrset->getType() == RRType::DNAME()) {
+                   rrset->getType() == RRType::DNAME()) {
             dname_rrset_ = rrset;
-        // Add some signatures
-        } else if (rrset->getName() == Name("example.com.") &&
-                   rrset->getType() == RRType::NS()) {
-            // For NS, we only have RRSIG for the origin name.
-            rrset->addRRsig(RdataPtr(new generic::RRSIG(
-                                         getCommonRRSIGText("NS"))));
-        } else {
-            // For others generate RRSIG unconditionally.  Technically this
-            // is wrong because we shouldn't have it for names under a zone
-            // cut.  But in our tests that doesn't matter, so we add them
-            // just for simplicity.
+        }
+
+        // Add some signatures.  For NS, we only have RRSIG for the origin
+        // name. For others generate RRSIG unconditionally.  Technically this
+        // is wrong because we shouldn't have it for names under a zone
+        // cut.  But in our tests that doesn't matter, so we add them
+        // just for simplicity.
+        // Note that this includes RRSIG for DS with secure delegations.
+        // They should have RRSIGs, so that's actually expected data, not just
+        // for simplicity.
+        if (rrset->getType() != RRType::NS() || rrset->getName() == origin_) {
             rrset->addRRsig(RdataPtr(new generic::RRSIG(
                                          getCommonRRSIGText(rrset->getType().
                                                             toText()))));
@@ -369,27 +430,28 @@ private:
 
     const Name origin_;
     // Names where we delegate somewhere else
-    const Name delegation_name_;
-    const Name signed_delegation_name_;
     const Name bad_signed_delegation_name_;
-    const Name unsigned_delegation_name_;
     const Name dname_name_;
     bool has_SOA_;
     bool has_apex_NS_;
-    ConstRRsetPtr dname_rrset_;
     const RRClass rrclass_;
     bool include_rrsig_anyway_;
     bool use_nsec3_;
     // The following two will be used for faked NSEC cases
     Name nsec_name_;
     boost::scoped_ptr<ZoneFinder::FindResult> nsec_result_;
+    // The following two are for faking bad NSEC3 responses
+    // Enabled when not NULL
+    const FindNSEC3Result* nsec3_fake_;
+public:
+    // Public, to allow tests looking up the right names for something
     map<Name, string> hash_map_;
 };
 
 // A helper function that generates a new RRset based on "wild_rrset",
 // replacing its owner name with 'real_name'.
 ConstRRsetPtr
-substituteWild(const RRset& wild_rrset, const Name& real_name) {
+substituteWild(const AbstractRRset& wild_rrset, const Name& real_name) {
     RRsetPtr rrset(new RRset(real_name, wild_rrset.getClass(),
                              wild_rrset.getType(), wild_rrset.getTTL()));
     // For simplicity we only consider the case with one RDATA (for now)
@@ -427,6 +489,14 @@ MockZoneFinder::findAll(const Name& name, std::vector<ConstRRsetPtr>& target,
 
 ZoneFinder::FindNSEC3Result
 MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
+    // Do we have a fake result set? If so, use it.
+    if (nsec3_fake_ != NULL) {
+        const FindNSEC3Result* result(nsec3_fake_);
+        // Disable the fake for the next call
+        nsec3_fake_ = NULL;
+        return (*result);
+    }
+
     ConstRRsetPtr covering_proof;
     const int labels = name.getLabelCount();
 
@@ -482,40 +552,23 @@ MockZoneFinder::find(const Name& name, const RRType& type,
         return (FindResult(NXDOMAIN, RRsetPtr()));
     }
 
-    // Special case for names on or under a zone cut
+    // Special case for names on or under a zone cut and under DNAME
+    Domains::iterator it;
     if ((options & FIND_GLUE_OK) == 0 &&
-        (name == delegation_name_ ||
-         name.compare(delegation_name_).getRelation() ==
-         NameComparisonResult::SUBDOMAIN)) {
-        return (FindResult(DELEGATION, delegation_rrset_));
-    // And under DNAME
-    } else if (name.compare(dname_name_).getRelation() ==
-        NameComparisonResult::SUBDOMAIN) {
-        if (type != RRType::DS()) {
-            return (FindResult(DNAME, dname_rrset_));
-        }
-    } else if (name == signed_delegation_name_ ||
-               name.compare(signed_delegation_name_).getRelation() ==
-               NameComparisonResult::SUBDOMAIN) {
-        if (type != RRType::DS()) {
-            return (FindResult(DELEGATION, signed_delegation_rrset_));
-        } else {
-            return (FindResult(SUCCESS, signed_delegation_ds_rrset_));
+        (it = find_if(delegations_.begin(), delegations_.end(),
+                      SubdomainMatch(name))) != delegations_.end()) {
+        ConstRRsetPtr delegation_ns = it->second[RRType::NS()];
+        assert(delegation_ns); // should be ensured by how we construct it
+
+        // DS query for the delegated domain (i.e. an exact match) will be
+        // handled just like an in-zone case below.  Others result in
+        // DELEGATION.
+        if (type != RRType::DS() || it->first != name) {
+            return (FindResult(DELEGATION, delegation_ns));
         }
-    } else if (name == unsigned_delegation_name_ ||
-               name.compare(unsigned_delegation_name_).getRelation() ==
-               NameComparisonResult::SUBDOMAIN) {
-        if (type != RRType::DS()) {
-            return (FindResult(DELEGATION, unsigned_delegation_rrset_));
-        }
-    } else if (name == bad_signed_delegation_name_ ||
-               name.compare(bad_signed_delegation_name_).getRelation() ==
+    } else if (name.compare(dname_name_).getRelation() ==
                NameComparisonResult::SUBDOMAIN) {
-        if (type != RRType::DS()) {
-            return (FindResult(DELEGATION, bad_signed_delegation_rrset_));
-        } else {
-            return (FindResult(NXDOMAIN, RRsetPtr()));
-        }
+        return (FindResult(DNAME, dname_rrset_));
     }
 
     // normal cases.  names are searched for only per exact-match basis
@@ -554,7 +607,13 @@ MockZoneFinder::find(const Name& name, const RRType& type,
             return (FindResult(CNAME, found_rrset->second));
         }
 
-        // Otherwise it's NXRRSET case.
+        // Otherwise it's NXRRSET case...
+        // ...but a special pathological case first:
+        if (found_domain->first == bad_signed_delegation_name_ &&
+            type == RRType::DS()) {
+            return (FindResult(NXDOMAIN, RRsetPtr()));
+        }
+        // normal cases follow.
         if ((options & FIND_DNSSEC) != 0) {
             if (use_nsec3_) {
                 return (FindResult(NXRRSET, RRsetPtr(), RESULT_NSEC3_SIGNED));
@@ -720,7 +779,14 @@ protected:
     QueryTest() :
         qname(Name("www.example.com")), qclass(RRClass::IN()),
         qtype(RRType::A()), response(Message::RENDER),
-        qid(response.getQid()), query_code(Opcode::QUERY().getCode())
+        qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
+        ns_addrs_and_sig_txt(string(ns_addrs_txt) +
+                             "glue.delegation.example.com. 3600 IN RRSIG " +
+                             getCommonRRSIGText("A") + "\n" +
+                             "glue.delegation.example.com. 3600 IN RRSIG " +
+                             getCommonRRSIGText("AAAA") + "\n" +
+                             "noglue.example.com. 3600 IN RRSIG " +
+                             getCommonRRSIGText("A"))
     {
         response.setRcode(Rcode::NOERROR());
         response.setOpcode(Opcode::QUERY());
@@ -740,6 +806,7 @@ protected:
     Message response;
     const qid_t qid;
     const uint16_t query_code;
+    const string ns_addrs_and_sig_txt; // convenient shortcut
 };
 
 // A wrapper to check resulting response message commonly used in
@@ -812,10 +879,6 @@ TEST_F(QueryTest, dnssecPositive) {
     Query query(memory_client, qname, qtype, response, true);
     EXPECT_NO_THROW(query.process());
     // find match rrset
-    // We can't let responseCheck to check the additional section as well,
-    // it gets confused by the two RRs for glue.delegation.../RRSIG due
-    // to it's design and fixing it would be hard. Therefore we simply
-    // check manually this one time.
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
                   (www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
                                            "A 5 3 3600 20000101000000 "
@@ -825,27 +888,8 @@ TEST_F(QueryTest, dnssecPositive) {
                                              "3 3600 20000101000000 "
                                              "20000201000000 12345 "
                                              "example.com. FAKEFAKEFAKE\n")).
-                  c_str(), NULL);
-    RRsetIterator iterator(response.beginSection(Message::SECTION_ADDITIONAL));
-    const char* additional[] = {
-        "glue.delegation.example.com. 3600 IN A 192.0.2.153\n",
-        "glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
-            "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
-        "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n",
-        "glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 "
-            "20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE\n",
-        "noglue.example.com. 3600 IN A 192.0.2.53\n",
-        "noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 "
-            "20000201000000 12345 example.com. FAKEFAKEFAKE\n",
-        NULL
-    };
-    for (const char** rr(additional); *rr != NULL; ++ rr) {
-        ASSERT_FALSE(iterator ==
-                     response.endSection(Message::SECTION_ADDITIONAL));
-        EXPECT_EQ(*rr, (*iterator)->toText());
-        iterator ++;
-    }
-    EXPECT_TRUE(iterator == response.endSection(Message::SECTION_ADDITIONAL));
+                  c_str(),
+                  ns_addrs_and_sig_txt.c_str());
 }
 
 TEST_F(QueryTest, exactAddrMatch) {
@@ -1046,7 +1090,7 @@ TEST_F(QueryTest, nxdomainBadNSEC1) {
     // ZoneFinder::find() returns NXDOMAIN with non NSEC RR.
     mock_finder->setNSECResult(Name("badnsec.example.com"),
                                ZoneFinder::NXDOMAIN,
-                               mock_finder->delegation_rrset_);
+                               mock_finder->dname_rrset_);
     EXPECT_THROW(Query(memory_client, Name("badnsec.example.com"), qtype,
                        response, true).process(),
                  std::bad_cast);
@@ -1066,7 +1110,7 @@ TEST_F(QueryTest, nxdomainBadNSEC3) {
     // "no-wildcard proof" returns SUCCESS.  it should be NXDOMAIN.
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::SUCCESS,
-                               mock_finder->delegation_rrset_);
+                               mock_finder->dname_rrset_);
     EXPECT_THROW(Query(memory_client, Name("nxdomain.example.com"), qtype,
                        response, true).process(),
                  Query::BadNSEC);
@@ -1085,18 +1129,20 @@ TEST_F(QueryTest, nxdomainBadNSEC5) {
     // "no-wildcard proof" returns non NSEC.
     mock_finder->setNSECResult(Name("*.example.com"),
                                ZoneFinder::NXDOMAIN,
-                               mock_finder->delegation_rrset_);
+                               mock_finder->dname_rrset_);
     // This is a bit odd, but we'll simply include the returned RRset.
     Query(memory_client, Name("nxdomain.example.com"), qtype,
           response, true).process();
-    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0,
+    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
                   NULL, (string(soa_txt) +
                          string("example.com. 3600 IN RRSIG ") +
                          getCommonRRSIGText("SOA") + "\n" +
                          string(nsec_nxdomain_txt) + "\n" +
                          string("noglue.example.com. 3600 IN RRSIG ") +
                          getCommonRRSIGText("NSEC") + "\n" +
-                         delegation_txt).c_str(),
+                         dname_txt + "\n" +
+                         string("dname.example.com. 3600 IN RRSIG ") +
+                         getCommonRRSIGText("DNAME")).c_str(),
                   NULL, mock_finder->getOrigin());
 }
 
@@ -1208,7 +1254,7 @@ TEST_F(QueryTest, badWildcardProof1) {
     // when NXDOMAIN is expected.
     mock_finder->setNSECResult(Name("www.wild.example.com"),
                                ZoneFinder::SUCCESS,
-                               mock_finder->delegation_rrset_);
+                               mock_finder->dname_rrset_);
     EXPECT_THROW(Query(memory_client, Name("www.wild.example.com"),
                        RRType::A(), response, true).process(),
                  Query::BadNSEC);
@@ -1625,6 +1671,254 @@ TEST_F(QueryTest, findNSEC3) {
                mock_finder->findNSEC3(Name("nxdomain3.example.com"), false));
 }
 
+// This tests that the DS is returned above the delegation point as
+// an authoritative answer, not a delegation. This is as described in
+// RFC 4035, section 3.1.4.1.
+
+// This mock finder is used for some DS-query tests to support the cases
+// where the query is expected to be handled in a different zone than our
+// main test zone, example.com.  Only limited methods are expected to called
+// (and for limited purposes) on this class object in these tests, which
+// are overridden below.
+class AlternateZoneFinder : public MockZoneFinder {
+public:
+    // This zone is expected not to have a DS by default and return NXRRSET
+    // for a DS query.  If have_ds is set to true on construction, it will
+    // return a faked DS answer.
+    AlternateZoneFinder(const Name& origin, bool have_ds = false) :
+        MockZoneFinder(), origin_(origin), have_ds_(have_ds)
+    {}
+    virtual isc::dns::Name getOrigin() const { return (origin_); }
+    virtual FindResult find(const isc::dns::Name&,
+                            const isc::dns::RRType& type,
+                            const FindOptions)
+    {
+        if (type == RRType::SOA()) {
+            RRsetPtr soa = textToRRset(origin_.toText() + " 3600 IN SOA . . "
+                                       "0 0 0 0 0\n", origin_);
+            soa->addRRsig(RdataPtr(new generic::RRSIG(
+                                       getCommonRRSIGText("SOA"))));
+            return (FindResult(SUCCESS, soa));
+        }
+        if (type == RRType::NS()) {
+            RRsetPtr ns = textToRRset(origin_.toText() + " 3600 IN NS " +
+                                      Name("ns").concatenate(origin_).toText());
+            ns->addRRsig(RdataPtr(new generic::RRSIG(
+                                      getCommonRRSIGText("NS"))));
+            return (FindResult(SUCCESS, ns));
+        }
+        if (type == RRType::DS()) {
+            if (have_ds_) {
+                RRsetPtr ds = textToRRset(origin_.toText() +
+                                          " 3600 IN DS 57855 5 1 " +
+                                          "49FD46E6C4B45C55D4AC69CBD"
+                                          "3CD34AC1AFE51DE");
+                ds->addRRsig(RdataPtr(new generic::RRSIG(
+                                          getCommonRRSIGText("DS"))));
+                return (FindResult(SUCCESS, ds));
+            } else {
+                RRsetPtr nsec = textToRRset(origin_.toText() +
+                                            " 3600 IN NSEC " +
+                                            origin_.toText() +
+                                            " SOA NSEC RRSIG");
+                nsec->addRRsig(RdataPtr(new generic::RRSIG(
+                                            getCommonRRSIGText("NSEC"))));
+                return (FindResult(NXRRSET, nsec, RESULT_NSEC_SIGNED));
+            }
+        }
+
+        // Returning NXDOMAIN is not correct, but doesn't matter for our tests.
+        return (FindResult(NXDOMAIN, ConstRRsetPtr()));
+    }
+private:
+    const Name origin_;
+    const bool have_ds_;
+};
+
+TEST_F(QueryTest, dsAboveDelegation) {
+    // Pretending to have authority for the child zone, too.
+    memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
+                                            Name("delegation.example.com"))));
+
+    // The following will succeed only if the search goes to the parent
+    // zone, not the child one we added above.
+    EXPECT_NO_THROW(Query(memory_client, Name("delegation.example.com"),
+                          RRType::DS(), response, true).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
+                  (string(delegation_ds_txt) + "\n" +
+                   "delegation.example.com. 3600 IN RRSIG " +
+                   getCommonRRSIGText("DS")).c_str(),
+                  (string(zone_ns_txt) + "\n" +
+                   "example.com. 3600 IN RRSIG " +
+                   getCommonRRSIGText("NS")).c_str(),
+                  ns_addrs_and_sig_txt.c_str());
+}
+
+TEST_F(QueryTest, dsAboveDelegationNoData) {
+    // Similar to the previous case, but the query is for an unsigned zone
+    // (which doesn't have a DS at the parent).  The response should be a
+    // "no data" error.  The query should still be handled at the parent.
+    memory_client.addZone(ZoneFinderPtr(
+                              new AlternateZoneFinder(
+                                  Name("unsigned-delegation.example.com"))));
+
+    // The following will succeed only if the search goes to the parent
+    // zone, not the child one we added above.
+    EXPECT_NO_THROW(Query(memory_client,
+                          Name("unsigned-delegation.example.com"),
+                          RRType::DS(), response, true).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+                  (string(soa_txt) +
+                   string("example.com. 3600 IN RRSIG ") +
+                   getCommonRRSIGText("SOA") + "\n" +
+                   string(unsigned_delegation_nsec_txt) +
+                   "unsigned-delegation.example.com. 3600 IN RRSIG " +
+                   getCommonRRSIGText("NSEC")).c_str(),
+                  NULL, mock_finder->getOrigin());
+}
+
+// This one checks that type-DS query results in a "no data" response
+// when it happens to be sent to the child zone, as described in RFC 4035,
+// section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
+TEST_F(QueryTest, dsBelowDelegation) {
+    EXPECT_NO_THROW(Query(memory_client, Name("example.com"),
+                          RRType::DS(), response, true).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                   getCommonRRSIGText("SOA") + "\n" +
+                   string(nsec_apex_txt) + "\n" +
+                   string("example.com. 3600 IN RRSIG ") +
+                   getCommonRRSIGText("NSEC")).c_str(), NULL,
+                  mock_finder->getOrigin());
+}
+
+// Similar to the previous case, but even more pathological: the DS somehow
+// exists in the child zone.  The Query module should still return SOA.
+// In our implementation NSEC/NSEC3 isn't attached in this case.
+TEST_F(QueryTest, dsBelowDelegationWithDS) {
+    mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
+    EXPECT_NO_THROW(Query(memory_client, Name("example.com"),
+                          RRType::DS(), response, true).process());
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
+                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                   getCommonRRSIGText("SOA")).c_str(), NULL,
+                  mock_finder->getOrigin());
+}
+
+// DS query received at a completely irrelevant (neither parent nor child)
+// server.  It should just like the "noZone" test case, but DS query involves
+// special processing, so we test it explicitly.
+TEST_F(QueryTest, dsNoZone) {
+    Query(memory_client, Name("example"), RRType::DS(), response,
+          true).process();
+    responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
+}
+
+// DS query for a "grandchild" zone.  This should result in normal
+// delegation (unless this server also has authority of the grandchild zone).
+TEST_F(QueryTest, dsAtGrandParent) {
+    Query(memory_client, Name("grand.delegation.example.com"), RRType::DS(),
+          response, true).process();
+    responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
+                  (string(delegation_txt) + string(delegation_ds_txt) +
+                   "delegation.example.com. 3600 IN RRSIG " +
+                   getCommonRRSIGText("DS")).c_str(),
+                  ns_addrs_and_sig_txt.c_str());
+}
+
+// DS query sent to a "grandparent" server that also has authority for the
+// child zone.  In this case the query should be handled in the child
+// side and should result in no data with SOA.  Note that the server doesn't
+// have authority for the "parent".  Unlike the dsAboveDelegation test case
+// the query should be handled in the child zone, not in the grandparent.
+TEST_F(QueryTest, dsAtGrandParentAndChild) {
+    // Pretending to have authority for the child zone, too.
+    const Name childname("grand.delegation.example.com");
+    memory_client.addZone(ZoneFinderPtr(
+                              new AlternateZoneFinder(childname)));
+    Query(memory_client, childname, RRType::DS(), response, true).process();
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+                  (childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
+                   childname.toText() + " 3600 IN RRSIG " +
+                   getCommonRRSIGText("SOA") + "\n" +
+                   childname.toText() + " 3600 IN NSEC " +
+                   childname.toText() + " SOA NSEC RRSIG\n" +
+                   childname.toText() + " 3600 IN RRSIG " +
+                   getCommonRRSIGText("NSEC")).c_str(), NULL, childname);
+}
+
+// DS query for the root name (quite pathological).  Since there's no "parent",
+// the query will be handled in the root zone anyway, and should (normally)
+// result in no data.
+TEST_F(QueryTest, dsAtRoot) {
+    // Pretend to be a root server.
+    memory_client.addZone(ZoneFinderPtr(
+                              new AlternateZoneFinder(Name::ROOT_NAME())));
+    Query(memory_client, Name::ROOT_NAME(), RRType::DS(), response,
+          true).process();
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+                  (string(". 3600 IN SOA . . 0 0 0 0 0\n") +
+                   ". 3600 IN RRSIG " + getCommonRRSIGText("SOA") + "\n" +
+                   ". 3600 IN NSEC " + ". SOA NSEC RRSIG\n" +
+                   ". 3600 IN RRSIG " +
+                   getCommonRRSIGText("NSEC")).c_str(), NULL);
+}
+
+// Even more pathological case: A faked root zone actually has its own DS
+// query.  How we respond wouldn't matter much in practice, but check if
+// it behaves as it's intended.  This implementation should return the DS.
+TEST_F(QueryTest, dsAtRootWithDS) {
+    memory_client.addZone(ZoneFinderPtr(
+                              new AlternateZoneFinder(Name::ROOT_NAME(),
+                                                      true)));
+    Query(memory_client, Name::ROOT_NAME(), RRType::DS(), response,
+          true).process();
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
+                  (string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
+                          "3CD34AC1AFE51DE\n") +
+                   ". 3600 IN RRSIG " + getCommonRRSIGText("DS")).c_str(),
+                  (string(". 3600 IN NS ns.\n") +
+                   ". 3600 IN RRSIG " + getCommonRRSIGText("NS")).c_str(),
+                  NULL);
+}
+
+// Check the signature is present when an NXRRSET is returned
+TEST_F(QueryTest, nxrrsetWithNSEC3) {
+    mock_finder->setNSEC3Flag(true);
+
+    // NXRRSET with DNSSEC proof.  We should have SOA, NSEC3 that proves the
+    // NXRRSET and their RRSIGs.
+    Query(memory_client, Name("www.example.com"), RRType::TXT(), response,
+          true).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
+                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                   getCommonRRSIGText("SOA") + "\n" +
+                   string(nsec3_www_txt) + "\n" +
+                   mock_finder->hash_map_[Name("www.example.com.")] +
+                   ".example.com. 3600 IN RRSIG " +
+                   getCommonRRSIGText("NSEC3") + "\n").c_str(),
+                  NULL, mock_finder->getOrigin());
+}
+
+// Check the exception is correctly raised when the NSEC3 thing isn't in the
+// zone
+TEST_F(QueryTest, nxrrsetMissingNSEC3) {
+    mock_finder->setNSEC3Flag(true);
+    // We just need it to return false for "matched". This indicates
+    // there's no exact match for NSEC3 on www.example.com.
+    ZoneFinder::FindNSEC3Result nsec3(false, 0, ConstRRsetPtr(),
+                                      ConstRRsetPtr());
+    mock_finder->setNSEC3Result(&nsec3);
+
+    EXPECT_THROW(Query(memory_client, Name("www.example.com"), RRType::TXT(),
+                       response, true).process(), Query::BadNSEC3);
+}
+
 // The following are tentative tests until we really add tests for the
 // query logic for these cases.  At that point it's probably better to
 // clean them up.
@@ -1638,16 +1932,6 @@ TEST_F(QueryTest, nxdomainWithNSEC3) {
     EXPECT_FALSE(result.isWildcard());
 }
 
-TEST_F(QueryTest, nxrrsetWithNSEC3) {
-    mock_finder->setNSEC3Flag(true);
-    ZoneFinder::FindResult result = mock_finder->find(
-        Name("www.example.com"), RRType::TXT(), ZoneFinder::FIND_DNSSEC);
-    EXPECT_EQ(ZoneFinder::NXRRSET, result.code);
-    EXPECT_FALSE(result.rrset);
-    EXPECT_TRUE(result.isNSEC3Signed());
-    EXPECT_FALSE(result.isWildcard());
-}
-
 TEST_F(QueryTest, emptyNameWithNSEC3) {
     mock_finder->setNSEC3Flag(true);
     ZoneFinder::FindResult result = mock_finder->find(
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 2f7456f..faf1582 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -686,7 +686,13 @@ class BoB:
     def shutdown(self):
         """Stop the BoB instance."""
         logger.info(BIND10_SHUTDOWN)
-        # first try using the BIND 10 request to stop
+        # If ccsession is still there, inform rest of the system this module
+        # is stopping. Since everything will be stopped shortly, this is not
+        # really necessary, but this is done to reflect that boss is also
+        # 'just' a module.
+        self.ccs.send_stopping()
+
+        # try using the BIND 10 request to stop
         try:
             self._component_configurator.shutdown()
         except:
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 5ac524d..882824d 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -36,6 +36,7 @@ import isc.bind10.socket_cache
 import errno
 
 from isc.testutils.parse_args import TestOptParser, OptsError
+from isc.testutils.ccsession_mock import MockModuleCCSession
 
 class TestProcessInfo(unittest.TestCase):
     def setUp(self):
@@ -1187,8 +1188,12 @@ class TestBossComponents(unittest.TestCase):
         bob._component_configurator.shutdown = self.__nullary_hook
         self.__called = False
 
+        bob.ccs = MockModuleCCSession()
+        self.assertFalse(bob.ccs.stopped)
+
         bob.shutdown()
 
+        self.assertTrue(bob.ccs.stopped)
         self.assertEqual([False, True], killed)
         self.assertTrue(self.__called)
 
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index ff221db..74fc364 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -310,12 +310,25 @@ class CommandControl():
     def command_handler(self, command, args):
         answer = ccsession.create_answer(0)
         if command == ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE:
+            # The 'value' of a specification update can be either
+            # a specification, or None. In the first case, simply
+            # set it. If it is None, delete the module if it is
+            # known.
             with self._lock:
-                self.modules_spec[args[0]] = args[1]
+                if args[1] is None:
+                    if args[0] in self.modules_spec:
+                        del self.modules_spec[args[0]]
+                    else:
+                        answer = ccsession.create_answer(1,
+                                                         'No such module: ' +
+                                                         args[0])
+                else:
+                    self.modules_spec[args[0]] = args[1]
 
         elif command == ccsession.COMMAND_SHUTDOWN:
             #When cmdctl get 'shutdown' command from boss,
             #shutdown the outer httpserver.
+            self._module_cc.send_stopping()
             self._httpserver.shutdown()
             self._serving = False
 
diff --git a/src/bin/cmdctl/tests/cmdctl_test.py b/src/bin/cmdctl/tests/cmdctl_test.py
index 3103f47..5fdabb4 100644
--- a/src/bin/cmdctl/tests/cmdctl_test.py
+++ b/src/bin/cmdctl/tests/cmdctl_test.py
@@ -345,6 +345,40 @@ class TestCommandControl(unittest.TestCase):
         self.assertEqual(rcode, 0)
         self.assertTrue(msg != None)
 
+    def test_command_handler_spec_update(self):
+        # Should not be present
+        self.assertFalse("foo" in self.cmdctl.modules_spec)
+
+        answer = self.cmdctl.command_handler(
+            ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", {} ])
+        rcode, msg = ccsession.parse_answer(answer)
+        self.assertEqual(rcode, 0)
+        self.assertEqual(msg, None)
+
+        # Should now be present
+        self.assertTrue("foo" in self.cmdctl.modules_spec)
+
+        # When sending specification 'None', it should be removed
+        answer = self.cmdctl.command_handler(
+            ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", None ])
+        rcode, msg = ccsession.parse_answer(answer)
+        self.assertEqual(rcode, 0)
+        self.assertEqual(msg, None)
+
+        # Should no longer be present
+        self.assertFalse("foo" in self.cmdctl.modules_spec)
+
+        # Don't store 'None' if it wasn't there in the first place!
+        answer = self.cmdctl.command_handler(
+            ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE, [ "foo", None ])
+        rcode, msg = ccsession.parse_answer(answer)
+        self.assertEqual(rcode, 1)
+        self.assertEqual(msg, "No such module: foo")
+
+        # Should still not present
+        self.assertFalse("foo" in self.cmdctl.modules_spec)
+
+
     def test_check_config_handler(self):
         answer = self.cmdctl.config_handler({'non-exist': 123})
         self._check_answer(answer, 1, 'unknown config item: non-exist')
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index cde14c7..22e4e9c 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -150,9 +150,10 @@ class DDNSServer:
         Perform any cleanup that is necessary when shutting down the server.
         Do NOT call this to initialize shutdown, use trigger_shutdown().
 
-        Currently, it does nothing, but cleanup routines are expected.
+        Currently, it only causes the ModuleCCSession to send a message that
+        this module is stopping.
         '''
-        pass
+        self._cc.send_stopping()
 
     def accept(self):
         """
diff --git a/src/bin/ddns/ddns.spec b/src/bin/ddns/ddns.spec
index 07cd2a9..55dab5c 100644
--- a/src/bin/ddns/ddns.spec
+++ b/src/bin/ddns/ddns.spec
@@ -34,7 +34,13 @@
       {
         "command_name": "shutdown",
         "command_description": "Shut down DDNS",
-        "command_args": []
+        "command_args": [
+          {
+            "item_name": "pid",
+            "item_type": "integer",
+            "item_optional": true
+          }
+        ]
       }
     ]
   }
diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py
index 6adb97c..395aacc 100755
--- a/src/bin/ddns/tests/ddns_test.py
+++ b/src/bin/ddns/tests/ddns_test.py
@@ -58,11 +58,16 @@ class MyCCSession(isc.config.ConfigData):
             ddns.SPECFILE_LOCATION)
         isc.config.ConfigData.__init__(self, module_spec)
         self._started = False
+        self._stopped = False
 
     def start(self):
         '''Called by DDNSServer initialization, but not used in tests'''
         self._started = True
 
+    def send_stopping(self):
+        '''Called by shutdown code'''
+        self._stopped = True
+
     def get_socket(self):
         """
         Used to get the file number for select.
@@ -289,6 +294,7 @@ class TestDDNSServer(unittest.TestCase):
         self.__select_answer = ([3], [], [])
         self.ddns_server.run()
         self.assertTrue(self.ddns_server._shutdown)
+        self.assertTrue(self.__cc_session._stopped)
         self.assertIsNone(self.__select_answer)
         self.assertEqual(3, self.__hook_called)
 
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index faa166f..2bb3768 100644
--- a/src/bin/resolver/Makefile.am
+++ b/src/bin/resolver/Makefile.am
@@ -51,6 +51,7 @@ b10_resolver_SOURCES += resolver_log.cc resolver_log.h
 b10_resolver_SOURCES += response_scrubber.cc response_scrubber.h
 b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/common.h
 b10_resolver_SOURCES += main.cc
+b10_resolver_SOURCES += common.cc common.h
 
 nodist_b10_resolver_SOURCES = resolver_messages.cc resolver_messages.h
 
diff --git a/src/bin/resolver/common.cc b/src/bin/resolver/common.cc
new file mode 100644
index 0000000..ad4fc50
--- /dev/null
+++ b/src/bin/resolver/common.cc
@@ -0,0 +1,17 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "common.h"
+
+const char* const RESOLVER_NAME = "b10-resolver";
diff --git a/src/bin/resolver/common.h b/src/bin/resolver/common.h
new file mode 100644
index 0000000..bcab8ba
--- /dev/null
+++ b/src/bin/resolver/common.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RESOLVER_COMMON_H
+#define RESOLVER_COMMON_H
+
+/// \brief The name used to identify the resolver between modules.
+///
+/// It is currently set to b10-resolver.
+extern const char* const RESOLVER_NAME;
+
+#endif
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index b390f33..c56e6f3 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -17,6 +17,7 @@
 #include <resolver/spec_config.h>
 #include <resolver/resolver.h>
 #include "resolver_log.h"
+#include "common.h"
 
 #include <asiodns/asiodns.h>
 #include <asiolink/asiolink.h>
@@ -43,6 +44,7 @@
 
 #include <log/logger_support.h>
 #include <log/logger_level.h>
+#include "resolver_log.h"
 
 #include <sys/types.h>
 #include <sys/socket.h>
@@ -88,8 +90,7 @@ my_command_handler(const string& command, ConstElementPtr args) {
             answer = createAnswer(0, args);
         } else if (command == "shutdown") {
             // Is the pid argument provided?
-            if (args && args->getType() == isc::data::Element::map &&
-                args->contains("pid")) {
+            if (args && args->contains("pid")) {
                 // If it is, we check it is the same as our PID
                 const int pid(args->get("pid")->intValue());
                 const pid_t my_pid(getpid());
@@ -139,7 +140,7 @@ main(int argc, char* argv[]) {
 
     // Until proper logging comes along, initialize the logging with the
     // temporary initLogger() code.  If verbose, we'll use maximum verbosity.
-    isc::log::initLogger("b10-resolver",
+    isc::log::initLogger(RESOLVER_NAME,
                          (verbose ? isc::log::DEBUG : isc::log::INFO),
                          isc::log::MAX_DEBUG_LEVEL, NULL);
 
@@ -220,7 +221,7 @@ main(int argc, char* argv[]) {
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);
 
         cc_session = new Session(io_service.get_io_service());
-        isc::server_common::initSocketRequestor(*cc_session);
+        isc::server_common::initSocketRequestor(*cc_session, RESOLVER_NAME);
 
         // We delay starting listening to new commands/config just before we
         // go into the main loop.   See auth/main.cc for the rationale.
diff --git a/src/bin/resolver/tests/resolver_config_unittest.cc b/src/bin/resolver/tests/resolver_config_unittest.cc
index 1a30941..ea42ba0 100644
--- a/src/bin/resolver/tests/resolver_config_unittest.cc
+++ b/src/bin/resolver/tests/resolver_config_unittest.cc
@@ -85,7 +85,10 @@ protected:
     scoped_ptr<const RequestContext> request;
     ResolverConfig() :
         dnss(ios, NULL, NULL, NULL),
-        sock_requestor_(dnss, address_store_, 53210)
+        // The empty string is expected value of the parameter of
+        // requestSocket, not the app_name (there's no fallback, it checks
+        // the empty string is passed).
+        sock_requestor_(dnss, address_store_, 53210, "")
     {
         server.setDNSService(dnss);
     }
diff --git a/src/bin/stats/stats-httpd.spec b/src/bin/stats/stats-httpd.spec
index 6307135..783611d 100644
--- a/src/bin/stats/stats-httpd.spec
+++ b/src/bin/stats/stats-httpd.spec
@@ -47,7 +47,13 @@
       {
         "command_name": "shutdown",
         "command_description": "Shut down the stats httpd",
-        "command_args": []
+        "command_args": [
+          {
+            "item_name": "pid",
+            "item_type": "integer",
+            "item_optional": true
+          }
+        ]
       }
     ]
   }
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 51c4e09..938a062 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -184,8 +184,11 @@ class Stats:
             raise StatsError("stats spec file is incorrect: "
                              + ", ".join(errors))
 
-        while self.running:
-            self.mccs.check_command(False)
+        try:
+            while self.running:
+                self.mccs.check_command(False)
+        finally:
+            self.mccs.send_stopping()
 
     def config_handler(self, new_config):
         """
@@ -301,9 +304,11 @@ class Stats:
         return isc.config.create_answer(
             0, "Stats is up. (PID " + str(os.getpid()) + ")")
 
-    def command_shutdown(self):
+    def command_shutdown(self, pid=None):
         """
         handle shutdown command
+
+        The pid argument is ignored, it is here to match the signature.
         """
         logger.info(STATS_RECEIVED_SHUTDOWN_COMMAND)
         self.running = False
diff --git a/src/bin/stats/stats.spec b/src/bin/stats/stats.spec
index e716b62..d3bdcca 100644
--- a/src/bin/stats/stats.spec
+++ b/src/bin/stats/stats.spec
@@ -12,7 +12,13 @@
       {
         "command_name": "shutdown",
         "command_description": "Shut down the stats module",
-        "command_args": []
+        "command_args": [
+          {
+            "item_name": "pid",
+            "item_type": "integer",
+            "item_optional": true
+          }
+        ]
       },
       {
         "command_name": "show",
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index f265abb..c9bd0f5 100644
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -203,6 +203,7 @@ class StatsHttpd:
         """Closes a ModuleCCSession object"""
         if self.mccs is None:
             return
+        self.mccs.send_stopping()
 
         logger.debug(DBG_STATHTTPD_INIT, STATHTTPD_CLOSING_CC_SESSION)
         self.mccs.close()
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index b6847bd..e74405a 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -37,7 +37,10 @@ import random
 import isc
 import stats_httpd
 import stats
-from test_utils import BaseModules, ThreadingServerManager, MyStats, MyStatsHttpd, SignalHandler, send_command, send_shutdown
+from test_utils import BaseModules, ThreadingServerManager, MyStats,\
+                       MyStatsHttpd, SignalHandler,\
+                       send_command, send_shutdown
+from isc.testutils.ccsession_mock import MockModuleCCSession
 
 DUMMY_DATA = {
     'Boss' : {
@@ -676,7 +679,13 @@ class TestStatsHttpd(unittest.TestCase):
 
     def test_openclose_mccs(self):
         self.stats_httpd = MyStatsHttpd(get_availaddr())
+        mccs = MockModuleCCSession()
+        self.stats_httpd.mccs = mccs
+        self.assertFalse(self.stats_httpd.mccs.stopped)
+        self.assertFalse(self.stats_httpd.mccs.closed)
         self.stats_httpd.close_mccs()
+        self.assertTrue(mccs.stopped)
+        self.assertTrue(mccs.closed)
         self.assertEqual(self.stats_httpd.mccs, None)
         self.stats_httpd.open_mccs()
         self.assertIsNotNone(self.stats_httpd.mccs)
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index 3c8599a..d9f8d37 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -31,6 +31,7 @@ import imp
 import stats
 import isc.cc.session
 from test_utils import BaseModules, ThreadingServerManager, MyStats, SignalHandler, send_command, send_shutdown
+from isc.testutils.ccsession_mock import MockModuleCCSession
 
 class TestUtilties(unittest.TestCase):
     items = [
@@ -201,9 +202,20 @@ class TestStats(unittest.TestCase):
         self.assertEqual(send_command("status", "Stats"),
                 (0, "Stats is up. (PID " + str(os.getpid()) + ")"))
         self.assertTrue(self.stats.running)
+        # Due to a race-condition related to the threads used in these
+        # tests, use of the mock session and the stopped check (see
+        # below), are temporarily disabled
+        # See ticket #1668
+        # Override moduleCCSession so we can check if send_stopping is called
+        #self.stats.mccs = MockModuleCCSession()
         self.assertEqual(send_shutdown("Stats"), (0, None))
         self.assertFalse(self.stats.running)
-        self.stats_server.shutdown()
+        # Call server.shutdown with argument True so the thread.join() call
+        # blocks and we are sure the main loop has finished (and set
+        # mccs.stopped)
+        self.stats_server.shutdown(True)
+        # Also temporarily disabled for #1668, see above
+        #self.assertTrue(self.stats.mccs.stopped)
 
         # start with err
         self.stats = stats.Stats()
diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py
index 3f6ff33..29fc785 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -72,9 +72,19 @@ class ThreadingServerManager:
         self.server._started.wait()
         self.server._started.clear()
 
-    def shutdown(self):
+    def shutdown(self, blocking=False):
+        """Shut down the server by calling its own shutdown() method.
+           Then wait for its thread to finish. If blocking is True,
+           the thread.join() blocks until the thread finishes. If not,
+           it uses a zero timeout. The latter is necessary in a number
+           of existing tests. We should redo this part (we should not
+           even need threads in most, if not all, of these threads, see
+           ticket #1668)"""
         self.server.shutdown()
-        self.server._thread.join(0) # timeout is 0
+        if blocking:
+            self.server._thread.join()
+        else:
+            self.server._thread.join(0) # timeout is 0
 
 def do_nothing(*args, **kwargs): pass
 
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 986957a..89e4d96 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -20,6 +20,7 @@ import socket
 import sys
 import io
 from isc.testutils.tsigctx_mock import MockTSIGContext
+from isc.testutils.ccsession_mock import MockModuleCCSession
 from isc.testutils.rrset_utils import *
 from xfrin import *
 import xfrin
@@ -105,7 +106,7 @@ class XfrinTestException(Exception):
 class XfrinTestTimeoutException(Exception):
     pass
 
-class MockCC():
+class MockCC(MockModuleCCSession):
     def get_default_value(self, identifier):
         # The returned values should be identical to the spec file
         # XXX: these should be retrieved from the spec file
@@ -2052,7 +2053,9 @@ class TestXfrin(unittest.TestCase):
         self.args['tsig_key'] = ''
 
     def tearDown(self):
+        self.assertFalse(self.xfr._module_cc.stopped);
         self.xfr.shutdown()
+        self.assertTrue(self.xfr._module_cc.stopped);
         sys.stderr= self.stderr_backup
 
     def _do_parse_zone_name_class(self):
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index 8c0e754..b59c2b6 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -1224,6 +1224,7 @@ class Xfrin:
         ''' shutdown the xfrin process. the thread which is doing xfrin should be
         terminated.
         '''
+        self._module_cc.send_stopping()
         self._shutdown_event.set()
         main_thread = threading.currentThread()
         for th in threading.enumerate():
diff --git a/src/bin/xfrin/xfrin.spec b/src/bin/xfrin/xfrin.spec
index c1ba61e..7ab1085 100644
--- a/src/bin/xfrin/xfrin.spec
+++ b/src/bin/xfrin/xfrin.spec
@@ -86,7 +86,13 @@
       {
         "command_name": "shutdown",
         "command_description": "Shut down xfrin module",
-        "command_args": []
+        "command_args": [
+          {
+            "item_name": "pid",
+            "item_type": "integer",
+            "item_optional": true
+          }
+        ]
       }
     ]
   }
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 396139e..3e953da 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -19,6 +19,7 @@
 import unittest
 import os
 from isc.testutils.tsigctx_mock import MockTSIGContext
+from isc.testutils.ccsession_mock import MockModuleCCSession
 from isc.cc.session import *
 import isc.config
 from isc.dns import *
@@ -1423,6 +1424,32 @@ class TestInitialization(unittest.TestCase):
         xfrout.init_paths()
         self.assertEqual(xfrout.UNIX_SOCKET_FILE, "The/Socket/File")
 
+class MyNotifier():
+    def __init__(self):
+        self.shutdown_called = False
+
+    def shutdown(self):
+        self.shutdown_called = True
+
+class MyXfroutServer(XfroutServer):
+    def __init__(self):
+        self._cc = MockModuleCCSession()
+        self._shutdown_event = threading.Event()
+        self._notifier = MyNotifier()
+        self._unix_socket_server = None
+        # Disable the wait for threads
+        self._wait_for_threads = lambda : None
+
+class TestXfroutServer(unittest.TestCase):
+    def setUp(self):
+        self.xfrout_server = MyXfroutServer()
+
+    def test_shutdown(self):
+        self.xfrout_server.shutdown()
+        self.assertTrue(self.xfrout_server._notifier.shutdown_called)
+        self.assertTrue(self.xfrout_server._cc.stopped)
+
+
 if __name__== "__main__":
     isc.log.resetUnitTestRootLogger()
     unittest.main()
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index 38ef9c7..5c82f19 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -969,12 +969,18 @@ class XfroutServer:
 
         global xfrout_server
         xfrout_server = None #Avoid shutdown is called twice
+        self._cc.send_stopping()
         self._shutdown_event.set()
         self._notifier.shutdown()
         if self._unix_socket_server:
             self._unix_socket_server.shutdown()
+        self._wait_for_threads()
 
-        # Wait for all threads to terminate
+    def _wait_for_threads(self):
+        # Wait for all threads to terminate. this is a call that is only used
+        # in shutdown(), but it has its own method, so we can test shutdown
+        # without involving thread operations (the test would override this
+        # method)
         main_thread = threading.currentThread()
         for th in threading.enumerate():
             if th is main_thread:
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index 0891a57..6a97dea 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -106,7 +106,13 @@
         {
           "command_name": "shutdown",
           "command_description": "Shut down Xfrout",
-          "command_args": []
+          "command_args": [
+          {
+            "item_name": "pid",
+            "item_type": "integer",
+            "item_optional": true
+          }
+        ]
         }
       ]
   }
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 600453d..29924c8 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -20,6 +20,7 @@ import unittest
 import os
 import tempfile
 from zonemgr import *
+from isc.testutils.ccsession_mock import MockModuleCCSession
 
 ZONE_NAME_CLASS1_IN = ("example.net.", "IN")
 ZONE_NAME_CLASS1_CH = ("example.net.", "CH")
@@ -48,10 +49,11 @@ class MySession():
     def group_recvmsg(self, nonblock, seq):
         return None, None
 
-class FakeCCSession(isc.config.ConfigData):
+class FakeCCSession(isc.config.ConfigData, MockModuleCCSession):
     def __init__(self):
         module_spec = isc.config.module_spec_from_file(SPECFILE_LOCATION)
         ConfigData.__init__(self, module_spec)
+        MockModuleCCSession.__init__(self)
 
     def get_remote_config_value(self, module_name, identifier):
         if module_name == "Auth" and identifier == "database_file":
@@ -683,6 +685,12 @@ class TestZonemgr(unittest.TestCase):
         self.zonemgr._config_data_check(config_data3)
         self.assertEqual(0.5, config_data3.get("refresh_jitter"))
 
+    def test_shutdown(self):
+        self.assertFalse(self.zonemgr._module_cc.stopped)
+        self.zonemgr._shutdown_event.set()
+        self.zonemgr.run()
+        self.assertTrue(self.zonemgr._module_cc.stopped)
+
     def tearDown(self):
         pass
 
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 4060bb5..7b16f1b 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -658,8 +658,11 @@ class Zonemgr:
 
     def run(self):
         self.running = True
-        while not self._shutdown_event.is_set():
-            self._module_cc.check_command(False)
+        try:
+            while not self._shutdown_event.is_set():
+                self._module_cc.check_command(False)
+        finally:
+            self._module_cc.send_stopping()
 
 zonemgrd = None
 
diff --git a/src/bin/zonemgr/zonemgr.spec.pre.in b/src/bin/zonemgr/zonemgr.spec.pre.in
index 36f02df..defa203 100644
--- a/src/bin/zonemgr/zonemgr.spec.pre.in
+++ b/src/bin/zonemgr/zonemgr.spec.pre.in
@@ -63,7 +63,13 @@
         {
           "command_name": "shutdown",
           "command_description": "Shut down Zonemgr",
-          "command_args": []
+          "command_args": [
+          {
+            "item_name": "pid",
+            "item_type": "integer",
+            "item_optional": true
+          }
+        ]
         }
       ]
   }
diff --git a/src/lib/cache/local_zone_data.cc b/src/lib/cache/local_zone_data.cc
index 13d1d75..29ab2bf 100644
--- a/src/lib/cache/local_zone_data.cc
+++ b/src/lib/cache/local_zone_data.cc
@@ -43,7 +43,7 @@ LocalZoneData::lookup(const isc::dns::Name& name,
 }
 
 void
-LocalZoneData::update(const isc::dns::RRset& rrset) {
+LocalZoneData::update(const isc::dns::AbstractRRset& rrset) {
     //TODO Do we really need to recreate the rrset again?
     string key = genCacheEntryName(rrset.getName(), rrset.getType());
     LOG_DEBUG(logger, DBG_TRACE_DATA, CACHE_LOCALZONE_UPDATE).arg(key);
diff --git a/src/lib/cache/local_zone_data.h b/src/lib/cache/local_zone_data.h
index 3015847..df77f40 100644
--- a/src/lib/cache/local_zone_data.h
+++ b/src/lib/cache/local_zone_data.h
@@ -47,7 +47,7 @@ public:
     /// Otherwise, the existed one will be overwritten.
     ///
     /// \param rrset The rrset to update
-    void update(const isc::dns::RRset& rrset);
+    void update(const isc::dns::AbstractRRset& rrset);
 
 private:
     std::map<std::string, isc::dns::RRsetPtr> rrsets_map_; // RRsets of the zone
diff --git a/src/lib/cache/rrset_cache.cc b/src/lib/cache/rrset_cache.cc
index 1a5fd48..bb4d339 100644
--- a/src/lib/cache/rrset_cache.cc
+++ b/src/lib/cache/rrset_cache.cc
@@ -70,7 +70,7 @@ RRsetCache::lookup(const isc::dns::Name& qname,
 }
 
 RRsetEntryPtr
-RRsetCache::update(const isc::dns::RRset& rrset,
+RRsetCache::update(const isc::dns::AbstractRRset& rrset,
                    const RRsetTrustLevel& level)
 {
     LOG_DEBUG(logger, DBG_TRACE_DATA, CACHE_RRSET_UPDATE).arg(rrset.getName()).
diff --git a/src/lib/cache/rrset_cache.h b/src/lib/cache/rrset_cache.h
index 73f9b58..a4ea54e 100644
--- a/src/lib/cache/rrset_cache.h
+++ b/src/lib/cache/rrset_cache.h
@@ -73,7 +73,7 @@ public:
     /// \param level trustworthiness of the rrset.
     /// \return return the rrset entry in the cache, it may be the
     /// new added rrset entry or existed one if it is not replaced.
-    RRsetEntryPtr update(const isc::dns::RRset& rrset,
+    RRsetEntryPtr update(const isc::dns::AbstractRRset& rrset,
                          const RRsetTrustLevel& level);
 
     /// \short Protected memebers, so they can be accessed by tests.
diff --git a/src/lib/cache/rrset_copy.cc b/src/lib/cache/rrset_copy.cc
index 05b139a..b395ce1 100644
--- a/src/lib/cache/rrset_copy.cc
+++ b/src/lib/cache/rrset_copy.cc
@@ -20,7 +20,7 @@ namespace isc {
 namespace cache {
 
 void
-rrsetCopy(const isc::dns::RRset& src, isc::dns::RRset& dst) {
+rrsetCopy(const isc::dns::AbstractRRset& src, isc::dns::AbstractRRset& dst) {
     RdataIteratorPtr rdata_itor = src.getRdataIterator();
     rdata_itor->first();
     while(!rdata_itor->isLast()){
diff --git a/src/lib/cache/rrset_copy.h b/src/lib/cache/rrset_copy.h
index b6af8d6..e1dc489 100644
--- a/src/lib/cache/rrset_copy.h
+++ b/src/lib/cache/rrset_copy.h
@@ -33,7 +33,7 @@ namespace cache {
 ///       we have to do the copy.
 
 void
-rrsetCopy(const isc::dns::RRset& src, isc::dns::RRset& dst);
+rrsetCopy(const isc::dns::AbstractRRset& src, isc::dns::AbstractRRset& dst);
 
 } // namespace cache
 } // namespace isc
diff --git a/src/lib/cache/rrset_entry.cc b/src/lib/cache/rrset_entry.cc
index c829956..359fd68 100644
--- a/src/lib/cache/rrset_entry.cc
+++ b/src/lib/cache/rrset_entry.cc
@@ -25,7 +25,8 @@ using namespace isc::dns;
 namespace isc {
 namespace cache {
 
-RRsetEntry::RRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level):
+RRsetEntry::RRsetEntry(const isc::dns::AbstractRRset& rrset,
+                       const RRsetTrustLevel& level):
     entry_name_(genCacheEntryName(rrset.getName(), rrset.getType())),
     expire_time_(time(NULL) + rrset.getTTL().getValue()),
     trust_level_(level),
diff --git a/src/lib/cache/rrset_entry.h b/src/lib/cache/rrset_entry.h
index 09cf79c..129300d 100644
--- a/src/lib/cache/rrset_entry.h
+++ b/src/lib/cache/rrset_entry.h
@@ -75,7 +75,8 @@ public:
     /// \brief Constructor
     /// \param rrset The RRset used to initialize the RRset entry.
     /// \param level trustworthiness of the RRset.
-    RRsetEntry(const isc::dns::RRset& rrset, const RRsetTrustLevel& level);
+    RRsetEntry(const isc::dns::AbstractRRset& rrset,
+               const RRsetTrustLevel& level);
 
     /// The destructor.
     ~RRsetEntry() {}
diff --git a/src/lib/config/ccsession.cc b/src/lib/config/ccsession.cc
index ac85077..63fa4cd 100644
--- a/src/lib/config/ccsession.cc
+++ b/src/lib/config/ccsession.cc
@@ -489,6 +489,18 @@ ModuleCCSession::ModuleCCSession(
 
 }
 
+ModuleCCSession::~ModuleCCSession() {
+    try {
+        sendStopping();
+    } catch (const std::exception& exc) {
+        LOG_ERROR(config_logger,
+                  CONFIG_CCSESSION_STOPPING).arg(exc.what());
+    } catch (...) {
+        LOG_ERROR(config_logger,
+                  CONFIG_CCSESSION_STOPPING_UNKNOWN);
+    }
+};
+
 void
 ModuleCCSession::start() {
     if (started_) {
@@ -741,5 +753,16 @@ ModuleCCSession::updateRemoteConfig(const std::string& module_name,
     }
 }
 
+void
+ModuleCCSession::sendStopping() {
+    // Inform the configuration manager that this module is stopping
+    ConstElementPtr cmd(createCommand("stopping",
+                                      Element::fromJSON(
+                                          "{\"module_name\": \"" +
+                                          module_name_ + "\"}")));
+    // It's just an FYI, configmanager is not expected to respond.
+    session_.group_sendmsg(cmd, "ConfigManager");
+}
+
 }
 }
diff --git a/src/lib/config/ccsession.h b/src/lib/config/ccsession.h
index 80ef7c5..059968c 100644
--- a/src/lib/config/ccsession.h
+++ b/src/lib/config/ccsession.h
@@ -192,6 +192,14 @@ public:
                     bool handle_logging = true
                     );
 
+    ///
+    /// Destructor
+    ///
+    /// The destructor automatically calls sendStopping(), which sends
+    /// a message to the ConfigManager that this module is stopping
+    ///
+    virtual ~ModuleCCSession();
+
     /// Start receiving new commands and configuration changes asynchronously.
     ///
     /// This method must be called only once, and only when the ModuleCCSession
@@ -353,6 +361,7 @@ public:
 private:
     ModuleSpec readModuleSpecification(const std::string& filename);
     void startCheck();
+    void sendStopping();
 
     bool started_;
     std::string module_name_;
diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes
index c439edd..552256c 100644
--- a/src/lib/config/config_messages.mes
+++ b/src/lib/config/config_messages.mes
@@ -30,6 +30,18 @@ but will not send back an answer.
 The most likely cause of this error is a programming error.  Please raise
 a bug report.
 
+% CONFIG_CCSESSION_STOPPING error sending stopping message: %1
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
+
+% CONFIG_CCSESSION_STOPPING_UNKNOWN unknown error sending stopping message
+Similar to CONFIG_CCSESSION_STOPPING, but in this case the exception that
+is seen is not a standard exception, and further information is unknown.
+This is a bug.
+
 % CONFIG_GET_FAIL error getting configuration from cfgmgr: %1
 The configuration manager returned an error when this module requested
 the configuration. The full error message answer from the configuration
@@ -37,6 +49,11 @@ manager is appended to the log error. The most likely cause is that
 the module is of a different (command specification) version than the
 running configuration manager.
 
+% CONFIG_JSON_PARSE JSON parse error in %1: %2
+There was an error parsing the JSON file. The given file does not appear
+to be in valid JSON format. Please verify that the filename is correct
+and that the contents are valid JSON.
+
 % CONFIG_LOG_EXPLICIT will use logging configuration for explicitly-named logger %1
 This is a debug message.  When processing the "loggers" part of the
 configuration file, the configuration library found an entry for the named
@@ -62,11 +79,6 @@ wildcard entry (one containing the "*" character) that matches a logger
 specification in the program. The logging configuration for the program
 will be updated with the information.
 
-% CONFIG_JSON_PARSE JSON parse error in %1: %2
-There was an error parsing the JSON file. The given file does not appear
-to be in valid JSON format. Please verify that the filename is correct
-and that the contents are valid JSON.
-
 % CONFIG_MOD_SPEC_FORMAT module specification error in %1: %2
 The given file does not appear to be a valid specification file: details
 are included in the message. Please verify that the filename is correct
diff --git a/src/lib/config/tests/ccsession_unittests.cc b/src/lib/config/tests/ccsession_unittests.cc
index 793fa30..abaff8e 100644
--- a/src/lib/config/tests/ccsession_unittests.cc
+++ b/src/lib/config/tests/ccsession_unittests.cc
@@ -26,6 +26,8 @@
 
 #include <log/logger_name.h>
 
+#include <boost/scoped_ptr.hpp>
+
 using namespace isc::data;
 using namespace isc::config;
 using namespace isc::cc;
@@ -190,6 +192,67 @@ TEST_F(CCSessionTest, session2) {
     EXPECT_EQ(0, session.getMsgQueue()->size());
 }
 
+TEST_F(CCSessionTest, session_close) {
+    // Test whether ModuleCCSession automatically sends a 'stopping'
+    // message when it is destroyed
+    ConstElementPtr msg;
+    std::string group, to;
+
+    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
+
+    boost::scoped_ptr<ModuleCCSession> mccs(new ModuleCCSession(
+                                         ccspecfile("spec2.spec"),
+                                         session, NULL, NULL,
+                                         true, false));
+    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+    // The initial message is irrelevant for this test
+    // (see session2 test), drop it
+    session.getFirstMessage(group, to);
+    // Queue should now be empty
+    ASSERT_EQ(0, session.getMsgQueue()->size());
+    // Invoke the destructor
+    mccs.reset();
+    // Destructor should have caused a new message
+    ASSERT_EQ(1, session.getMsgQueue()->size());
+    msg = session.getFirstMessage(group, to);
+    EXPECT_EQ("{ \"command\": [ \"stopping\", "
+              "{ \"module_name\": \"Spec2\" } ] }", msg->str());
+    EXPECT_EQ("ConfigManager", group);
+    EXPECT_EQ("*", to);
+    EXPECT_EQ(0, session.getMsgQueue()->size());
+}
+
+TEST_F(CCSessionTest, session_close_exception) {
+    // Test whether an exception encountered during the destructor is
+    // handled correctly
+    ConstElementPtr msg;
+    std::string group, to;
+
+    EXPECT_FALSE(session.haveSubscription("Spec2", "*"));
+
+    boost::scoped_ptr<ModuleCCSession> mccs(new ModuleCCSession(
+                                         ccspecfile("spec2.spec"),
+                                         session, NULL, NULL,
+                                         true, false));
+    EXPECT_TRUE(session.haveSubscription("Spec2", "*"));
+    // The initial message is irrelevant for this test
+    // (see session2 test), drop it
+    session.getFirstMessage(group, to);
+    // Queue should now be empty
+    ASSERT_EQ(0, session.getMsgQueue()->size());
+
+    // Set fake session to throw an exception
+    session.setThrowOnSend(true);
+
+    // Invoke the destructor
+    mccs.reset();
+    // Destructor should not have caused a new message (since fakesession
+    // should have thrown an exception)
+    ASSERT_EQ(0, session.getMsgQueue()->size());
+    //EXPECT_EQ(0, session.getMsgQueue()->size());
+}
+
+
 ConstElementPtr my_config_handler(ConstElementPtr new_config) {
     if (new_config && new_config->contains("item1") &&
         new_config->get("item1")->intValue() == 5) {
diff --git a/src/lib/config/tests/fake_session.cc b/src/lib/config/tests/fake_session.cc
index 2b216e7..177e629 100644
--- a/src/lib/config/tests/fake_session.cc
+++ b/src/lib/config/tests/fake_session.cc
@@ -72,7 +72,8 @@ FakeSession::FakeSession(isc::data::ElementPtr initial_messages,
     messages_(initial_messages),
     subscriptions_(subscriptions),
     msg_queue_(msg_queue),
-    started_(false)
+    started_(false),
+    throw_on_send_(false)
 {
 }
 
@@ -181,8 +182,9 @@ int
 FakeSession::group_sendmsg(ConstElementPtr msg, std::string group,
                            std::string to, std::string)
 {
-    //cout << "[XX] client sends message: " << msg << endl;
-    //cout << "[XX] to: " << group << " . " << instance << "." << to << endl;
+    if (throw_on_send_) {
+        isc_throw(Exception, "Throw on send is set in FakeSession");
+    }
     addMessage(msg, group, to);
     return (1);
 }
@@ -261,6 +263,5 @@ FakeSession::haveSubscription(ConstElementPtr group, ConstElementPtr instance)
 {
     return (haveSubscription(group->stringValue(), instance->stringValue()));
 }
-
 }
 }
diff --git a/src/lib/config/tests/fake_session.h b/src/lib/config/tests/fake_session.h
index 85e47d5..79ff174 100644
--- a/src/lib/config/tests/fake_session.h
+++ b/src/lib/config/tests/fake_session.h
@@ -87,6 +87,14 @@ public:
     isc::data::ElementPtr getMessages() { return (messages_); }
     isc::data::ElementPtr getMsgQueue() { return (msg_queue_); }
 
+    /// Throw exception on sendmsg()
+    ///
+    /// When set to true, and sendmsg() is later called, this
+    /// will throw isc::Exception
+    ///
+    /// \param value If true, enable throw. If false, disable it
+    void setThrowOnSend(bool value) { throw_on_send_ = value; }
+
 private:
     bool recvmsg(isc::data::ConstElementPtr& msg,
                  bool nonblock = true, int seq = -1);
@@ -98,6 +106,7 @@ private:
     isc::data::ElementPtr subscriptions_;
     isc::data::ElementPtr msg_queue_;
     bool started_;
+    bool throw_on_send_;
 };
 } // namespace cc
 } // namespace isc
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index 1dc5359..139576a 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -1120,8 +1120,8 @@ public:
 
     virtual ZoneFinder& getFinder() { return (*finder_); }
 
-    virtual void addRRset(const RRset& rrset);
-    virtual void deleteRRset(const RRset& rrset);
+    virtual void addRRset(const AbstractRRset& rrset);
+    virtual void deleteRRset(const AbstractRRset& rrset);
     virtual void commit();
 
 private:
@@ -1148,14 +1148,15 @@ private:
     // This is a set of validation checks commonly used for addRRset() and
     // deleteRRset to minimize duplicate code logic and to make the main
     // code concise.
-    void validateAddOrDelete(const char* const op_str, const RRset& rrset,
+    void validateAddOrDelete(const char* const op_str,
+                             const AbstractRRset& rrset,
                              DiffPhase prev_phase,
                              DiffPhase current_phase) const;
 };
 
 void
 DatabaseUpdater::validateAddOrDelete(const char* const op_str,
-                                     const RRset& rrset,
+                                     const AbstractRRset& rrset,
                                      DiffPhase prev_phase,
                                      DiffPhase current_phase) const
 {
@@ -1193,7 +1194,7 @@ DatabaseUpdater::validateAddOrDelete(const char* const op_str,
 }
 
 void
-DatabaseUpdater::addRRset(const RRset& rrset) {
+DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
     validateAddOrDelete("add", rrset, DELETE, ADD);
 
     // It's guaranteed rrset has at least one RDATA at this point.
@@ -1239,7 +1240,7 @@ DatabaseUpdater::addRRset(const RRset& rrset) {
 }
 
 void
-DatabaseUpdater::deleteRRset(const RRset& rrset) {
+DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
     // If this is the first operation, pretend we are starting a new delete
     // sequence after adds.  This will simplify the validation below.
     if (diff_phase_ == NOT_STARTED) {
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index f05ff21..d8ad07b 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -332,6 +332,30 @@ should be followed. The requested domain is an apex of some zone.
 % DATASRC_MEM_FIND find '%1/%2'
 Debug information. A search for the requested RRset is being started.
 
+% DATASRC_MEM_FINDNSEC3 finding NSEC3 for %1, mode %2
+Debug information. A search in an in-memory data source for NSEC3 that
+matches or covers the given name is being started.
+
+% DATASRC_MEM_FINDNSEC3_COVER found a covering NSEC3 for %1: %2
+Debug information. An NSEC3 that covers the given name is found and
+being returned.  The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEM_FINDNSEC3_MATCH found a matching NSEC3 for %1 at label count %2: %3
+Debug information. An NSEC3 that matches (a possibly superdomain of)
+the given name is found and being returned.  When the shown label
+count is smaller than that of the given name, the matching NSEC3 is
+for a superdomain of the given name (see DATASRC_MEM_FINDNSEC3_TRYHASH).
+The found NSEC3 RRset is also displayed.
+
+% DATASRC_MEM_FINDNSEC3_TRYHASH looking for NSEC3 for %1 at label count %2 (hash %3)
+Debug information. In an attempt of finding an NSEC3 for the give name,
+(a possibly superdomain of) the name is hashed and searched for in the
+NSEC3 name space.  When the shown label count is smaller than that of the
+shown name, the search tries the superdomain name that share the shown
+(higher) label count of the shown name (e.g., for
+www.example.com. with shown label count of 3, example.com. is being
+tried).
+
 % DATASRC_MEM_FIND_ZONE looking for zone '%1'
 Debug information. A zone object for this zone is being searched for in the
 in-memory data source.
diff --git a/src/lib/datasrc/memory_datasrc.cc b/src/lib/datasrc/memory_datasrc.cc
index bfff5c1..5137727 100644
--- a/src/lib/datasrc/memory_datasrc.cc
+++ b/src/lib/datasrc/memory_datasrc.cc
@@ -17,6 +17,7 @@
 #include <utility>
 #include <cctype>
 #include <cassert>
+
 #include <boost/shared_ptr.hpp>
 #include <boost/scoped_ptr.hpp>
 #include <boost/bind.hpp>
@@ -169,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
@@ -176,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());
         }
 
         /*
@@ -201,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());
         }
     }
 
@@ -373,7 +379,7 @@ 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<RRset>(covered_rrset)->addRRsig(sig_rrset);
+        boost::const_pointer_cast<AbstractRRset>(covered_rrset)->addRRsig(sig_rrset);
 
         return (result::SUCCESS);
     }
@@ -461,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) {
@@ -875,57 +881,80 @@ InMemoryZoneFinder::findAll(const Name& name,
 }
 
 ZoneFinder::FindNSEC3Result
-InMemoryZoneFinder::findNSEC3(const Name&, bool) {
-    isc_throw(NotImplemented, "findNSEC3 is not yet implemented for in memory "
-              "data source");
-}
+InMemoryZoneFinder::findNSEC3(const Name& name, bool recursive) {
+    LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FINDNSEC3).arg(name).
+        arg(recursive ? "recursive" : "non-recursive");
 
-ZoneFinder::FindNSEC3Result
-InMemoryZoneFinder::findNSEC3Tmp(const Name& name, bool recursive) {
     if (!impl_->zone_data_->nsec3_data_) {
-        isc_throw(Unexpected, "findNSEC3 is called for non NSEC3 zone");
+        isc_throw(DataSourceError,
+                  "findNSEC3 attempt for non NSEC3 signed zone: " <<
+                  impl_->origin_ << "/" << impl_->zone_class_);
     }
-    if (recursive) {
-        isc_throw(Unexpected, "recursive mode isn't expected in tests");
+    const NSEC3Map& map = impl_->zone_data_->nsec3_data_->map_;
+    if (map.empty()) {
+        isc_throw(DataSourceError,
+                  "findNSEC3 attempt but zone has no NSEC3 RR: " <<
+                  impl_->origin_ << "/" << impl_->zone_class_);
     }
-
-    // A temporary workaround for testing: convert the original name to
-    // NSEC3-hashed name using hardcoded mapping.
-    string hname_text;
-    if (name == Name("example.org")) {
-        hname_text = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-    } else if (name == Name("www.example.org")) {
-        hname_text = "2S9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-    } else if (name == Name("xxx.example.org")) {
-        hname_text = "Q09MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-    } else if (name == Name("yyy.example.org")) {
-        hname_text = "0A9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-    } else {
-        isc_throw(Unexpected, "unexpected name for NSEC3 test: " << name);
+    const NameComparisonResult cmp_result = name.compare(impl_->origin_);
+    if (cmp_result.getRelation() != NameComparisonResult::EQUAL &&
+        cmp_result.getRelation() != NameComparisonResult::SUBDOMAIN) {
+        isc_throw(InvalidParameter, "findNSEC3 attempt for out-of-zone name: "
+                  << name << ", zone: " << impl_->origin_ << "/"
+                  << impl_->zone_class_);
     }
 
-    // Below we assume the map is not empty for simplicity.
-    NSEC3Map::const_iterator found =
-        impl_->zone_data_->nsec3_data_->map_.lower_bound(hname_text);
-    if (found != impl_->zone_data_->nsec3_data_->map_.end() &&
-        found->first == hname_text) {
-        // exact match
-        return (FindNSEC3Result(true, 2, found->second, ConstRRsetPtr()));
-    } else if (found == impl_->zone_data_->nsec3_data_->map_.end() ||
-               found == impl_->zone_data_->nsec3_data_->map_.begin()) {
-        // the search key is "smaller" than the smallest or "larger" than
-        // largest.  In either case "previous" is the largest one.
-        return (FindNSEC3Result(false, 2,
-                                impl_->zone_data_->nsec3_data_->map_.
-                                rbegin()->second, ConstRRsetPtr()));
-    } else {
-        // Otherwise, H(found_domain-1) < given_hash < H(found_domain)
-        // The covering proof is the first one.
-        return (FindNSEC3Result(false, 2, (--found)->second, ConstRRsetPtr()));
+    // Convenient shortcuts
+    const NSEC3Hash& nsec3hash = *impl_->zone_data_->nsec3_data_->hash_;
+    const unsigned int olabels = impl_->origin_.getLabelCount();
+    const unsigned int qlabels = name.getLabelCount();
+
+    ConstRRsetPtr covering_proof; // placeholder of the next closer proof
+    // Examine all names from the query name to the origin name, stripping
+    // the deepest label one by one, until we find a name that has a matching
+    // NSEC3 hash.
+    for (unsigned int labels = qlabels; labels >= olabels; --labels) {
+        const string hlabel = nsec3hash.calculate(
+            labels == qlabels ? name : name.split(qlabels - labels, labels));
+        NSEC3Map::const_iterator found = map.lower_bound(hlabel);
+        LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_MEM_FINDNSEC3_TRYHASH).
+            arg(name).arg(labels).arg(hlabel);
+
+        // If the given hash is larger than the largest stored hash or
+        // the first label doesn't match the target, identify the "previous"
+        // hash value and remember it as the candidate next closer proof.
+        if (found == map.end() || found->first != hlabel) {
+            // If the given hash is larger or smaller than everything,
+            // the covering proof is the NSEC3 that has the largest hash.
+            // Note that we know the map isn't empty, so rbegin() is
+            // safe.
+            if (found == map.end() || found == map.begin()) {
+                covering_proof = map.rbegin()->second;
+            } else {
+                // Otherwise, H(found_entry-1) < given_hash < H(found_entry).
+                // The covering proof is the first one (and it's valid
+                // because found is neither begin nor end)
+                covering_proof = (--found)->second;
+            }
+            if (!recursive) {   // in non recursive mode, we are done.
+                LOG_DEBUG(logger, DBG_TRACE_BASIC,
+                          DATASRC_MEM_FINDNSEC3_COVER).
+                    arg(name).arg(*covering_proof);
+                return (FindNSEC3Result(false, labels, covering_proof,
+                                        ConstRRsetPtr()));
+            }
+        } else {                // found an exact match.
+                LOG_DEBUG(logger, DBG_TRACE_BASIC,
+                          DATASRC_MEM_FINDNSEC3_MATCH).arg(name).arg(labels).
+                    arg(*found->second);
+            return (FindNSEC3Result(true, labels, found->second,
+                                    covering_proof));
+        }
     }
 
-    // We should have covered all cases.
-    isc_throw(Unexpected, "Impossible NSEC3 search result for " << name);
+    isc_throw(DataSourceError, "recursive findNSEC3 mode didn't stop, likely "
+              "a broken NSEC3 zone: " << impl_->origin_ << "/"
+              << impl_->zone_class_);
 }
 
 result::Result
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index 85fa5cf..b960ab9 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -89,16 +89,6 @@ public:
     virtual FindNSEC3Result
     findNSEC3(const isc::dns::Name& name, bool recursive);
 
-    // A temporary fake version of findNSEC3 for tests
-    //
-    // This method intentionally has the same interface as findNSEC3 but
-    // uses internally hardcoded hash values and offers a limited set
-    // of functionality for the convenience of tests.  This is a temporary
-    // workaround until #1577 is completed.  At that point this method
-    // should be removed.
-    FindNSEC3Result
-    findNSEC3Tmp(const isc::dns::Name& name, bool recursive);
-
     /// \brief Imelementation of the ZoneFinder::findPreviousName method
     ///
     /// This one throws NotImplemented exception, as InMemory doesn't
diff --git a/src/lib/datasrc/static_datasrc.cc b/src/lib/datasrc/static_datasrc.cc
index fd43e1c..03f56be 100644
--- a/src/lib/datasrc/static_datasrc.cc
+++ b/src/lib/datasrc/static_datasrc.cc
@@ -76,6 +76,7 @@ StaticDataSrcImpl::StaticDataSrcImpl() :
     authors->addRdata(generic::TXT("Han Feng"));
     authors->addRdata(generic::TXT("Jelte Jansen"));
     authors->addRdata(generic::TXT("Jeremy C. Reed")); 
+    authors->addRdata(generic::TXT("Xie Jiagui")); // Kevin Tes
     authors->addRdata(generic::TXT("Jin Jian"));
     authors->addRdata(generic::TXT("JINMEI Tatuya"));
     authors->addRdata(generic::TXT("Kazunori Fujiwara"));
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 69c7959..163d3d7 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -124,3 +124,4 @@ EXTRA_DIST += testdata/test.sqlite3
 EXTRA_DIST += testdata/test.sqlite3.nodiffs
 EXTRA_DIST += testdata/rwtest.sqlite3
 EXTRA_DIST += testdata/diffs.sqlite3
+EXTRA_DIST += testdata/rfc5155-example.zone.signed
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index f10adac..bd5070c 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -22,6 +22,7 @@
 
 #include <dns/masterload.h>
 #include <dns/name.h>
+#include <dns/nsec3hash.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
@@ -275,6 +276,83 @@ setRRset(RRsetPtr rrset, vector<RRsetPtr*>::iterator& it) {
     ++it;
 }
 
+ConstRRsetPtr
+textToRRset(const string& text_rrset, const RRClass& rrclass = RRClass::IN()) {
+    stringstream ss(text_rrset);
+    RRsetPtr rrset;
+    vector<RRsetPtr*> rrsets;
+    rrsets.push_back(&rrset);
+    masterLoad(ss, Name::ROOT_NAME(), rrclass,
+               boost::bind(setRRset, _1, rrsets.begin()));
+    return (rrset);
+}
+
+// Some faked NSEC3 hash values commonly used in tests and the faked NSEC3Hash
+// object.
+//
+// For apex (example.org)
+const char* const apex_hash = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+const char* const apex_hash_lower = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
+// For ns1.example.org
+const char* const ns1_hash = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR";
+// For w.example.org
+const char* const w_hash = "01UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
+// For x.y.w.example.org (lower-cased)
+const char* const xyw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
+// For zzz.example.org.
+const char* const zzz_hash = "R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN";
+
+// A simple faked NSEC3 hash calculator with a dedicated creator for it.
+//
+// This is used in some NSEC3-related tests below.
+class TestNSEC3HashCreator : public NSEC3HashCreator {
+    class TestNSEC3Hash : public NSEC3Hash {
+    private:
+        typedef map<Name, string> NSEC3HashMap;
+        typedef NSEC3HashMap::value_type NSEC3HashPair;
+        NSEC3HashMap map_;
+    public:
+        TestNSEC3Hash() {
+            // Build pre-defined hash
+            map_[Name("example.org")] = apex_hash;
+            map_[Name("www.example.org")] = "2S9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+            map_[Name("xxx.example.org")] = "Q09MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+            map_[Name("yyy.example.org")] = "0A9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
+            map_[Name("x.y.w.example.org")] =
+                "2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S";
+            map_[Name("y.w.example.org")] = "K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H";
+            map_[Name("w.example.org")] = w_hash;
+            map_[Name("zzz.example.org")] = zzz_hash;
+            map_[Name("smallest.example.org")] =
+                "00000000000000000000000000000000";
+            map_[Name("largest.example.org")] =
+                "UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU";
+        }
+        virtual string calculate(const Name& name) const {
+            const NSEC3HashMap::const_iterator found = map_.find(name);
+            if (found != map_.end()) {
+                return (found->second);
+            }
+            isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+                      << name);
+        }
+        virtual bool match(const generic::NSEC3PARAM&) const {
+            return (true);
+        }
+        virtual bool match(const generic::NSEC3&) const {
+            return (true);
+        }
+    };
+
+public:
+    virtual NSEC3Hash* create(const generic::NSEC3PARAM&) const {
+        return (new TestNSEC3Hash);
+    }
+    virtual NSEC3Hash* create(const generic::NSEC3&) const {
+        return (new TestNSEC3Hash);
+    }
+};
+
 /// \brief Test fixture for the InMemoryZoneFinder class
 class InMemoryZoneFinderTest : public ::testing::Test {
     // A straightforward pair of textual RR(set) and a RRsetPtr variable
@@ -368,6 +446,12 @@ public:
         masterLoad(zone_data_stream, Name::ROOT_NAME(), class_,
                    boost::bind(setRRset, _1, rrsets.begin()));
     }
+
+    ~InMemoryZoneFinderTest() {
+        // Make sure we reset the hash creator to the default
+        setNSEC3HashCreator(NULL);
+    }
+
     // Some data to test with
     const RRClass class_;
     const Name origin_;
@@ -414,6 +498,12 @@ public:
     RRsetPtr rr_not_wild_another_;
     RRsetPtr rr_nsec3_;
 
+    // A faked NSEC3 hash calculator for convenience.
+    // Tests that need to use the faked hashed values should call
+    // setNSEC3HashCreator() with a pointer to this variable at the beginning
+    // of the test (at least before adding any NSEC3/NSEC3PARAM RR).
+    TestNSEC3HashCreator nsec3_hash_creator_;
+
     /**
      * \brief Test one find query to the zone finder.
      *
@@ -522,18 +612,6 @@ public:
         rrsetsCheck(expected_rrsets.begin(), expected_rrsets.end(),
                     target.begin(), target.end());
     }
-
-    ConstRRsetPtr textToRRset(const string& text_rrset,
-                              const RRClass& rrclass = RRClass::IN()) const
-    {
-        stringstream ss(text_rrset);
-        RRsetPtr rrset;
-        vector<RRsetPtr*> rrsets;
-        rrsets.push_back(&rrset);
-        masterLoad(ss, Name::ROOT_NAME(), rrclass,
-                   boost::bind(setRRset, _1, rrsets.begin()));
-        return (rrset);
-    }
 };
 
 /**
@@ -593,6 +671,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_));
@@ -731,11 +840,13 @@ TEST_F(InMemoryZoneFinderTest, delegationWithDS) {
     EXPECT_EQ(SUCCESS, zone_finder_.add(rr_child_ds_));
 
     // Normal types of query should result in delegation, but DS query
-    // should be considered in-zone.
+    // should be considered in-zone (but only exactly at the delegation point).
     findTest(Name("child.example.org"), RRType::A(), ZoneFinder::DELEGATION,
              true, rr_child_ns_);
     findTest(Name("child.example.org"), RRType::DS(), ZoneFinder::SUCCESS,
              true, rr_child_ds_);
+    findTest(Name("grand.child.example.org"), RRType::DS(),
+             ZoneFinder::DELEGATION, true, rr_child_ns_);
 
     // There's nothing special for DS query at the zone apex.  It would
     // normally result in NXRRSET.
@@ -862,7 +973,7 @@ TEST_F(InMemoryZoneFinderTest, find) {
     findCheck();
 }
 
-TEST_F(InMemoryZoneFinderTest, findNSEC3) {
+TEST_F(InMemoryZoneFinderTest, findNSEC3Signed) {
     findCheck(ZoneFinder::RESULT_NSEC3_SIGNED);
 }
 
@@ -1485,40 +1596,45 @@ const char* const nsec3_common = " 300 IN NSEC3 1 1 12 aabbccdd "
 const char* const nsec3_rrsig_common = " 300 IN RRSIG NSEC3 5 3 3600 "
     "20000101000000 20000201000000 12345 example.org. FAKEFAKEFAKE";
 
-// For apex (example.org)
-const char* const apex_hash = "0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM";
-const char* const apex_hash_lower = "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom";
-// For ns1.example.org
-const char* const ns1_hash = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR";
-// For x.y.w.example.org (lower-cased)
-const char* const xrw_hash = "2vptu5timamqttgl4luu9kg21e0aor3s";
-
 void
-nsec3Check(bool expected_matched, const string& expected_rrsets_txt,
-           const ZoneFinder::FindNSEC3Result& result,
-           bool expected_sig = false)
+findNSEC3Check(bool expected_matched, uint8_t expected_labels,
+               const string& expected_closest,
+               const string& expected_next,
+               const ZoneFinder::FindNSEC3Result& result,
+               bool expected_sig = false)
 {
-    vector<ConstRRsetPtr> actual_rrsets;
     EXPECT_EQ(expected_matched, result.matched);
+    // Convert to int so the error messages would be more readable:
+    EXPECT_EQ(static_cast<int>(expected_labels),
+              static_cast<int>(result.closest_labels));
+
+    vector<ConstRRsetPtr> actual_rrsets;
     ASSERT_TRUE(result.closest_proof);
-    if (expected_sig) {
-        ASSERT_TRUE(result.closest_proof->getRRsig());
-    }
     actual_rrsets.push_back(result.closest_proof);
     if (expected_sig) {
         actual_rrsets.push_back(result.closest_proof->getRRsig());
     }
-    rrsetsCheck(expected_rrsets_txt, actual_rrsets.begin(),
+    rrsetsCheck(expected_closest, actual_rrsets.begin(),
                 actual_rrsets.end());
-}
 
-// In the following tests we use a temporary faked version of findNSEC3
-// as the real version isn't implemented yet (it's a task for #1577).
-// When #1577 is done the tests should be updated using the real version.
-// If we can use fake hash calculator (see #1575), we should be able to
-// just replace findNSEC3Tmp with findNSEC3.
+    actual_rrsets.clear();
+    if (expected_next.empty()) {
+        EXPECT_FALSE(result.next_proof);
+    } else {
+        ASSERT_TRUE(result.next_proof);
+        actual_rrsets.push_back(result.next_proof);
+        if (expected_sig) {
+            actual_rrsets.push_back(result.next_proof->getRRsig());
+        }
+        rrsetsCheck(expected_next, actual_rrsets.begin(),
+                    actual_rrsets.end());
+    }
+}
 
 TEST_F(InMemoryZoneFinderTest, addNSEC3) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
     const string nsec3_text = string(apex_hash) + ".example.org." +
         string(nsec3_common);
     // This name shouldn't be found in the normal domain tree.
@@ -1527,8 +1643,8 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3) {
               zone_finder_.find(Name(string(apex_hash) + ".example.org"),
                                 RRType::NSEC3()).code);
     // Dedicated NSEC3 find should be able to find it.
-    nsec3Check(true, nsec3_text,
-               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+    findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+                   zone_finder_.findNSEC3(Name("example.org"), false));
 
     // This implementation rejects duplicate/update add of the same hash name
     EXPECT_EQ(result::EXIST,
@@ -1536,8 +1652,8 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3) {
                                    string(apex_hash) + ".example.org." +
                                    string(nsec3_common) + " AAAA")));
     // The original NSEC3 should be intact
-    nsec3Check(true, nsec3_text,
-               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+    findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+                   zone_finder_.findNSEC3(Name("example.org"), false));
 
     // NSEC3-like name but of ordinary RR type should go to normal tree.
     const string nonsec3_text = string(apex_hash) + ".example.org. " +
@@ -1549,15 +1665,21 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3) {
 }
 
 TEST_F(InMemoryZoneFinderTest, addNSEC3Lower) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
     // Similar to the previous case, but NSEC3 owner name is lower-cased.
     const string nsec3_text = string(apex_hash_lower) + ".example.org." +
         string(nsec3_common);
     EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_text)));
-    nsec3Check(true, nsec3_text,
-               zone_finder_.findNSEC3Tmp(Name("example.org"), false));
+    findNSEC3Check(true, origin_.getLabelCount(), nsec3_text, "",
+                   zone_finder_.findNSEC3(Name("example.org"), false));
 }
 
 TEST_F(InMemoryZoneFinderTest, addNSEC3Ordering) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
     // Check that the internal storage ensures comparison based on the NSEC3
     // semantics, regardless of the add order or the letter-case of hash.
 
@@ -1566,7 +1688,7 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3Ordering) {
         string(nsec3_common);
     const string middle = string(ns1_hash) + ".example.org." +
         string(nsec3_common);
-    const string largest = string(xrw_hash) + ".example.org." +
+    const string largest = string(xyw_hash) + ".example.org." +
         string(nsec3_common);
     zone_finder_.add(textToRRset(smallest));
     zone_finder_.add(textToRRset(largest));
@@ -1574,15 +1696,15 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3Ordering) {
 
     // Then look for NSEC3 that covers a name whose hash is "2S.."
     // The covering NSEC3 should be "0P.."
-    nsec3Check(false, smallest,
-               zone_finder_.findNSEC3Tmp(Name("www.example.org"), false));
+    findNSEC3Check(false, 4, smallest, "",
+                   zone_finder_.findNSEC3(Name("www.example.org"), false));
 
     // Look for NSEC3 that covers names whose hash are "Q0.." and "0A.."
     // The covering NSEC3 should be "2v.." in both cases
-    nsec3Check(false, largest,
-               zone_finder_.findNSEC3Tmp(Name("xxx.example.org"), false));
-    nsec3Check(false, largest,
-               zone_finder_.findNSEC3Tmp(Name("yyy.example.org"), false));
+    findNSEC3Check(false, 4, largest, "",
+                   zone_finder_.findNSEC3(Name("xxx.example.org"), false));
+    findNSEC3Check(false, 4, largest, "",
+                   zone_finder_.findNSEC3(Name("yyy.example.org"), false));
 }
 
 TEST_F(InMemoryZoneFinderTest, badNSEC3Name) {
@@ -1610,6 +1732,9 @@ TEST_F(InMemoryZoneFinderTest, addMultiNSEC3) {
 }
 
 TEST_F(InMemoryZoneFinderTest, addNSEC3WithRRSIG) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
     // Adding NSEC3 and its RRSIG
     const string nsec3_text = string(apex_hash) + ".example.org." +
         string(nsec3_common);
@@ -1619,8 +1744,9 @@ TEST_F(InMemoryZoneFinderTest, addNSEC3WithRRSIG) {
     EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(nsec3_rrsig_text)));
 
     // Then look for it.  The NSEC3 should have the RRSIG that was just added.
-    nsec3Check(true, nsec3_text + "\n" + nsec3_rrsig_text,
-               zone_finder_.findNSEC3Tmp(Name("example.org"), false), true);
+    findNSEC3Check(true, origin_.getLabelCount(),
+                   nsec3_text + "\n" + nsec3_rrsig_text, "",
+                   zone_finder_.findNSEC3(Name("example.org"), false), true);
 
     // Duplicate add of RRSIG for the same NSEC3 is prohibited.
     EXPECT_THROW(zone_finder_.add(textToRRset(nsec3_rrsig_text)),
@@ -1718,4 +1844,215 @@ TEST_F(InMemoryZoneFinderTest, loadNSEC3Zone) {
     // This is an abnormal case, but the implementation accepts it.
     zone_finder_.load(TEST_DATA_DIR "/example.org.nsec3-signed-noparam");
 }
+
+// This test checks that the NSEC3 names don't really exist in the real
+// namespace.
+TEST_F(InMemoryZoneFinderTest, queryToNSEC3Name) {
+    // Add the NSEC3 and NSEC3PARAM there.
+    EXPECT_EQ(result::SUCCESS,
+              zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+                                           "1 0 12 aabbccdd")));
+    const Name nsec3domain(string(apex_hash) + ".example.org.");
+    // 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))));
+    // Now, the domain should not exist
+    findTest(nsec3domain, RRType::AAAA(), ZoneFinder::NXDOMAIN, false,
+             ConstRRsetPtr(), ZoneFinder::RESULT_NSEC3_SIGNED, &zone_finder_,
+             ZoneFinder::FIND_DNSSEC);
+    // If we add an A record, the domain should exist
+    ConstRRsetPtr rrset(textToRRset(string(apex_hash) +
+                                    ".example.org. 300 IN A 192.0.2.1"));
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(rrset));
+    // Searching for a different RRType will tell us this RRset doesn't exist
+    findTest(nsec3domain, RRType::AAAA(), ZoneFinder::NXRRSET, false,
+             ConstRRsetPtr(), ZoneFinder::RESULT_NSEC3_SIGNED, &zone_finder_,
+             ZoneFinder::FIND_DNSSEC);
+    // Searching for the A record would find it
+    findTest(nsec3domain, RRType::A(), ZoneFinder::SUCCESS, true,
+             rrset, ZoneFinder::RESULT_DEFAULT, &zone_finder_,
+             ZoneFinder::FIND_DNSSEC);
+}
+
+// Continuation of the previous test (queryToNSEC3Name), we check we don't break
+// the empty nonterminal case by existence of NSEC3 record with that name.
+TEST_F(InMemoryZoneFinderTest, queryToNSEC3NameNonterminal) {
+    // Add the NSEC3 and NSEC3PARAM there.
+    EXPECT_EQ(result::SUCCESS,
+              zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+                                           "1 0 12 aabbccdd")));
+    const Name nsec3domain(string(apex_hash) + ".example.org.");
+    // 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))));
+    // Something below the name
+    ConstRRsetPtr rrset(textToRRset("below." + string(apex_hash) +
+                                    ".example.org. 300 IN A 192.0.2.1"));
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(rrset));
+    // Now, the node is empty non-terminal.
+    findTest(nsec3domain, RRType::AAAA(), ZoneFinder::NXRRSET, false,
+             ConstRRsetPtr(), ZoneFinder::RESULT_NSEC3_SIGNED, &zone_finder_,
+             ZoneFinder::FIND_DNSSEC);
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSEC3) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
+    // Add a few NSEC3 records:
+    // apex (example.org.): hash=0P..
+    // ns1.example.org:     hash=2T..
+    // w.example.org:       hash=01..
+    // zzz.example.org:     hash=R5..
+    const string apex_nsec3_text = string(apex_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(apex_nsec3_text)));
+    const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(ns1_nsec3_text)));
+    const string w_nsec3_text = string(w_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(w_nsec3_text)));
+    const string zzz_nsec3_text = string(zzz_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(zzz_nsec3_text)));
+
+    // Parameter validation: the query name must be in or below the zone
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("example.com"), false),
+                 isc::InvalidParameter);
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("org"), true),
+                 isc::InvalidParameter);
+
+    // Apex name.  It should have a matching NSEC3.
+    {
+        SCOPED_TRACE("apex, non recursive mode");
+        findNSEC3Check(true, origin_.getLabelCount(), apex_nsec3_text, "",
+                       zone_finder_.findNSEC3(origin_, false));
+    }
+
+    // Recursive mode doesn't change the result in this case.
+    {
+        SCOPED_TRACE("apex, recursive mode");
+        findNSEC3Check(true, origin_.getLabelCount(), apex_nsec3_text, "",
+                       zone_finder_.findNSEC3(origin_, true));
+    }
+
+    // Non existent name.  Disabling recursion, a covering NSEC3 should be
+    // returned.
+    const Name www_name("www.example.org");
+    {
+        SCOPED_TRACE("non existent name, non recursive mode");
+        findNSEC3Check(false, www_name.getLabelCount(), apex_nsec3_text, "",
+                       zone_finder_.findNSEC3(www_name, false));
+    }
+
+    // Non existent name.  The closest provable encloser is the apex,
+    // and next closer is the query name itself (which NSEC3 for ns1
+    // covers)
+    // H(ns1) = 2T... < H(xxx) = Q0... < H(zzz) = R5...
+    {
+        SCOPED_TRACE("non existent name, recursive mode");
+        findNSEC3Check(true, origin_.getLabelCount(), apex_nsec3_text,
+                       ns1_nsec3_text,
+                       zone_finder_.findNSEC3(Name("xxx.example.org"), true));
+    }
+
+    // Similar to the previous case, but next closer name is different
+    // from the query name.  The closet encloser is w.example.org, and
+    // next closer is y.w.example.org.
+    // H(ns1) = 2T.. < H(y.w) = K8.. < H(zzz) = R5
+    {
+        SCOPED_TRACE("non existent name, non qname next closer");
+        findNSEC3Check(true, Name("w.example.org").getLabelCount(),
+                       w_nsec3_text, ns1_nsec3_text,
+                       zone_finder_.findNSEC3(Name("x.y.w.example.org"),
+                                              true));
+    }
+
+    // In the rest of test we check hash comparison for wrap around cases.
+    {
+        SCOPED_TRACE("very small hash");
+        const Name smallest_name("smallest.example.org");
+        findNSEC3Check(false, smallest_name.getLabelCount(),
+                       zzz_nsec3_text, "",
+                       zone_finder_.findNSEC3(smallest_name, false));
+    }
+    {
+        SCOPED_TRACE("very large hash");
+        const Name largest_name("largest.example.org");
+        findNSEC3Check(false, largest_name.getLabelCount(),
+                       zzz_nsec3_text, "",
+                       zone_finder_.findNSEC3(largest_name, false));
+    }
+}
+
+TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {
+    // Set up the faked hash calculator.
+    setNSEC3HashCreator(&nsec3_hash_creator_);
+
+    // If the zone has nothing about NSEC3 (neither NSEC3 or NSEC3PARAM),
+    // findNSEC3() should be rejected.
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+                 DataSourceError);
+
+    // Only having NSEC3PARAM isn't enough
+    EXPECT_EQ(result::SUCCESS,
+              zone_finder_.add(textToRRset("example.org. 300 IN NSEC3PARAM "
+                                           "1 0 12 aabbccdd")));
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+                 DataSourceError);
+
+    // Unless NSEC3 for apex is added the result in the recursive mode
+    // is guaranteed.
+    const string ns1_nsec3_text = string(ns1_hash) + ".example.org." +
+        string(nsec3_common);
+    EXPECT_EQ(result::SUCCESS, zone_finder_.add(textToRRset(ns1_nsec3_text)));
+    EXPECT_THROW(zone_finder_.findNSEC3(Name("www.example.org"), true),
+                 DataSourceError);
+}
+
+TEST_F(InMemoryZoneFinderTest, loadAndFindNSEC3) {
+    // Using more realistic example, borrowed from RFC5155, with compliant
+    // hash calculator.  We only confirm the data source can load it
+    // successfully and find correct NSEC3 RRs for some selected cases
+    // (detailed tests have been done above).
+
+    InMemoryZoneFinder finder(class_, Name("example"));
+    finder.load(TEST_DATA_DIR "/rfc5155-example.zone.signed");
+
+    // See RFC5155 B.1
+    ZoneFinder::FindNSEC3Result result1 =
+        finder.findNSEC3(Name("c.x.w.example"), true);
+    ASSERT_TRUE(result1.closest_proof);
+    // We compare closest_labels as int so the error report will be more
+    // readable in case it fails.
+    EXPECT_EQ(4, static_cast<int>(result1.closest_labels));
+    EXPECT_EQ(Name("b4um86eghhds6nea196smvmlo4ors995.example"),
+              result1.closest_proof->getName());
+    ASSERT_TRUE(result1.next_proof);
+    EXPECT_EQ(Name("0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example"),
+              result1.next_proof->getName());
+
+    // See RFC5155 B.2.
+    ZoneFinder::FindNSEC3Result result2 =
+        finder.findNSEC3(Name("ns1.example"), true);
+    ASSERT_TRUE(result2.closest_proof);
+    EXPECT_EQ(3, static_cast<int>(result2.closest_labels));
+    EXPECT_EQ(Name("2t7b4g4vsa5smi47k61mv5bv1a22bojr.example"),
+              result2.closest_proof->getName());
+    ASSERT_FALSE(result2.next_proof);
+
+    // See RFC5155 B.5.
+    ZoneFinder::FindNSEC3Result result3 =
+        finder.findNSEC3(Name("a.z.w.example"), true);
+    ASSERT_TRUE(result3.closest_proof);
+    EXPECT_EQ(3, static_cast<int>(result3.closest_labels));
+    EXPECT_EQ(Name("k8udemvp1j2f7eg6jebps17vp3n8i58h.example"),
+              result3.closest_proof->getName());
+    ASSERT_TRUE(result3.next_proof);
+    EXPECT_EQ(Name("q04jkcevqvmu85r014c7dkba38o0ji5r.example"),
+              result3.next_proof->getName());
+}
 }
diff --git a/src/lib/datasrc/tests/static_unittest.cc b/src/lib/datasrc/tests/static_unittest.cc
index 4c9fe42..08f2582 100644
--- a/src/lib/datasrc/tests/static_unittest.cc
+++ b/src/lib/datasrc/tests/static_unittest.cc
@@ -58,7 +58,8 @@ protected:
         authors_data.push_back("Haidong Wang");
         authors_data.push_back("Han Feng");
         authors_data.push_back("Jelte Jansen");
-        authors_data.push_back("Jeremy C. Reed"); 
+        authors_data.push_back("Jeremy C. Reed");
+        authors_data.push_back("Xie Jiagui");
         authors_data.push_back("Jin Jian");
         authors_data.push_back("JINMEI Tatuya");
         authors_data.push_back("Kazunori Fujiwara");
diff --git a/src/lib/datasrc/tests/testdata/rfc5155-example.zone.signed b/src/lib/datasrc/tests/testdata/rfc5155-example.zone.signed
new file mode 100644
index 0000000..4120224
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/rfc5155-example.zone.signed
@@ -0,0 +1,72 @@
+;; The example NSEC3-signed zone used in RFC5155.
+
+example.				      3600 IN SOA	ns1.example. bugs.x.w.example. 1 3600 300 3600000 3600
+example.				      3600 IN RRSIG	SOA 7 1 3600 20150420235959 20051021000000 40430 example. Hu25UIyNPmvPIVBrldN+9Mlp9Zql39qaUd8iq4ZLlYWfUUbbAS41pG+6 8z81q1xhkYAcEyHdVI2LmKusbZsT0Q==
+example.				      3600 IN NS	ns1.example.
+example.				      3600 IN NS	ns2.example.
+example.				      3600 IN RRSIG	NS 7 1 3600 20150420235959 20051021000000 40430 example. PVOgtMK1HHeSTau+HwDWC8Ts+6C8qtqd4pQJqOtdEVgg+MA+ai4fWDEh u3qHJyLcQ9tbD2vvCnMXjtz6SyObxA==
+example.				      3600 IN MX	1 xx.example.
+example.				      3600 IN RRSIG	MX 7 1 3600 20150420235959 20051021000000 40430 example. GgQ1A9xs47k42VPvpL/a1BWUz/6XsnHkjotw9So8MQtZtl2wJBsnOQsa oHrRCrRbyriEl/GZn9Mto/Kx+wBo+w==
+example.				      3600 IN DNSKEY	256 3 7 AwEAAaetidLzsKWUt4swWR8yu0wPHPiUi8LUsAD0QPWU+wzt89epO6tH zkMBVDkC7qphQO2hTY4hHn9npWFRw5BYubE=
+example.				      3600 IN DNSKEY	257 3 7 AwEAAcUlFV1vhmqx6NSOUOq2R/dsR7Xm3upJj7IommWSpJABVfW8Q0rO vXdM6kzt+TAu92L9AbsUdblMFin8CVF3n4s=
+example.				      3600 IN RRSIG	DNSKEY 7 1 3600 20150420235959 20051021000000 12708 example. AuU4juU9RaxescSmStrQks3Gh9FblGBlVU31uzMZ/U/FpsUb8aC6QZS+ sTsJXnLnz7flGOsmMGQZf3bH+QsCtg==
+example.				      3600 IN NSEC3PARAM 1 0 12 AABBCCDD
+example.				      3600 IN RRSIG	NSEC3PARAM 7 1 3600 20150420235959 20051021000000 40430 example. C1Gl8tPZNtnjlrYWDeeUV/sGLCyy/IHie2rerN05XSA3Pq0U3+4VvGWY WdUMfflOdxqnXHwJTLQsjlkynhG6Cg==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.     3600 IN A		192.0.2.127
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.     3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. h6c++bzhRuWWt2bykN6mjaTNBcXNq5UuL5EdK+iDP4eY8I0kSiKaCjg3 tC1SQkeloMeub2GWk8p6xHMPZumXlw==
+a.example.				      3600 IN NS	ns1.a.example.
+a.example.				      3600 IN NS	ns2.a.example.
+a.example.				      3600 IN DS	58470 5 1 3079F1593EBAD6DC121E202A8B766A6A4837206C
+a.example.				      3600 IN RRSIG	DS 7 2 3600 20150420235959 20051021000000 40430 example. XacFcQVHLVzdoc45EJhN616zQ4mEXtE8FzUhM2KWjfy1VfRKD9r1MeVG wwoukOKgJxBPFsWoo722vZ4UZ2dIdA==
+ns1.a.example.				      3600 IN A		192.0.2.5
+ns2.a.example.				      3600 IN A		192.0.2.6
+ai.example.				      3600 IN A		192.0.2.9
+ai.example.				      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. hVe+wKYMlObTRPhX0NL67GxeZfdxqr/QeR6FtfdAj5+FgYxyzPEjIzvK Wy00hWIl6wD3Vws+rznEn8sQ64UdqA==
+ai.example.				      3600 IN HINFO	"KLH-10" "ITS"
+ai.example.				      3600 IN RRSIG	HINFO 7 2 3600 20150420235959 20051021000000 40430 example. Yi42uOq43eyO6qXHNvwwfFnIustWgV5urFcxenkLvs6pKRh00VBjODmf 3Z4nMO7IOl6nHSQ1v0wLHpEZG7Xj2w==
+ai.example.				      3600 IN AAAA	2001:db8::f00:baa9
+ai.example.				      3600 IN RRSIG	AAAA 7 2 3600 20150420235959 20051021000000 40430 example. LcdxKaCB5bGZwPDg+3JJ4O02zoMBrjxqlf6WuaHQZZfTUpb9Nf2nxFGe 2XRPfR5tpJT6GdRGcHueLuXkMjBArQ==
+c.example.				      3600 IN NS	ns1.c.example.
+c.example.				      3600 IN NS	ns2.c.example.
+ns1.c.example.				      3600 IN A		192.0.2.7
+ns2.c.example.				      3600 IN A		192.0.2.8
+ns1.example.				      3600 IN A		192.0.2.1
+ns1.example.				      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. bu6kx73n6XEunoVGuRfAgY7EF/AJqHy7hj0jkiqJjB0dOrx3wuz9SaBe GfqWIdn/uta3SavN4FRvZR9SCFHF5Q==
+ns2.example.				      3600 IN A		192.0.2.2
+ns2.example.				      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. ktQ3TqE0CfRfki0Rb/Ip5BM0VnxelbuejCC4zpLbFKA/7eD7UNAwxMgx JPtbdST+syjYSJaj4IHfeX6n8vfoGA==
+*.w.example.				      3600 IN MX	1 ai.example.
+*.w.example.				      3600 IN RRSIG	MX 7 2 3600 20150420235959 20051021000000 40430 example. CikebjQwGQPwijVcxgcZcSJKtfynugtlBiKb9FcBTrmOoyQ4InoWVudh CWsh/URX3lc4WRUMivEBP6+4KS3ldA==
+x.w.example.				      3600 IN MX	1 xx.example.
+x.w.example.				      3600 IN RRSIG	MX 7 3 3600 20150420235959 20051021000000 40430 example. IrK3tq/tHFIBF0scHiE/1IwMAvckS/55hAVvQyxTFbkAdDloP3NbZzu+ yoSsr3b3OX6qbBpY7WCtwwekLKRAwQ==
+x.y.w.example.				      3600 IN MX	1 xx.example.
+x.y.w.example.				      3600 IN RRSIG	MX 7 4 3600 20150420235959 20051021000000 40430 example. MqSt5HqJIN8+SLlzTOImrh5h9Xa6gDvAW/GnnbdPc6Z7nXvCpLPJj/5l Cwx3VuzVOjkbvXze8/8Ccl2Zn2hbug==
+xx.example.				      3600 IN A		192.0.2.10
+xx.example.				      3600 IN RRSIG	A 7 2 3600 20150420235959 20051021000000 40430 example. T35hBWEZ017VC5u2c4OriKyVn/pu+fVK4AlXYOxJ6iQylfV2HQIKjv6b 7DzINB3aF/wjJqgXpQvhq+Ac6+ZiFg==
+xx.example.				      3600 IN HINFO	"KLH-10" "TOPS-20"
+xx.example.				      3600 IN RRSIG	HINFO 7 2 3600 20150420235959 20051021000000 40430 example. KimG+rDd+7VA1zRsu0ITNAQUTRlpnsmqWrihFRnU+bRa93v2e5oFNFYC s3Rqgv62K93N7AhW6Jfqj/8NzWjvKg==
+xx.example.				      3600 IN AAAA	2001:db8::f00:baaa
+xx.example.				      3600 IN RRSIG	AAAA 7 2 3600 20150420235959 20051021000000 40430 example. IXBcXORITNwd8h3gNwyxtYFvAupS/CYWufVeuBUX0O25ivBCULjZjpDx FSxfohb/KA7YRdxENzYfMItpILl/Xw==
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.     3600 IN NSEC3	1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR NS SOA MX RRSIG DNSKEY NSEC3PARAM
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OSgWSm26B+cS+dDL8b5QrWr/dEWhtCsKlwKLIBHYH6blRxK9rC0bMJPw Q4mLIuw85H2EY762BOCXJZMnpuwhpA==
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.     3600 IN NSEC3	1 1 12 AABBCCDD 2VPTU5TIMAMQTTGL4LUU9KG21E0AOR3S A RRSIG
+2t7b4g4vsa5smi47k61mv5bv1a22bojr.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. OmBvJ1Vgg1hCKMXHFiNeIYHK9XVW0iLDLwJN4TFoNxZuP03gAXEI634Y wOc4YBNITrj413iqNI6mRk/r1dOSUw==
+2vptu5timamqttgl4luu9kg21e0aor3s.example.     3600 IN NSEC3	1 1 12 AABBCCDD 35MTHGPGCU1QG68FAB165KLNSNK3DPVL MX RRSIG
+2vptu5timamqttgl4luu9kg21e0aor3s.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. KL1V2oFYghNV0Hm7Tf2vpJjM6l+0g1JCcVYGVfI0lKrhPmTsOA96cLEA Cgo1x8I7kApJX+obTuktZ+sdsZPY1w==
+35mthgpgcu1qg68fab165klnsnk3dpvl.example.     3600 IN NSEC3	1 1 12 AABBCCDD B4UM86EGHHDS6NEA196SMVMLO4ORS995 NS DS RRSIG
+35mthgpgcu1qg68fab165klnsnk3dpvl.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. g6jPUUpduAJKRljUsN8gB4UagAX0NxY9shwQAynzo8EUWH+z6hEIBlUT PGj15eZll6VhQqgZXtAIR3chwgW+SA==
+b4um86eghhds6nea196smvmlo4ors995.example.     3600 IN NSEC3	1 1 12 AABBCCDD GJEQE526PLBF1G8MKLP59ENFD789NJGI MX RRSIG
+b4um86eghhds6nea196smvmlo4ors995.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. ZkPG3M32lmoHM6pa3D6gZFGB/rhL//Bs3Omh5u4m/CUiwtblEVOaAKKZ d7S959OeiX43aLX3pOv0TSTyiTxIZg==
+gjeqe526plbf1g8mklp59enfd789njgi.example.     3600 IN NSEC3	1 1 12 AABBCCDD JI6NEOAEPV8B5O6K4EV33ABHA8HT9FGC A HINFO AAAA RRSIG
+gjeqe526plbf1g8mklp59enfd789njgi.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. IVnezTJ9iqblFF97vPSmfXZ5Zozngx3KX3byLTZC4QBH2dFWhf6scrGF ZB980AfCxoD9qbbKDy+rdGIeRSVNyw==
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.     3600 IN NSEC3	1 1 12 AABBCCDD K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H
+ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. gPkFp1s2QDQ6wQzcg1uSebZ61W33rUBDcTj72F3kQ490fEdp7k1BUIfb cZtPbX3YCpE+sIt0MpzVSKfTwx4uYA==
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example.     3600 IN NSEC3	1 1 12 AABBCCDD KOHAR7MBB8DC2CE8A9QVL8HON4K53UHI
+k8udemvp1j2f7eg6jebps17vp3n8i58h.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. FtXGbvF0+wf8iWkyo73enAuVx03klN+pILBKS6qCcftVtfH4yVzsEZqu J27NHR7ruxJWDNMtOtx7w9WfcIg62A==
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example.     3600 IN NSEC3	1 1 12 AABBCCDD Q04JKCEVQVMU85R014C7DKBA38O0JI5R A RRSIG
+kohar7mbb8dc2ce8a9qvl8hon4k53uhi.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. VrDXs2uVW21N08SyQIz88zml+y4ZCInTwgDr6zz43yAg+LFERjOrj3Oj ct51ac7Dp4eZbf9FQJazmASFKGxGXg==
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.     3600 IN NSEC3	1 1 12 AABBCCDD R53BQ7CC2UVMUBFU5OCMM6PERS9TK9EN A RRSIG
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. hV5I89b+4FHJDATp09g4bbN0R1F845CaXpL3ZxlMKimoPAyqletMlEWw LfFia7sdpSzn+ZlNNlkxWcLsIlMmUg==
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example.     3600 IN NSEC3	1 1 12 AABBCCDD T644EBQK9BIBCNA874GIVR6JOJ62MLHV MX RRSIG
+r53bq7cc2uvmubfu5ocmm6pers9tk9en.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. aupviViruXs4bDg9rCbezzBMf9h1ZlDvbW/CZFKulIGXXLj8B/fsDJar XVDA9bnUoRhEbKp+HF1FWKW7RIJdtQ==
+t644ebqk9bibcna874givr6joj62mlhv.example.     3600 IN NSEC3	1 1 12 AABBCCDD 0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM A HINFO AAAA RRSIG
+t644ebqk9bibcna874givr6joj62mlhv.example.     3600 IN RRSIG	NSEC3 7 2 3600 20150420235959 20051021000000 40430 example. RAjGECB8P7O+F4Pa4Dx3tC0M+Z3KmlLKImcafb9XWwx+NWUNz7NBEDBQ HivIyKPVDkChcePIX1xPl1ATNa+8Dw==
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index 93d3ca9..ff88746 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -667,7 +667,7 @@ public:
     /// \exception std::bad_alloc Resource allocation failure
     ///
     /// \param rrset The RRset to be added
-    virtual void addRRset(const isc::dns::RRset& rrset) = 0;
+    virtual void addRRset(const isc::dns::AbstractRRset& rrset) = 0;
 
     /// Delete an RRset from a zone via the updater
     ///
@@ -739,7 +739,7 @@ public:
     /// \exception std::bad_alloc Resource allocation failure
     ///
     /// \param rrset The RRset to be deleted
-    virtual void deleteRRset(const isc::dns::RRset& rrset) = 0;
+    virtual void deleteRRset(const isc::dns::AbstractRRset& rrset) = 0;
 
     /// Commit the updates made in the updater to the zone
     ///
diff --git a/src/lib/dns/nsec3hash.cc b/src/lib/dns/nsec3hash.cc
index 8755959..159dff3 100644
--- a/src/lib/dns/nsec3hash.cc
+++ b/src/lib/dns/nsec3hash.cc
@@ -142,6 +142,23 @@ NSEC3HashRFC5155::match(const generic::NSEC3PARAM& nsec3param) const {
     return (match(nsec3param.getHashalg(), nsec3param.getIterations(),
                   nsec3param.getSalt()));
 }
+
+// A static pointer that refers to the currently usable creator.
+// Only get/setNSEC3HashCreator are expected to get access to this variable
+// directly.
+const NSEC3HashCreator* creator;
+
+// The accessor to the current creator.  If it's not explicitly set or has
+// been reset from a customized one, the default creator will be used.
+const NSEC3HashCreator*
+getNSEC3HashCreator() {
+    static DefaultNSEC3HashCreator default_creator;
+    if (creator == NULL) {
+        creator = &default_creator;
+    }
+    return (creator);
+}
+
 } // end of unnamed namespace
 
 namespace isc {
@@ -149,15 +166,30 @@ namespace dns {
 
 NSEC3Hash*
 NSEC3Hash::create(const generic::NSEC3PARAM& param) {
+    return (getNSEC3HashCreator()->create(param));
+}
+
+NSEC3Hash*
+NSEC3Hash::create(const generic::NSEC3& nsec3) {
+    return (getNSEC3HashCreator()->create(nsec3));
+}
+
+NSEC3Hash*
+DefaultNSEC3HashCreator::create(const generic::NSEC3PARAM& param) const {
     return (new NSEC3HashRFC5155(param.getHashalg(), param.getIterations(),
                                  param.getSalt()));
 }
 
 NSEC3Hash*
-NSEC3Hash::create(const generic::NSEC3& nsec3) {
+DefaultNSEC3HashCreator::create(const generic::NSEC3& nsec3) const {
     return (new NSEC3HashRFC5155(nsec3.getHashalg(), nsec3.getIterations(),
                                  nsec3.getSalt()));
 }
 
+void
+setNSEC3HashCreator(const NSEC3HashCreator* new_creator) {
+    creator = new_creator;
+}
+
 } // namespace dns
 } // namespace isc
diff --git a/src/lib/dns/nsec3hash.h b/src/lib/dns/nsec3hash.h
index 7937e9d..2056708 100644
--- a/src/lib/dns/nsec3hash.h
+++ b/src/lib/dns/nsec3hash.h
@@ -154,6 +154,96 @@ public:
     virtual bool match(const rdata::generic::NSEC3PARAM& nsec3param) const = 0;
 };
 
+/// \brief Factory class of NSEC3Hash.
+///
+/// This class is an abstract base class that provides the creation interfaces
+/// of \c NSEC3Hash objects.  By defining a specific derived class of the
+/// creator, normally with a different specific class of \c NSEC3Hash,
+/// the application can use a customized implementation of \c NSEC3Hash
+/// without changing the library itself.  The intended primary application of
+/// such customization is tests (it would be convenient for a test to produce
+/// a faked hash value regardless of the input so it doesn't have to identify
+/// a specific input value to produce a particular hash).  Another possibility
+/// would be an experimental extension for a newer hash algorithm or
+/// implementation.
+///
+/// The two main methods named \c create() correspond to the static factory
+/// methods of \c NSEC3Hash of the same name.
+///
+/// By default, the library uses the \c DefaultNSEC3HashCreator creator.
+/// The \c setNSEC3HashCreator() function can be used to replace it with a
+/// user defined version.  For such customization purposes as implementing
+/// experimental new hash algorithms, the application may internally want to
+/// use the \c DefaultNSEC3HashCreator in general cases while creating a
+/// customized type of \c NSEC3Hash object for that particular hash algorithm.
+///
+/// The creator objects are generally expected to be stateless; they will
+/// only encapsulate the factory logic.  The \c create() methods are declared
+/// as const member functions for this reason.  But if we see the need for
+/// having a customized creator that benefits from its own state in future,
+/// this condition can be loosened.
+class NSEC3HashCreator {
+protected:
+    /// \brief The default constructor.
+    ///
+    /// Make very sure this isn't directly instantiated by making it protected
+    /// even if this class is modified to lose all pure virtual methods.
+    NSEC3HashCreator() {}
+
+public:
+    /// \brief The destructor.
+    ///
+    /// This does nothing; defined only for allowing derived classes to
+    /// specialize its behavior.
+    virtual ~NSEC3HashCreator() {}
+
+    /// \brief Factory method of NSECHash from NSEC3PARAM RDATA.
+    ///
+    /// See
+    /// <code>NSEC3Hash::create(const rdata::generic::NSEC3PARAM& param)</code>
+    virtual NSEC3Hash* create(const rdata::generic::NSEC3PARAM& nsec3param)
+        const = 0;
+
+    /// \brief Factory method of NSECHash from NSEC3 RDATA.
+    ///
+    /// See
+    /// <code>NSEC3Hash::create(const rdata::generic::NSEC3& param)</code>
+    virtual NSEC3Hash* create(const rdata::generic::NSEC3& nsec3)
+        const = 0;
+};
+
+/// \brief The default NSEC3Hash creator.
+///
+/// This derived class implements the \c NSEC3HashCreator interfaces for
+/// the standard NSEC3 hash calculator as defined in RFC5155.  The library
+/// will use this creator by default, so normal applications don't have to
+/// be aware of this class at all.  This class is publicly visible for the
+/// convenience of special applications that want to customize the creator
+/// behavior for a particular type of parameters while preserving the default
+/// behavior for others.
+class DefaultNSEC3HashCreator : public NSEC3HashCreator {
+public:
+    virtual NSEC3Hash* create(const rdata::generic::NSEC3PARAM& param) const;
+    virtual NSEC3Hash* create(const rdata::generic::NSEC3& nsec3) const;
+};
+
+/// \brief The registrar of \c NSEC3HashCreator.
+///
+/// This function sets or resets the system-wide \c NSEC3HashCreator that
+/// is used by \c NSEC3Hash::create().
+///
+/// If \c new_creator is non NULL, the given creator object will replace
+/// any existing creator.  If it's NULL, the default builtin creator will be
+/// used again from that point.
+///
+/// When \c new_creator is non NULL, the caller is responsible for keeping
+/// the referenced object valid as long as it can be used via
+/// \c NSEC3Hash::create().
+///
+/// \exception None
+/// \param new_creator A pointer to the new creator object or NULL.
+void setNSEC3HashCreator(const NSEC3HashCreator* new_creator);
+
 }
 }
 #endif  // __NSEC3HASH_H
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index 48fff94..2f1e2e5 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -433,7 +433,7 @@ private:
 };
 
 typedef SectionInserter<ConstQuestionPtr, Question> QuestionInserter;
-typedef SectionInserter<ConstRRsetPtr, RRset> RRsetInserter;
+typedef SectionInserter<ConstRRsetPtr, AbstractRRset> RRsetInserter;
 
 // TODO use direct iterators for these? (or simply lists for now?)
 PyObject*
diff --git a/src/lib/dns/python/rrset_python.cc b/src/lib/dns/python/rrset_python.cc
index 77d520b..2992522 100644
--- a/src/lib/dns/python/rrset_python.cc
+++ b/src/lib/dns/python/rrset_python.cc
@@ -415,7 +415,7 @@ PyTypeObject rrset_type = {
 };
 
 PyObject*
-createRRsetObject(const RRset& source) {
+createRRsetObject(const AbstractRRset& source) {
 
     // RRsets are noncopyable, so as a workaround we recreate a new one
     // and copy over all content
@@ -450,7 +450,7 @@ PyRRset_Check(PyObject* obj) {
     return (PyObject_TypeCheck(obj, &rrset_type));
 }
 
-RRset&
+AbstractRRset&
 PyRRset_ToRRset(PyObject* rrset_obj) {
     s_RRset* rrset = static_cast<s_RRset*>(rrset_obj);
     return (*rrset->cppobj);
diff --git a/src/lib/dns/python/rrset_python.h b/src/lib/dns/python/rrset_python.h
index 4268678..2435397 100644
--- a/src/lib/dns/python/rrset_python.h
+++ b/src/lib/dns/python/rrset_python.h
@@ -36,7 +36,7 @@ extern PyTypeObject rrset_type;
 /// returns a NULL pointer).
 /// This function is expected to be called within a try block
 /// followed by necessary setup for python exception.
-PyObject* createRRsetObject(const RRset& source);
+PyObject* createRRsetObject(const AbstractRRset& source);
 
 /// \brief Checks if the given python object is a RRset object
 ///
@@ -56,7 +56,7 @@ bool PyRRset_Check(PyObject* obj);
 /// may be destroyed, the caller must copy it itself.
 ///
 /// \param rrset_obj The rrset object to convert
-RRset& PyRRset_ToRRset(PyObject* rrset_obj);
+AbstractRRset& PyRRset_ToRRset(PyObject* rrset_obj);
 
 /// \brief Returns the shared_ptr of the RRset object contained within the
 ///        given Python object.
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
index a72058f..bb48705 100644
--- a/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.cc
@@ -12,11 +12,17 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include <stdint.h>
-
-#include <vector>
+#include <exceptions/exceptions.h>
 
 #include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rrtype.h>
+
+#include <cassert>
+#include <sstream>
+#include <vector>
+#include <cstring>
+#include <stdint.h>
 
 using namespace std;
 
@@ -70,6 +76,78 @@ checkRRTypeBitmaps(const char* const rrtype_name,
         first = false;
     }
 }
+
+void
+buildBitmapsFromText(const char* const rrtype_name,
+                     istringstream& iss, vector<uint8_t>& typebits)
+{
+    uint8_t bitmap[8 * 1024];       // 64k bits
+    memset(bitmap, 0, sizeof(bitmap));
+
+    do {
+        string type;
+        iss >> type;
+        if (iss.bad() || iss.fail()) {
+            isc_throw(InvalidRdataText, "Unexpected input for "
+                      << rrtype_name << " bitmap");
+        }
+        try {
+            const int code = RRType(type).getCode();
+            bitmap[code / 8] |= (0x80 >> (code % 8));
+        } catch (const InvalidRRType&) {
+            isc_throw(InvalidRdataText, "Invalid RRtype in "
+                      << rrtype_name << " bitmap: " << type);
+        }
+    } while (!iss.eof());
+
+    for (int window = 0; window < 256; ++window) {
+        int octet;
+        for (octet = 31; octet >= 0; octet--) {
+            if (bitmap[window * 32 + octet] != 0) {
+                break;
+            }
+        }
+        if (octet < 0) {
+            continue;
+        }
+        typebits.push_back(window);
+        typebits.push_back(octet + 1);
+        for (int i = 0; i <= octet; ++i) {
+            typebits.push_back(bitmap[window * 32 + i]);
+        }
+    }
+}
+
+void
+bitmapsToText(const vector<uint8_t>& typebits, ostringstream& oss) {
+    // In the following loop we use string::at() rather than operator[].
+    // Since the index calculation is a bit complicated, it will be safer
+    // and easier to find a bug (if any).  Note that this conversion method
+    // is generally not expected to be very efficient, so the slight overhead
+    // of at() should be acceptable.
+    const size_t typebits_len = typebits.size();
+    size_t len = 0;
+    for (size_t i = 0; i < typebits_len; i += len) {
+        assert(i + 2 <= typebits.size());
+        const unsigned int block = typebits.at(i);
+        len = typebits.at(i + 1);
+        assert(len > 0 && len <= 32);
+        i += 2;
+        for (size_t j = 0; j < len; ++j) {
+            if (typebits.at(i + j) == 0) {
+                continue;
+            }
+            for (size_t k = 0; k < 8; ++k) {
+                if ((typebits.at(i + j) & (0x80 >> k)) == 0) {
+                    continue;
+                }
+                const unsigned int t = block * 256 + j * 8 + k;
+                assert(t < 65536);
+                oss << " " << RRType(t);
+            }
+        }
+    }
+}
 }
 }
 }
diff --git a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
index 6431e10..27a5166 100644
--- a/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
+++ b/src/lib/dns/rdata/generic/detail/nsec_bitmap.h
@@ -14,6 +14,7 @@
 
 #include <stdint.h>
 
+#include <sstream>
 #include <vector>
 
 namespace isc {
@@ -22,14 +23,22 @@ namespace rdata {
 namespace generic {
 namespace detail {
 namespace nsec {
-/// Check if a given "type bitmap" for NSEC/NSEC3 is valid.
+
+/// \file
+///
+/// This helper module provides some utilities that handle NSEC and NSEC3
+/// type bitmaps.  The format and processing of the type bitmaps are generally
+/// the same for these two RRs, so it would make sense to consolidate
+/// the processing logic into a single implementation module.
+///
+/// The functions defined here are essentially private and are only expected
+/// to be called from the \c NSEC and \c NSEC3 class implementations.
+
+/// \brief Check if a given "type bitmap" for NSEC/NSEC3 is valid.
 ///
-/// This helper function checks given wire format data (stored in a
+/// This function checks given wire format data (stored in a
 /// \c std::vector) is a valid type bitmaps used for the NSEC and NSEC3 RRs
-/// according to RFC4034 and RFC5155.  The validation logic is the same
-/// for these two RRs, so a unified check function is provided.
-/// This function is essentially private and is only expected to be called
-/// from the \c NSEC and \c NSEC3 class implementations.
+/// according to RFC4034 and RFC5155.
 ///
 /// \exception DNSMessageFORMERR The bitmap is not valid.
 ///
@@ -39,6 +48,48 @@ namespace nsec {
 /// is the total length of the bitmaps.
 void checkRRTypeBitmaps(const char* const rrtype_name,
                         const std::vector<uint8_t>& typebits);
+
+/// \brief Convert textual sequence of RR types into type bitmaps.
+///
+/// This function extracts a sequence of strings, converts each sequence
+/// into an RR type, and builds NSEC/NSEC3 type bitmaps with the corresponding
+/// bits for the extracted types being on.  The resulting bitmaps (which are
+/// in the wire format according to RFC4034 and RFC5155) are stored in the
+/// given vector.  This function expects the given string stream ends with
+/// the sequence.
+///
+/// \exception InvalidRdataText The given input stream does not meet the
+/// assumption (e.g. including invalid form of RR type, not ending with
+/// an RR type string).
+///
+/// \param rrtype_name Either "NSEC" or "NSEC3"; used as part of exception
+/// messages.
+/// \param iss Input stream that consists of a complete sequence of textual
+/// RR types for which the corresponding bits are set.
+/// \param typebits A placeholder for the resulting bitmaps.  Expected to be
+/// empty, but it's not checked.
+void buildBitmapsFromText(const char* const rrtype_name,
+                          std::istringstream& iss,
+                          std::vector<uint8_t>& typebits);
+
+/// \brief Convert type bitmaps to textual sequence of RR types.
+///
+/// This function converts wire-format data of type bitmaps for NSEC/NSEC3
+/// into a sequence of corresponding RR type strings, and inserts them
+/// into the given output stream with separating them by a single space
+/// character.
+///
+/// This function assumes the given bitmaps are valid in terms of RFC4034
+/// and RFC5155 (in practice, it assumes it's from a validly constructed
+/// NSEC or NSEC3 object); if it detects a format error, it aborts the
+/// program with assert().
+///
+/// \param typebits The type bitmaps in wire format.  The size of vector
+/// is the total length of the bitmaps.
+/// \param oss The output stream to which the converted RR type sequence
+/// are to be inserted.
+void bitmapsToText(const std::vector<uint8_t>& typebits,
+                   std::ostringstream& oss);
 }
 }
 }
diff --git a/src/lib/dns/rdata/generic/nsec3_50.cc b/src/lib/dns/rdata/generic/nsec3_50.cc
index 092695c..3817941 100644
--- a/src/lib/dns/rdata/generic/nsec3_50.cc
+++ b/src/lib/dns/rdata/generic/nsec3_50.cc
@@ -53,12 +53,12 @@ struct NSEC3Impl {
         salt_(salt), next_(next), typebits_(typebits)
     {}
 
-    uint8_t hashalg_;
-    uint8_t flags_;
-    uint16_t iterations_;
-    vector<uint8_t> salt_;
-    vector<uint8_t> next_;
-    vector<uint8_t> typebits_;
+    const uint8_t hashalg_;
+    const uint8_t flags_;
+    const uint16_t iterations_;
+    const vector<uint8_t> salt_;
+    const vector<uint8_t> next_;
+    const vector<uint8_t> typebits_;
 };
 
 NSEC3::NSEC3(const string& nsec3_str) :
@@ -116,36 +116,7 @@ NSEC3::NSEC3(const string& nsec3_str) :
     }
 
     vector<uint8_t> typebits;
-    uint8_t bitmap[8 * 1024];       // 64k bits
-    memset(bitmap, 0, sizeof(bitmap));
-    do { 
-        string type;
-        iss >> type;
-        if (type.length() != 0) {
-            try {
-                const int code = RRType(type).getCode();
-                bitmap[code / 8] |= (0x80 >> (code % 8));
-            } catch (...) {
-                isc_throw(InvalidRdataText, "Invalid RRtype in NSEC3");
-            }
-        }
-    } while (!iss.eof());
-
-    for (int window = 0; window < 256; window++) {
-        int octet;
-        for (octet = 31; octet >= 0; octet--) {
-            if (bitmap[window * 32 + octet] != 0) {
-                break;
-            }
-        }
-        if (octet < 0)
-            continue;
-        typebits.push_back(window);
-        typebits.push_back(octet + 1);
-        for (int i = 0; i <= octet; i++) {
-            typebits.push_back(bitmap[window * 32 + i]);
-        }
-    }
+    buildBitmapsFromText("NSEC3", iss, typebits);
 
     impl_ = new NSEC3Impl(hashalg, flags, iterations, salt, next, typebits);
 }
@@ -174,6 +145,10 @@ NSEC3::NSEC3(InputBuffer& buffer, size_t rdata_len) {
         rdata_len -= saltlen;
     }
 
+    if (rdata_len < 1) {
+        isc_throw(DNSMessageFORMERR, "NSEC3 too short to contain hash length, "
+                  "length: " << rdata_len + saltlen + 5);
+    }
     const uint8_t nextlen = buffer.readUint8();
     --rdata_len;
     if (nextlen == 0 || rdata_len < nextlen) {
@@ -220,57 +195,78 @@ NSEC3::~NSEC3() {
 string
 NSEC3::toText() const {
     ostringstream s;
-    int len = 0;
-    for (size_t i = 0; i < impl_->typebits_.size(); i += len) {
-        assert(i + 2 <= impl_->typebits_.size());
-        int window = impl_->typebits_[i];
-        len = impl_->typebits_[i + 1];
-        assert(len >= 0 && len < 32);
-        i += 2;
-        for (int j = 0; j < len; j++) {
-            if (impl_->typebits_[i + j] == 0) {
-                continue;
-            }
-            for (int k = 0; k < 8; k++) {
-                if ((impl_->typebits_[i + j] & (0x80 >> k)) == 0) {
-                    continue;
-                }
-                int t = window * 256 + j * 8 + k;
-                s << " " << RRType(t).toText();
-            }
-        }
-    }
+    bitmapsToText(impl_->typebits_, s);
 
     using namespace boost;
     return (lexical_cast<string>(static_cast<int>(impl_->hashalg_)) +
-        " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
-        " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
-        " " + encodeHex(impl_->salt_) +
-        " " + encodeBase32Hex(impl_->next_) + s.str());
+            " " + lexical_cast<string>(static_cast<int>(impl_->flags_)) +
+            " " + lexical_cast<string>(static_cast<int>(impl_->iterations_)) +
+            " " + (impl_->salt_.empty() ? "-" : encodeHex(impl_->salt_)) +
+            " " + encodeBase32Hex(impl_->next_) + s.str());
+}
+
+template <typename OUTPUT_TYPE>
+void
+toWireHelper(const NSEC3Impl& impl, OUTPUT_TYPE& output) {
+    output.writeUint8(impl.hashalg_);
+    output.writeUint8(impl.flags_);
+    output.writeUint16(impl.iterations_);
+    output.writeUint8(impl.salt_.size());
+    if (!impl.salt_.empty()) {
+        output.writeData(&impl.salt_[0], impl.salt_.size());
+    }
+    assert(!impl.next_.empty());
+    output.writeUint8(impl.next_.size());
+    output.writeData(&impl.next_[0], impl.next_.size());
+    if (!impl.typebits_.empty()) {
+        output.writeData(&impl.typebits_[0], impl.typebits_.size());
+    }
 }
 
 void
 NSEC3::toWire(OutputBuffer& buffer) const {
-    buffer.writeUint8(impl_->hashalg_);
-    buffer.writeUint8(impl_->flags_);
-    buffer.writeUint16(impl_->iterations_);
-    buffer.writeUint8(impl_->salt_.size());
-    buffer.writeData(&impl_->salt_[0], impl_->salt_.size());
-    buffer.writeUint8(impl_->next_.size());
-    buffer.writeData(&impl_->next_[0], impl_->next_.size());
-    buffer.writeData(&impl_->typebits_[0], impl_->typebits_.size());
+    toWireHelper(*impl_, buffer);
 }
 
 void
 NSEC3::toWire(AbstractMessageRenderer& renderer) const {
-    renderer.writeUint8(impl_->hashalg_);
-    renderer.writeUint8(impl_->flags_);
-    renderer.writeUint16(impl_->iterations_);
-    renderer.writeUint8(impl_->salt_.size());
-    renderer.writeData(&impl_->salt_[0], impl_->salt_.size());
-    renderer.writeUint8(impl_->next_.size());
-    renderer.writeData(&impl_->next_[0], impl_->next_.size());
-    renderer.writeData(&impl_->typebits_[0], impl_->typebits_.size());
+    toWireHelper(*impl_, renderer);
+}
+
+namespace {
+// This is a helper subroutine for compare().  It compares two binary
+// data stored in vector<uint8_t> objects based on the "Canonical RR Ordering"
+// as defined in Section 6.3 of RFC4034, that is, the data are treated
+// "as a left-justified unsigned octet sequence in which the absence of an
+// octet sorts before a zero octet."
+//
+// If check_length_first is true, it treats the compared data as if they
+// began with a single-octet "length" field whose value is the size of the
+// corresponding vector.  In this case, if the sizes of the two vectors are
+// different the shorter one is always considered the "smaller"; the contents
+// of the vector don't matter.
+//
+// This function returns:
+// -1 if v1 is considered smaller than v2
+// 1 if v1 is considered larger than v2
+// 0 otherwise
+int
+compareVectors(const vector<uint8_t>& v1, const vector<uint8_t>& v2,
+               bool check_length_first = true)
+{
+    const size_t len1 = v1.size();
+    const size_t len2 = v2.size();
+    const size_t cmplen = min(len1, len2);
+    if (check_length_first && len1 != len2) {
+        return (len1 - len2);
+    }
+    const int cmp = cmplen == 0 ? 0 : memcmp(&v1.at(0), &v2.at(0), cmplen);
+    if (cmp != 0) {
+        return (cmp);
+    } else {
+        return (len1 - len2);
+    }
+}
 }
 
 int
@@ -287,44 +283,18 @@ NSEC3::compare(const Rdata& other) const {
         return (impl_->iterations_ < other_nsec3.impl_->iterations_ ? -1 : 1);
     }
 
-    size_t this_len = impl_->salt_.size();
-    size_t other_len = other_nsec3.impl_->salt_.size();
-    size_t cmplen = min(this_len, other_len);
-    int cmp = memcmp(&impl_->salt_[0], &other_nsec3.impl_->salt_[0], cmplen);
+    int cmp = compareVectors(impl_->salt_, other_nsec3.impl_->salt_);
     if (cmp != 0) {
         return (cmp);
-    } else if (this_len < other_len) {
-        return (-1);
-    } else if (this_len > other_len) {
-        return (1);
     }
-
-    this_len = impl_->salt_.size();
-    other_len = other_nsec3.impl_->salt_.size();
-    cmplen = min(this_len, other_len);
-    cmp = memcmp(&impl_->next_[0], &other_nsec3.impl_->next_[0], cmplen);
-    if (cmp != 0) {
-        return (cmp);
-    } else if (this_len < other_len) {
-        return (-1);
-    } else if (this_len > other_len) {
-        return (1);
-    }
-
-    this_len = impl_->typebits_.size();
-    other_len = other_nsec3.impl_->typebits_.size();
-    cmplen = min(this_len, other_len);
-    cmp = memcmp(&impl_->typebits_[0], &other_nsec3.impl_->typebits_[0],
-                 cmplen);
+    cmp = compareVectors(impl_->next_, other_nsec3.impl_->next_);
     if (cmp != 0) {
         return (cmp);
-    } else if (this_len < other_len) {
-        return (-1);
-    } else if (this_len > other_len) {
-        return (1);
-    } else {
-        return (0);
     }
+    // Note that bitmap doesn't have a dedicated length field, so we shouldn't
+    // terminate the comparison just because the lengths are different.
+    return (compareVectors(impl_->typebits_, other_nsec3.impl_->typebits_,
+                           false));
 }
 
 uint8_t
diff --git a/src/lib/dns/rdata/generic/nsec_47.cc b/src/lib/dns/rdata/generic/nsec_47.cc
index a9a9f75..08825db 100644
--- a/src/lib/dns/rdata/generic/nsec_47.cc
+++ b/src/lib/dns/rdata/generic/nsec_47.cc
@@ -54,42 +54,18 @@ NSEC::NSEC(const string& nsec_str) :
 {
     istringstream iss(nsec_str);
     string nextname;
-    uint8_t bitmap[8 * 1024];       // 64k bits
-    vector<uint8_t> typebits;
 
     iss >> nextname;
     if (iss.bad() || iss.fail()) {
         isc_throw(InvalidRdataText, "Invalid NSEC name");
     }
-
-    memset(bitmap, 0, sizeof(bitmap));
-    do { 
-        string type;
-        iss >> type;
-        try {
-            const int code = RRType(type).getCode();
-            bitmap[code / 8] |= (0x80 >> (code % 8));
-        } catch (...) {
-            isc_throw(InvalidRdataText, "Invalid RRtype in NSEC");
-        }
-    } while (!iss.eof());
-
-    for (int window = 0; window < 256; window++) {
-        int octet;
-        for (octet = 31; octet >= 0; octet--) {
-            if (bitmap[window * 32 + octet] != 0) {
-                break;
-            }
-        }
-        if (octet < 0)
-            continue;
-        typebits.push_back(window);
-        typebits.push_back(octet + 1);
-        for (int i = 0; i <= octet; i++) {
-            typebits.push_back(bitmap[window * 32 + i]);
-        }
+    if (iss.eof()) {
+        isc_throw(InvalidRdataText, "NSEC bitmap is missing");
     }
 
+    vector<uint8_t> typebits;
+    buildBitmapsFromText("NSEC", iss, typebits);
+
     impl_ = new NSECImpl(Name(nextname), typebits);
 }
 
@@ -135,34 +111,8 @@ NSEC::~NSEC() {
 string
 NSEC::toText() const {
     ostringstream s;
-    int len = 0;
     s << impl_->nextname_;
-
-    // In the following loop we use string::at() rather than operator[].
-    // Since the index calculation is a bit complicated, it will be safer
-    // and easier to find a bug (if any).  Note that this conversion method
-    // is generally not expected to be very efficient, so the slight overhead
-    // of at() should be acceptable.
-    for (size_t i = 0; i < impl_->typebits_.size(); i += len) {
-        assert(i + 2 <= impl_->typebits_.size());
-        const int block = impl_->typebits_.at(i);
-        len = impl_->typebits_.at(i + 1);
-        assert(len > 0 && len <= 32);
-        i += 2;
-        for (int j = 0; j < len; j++) {
-            if (impl_->typebits_.at(i + j) == 0) {
-                continue;
-            }
-            for (int k = 0; k < 8; k++) {
-                if ((impl_->typebits_.at(i + j) & (0x80 >> k)) == 0) {
-                    continue;
-                }
-                const int t = block * 256 + j * 8 + k;
-                s << " " << RRType(t);
-            }
-        }
-    }
-
+    bitmapsToText(impl_->typebits_, s);
     return (s.str());
 }
 
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index aad50aa..5042a98 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -58,14 +58,14 @@ class RRset;
 ///
 /// This type is commonly used as an argument of various functions defined
 /// in this library in order to handle RRsets in a polymorphic manner.
-typedef boost::shared_ptr<RRset> RRsetPtr;
+typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
 
 /// \brief A pointer-like type pointing to an (immutable) \c RRset
 /// object.
 ///
 /// This type is commonly used as an argument of various functions defined
 /// in this library in order to handle RRsets in a polymorphic manner.
-typedef boost::shared_ptr<const RRset> ConstRRsetPtr;
+typedef boost::shared_ptr<const AbstractRRset> ConstRRsetPtr;
 
 /// \brief A pointer-like type point to an \c RdataIterator object.
 typedef boost::shared_ptr<RdataIterator> RdataIteratorPtr;
@@ -400,6 +400,82 @@ public:
     /// object.
     virtual RdataIteratorPtr getRdataIterator() const = 0;
     //@}
+
+    ///
+    /// \name Associated RRSIG methods
+    ///
+    /// These methods access an "associated" RRset, that containing the DNSSEC
+    /// signatures for this RRset.  It can be argued that this is not a
+    /// fundamental part of the RRset abstraction, since RFC 2181 defined an
+    /// RRset as a group of records with the same label, class and type but
+    /// different data.  However, BIND 10 has to deal with DNSSEC and in
+    /// practice, including the information at the AbstractRRset level makes
+    /// implementation easier.  (If a class is ever needed that must be
+    /// ignorant of the idea of an associated RRSIG RRset - e.g. a specialised
+    /// RRSIG RRset class - these methods can just throw a "NotImplemented"
+    /// exception.)
+    //@{
+    /// \brief Return pointer to this RRset's RRSIG RRset
+    ///
+    /// \return Pointer to the associated RRSIG RRset or null if there is none.
+    virtual RRsetPtr getRRsig() const = 0;
+
+    /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+    ///
+    /// Adds the (assumed) RRSIG rdata the RRSIG RRset associated with this
+    /// RRset.  If one does not exist, it is created using the data given.
+    ///
+    /// \param rdata Pointer to RRSIG rdata to be added.
+    virtual void addRRsig(const rdata::ConstRdataPtr& rdata) = 0;
+
+    /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+    ///
+    /// Adds the (assumed) RRSIG rdata the RRSIG RRset associated with this
+    /// RRset.  If one does not exist, it is created using the data given.
+    ///
+    /// (This overload is for an older version of boost that doesn't support
+    /// conversion from shared_ptr<X> to shared_ptr<const X>.)
+    ///
+    /// \param rdata Pointer to RRSIG rdata to be added.
+    virtual void addRRsig(const rdata::RdataPtr& rdata) = 0;
+
+    /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+    ///
+    /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+    /// RRset associated with this RRset.  If one does not exist, it is created
+    /// using the data given.
+    ///
+    /// \param sigs RRSIG RRset containing signatures to be added to the
+    ///             RRSIG RRset associated with this class.
+    virtual void addRRsig(const AbstractRRset& sigs) = 0;
+
+    /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+    ///
+    /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+    /// RRset associated with this RRset.  If one does not exist, it is created
+    /// using the data given.
+    ///
+    /// \param sigs Pointer to a RRSIG RRset containing signatures to be added
+    ///             to the RRSIG RRset associated with this class.
+    virtual void addRRsig(const ConstRRsetPtr& sigs) = 0;
+
+    /// \brief Adds RRSIG RRset RRs to the associated RRSIG RRset
+    ///
+    /// Adds the signatures in the given (assumed) RRSIG RRset to the RRSIG
+    /// RRset associated with this RRset.  If one does not exist, it is created
+    /// using the data given.
+    ///
+    /// (This overload is for an older version of boost that doesn't support
+    /// conversion from shared_ptr<X> to shared_ptr<const X>.)
+    ///
+    /// \param sigs Pointer to a RRSIG RRset containing signatures to be added
+    ///             to the RRSIG RRset associated with this class.
+    virtual void addRRsig(const RRsetPtr& sigs) = 0;
+
+    /// \brief Clear the RRSIGs for this RRset
+    virtual void removeRRsig() = 0;
+
+    //@}
 };
 
 /// \brief The \c RdataIterator class is an abstract base class that
@@ -660,6 +736,56 @@ public:
     /// object for the \c BasicRRset class.
     virtual RdataIteratorPtr getRdataIterator() const;
     //@}
+
+    ///
+    /// \name Associated RRSIG methods
+    ///
+    /// The associated RRSIG RRset is not supported in BasicRRset.  For
+    /// ease of use, getRRsig() returns a null pointer (indicating no RRset).
+    /// The addRRsig()/removeRRsig() methods throw a "NotImplemented"
+    /// exception - if you are using a BasicRRset, you should not be trying
+    /// to modify signatures on it.
+    //@{
+    /// \brief Return pointer to this RRset's RRSIG RRset
+    ///
+    /// \exception NotImplemented Always thrown.  Associated RRSIG RRsets are
+    ///            not supported in this class.
+    ///
+    /// \return Null pointer, as this class does not support RRSIG records.
+    virtual RRsetPtr getRRsig() const {
+        return (RRsetPtr());
+    }
+
+    virtual void addRRsig(const rdata::ConstRdataPtr&) {
+        isc_throw(NotImplemented,
+                  "BasicRRset does not implement the addRRsig() method");
+    }
+
+    virtual void addRRsig(const rdata::RdataPtr&) {
+        isc_throw(NotImplemented,
+                  "BasicRRset does not implement the addRRsig() method");
+    }
+
+    virtual void addRRsig(const AbstractRRset&) {
+        isc_throw(NotImplemented,
+                  "BasicRRset does not implement the addRRsig() method");
+    }
+
+    virtual void addRRsig(const ConstRRsetPtr&) {
+        isc_throw(NotImplemented,
+                  "BasicRRset does not implement the addRRsig() method");
+    }
+
+    virtual void addRRsig(const RRsetPtr&) {
+        isc_throw(NotImplemented,
+                  "BasicRRset does not implement the addRRsig() method");
+    }
+
+    virtual void removeRRsig() {
+        isc_throw(NotImplemented,
+                  "BasicRRset does not implement the removeRRsig() method");
+    }
+    //@}
 private:
     BasicRRsetImpl* impl_;
 };
@@ -693,7 +819,7 @@ public:
     }
 
     /// \brief Adds an RRSIG RR to this RRset's signatures
-    virtual void addRRsig(const rdata::ConstRdataPtr rdata) {
+    virtual void addRRsig(const rdata::ConstRdataPtr& rdata) {
         if (!rrsig_) {
             rrsig_ = RRsetPtr(new RRset(getName(), getClass(),
                                         RRType::RRSIG(), getTTL()));
@@ -705,12 +831,13 @@ public:
     // conversion from shared_ptr<X> to shared_ptr<const X>.  Note: we should
     // revisit the interface of managing RRset signatures, at which point this
     // problem may go away.
-    void addRRsig(const rdata::RdataPtr rdata) {
-        addRRsig(static_cast<rdata::ConstRdataPtr>(rdata));
+    virtual void addRRsig(const rdata::RdataPtr& rdata) {
+        // Don't try to convert as a reference here.  SunStudio will reject it.
+        addRRsig(static_cast<const rdata::ConstRdataPtr>(rdata));
     }
 
     /// \brief Adds an RRSIG RRset to this RRset
-    void addRRsig(const AbstractRRset& sigs) {
+    virtual void addRRsig(const AbstractRRset& sigs) {
         RdataIteratorPtr it = sigs.getRdataIterator();
 
         if (!rrsig_) {
@@ -723,13 +850,13 @@ public:
         }
     }
 
-    void addRRsig(ConstRRsetPtr sigs) { addRRsig(*sigs); }
+    virtual void addRRsig(const ConstRRsetPtr& sigs) { addRRsig(*sigs); }
 
     // Another workaround for older boost (see above)
-    void addRRsig(RRsetPtr sigs) { addRRsig(*sigs); }
+    virtual void addRRsig(const RRsetPtr& sigs) { addRRsig(*sigs); }
 
     /// \brief Clear the RRSIGs for this RRset
-    void removeRRsig() { rrsig_ = RRsetPtr(); }
+    virtual void removeRRsig() { rrsig_ = RRsetPtr(); }
 
     /// \brief Return a pointer to this RRset's RRSIG RRset
     RRsetPtr getRRsig() const { return (rrsig_); }
diff --git a/src/lib/dns/tests/nsec3hash_unittest.cc b/src/lib/dns/tests/nsec3hash_unittest.cc
index 36a1d4d..e607c74 100644
--- a/src/lib/dns/tests/nsec3hash_unittest.cc
+++ b/src/lib/dns/tests/nsec3hash_unittest.cc
@@ -29,7 +29,7 @@ using namespace isc::dns::rdata;
 namespace {
 typedef scoped_ptr<NSEC3Hash> NSEC3HashPtr;
 
-// Commonly used NSEC3 suffix, defined to reduce amount of type
+// Commonly used NSEC3 suffix, defined to reduce the amount of typing
 const char* const nsec3_common = "2T7B4G4VSA5SMI47K61MV5BV1A22BOJR A RRSIG";
 
 class NSEC3HashTest : public ::testing::Test {
@@ -41,6 +41,11 @@ protected:
                                            string(nsec3_common))))
     {}
 
+    ~NSEC3HashTest() {
+        // Make sure we reset the hash creator to the default
+        setNSEC3HashCreator(NULL);
+    }
+
     // An NSEC3Hash object commonly used in tests.  Parameters are borrowed
     // from the RFC5155 example.  Construction of this object implicitly
     // checks a successful case of the creation.
@@ -142,4 +147,76 @@ TEST_F(NSEC3HashTest, matchWithNSEC3PARAM) {
     }
 }
 
+// A simple faked hash calculator and a dedicated creator for it.
+class TestNSEC3Hash : public NSEC3Hash {
+    virtual string calculate(const Name&) const {
+        return ("00000000000000000000000000000000");
+    }
+    virtual bool match(const generic::NSEC3PARAM&) const {
+        return (true);
+    }
+    virtual bool match(const generic::NSEC3&) const {
+        return (true);
+    }
+};
+
+// This faked creator basically creates the faked calculator regardless of
+// the passed NSEC3PARAM or NSEC3.  But if the most significant bit of flags
+// is set, it will behave like the default creator.
+class TestNSEC3HashCreator : public NSEC3HashCreator {
+public:
+    virtual NSEC3Hash* create(const generic::NSEC3PARAM& param) const {
+        if ((param.getFlags() & 0x80) != 0) {
+            return (default_creator_.create(param));
+        }
+        return (new TestNSEC3Hash);
+    }
+    virtual NSEC3Hash* create(const generic::NSEC3& nsec3) const {
+        if ((nsec3.getFlags() & 0x80) != 0) {
+            return (default_creator_.create(nsec3));
+        }
+        return (new TestNSEC3Hash);
+    }
+private:
+    DefaultNSEC3HashCreator default_creator_;
+};
+
+TEST_F(NSEC3HashTest, setCreator) {
+    // Re-check an existing case using the default creator/hash implementation
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+
+    // Replace the creator, and confirm the hash values are faked
+    TestNSEC3HashCreator test_creator;
+    setNSEC3HashCreator(&test_creator);
+    // Re-create the hash object with the new creator
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
+    EXPECT_EQ("00000000000000000000000000000000",
+              test_hash->calculate(Name("example")));
+    // Same for hash from NSEC3 RDATA
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3
+                                      ("1 0 12 aabbccdd " +
+                                       string(nsec3_common))));
+    EXPECT_EQ("00000000000000000000000000000000",
+              test_hash->calculate(Name("example")));
+
+    // If we set a special flag big (0x80) on creation, it will act like the
+    // default creator.
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM(
+                                          "1 128 12 aabbccdd")));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3
+                                      ("1 128 12 aabbccdd " +
+                                       string(nsec3_common))));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+
+    // Reset the creator to default, and confirm that
+    setNSEC3HashCreator(NULL);
+    test_hash.reset(NSEC3Hash::create(generic::NSEC3PARAM("1 0 12 aabbccdd")));
+    EXPECT_EQ("0P9MHAVEQVM6T7VBL5LOP2U3T2RP3TOM",
+              test_hash->calculate(Name("example")));
+}
+
 } // end namespace
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index 441c6d8..c8d21cf 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -44,8 +44,14 @@ class Rdata_NSEC3_Test : public RdataTest {
 public:
     Rdata_NSEC3_Test() :
         nsec3_txt("1 1 1 D399EAAB H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
-                  "NS SOA RRSIG DNSKEY NSEC3PARAM") {}
-    string nsec3_txt;
+                  "NS SOA RRSIG DNSKEY NSEC3PARAM"),
+        nsec3_nosalt_txt("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 A" ),
+        obuffer(0), renderer(obuffer)
+    {}
+    const string nsec3_txt;
+    const string nsec3_nosalt_txt;
+    OutputBuffer obuffer;
+    MessageRenderer renderer;
 };
 
 TEST_F(Rdata_NSEC3_Test, fromText) {
@@ -60,8 +66,7 @@ TEST_F(Rdata_NSEC3_Test, fromText) {
                                    "NS SOA RRSIG DNSKEY NSEC3PARAM"));
 
     // 0-length salt
-    EXPECT_EQ(0, generic::NSEC3("1 1 1 - H9RSFB7FPF2L8HG35CMPC765TDK23RP6 "
-                                "A").getSalt().size());
+    EXPECT_EQ(0, generic::NSEC3(nsec3_nosalt_txt).getSalt().size());
 
     // salt that has the possible max length
     EXPECT_EQ(255, generic::NSEC3("1 1 1 " + string(255 * 2, '0') +
@@ -80,8 +85,12 @@ TEST_F(Rdata_NSEC3_Test, fromText) {
 }
 
 TEST_F(Rdata_NSEC3_Test, toText) {
+    // normal case
     const generic::NSEC3 rdata_nsec3(nsec3_txt);
     EXPECT_EQ(nsec3_txt, rdata_nsec3.toText());
+
+    // empty salt case
+    EXPECT_EQ(nsec3_nosalt_txt, generic::NSEC3(nsec3_nosalt_txt).toText());
 }
 
 TEST_F(Rdata_NSEC3_Test, badText) {
@@ -165,7 +174,11 @@ TEST_F(Rdata_NSEC3_Test, createFromWire) {
                                       "rdata_nsec3_fromWire14.wire"),
                  DNSMessageFORMERR);
 
-    //
+    // RDLEN is too short to hold the hash length field
+    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
+                                      "rdata_nsec3_fromWire17.wire"),
+                 DNSMessageFORMERR);
+
     // Short buffer cases.  The data is valid NSEC3 RDATA, but the buffer
     // is trimmed at the end.  All cases should result in an exception from
     // the buffer class.
@@ -180,21 +193,35 @@ TEST_F(Rdata_NSEC3_Test, createFromWire) {
     }
 }
 
-TEST_F(Rdata_NSEC3_Test, toWireRenderer) {
-    renderer.skip(2);
-    const generic::NSEC3 rdata_nsec3(nsec3_txt);
-    rdata_nsec3.toWire(renderer);
-
-    vector<unsigned char> data;
-    UnitTestUtil::readWireData("rdata_nsec3_fromWire1", data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        static_cast<const uint8_t *>(obuffer.getData()) + 2,
-                        obuffer.getLength() - 2, &data[2], data.size() - 2);
+template <typename OUTPUT_TYPE>
+void
+toWireCheck(OUTPUT_TYPE& output, const char* const data_file) {
+    vector<uint8_t> data;
+    UnitTestUtil::readWireData(data_file, data);
+    InputBuffer buffer(&data[0], data.size());
+    const uint16_t rdlen = buffer.readUint16();
+    const generic::NSEC3 nsec3 =
+        dynamic_cast<const generic::NSEC3&>(*createRdata(
+                                                RRType::NSEC3(), RRClass::IN(),
+                                                buffer, rdlen));
+
+    output.clear();
+    output.writeUint16(rdlen);
+    nsec3.toWire(output);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, output.getData(),
+                        output.getLength(), &data[0], data.size());
 }
 
-TEST_F(Rdata_NSEC3_Test, toWireBuffer) {
-    const generic::NSEC3 rdata_nsec3(nsec3_txt);
-    rdata_nsec3.toWire(obuffer);
+TEST_F(Rdata_NSEC3_Test, toWire) {
+    // normal case
+    toWireCheck(renderer, "rdata_nsec3_fromWire1");
+    toWireCheck(obuffer, "rdata_nsec3_fromWire1");
+
+    // empty salt
+    toWireCheck(renderer, "rdata_nsec3_fromWire13.wire");
+    toWireCheck(obuffer, "rdata_nsec3_fromWire13.wire");
+
+    // empty bitmap case is handled in the bitmap tests
 }
 
 TEST_F(Rdata_NSEC3_Test, assign) {
@@ -203,4 +230,35 @@ TEST_F(Rdata_NSEC3_Test, assign) {
     EXPECT_EQ(0, rdata_nsec3.compare(other_nsec3));
 }
 
+TEST_F(Rdata_NSEC3_Test, compare) {
+    // trivial case: self equivalence
+    EXPECT_EQ(0, generic::NSEC3(nsec3_txt).compare(generic::NSEC3(nsec3_txt)));
+
+    // comparison attempt between incompatible RR types should be rejected
+    EXPECT_THROW(generic::NSEC3(nsec3_txt).compare(*rdata_nomatch),
+                 bad_cast);
+
+    // test RDATAs, sorted in the ascendent order.  We only check comparison
+    // on NSEC3-specific fields.  Bitmap comparison is tested in the bitmap
+    // tests.
+    vector<generic::NSEC3> compare_set;
+    compare_set.push_back(generic::NSEC3("0 0 0 D399EAAB D1K6GQ38"));
+    compare_set.push_back(generic::NSEC3("1 0 0 D399EAAB D1K6GQ38"));
+    compare_set.push_back(generic::NSEC3("1 1 0 D399EAAB D1K6GQ38"));
+    compare_set.push_back(generic::NSEC3("1 1 1 - D1K6GQ38"));
+    compare_set.push_back(generic::NSEC3("1 1 1 D399EAAB D1K6GQ38"));
+    compare_set.push_back(generic::NSEC3("1 1 1 FF99EAAB D1K6GQ38"));
+    compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ38"));
+    compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ0000000000"));
+    compare_set.push_back(generic::NSEC3("1 1 1 FF99EA0000 D1K6GQ00UUUUUUUU"));
+
+    vector<generic::NSEC3>::const_iterator it;
+    const vector<generic::NSEC3>::const_iterator it_end = compare_set.end();
+    for (it = compare_set.begin(); it != it_end - 1; ++it) {
+        SCOPED_TRACE("compare " + it->toText() + " to " + (it + 1)->toText());
+        EXPECT_GT(0, (*it).compare(*(it + 1)));
+        EXPECT_LT(0, (*(it + 1)).compare(*it));
+    }
+}
+
 }
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
index f081cd8..feb70c2 100644
--- a/src/lib/dns/tests/rdata_nsec_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -38,7 +38,7 @@ class Rdata_NSEC_Test : public RdataTest {
     // there's nothing to specialize
 };
 
-string nsec_txt("www2.isc.org. CNAME RRSIG NSEC");
+const char* const nsec_txt = "www2.isc.org. CNAME RRSIG NSEC";
 
 TEST_F(Rdata_NSEC_Test, toText_NSEC) {
     const generic::NSEC rdata_nsec(nsec_txt);
@@ -95,4 +95,31 @@ TEST_F(Rdata_NSEC_Test, getNextName) {
     EXPECT_EQ(Name("www2.isc.org"), generic::NSEC((nsec_txt)).getNextName());
 }
 
+TEST_F(Rdata_NSEC_Test, compare) {
+    // trivial case: self equivalence
+    EXPECT_EQ(0, generic::NSEC("example A").
+              compare(generic::NSEC("example. A")));
+    EXPECT_EQ(0, generic::NSEC("EXAMPLE A"). // should be case insensitive
+              compare(generic::NSEC("example. A")));
+
+    // comparison attempt between incompatible RR types should be rejected
+    EXPECT_THROW(generic::NSEC(nsec_txt).compare(*rdata_nomatch),
+                 bad_cast);
+
+    // test RDATAs, sorted in the ascendent order.  We only compare the
+    // next name here.  Bitmap comparison is tested in the bitmap tests.
+    // Note that names are compared as wire-format data, not based on the
+    // domain name comparison.
+    vector<generic::NSEC> compare_set;
+    compare_set.push_back(generic::NSEC("a.example. A"));
+    compare_set.push_back(generic::NSEC("example. A"));
+    vector<generic::NSEC>::const_iterator it;
+    const vector<generic::NSEC>::const_iterator it_end = compare_set.end();
+    for (it = compare_set.begin(); it != it_end - 1; ++it) {
+        SCOPED_TRACE("compare " + it->toText() + " to " + (it + 1)->toText());
+        EXPECT_GT(0, (*it).compare(*(it + 1)));
+        EXPECT_LT(0, (*(it + 1)).compare(*it));
+    }
+}
+
 }
diff --git a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc b/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
index 8a90878..426a69e 100644
--- a/src/lib/dns/tests/rdata_nsecbitmap_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsecbitmap_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 <dns/tests/unittest_util.h>
+
 #include <dns/exceptions.h>
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
@@ -22,82 +24,251 @@
 
 #include <dns/tests/rdata_unittest.h>
 
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <vector>
+
+using namespace std;
+using boost::lexical_cast;
+using isc::UnitTestUtil;
 using namespace isc::dns;
 using namespace isc::dns::rdata;
 
 namespace {
-class Rdata_NSECBITMAP_Test : public RdataTest {
-    // there's nothing to specialize
+
+// Template for shared tests for NSEC and NSEC3 bitmaps
+template <typename RDATA_TYPE>
+class NSECLikeBitmapTest : public RdataTest {
+protected:
+    RDATA_TYPE fromText(const string& rdata_text) {
+        return (RDATA_TYPE(rdata_text));
+    }
+
+    vector<RDATA_TYPE> compare_set; // used in compare() tests
+
+    void compareCheck() const {
+        typename vector<RDATA_TYPE>::const_iterator it;
+        typename vector<RDATA_TYPE>::const_iterator const it_end =
+            compare_set.end();
+        for (it = compare_set.begin(); it != it_end - 1; ++it) {
+            SCOPED_TRACE("compare " + it->toText() + " to " +
+                         (it + 1)->toText());
+            EXPECT_GT(0, (*it).compare(*(it + 1)));
+            EXPECT_LT(0, (*(it + 1)).compare(*it));
+        }
+    }
+
+    // These depend on the specific RR type.  We use specialized methods
+    // for them.
+    static RRType getType();    // return either RRType::NSEC() or NSEC3()
+    static string getWireFilePrefix();
+    static string getCommonText(); // commonly used part of textual form
 };
 
+// Instantiate specific typed tests
+typedef ::testing::Types<generic::NSEC, generic::NSEC3> TestRdataTypes;
+TYPED_TEST_CASE(NSECLikeBitmapTest, TestRdataTypes);
+
+// NSEC and NSEC3 bitmaps have some subtle differences, in which case we
+// need to test them separately.  Using these typedef type names with TEST_F
+// will do the trick.
+typedef NSECLikeBitmapTest<generic::NSEC3> NSEC3BitmapTest;
+typedef NSECLikeBitmapTest<generic::NSEC> NSECBitmapTest;
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC>::getWireFilePrefix() {
+    return ("rdata_nsec_");
+}
+
+template <>
+RRType
+NSECLikeBitmapTest<generic::NSEC>::getType() {
+    return (RRType::NSEC());
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC3>::getWireFilePrefix() {
+    return ("rdata_nsec3_");
+}
+
+template <>
+RRType
+NSECLikeBitmapTest<generic::NSEC3>::getType() {
+    return (RRType::NSEC3());
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC>::getCommonText() {
+    return ("next. ");
+}
+
+template <>
+string
+NSECLikeBitmapTest<generic::NSEC3>::getCommonText() {
+    return ("1 1 12 AABBCCDD 2T7B4G4VSA5SMI47K61MV5BV1A22BOJR ");
+}
+
 // Tests against various types of bogus NSEC/NSEC3 type bitmaps.
 // The syntax and semantics are common for both RR types, and our
 // implementation of that part is shared, so in theory it should be sufficient
 // to test for only one RR type.  But we check for both just in case.
-TEST_F(Rdata_NSECBITMAP_Test, createFromWire_NSEC) {
+TYPED_TEST(NSECLikeBitmapTest, createFromWire) {
     // A malformed NSEC bitmap length field that could cause overflow.
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
-                                      "rdata_nsec_fromWire4.wire"),
-                 DNSMessageFORMERR);
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
-                                      "rdata_nsec3_fromWire4.wire"),
+    EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                            (this->getWireFilePrefix() +
+                                             "fromWire4.wire").c_str()),
                  DNSMessageFORMERR);
 
     // The bitmap field is incomplete (only the first byte is included)
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
-                                      "rdata_nsec_fromWire5.wire"),
-                 DNSMessageFORMERR);
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
-                                      "rdata_nsec3_fromWire5.wire"),
+    EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                            (this->getWireFilePrefix() +
+                                             "fromWire5.wire").c_str()),
                  DNSMessageFORMERR);
 
     // Bitmap length is 0, which is invalid.
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
-                                      "rdata_nsec_fromWire6.wire"),
-                 DNSMessageFORMERR);
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
-                                      "rdata_nsec3_fromWire6.wire"),
+    EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                            (this->getWireFilePrefix() +
+                                             "fromWire6.wire").c_str()),
                  DNSMessageFORMERR);
 
     // Too large bitmap length with a short buffer.
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
-                                      "rdata_nsec_fromWire3"),
-                 DNSMessageFORMERR);
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
-                                      "rdata_nsec3_fromWire3"),
+    EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                            (this->getWireFilePrefix() +
+                                             "fromWire3").c_str()),
                  DNSMessageFORMERR);
 
     // A boundary case: longest possible bitmaps (32 maps).  This should be
     // accepted.
-    EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
-                                         "rdata_nsec_fromWire7.wire"));
-    EXPECT_NO_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
-                                         "rdata_nsec3_fromWire7.wire"));
+    EXPECT_NO_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                               (this->getWireFilePrefix() +
+                                                "fromWire7.wire").c_str()));
 
     // Another boundary condition: 33 bitmaps, which should be rejected.
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
-                                      "rdata_nsec_fromWire8.wire"),
-                 DNSMessageFORMERR);
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
-                                      "rdata_nsec3_fromWire8.wire"),
+    EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                            (this->getWireFilePrefix() +
+                                             "fromWire8.wire").c_str()),
                  DNSMessageFORMERR);
 
     // Disordered bitmap window blocks.
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
-                                      "rdata_nsec_fromWire9.wire"),
-                 DNSMessageFORMERR);
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
-                                      "rdata_nsec3_fromWire9.wire"),
+    EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                            (this->getWireFilePrefix() +
+                                             "fromWire9.wire").c_str()),
                  DNSMessageFORMERR);
 
     // Bitmap ending with all-zero bytes.  Not necessarily harmful except
     // the additional overhead of parsing, but invalid according to the
     // spec anyway.
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC(), RRClass::IN(),
-                                      "rdata_nsec_fromWire10.wire"),
+    EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                            (this->getWireFilePrefix() +
+                                             "fromWire10.wire").c_str()),
                  DNSMessageFORMERR);
-    EXPECT_THROW(rdataFactoryFromFile(RRType::NSEC3(), RRClass::IN(),
-                                      "rdata_nsec3_fromWire10.wire"),
+}
+
+TYPED_TEST(NSECLikeBitmapTest, badText) {
+    // redundant space after the sequence
+    EXPECT_THROW(this->fromText(this->getCommonText() + "A "),
+                 InvalidRdataText);
+}
+
+// This tests the result of toText() with various kinds of NSEC/NSEC3 bitmaps.
+// It also tests the "from text" constructor as a result.
+TYPED_TEST(NSECLikeBitmapTest, toText) {
+    // A simple case (some commonly seen RR types in NSEC(3) bitmaps)
+    string rdata_text = this->getCommonText() + "NS SOA RRSIG DNSKEY";
+    EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+
+    // Similar to above, but involves more than one bitmap window blocks.
+    rdata_text = this->getCommonText() + "NS DLV";
+    EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+
+    // Make sure all possible bits in a one-octet bitmap field are handled
+    // correctly.
+    // We use the range around 1024 (reasonably higher number) so it's
+    // unlikely that they have predefined mnemonic and can be safely converted
+    // to TYPEnnnn by toText().
+    for (unsigned int i = 1024; i < 1032; ++i) {
+        rdata_text = this->getCommonText() + "TYPE" + lexical_cast<string>(i);
+        EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+    }
+
+    // Make sure all possible 32 octets in a longest possible block are
+    // handled correctly.
+    for (unsigned int i = 1024; i < 1024 + 256; i += 8) {
+        rdata_text = this->getCommonText() + "TYPE" + lexical_cast<string>(i);
+        EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+    }
+
+    // Check for the highest window block.
+    rdata_text = this->getCommonText() + "TYPE65535";
+    EXPECT_EQ(rdata_text, this->fromText(rdata_text).toText());
+}
+
+TYPED_TEST(NSECLikeBitmapTest, compare) {
+    // Bit map: [win=0][len=1] 00000010
+    this->compare_set.push_back(this->fromText(this->getCommonText() + "SOA"));
+    // Bit map: [win=0][len=1] 00000010, [win=4][len=1] 10000000
+    this->compare_set.push_back(this->fromText(this->getCommonText() +
+                                               "SOA TYPE1024"));
+    // Bit map: [win=0][len=1] 00100000
+    this->compare_set.push_back(this->fromText(this->getCommonText() + "NS"));
+    // Bit map: [win=0][len=1] 00100010
+    this->compare_set.push_back(this->fromText(this->getCommonText() +
+                                               "NS SOA"));
+    // Bit map: [win=0][len=2] 00100000, 00000001
+    this->compare_set.push_back(this->fromText(this->getCommonText() +
+                                               "NS MX"));
+    // Bit map: [win=4][len=1] 10000000
+    this->compare_set.push_back(this->fromText(this->getCommonText() +
+                                               "TYPE1024"));
+
+    this->compareCheck();
+}
+
+// NSEC bitmaps must not be empty
+TEST_F(NSECBitmapTest, emptyMap) {
+    EXPECT_THROW(this->fromText("next.example").toText(), InvalidRdataText);
+
+    EXPECT_THROW(this->rdataFactoryFromFile(this->getType(), RRClass::IN(),
+                                            (this->getWireFilePrefix() +
+                                             "fromWire16.wire").c_str()),
                  DNSMessageFORMERR);
 }
+
+// NSEC3 bitmaps can be empty
+TEST_F(NSEC3BitmapTest, emptyMap) {
+    // Read wire data wit an empty NSEC3 bitmap.  This should succeed.
+    vector<uint8_t> data;
+    UnitTestUtil::readWireData((this->getWireFilePrefix() +
+                                "fromWire16.wire").c_str(), data);
+    InputBuffer buffer(&data[0], data.size());
+    const uint16_t rdlen = buffer.readUint16();
+    const generic::NSEC3 empty_nsec3 =
+        dynamic_cast<const generic::NSEC3&>(*createRdata(
+                                                RRType::NSEC3(), RRClass::IN(),
+                                                buffer, rdlen));
+
+    // Check the toText() result.
+    EXPECT_EQ("1 0 1 7373737373 D1K6GQ38D1K6GQ38D1K6GQ38D1K6GQ38",
+              empty_nsec3.toText());
+
+    // Check the toWire() result.
+    OutputBuffer obuffer(0);
+    obuffer.writeUint16(rdlen);
+    empty_nsec3.toWire(obuffer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, obuffer.getData(),
+                        obuffer.getLength(), &data[0], data.size());
+
+    // Same for MessageRenderer.
+    obuffer.clear();
+    MessageRenderer renderer(obuffer);
+    renderer.writeUint16(rdlen);
+    empty_nsec3.toWire(renderer);
+    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, renderer.getData(),
+                        renderer.getLength(), &data[0], data.size());
+}
+
 }
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 27edf5f..ae1bcc9 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -20,6 +20,8 @@ BUILT_SOURCES += rdata_nsec_fromWire4.wire rdata_nsec_fromWire5.wire
 BUILT_SOURCES += rdata_nsec_fromWire6.wire rdata_nsec_fromWire7.wire
 BUILT_SOURCES += rdata_nsec_fromWire8.wire rdata_nsec_fromWire9.wire
 BUILT_SOURCES += rdata_nsec_fromWire10.wire
+# 11-15 are skipped to be consistent with NSEC3 test data
+BUILT_SOURCES += rdata_nsec_fromWire16.wire
 BUILT_SOURCES += rdata_nsec3_fromWire2.wire
 BUILT_SOURCES += rdata_nsec3_fromWire4.wire rdata_nsec3_fromWire5.wire
 BUILT_SOURCES += rdata_nsec3_fromWire6.wire rdata_nsec3_fromWire7.wire
@@ -27,6 +29,7 @@ BUILT_SOURCES += rdata_nsec3_fromWire8.wire rdata_nsec3_fromWire9.wire
 BUILT_SOURCES += rdata_nsec3_fromWire10.wire rdata_nsec3_fromWire11.wire
 BUILT_SOURCES += rdata_nsec3_fromWire12.wire rdata_nsec3_fromWire13.wire
 BUILT_SOURCES += rdata_nsec3_fromWire14.wire rdata_nsec3_fromWire15.wire
+BUILT_SOURCES += rdata_nsec3_fromWire16.wire rdata_nsec3_fromWire17.wire
 BUILT_SOURCES += rdata_rrsig_fromWire2.wire
 BUILT_SOURCES += rdata_minfo_fromWire1.wire rdata_minfo_fromWire2.wire
 BUILT_SOURCES += rdata_minfo_fromWire3.wire rdata_minfo_fromWire4.wire
@@ -99,6 +102,7 @@ EXTRA_DIST += rdata_nsec_fromWire4.spec rdata_nsec_fromWire5.spec
 EXTRA_DIST += rdata_nsec_fromWire6.spec rdata_nsec_fromWire7.spec
 EXTRA_DIST += rdata_nsec_fromWire8.spec rdata_nsec_fromWire9.spec
 EXTRA_DIST += rdata_nsec_fromWire10.spec
+EXTRA_DIST += rdata_nsec_fromWire16.spec
 EXTRA_DIST += rdata_nsec3param_fromWire1
 EXTRA_DIST += rdata_nsec3_fromWire1
 EXTRA_DIST += rdata_nsec3_fromWire2.spec rdata_nsec3_fromWire3
@@ -108,6 +112,7 @@ EXTRA_DIST += rdata_nsec3_fromWire8.spec rdata_nsec3_fromWire9.spec
 EXTRA_DIST += rdata_nsec3_fromWire10.spec rdata_nsec3_fromWire11.spec
 EXTRA_DIST += rdata_nsec3_fromWire12.spec rdata_nsec3_fromWire13.spec
 EXTRA_DIST += rdata_nsec3_fromWire14.spec rdata_nsec3_fromWire15.spec
+EXTRA_DIST += rdata_nsec3_fromWire16.spec rdata_nsec3_fromWire17.spec
 EXTRA_DIST += rdata_opt_fromWire rdata_rrsig_fromWire1
 EXTRA_DIST += rdata_rrsig_fromWire2.spec
 EXTRA_DIST += rdata_rp_fromWire1.spec rdata_rp_fromWire2.spec
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec
new file mode 100644
index 0000000..dac14ea
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire16.spec
@@ -0,0 +1,8 @@
+#
+# NSEC3 RDATA with an empty bitmap
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+nbitmap: 0
diff --git a/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec
new file mode 100644
index 0000000..4253349
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec3_fromWire17.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC3 RDATA: RDLEN is too short to include the hash len field.
+#
+
+[custom]
+sections: nsec3
+[nsec3]
+rdlen: 10
diff --git a/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec b/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec
new file mode 100644
index 0000000..d7faeed
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_nsec_fromWire16.spec
@@ -0,0 +1,8 @@
+#
+# An invalid NSEC RDATA: with an empty bitmap
+#
+
+[custom]
+sections: nsec
+[nsec]
+nbitmap: 0
diff --git a/src/lib/nsas/tests/nameserver_address_store_unittest.cc b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
index 4785627..6ddae72 100644
--- a/src/lib/nsas/tests/nameserver_address_store_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
@@ -475,7 +475,7 @@ TEST_F(NameserverAddressStoreTest, updateRTT) {
     // for ns.example.com (the nameserver set for example.net in the class
     // initialization).  We'll set two addresses.
     Name ns_example_com(ns_name);
-    RRsetPtr ns_address = boost::shared_ptr<RRset>(new RRset(
+    isc::dns::RRsetPtr ns_address = boost::shared_ptr<RRset>(new RRset(
         ns_example_com, RRClass::IN(), RRType::A(), RRTTL(300)));
     BOOST_FOREACH(string addr, address) {
         ns_address->addRdata(rdata::in::A(addr));
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index 760ae55..b505399 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -97,6 +97,7 @@ COMMAND_SET_CONFIG = "set_config"
 COMMAND_GET_MODULE_SPEC = "get_module_spec"
 COMMAND_MODULE_SPEC = "module_spec"
 COMMAND_SHUTDOWN = "shutdown"
+COMMAND_MODULE_STOPPING = "stopping"
 
 def parse_command(msg):
     """Parses what may be a command message. If it looks like one,
@@ -210,6 +211,24 @@ class ModuleCCSession(ConfigData):
         self.__send_spec()
         self.__request_config()
 
+    def send_stopping(self):
+        """Sends a 'stopping' message to the configuration manager. This
+           message is just an FYI, and no response is expected. Any errors
+           when sending this message (for instance if the msgq session has
+           previously been closed) are logged, but ignored."""
+        # create_command could raise an exception as well, but except for
+        # out of memory related errors, these should all be programming
+        # failures and are not caught
+        msg = create_command(COMMAND_MODULE_STOPPING,
+                             self.get_module_spec().get_full_spec())
+        try:
+            self._session.group_sendmsg(msg, "ConfigManager")
+        except Exception as se:
+            # If the session was previously closed, obvously trying to send
+            # a message fails. (TODO: check if session is open so we can
+            # error on real problems?)
+            logger.error(CONFIG_SESSION_STOPPING_FAILED, se)
+
     def get_socket(self):
         """Returns the socket from the command channel session. This
            should *only* be used for select() loops to see if there
@@ -371,7 +390,7 @@ class ModuleCCSession(ConfigData):
         except isc.cc.SessionTimeout:
             # TODO: log an error?
             pass
-        
+
     def __request_config(self):
         """Asks the configuration manager for the current configuration, and call the config handler if set.
            Raises a ModuleCCSessionError if there is no answer from the configuration manager"""
@@ -410,30 +429,38 @@ class UIModuleCCSession(MultiConfigData):
            passed must have send_GET and send_POST functions"""
         MultiConfigData.__init__(self)
         self._conn = conn
-        self.request_specifications()
-        self.request_current_config()
+        self.update_specs_and_config()
 
     def request_specifications(self):
-        """Request the module specifications from b10-cmdctl"""
-        # this step should be unnecessary but is the current way cmdctl returns stuff
-        # so changes are needed there to make this clean (we need a command to simply get the
-        # full specs for everything, including commands etc, not separate gets for that)
+        """Clears the current list of specifications, and requests a new
+            list from b10-cmdctl. As other actions may have caused modules
+            to be stopped, or new modules to be added, this is expected to
+            be run after each interaction (at this moment). It is usually
+            also combined with request_current_config(). For that reason,
+            we provide update_specs_and_config() which calls both."""
         specs = self._conn.send_GET('/module_spec')
+        self.clear_specifications()
         for module in specs.keys():
             self.set_specification(isc.config.ModuleSpec(specs[module]))
 
-    def update_specs_and_config(self):
-        self.request_specifications()
-        self.request_current_config()
-
     def request_current_config(self):
         """Requests the current configuration from the configuration
-           manager through b10-cmdctl, and stores those as CURRENT"""
+           manager through b10-cmdctl, and stores those as CURRENT. This
+           does not modify any local changes, it just updates to the current
+           state of the server itself."""
         config = self._conn.send_GET('/config_data')
         if 'version' not in config or config['version'] != BIND10_CONFIG_DATA_VERSION:
             raise ModuleCCSessionError("Bad config version")
         self._set_current_config(config)
 
+    def update_specs_and_config(self):
+        """Convenience function to both clear and update the known list of
+           module specifications, and update the current configuration on
+           the server side. There are a few cases where the caller might only
+           want to run one of these tasks, but often they are both needed."""
+        self.request_specifications()
+        self.request_current_config()
+
     def _add_value_to_list(self, identifier, value, module_spec):
         cur_list, status = self.get_value(identifier)
         if not cur_list:
@@ -582,7 +609,6 @@ class UIModuleCCSession(MultiConfigData):
             # answer is either an empty dict (on success), or one
             # containing errors
             if answer == {}:
-                self.request_current_config()
                 self.clear_local_changes()
             elif "error" in answer:
                 raise ModuleCCSessionError("Error: " + str(answer["error"]) + "\n" + "Configuration not committed")
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index 4d568be..dd97827 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -297,7 +297,7 @@ class ConfigManager:
         """Write the current configuration to the file specificied at init()"""
         self.config.write_to_file()
 
-    def _handle_get_module_spec(self, cmd):
+    def __handle_get_module_spec(self, cmd):
         """Private function that handles the 'get_module_spec' command"""
         answer = {}
         if cmd != None:
@@ -318,7 +318,7 @@ class ConfigManager:
             answer = ccsession.create_answer(0, self.get_module_spec())
         return answer
 
-    def _handle_get_config_dict(self, cmd):
+    def __handle_get_config_dict(self, cmd):
         """Private function that handles the 'get_config' command
            where the command has been checked to be a dict"""
         if 'module_name' in cmd and cmd['module_name'] != '':
@@ -332,17 +332,17 @@ class ConfigManager:
         else:
             return ccsession.create_answer(1, "Bad module_name in get_config command")
 
-    def _handle_get_config(self, cmd):
+    def __handle_get_config(self, cmd):
         """Private function that handles the 'get_config' command"""
         if cmd != None:
             if type(cmd) == dict:
-                return self._handle_get_config_dict(cmd)
+                return self.__handle_get_config_dict(cmd)
             else:
                 return ccsession.create_answer(1, "Bad get_config command, argument not a dict")
         else:
             return ccsession.create_answer(0, self.config.data)
 
-    def _handle_set_config_module(self, module_name, cmd):
+    def __handle_set_config_module(self, module_name, cmd):
         # the answer comes (or does not come) from the relevant module
         # so we need a variable to see if we got it
         answer = None
@@ -405,7 +405,7 @@ class ConfigManager:
                 self.config.data = old_data
         return answer
 
-    def _handle_set_config_all(self, cmd):
+    def __handle_set_config_all(self, cmd):
         old_data = copy.deepcopy(self.config.data)
         got_error = False
         err_list = []
@@ -413,7 +413,7 @@ class ConfigManager:
         # sets, so we simply call set_config_module for each of those
         for module in cmd:
             if module != "version":
-                answer = self._handle_set_config_module(module, cmd[module])
+                answer = self.__handle_set_config_module(module, cmd[module])
                 if answer == None:
                     got_error = True
                     err_list.append("No answer message from " + module)
@@ -432,16 +432,16 @@ class ConfigManager:
             self.config.data = old_data
             return ccsession.create_answer(1, " ".join(err_list))
 
-    def _handle_set_config(self, cmd):
+    def __handle_set_config(self, cmd):
         """Private function that handles the 'set_config' command"""
         answer = None
 
         if cmd == None:
             return ccsession.create_answer(1, "Wrong number of arguments")
         if len(cmd) == 2:
-            answer = self._handle_set_config_module(cmd[0], cmd[1])
+            answer = self.__handle_set_config_module(cmd[0], cmd[1])
         elif len(cmd) == 1:
-            answer = self._handle_set_config_all(cmd[0])
+            answer = self.__handle_set_config_all(cmd[0])
         else:
             answer = ccsession.create_answer(1, "Wrong number of arguments")
         if not answer:
@@ -449,20 +449,41 @@ class ConfigManager:
 
         return answer
 
-    def _handle_module_spec(self, spec):
+    def __handle_module_spec(self, spec):
         """Private function that handles the 'module_spec' command"""
         # todo: validate? (no direct access to spec as
         # todo: use ModuleSpec class
         # todo: error checking (like keyerrors)
         answer = {}
         self.set_module_spec(spec)
+        self._send_module_spec_to_cmdctl(spec.get_module_name(),
+                                         spec.get_full_spec())
+        return ccsession.create_answer(0)
 
-        # We should make one general 'spec update for module' that
-        # passes both specification and commands at once
+    def __handle_module_stopping(self, arg):
+        """Private function that handles a 'stopping' command;
+           The argument is of the form { 'module_name': <name> }.
+           If the module is known, it is removed from the known list,
+           and a message is sent to the Cmdctl channel to remove it as well.
+           If it is unknown, the message is ignored."""
+        if arg['module_name'] in self.module_specs:
+            del self.module_specs[arg['module_name']]
+            self._send_module_spec_to_cmdctl(arg['module_name'], None)
+        # This command is not expected to be answered
+        return None
+
+    def _send_module_spec_to_cmdctl(self, module_name, spec):
+        """Sends the given module spec for the given module name to Cmdctl.
+           Parameters:
+           module_name: A string with the name of the module
+           spec: dict containing full module specification, as returned by
+                 ModuleSpec.get_full_spec(). This argument may also be None,
+                 in which case it signals Cmdctl to remove said module from
+                 its list.
+           No response from Cmdctl is expected."""
         spec_update = ccsession.create_command(ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
-                                               [ spec.get_module_name(), spec.get_full_spec() ])
+                                               [ module_name, spec ])
         self.cc.group_sendmsg(spec_update, "Cmdctl")
-        return ccsession.create_answer(0)
 
     def handle_msg(self, msg):
         """Handle a command from the cc channel to the configuration manager"""
@@ -474,17 +495,19 @@ class ConfigManager:
             elif cmd == ccsession.COMMAND_GET_STATISTICS_SPEC:
                 answer = ccsession.create_answer(0, self.get_statistics_spec())
             elif cmd == ccsession.COMMAND_GET_MODULE_SPEC:
-                answer = self._handle_get_module_spec(arg)
+                answer = self.__handle_get_module_spec(arg)
             elif cmd == ccsession.COMMAND_GET_CONFIG:
-                answer = self._handle_get_config(arg)
+                answer = self.__handle_get_config(arg)
             elif cmd == ccsession.COMMAND_SET_CONFIG:
-                answer = self._handle_set_config(arg)
+                answer = self.__handle_set_config(arg)
+            elif cmd == ccsession.COMMAND_MODULE_STOPPING:
+                answer = self.__handle_module_stopping(arg)
             elif cmd == ccsession.COMMAND_SHUTDOWN:
                 self.running = False
                 answer = ccsession.create_answer(0)
             elif cmd == ccsession.COMMAND_MODULE_SPEC:
                 try:
-                    answer = self._handle_module_spec(isc.config.ModuleSpec(arg))
+                    answer = self.__handle_module_spec(isc.config.ModuleSpec(arg))
                 except isc.config.ModuleSpecError as dde:
                     answer = ccsession.create_answer(1, "Error in data definition: " + str(dde))
             else:
@@ -508,4 +531,6 @@ class ConfigManager:
             # not ask
             if msg is not None and not 'result' in msg:
                 answer = self.handle_msg(msg);
-                self.cc.group_reply(env, answer)
+                # Only respond if there actually is something to respond with
+                if answer is not None:
+                    self.cc.group_reply(env, answer)
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index b2cf048..f53a4d8 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -313,6 +313,10 @@ class MultiConfigData:
         self._current_config = {}
         self._local_changes = {}
 
+    def clear_specifications(self):
+        """Remove all known module specifications"""
+        self._specifications = {}
+
     def set_specification(self, spec):
         """Add or update a ModuleSpec. Raises a ConfigDataError is spec is not a ModuleSpec"""
         if type(spec) != isc.config.ModuleSpec:
diff --git a/src/lib/python/isc/config/config_messages.mes b/src/lib/python/isc/config/config_messages.mes
index c52efb4..9e93ca3 100644
--- a/src/lib/python/isc/config/config_messages.mes
+++ b/src/lib/python/isc/config/config_messages.mes
@@ -31,3 +31,9 @@ The configuration manager returned an error response when the module
 requested its configuration. The full error message answer from the
 configuration manager is appended to the log error.
 
+% CONFIG_SESSION_STOPPING_FAILED error sending stopping message: %1
+There was a problem when sending a message signaling that the module using
+this CCSession is stopping. This message is sent so that the rest of the
+system is aware that the module is no longer running. Apart from logging
+this message, the error itself is ignored, and the ModuleCCSession is
+still stopped. The specific exception message is printed.
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index 7bd4476..df39550 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -250,6 +250,18 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertEqual({'command': ['get_config', {'module_name': 'Spec2'}]},
                          fake_session.get_message('ConfigManager', None))
 
+    def test_stop(self):
+        fake_session = FakeModuleCCSession()
+        self.assertFalse("Spec1" in fake_session.subscriptions)
+        mccs = self.create_session("spec1.spec", None, None, fake_session)
+        self.assertTrue("Spec1" in fake_session.subscriptions)
+
+        self.assertEqual(len(fake_session.message_queue), 0)
+        mccs.send_stopping()
+        self.assertEqual(len(fake_session.message_queue), 1)
+        self.assertEqual({'command': ['stopping', {'module_name': 'Spec1'}]},
+                         fake_session.get_message('ConfigManager', None))
+
     def test_get_socket(self):
         fake_session = FakeModuleCCSession()
         mccs = self.create_session("spec1.spec", None, None, fake_session)
@@ -724,6 +736,38 @@ class TestUIModuleCCSession(unittest.TestCase):
         fake_conn.set_get_answer('/config_data', { 'version': 123123 })
         self.assertRaises(ModuleCCSessionError, UIModuleCCSession, fake_conn)
 
+    def test_request_specifications(self):
+        module_spec1 = isc.config.module_spec_from_file(
+                          self.spec_file("spec1.spec"))
+        module_spec_dict1 = { "module_spec": module_spec1.get_full_spec() }
+        module_spec2 = isc.config.module_spec_from_file(
+                          self.spec_file("spec2.spec"))
+        module_spec_dict2 = { "module_spec": module_spec2.get_full_spec() }
+
+        fake_conn = fakeUIConn()
+        # Set the first one in the answer
+        fake_conn.set_get_answer('/module_spec', module_spec_dict1)
+        fake_conn.set_get_answer('/config_data',
+                                 { 'version': BIND10_CONFIG_DATA_VERSION })
+        uccs = UIModuleCCSession(fake_conn)
+
+        # We should now have the first one, but not the second.
+        self.assertTrue("Spec1" in uccs._specifications)
+        self.assertEqual(module_spec1.get_full_spec(),
+                         uccs._specifications["Spec1"].get_full_spec())
+        self.assertFalse("Spec2" in uccs._specifications)
+
+        # Now set an answer where only the second one is present
+        fake_conn.set_get_answer('/module_spec', module_spec_dict2)
+
+        uccs.request_specifications()
+
+        # Now Spec1 should have been removed, and spec2 should be there
+        self.assertFalse("Spec1" in uccs._specifications)
+        self.assertTrue("Spec2" in uccs._specifications)
+        self.assertEqual(module_spec2.get_full_spec(),
+                         uccs._specifications["Spec2"].get_full_spec())
+
     def test_add_remove_value(self):
         fake_conn = fakeUIConn()
         uccs = self.create_uccs2(fake_conn)
diff --git a/src/lib/python/isc/config/tests/cfgmgr_test.py b/src/lib/python/isc/config/tests/cfgmgr_test.py
index 589a398..7fe8212 100644
--- a/src/lib/python/isc/config/tests/cfgmgr_test.py
+++ b/src/lib/python/isc/config/tests/cfgmgr_test.py
@@ -240,9 +240,6 @@ class TestConfigManager(unittest.TestCase):
 
     def test_read_config(self):
         self.assertEqual(self.cm.config.data, {'version': config_data.BIND10_CONFIG_DATA_VERSION})
-        self.cm.read_config()
-        # due to what get written, the value here is what the last set_config command in test_handle_msg does
-        self.assertEqual(self.cm.config.data, {'TestModule': {'test': 125}, 'version': config_data.BIND10_CONFIG_DATA_VERSION})
         self.cm.data_path = "/no_such_path"
         self.cm.read_config()
         self.assertEqual(self.cm.config.data, {'version': config_data.BIND10_CONFIG_DATA_VERSION})
@@ -255,115 +252,195 @@ class TestConfigManager(unittest.TestCase):
         answer = self.cm.handle_msg(msg)
         self.assertEqual(expected_answer, answer)
 
-    def test_handle_msg(self):
-        self._handle_msg_helper({}, { 'result': [ 1, 'Unknown message format: {}']})
-        self._handle_msg_helper("", { 'result': [ 1, 'Unknown message format: ']})
-        self._handle_msg_helper({ "command": [ "badcommand" ] }, { 'result': [ 1, "Unknown command: badcommand"]})
-        self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, {} ]})
-        self._handle_msg_helper({ "command": [ "get_statistics_spec" ] }, { 'result': [ 0, {} ]})
-        self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, {} ]})
-        self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "Spec2" } ] }, { 'result': [ 0, {} ]})
-        #self._handle_msg_helper({ "command": [ "get_module_spec", { "module_name": "nosuchmodule" } ] },
-        #                        {'result': [1, 'No specification for module nosuchmodule']})
+    def test_handle_msg_basic_commands(self):
+        # Some basic commands, where not much interaction happens, just
+        # check the result
+        self._handle_msg_helper({},
+            { 'result': [ 1, 'Unknown message format: {}']})
+        self._handle_msg_helper("",
+            { 'result': [ 1, 'Unknown message format: ']})
+        self._handle_msg_helper({ "command": [ "badcommand" ] },
+            { 'result': [ 1, "Unknown command: badcommand"]})
+        self._handle_msg_helper({ "command": [ "get_commands_spec" ] },
+                                { 'result': [ 0, {} ]})
+        self._handle_msg_helper({ "command": [ "get_statistics_spec" ] },
+                                { 'result': [ 0, {} ]})
+        self._handle_msg_helper({ "command": [ "get_module_spec" ] },
+                                { 'result': [ 0, {} ]})
+        self._handle_msg_helper({ "command": [ "get_module_spec",
+                                               { "module_name": "Spec2" } ] },
+                                { 'result': [ 0, {} ]})
         self._handle_msg_helper({ "command": [ "get_module_spec", 1 ] },
-                                {'result': [1, 'Bad get_module_spec command, argument not a dict']})
+                                {'result': [1, 'Bad get_module_spec command, '+
+                                               'argument not a dict']})
         self._handle_msg_helper({ "command": [ "get_module_spec", { } ] },
-                                {'result': [1, 'Bad module_name in get_module_spec command']})
-        self._handle_msg_helper({ "command": [ "get_config" ] }, { 'result': [ 0, { 'version': config_data.BIND10_CONFIG_DATA_VERSION } ]})
-        self._handle_msg_helper({ "command": [ "get_config", { "module_name": "nosuchmodule" } ] },
-                                {'result': [0, { 'version': config_data.BIND10_CONFIG_DATA_VERSION }]})
+                                {'result': [1, 'Bad module_name in '+
+                                               'get_module_spec command']})
+        self._handle_msg_helper({ "command": [ "get_config" ] },
+                                { 'result': [ 0, { 'version':
+                                    config_data.BIND10_CONFIG_DATA_VERSION }]})
+        self._handle_msg_helper({ "command": [ "get_config",
+                                    { "module_name": "nosuchmodule" } ] },
+                                {'result': [0, { 'version':
+                                    config_data.BIND10_CONFIG_DATA_VERSION }]})
         self._handle_msg_helper({ "command": [ "get_config", 1 ] },
-                                {'result': [1, 'Bad get_config command, argument not a dict']})
+                                {'result': [1, 'Bad get_config command, '+
+                                               'argument not a dict']})
         self._handle_msg_helper({ "command": [ "get_config", { } ] },
-                                {'result': [1, 'Bad module_name in get_config command']})
+                                {'result': [1, 'Bad module_name in '+
+                                               'get_config command']})
         self._handle_msg_helper({ "command": [ "set_config" ] },
                                 {'result': [1, 'Wrong number of arguments']})
         self._handle_msg_helper({ "command": [ "set_config", [{}]] },
                                 {'result': [0]})
+
         self.assertEqual(len(self.fake_session.message_queue), 0)
 
-        # the targets of some of these tests expect specific answers, put
-        # those in our fake msgq first.
-        my_ok_answer = { 'result': [ 0 ] }
+    def test_handle_msg_module_and_stats_commands(self):
+        self._handle_msg_helper({ "command":
+                                  ["module_spec", self.spec.get_full_spec()]
+                                },
+                                {'result': [0]})
+        # There should be a message on the queue about the 'new' Spec2 module
+        # from ConfigManager to Cmdctl, containing its name and full
+        # specification
+        self.assertEqual(ccsession.create_command(
+                            ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
+                            [ self.spec.get_module_name(),
+                              self.spec.get_full_spec()]),
+                         self.fake_session.get_message("Cmdctl", None))
+
+        self._handle_msg_helper({ "command": [ "module_spec", { 'foo': 1 } ] },
+                                {'result': [1, 'Error in data definition: no '+
+                                               'module_name in module_spec']})
+
+        self._handle_msg_helper({ "command": [ "get_module_spec" ] },
+                                { 'result': [ 0, { self.spec.get_module_name():
+                                                 self.spec.get_full_spec() } ]})
+        self._handle_msg_helper({ "command": [ "get_module_spec",
+                                               { "module_name" : "Spec2" } ] },
+                                { 'result': [ 0, self.spec.get_full_spec() ] })
+        self._handle_msg_helper({ "command": [ "get_commands_spec" ] },
+                                { 'result': [ 0, { self.spec.get_module_name():
+                                              self.spec.get_commands_spec()}]})
+        self._handle_msg_helper({ "command": [ "get_statistics_spec" ] },
+                                { 'result': [ 0, { self.spec.get_module_name():
+                                             self.spec.get_statistics_spec()}]})
+
 
+    def __test_handle_msg_update_config_helper(self, new_config):
+        # Helper function for the common pattern in
+        # test_handle_msg_update_config; send 'set config', check for
+        # update message, check if config has indeed been updated
 
+        my_ok_answer = { 'result': [ 0 ] }
         # Send the 'ok' that cfgmgr expects back to the fake queue first
         self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        # then send the command
-        self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 123 }] ] },
+
+        config_version = config_data.BIND10_CONFIG_DATA_VERSION
+        self._handle_msg_helper({ "command": [ "set_config",
+                                               [ { "version": config_version,
+                                                 self.name: new_config } ] ] },
                                 my_ok_answer)
-        # The cfgmgr should have eaten the ok message, and sent out an update again
+
+        # The cfgmgr should have eaten the ok message, and sent out an update
+        # message
         self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'command': [ 'config_update', {'test': 123}]},
+        self.assertEqual({'command': [ 'config_update', new_config]},
                          self.fake_session.get_message(self.name, None))
+
+        # Config should have been updated
+        self.assertEqual(self.cm.config.data, {self.name: new_config,
+                            'version': config_version})
+
         # and the queue should now be empty again
         self.assertEqual(len(self.fake_session.message_queue), 0)
 
-        # below are variations of the theme above
-        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self._handle_msg_helper({ "command": [ "set_config", [self.name, { "test": 124 }] ] },
-                                my_ok_answer)
-        self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'command': [ 'config_update', {'test': 124}]},
-                         self.fake_session.get_message(self.name, None))
-        self.assertEqual(len(self.fake_session.message_queue), 0)
+    def test_handle_msg_update_config(self):
+        # Update the configuration and check results a few times
+        # only work the first time
+        self.__test_handle_msg_update_config_helper({ "test": 123 })
 
+        self.__test_handle_msg_update_config_helper({ "test": 124 })
 
-        # This is the last 'succes' one, the value set here is what test_read_config expects
-        self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self._handle_msg_helper({ "command": [ "set_config", [ { self.name: { "test": 125 } }] ] },
-                                my_ok_answer )
-        self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'command': [ 'config_update', {'test': 125}]},
-                         self.fake_session.get_message(self.name, None))
-        self.assertEqual(len(self.fake_session.message_queue), 0)
+        self.__test_handle_msg_update_config_helper({ "test": 125 })
+
+        self.__test_handle_msg_update_config_helper({ "test": 126 })
 
-        my_bad_answer = { 'result': [1, "bad_answer"] }
+        # Now send an error result (i.e. config not accepted)
+        my_bad_answer = { 'result': [1, "bad config"] }
         self.fake_session.group_sendmsg(my_bad_answer, "ConfigManager")
-        self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] },
+        self._handle_msg_helper({ "command": [ "set_config",
+                                               [self.name, { "test": 127 }] ] },
                                 my_bad_answer )
         self.assertEqual(len(self.fake_session.message_queue), 1)
-        self.assertEqual({'command': [ 'config_update', {'test': 125}]},
+        self.assertEqual({'command': [ 'config_update', {'test': 127}]},
                          self.fake_session.get_message(self.name, None))
+        # Config should not be updated due to the error
+        self.cm.read_config()
+        self.assertEqual(self.cm.config.data, { self.name: {'test': 126},
+                            'version': config_data.BIND10_CONFIG_DATA_VERSION})
+
         self.assertEqual(len(self.fake_session.message_queue), 0)
 
         self.fake_session.group_sendmsg(None, 'ConfigManager')
         self._handle_msg_helper({ "command": [ "set_config", [ ] ] },
                                 {'result': [1, 'Wrong number of arguments']} )
-        self._handle_msg_helper({ "command": [ "set_config", [ self.name, { "test": 125 }] ] },
-                                { 'result': [1, 'No answer message from TestModule']} )
-
-        #self.assertEqual(len(self.fake_session.message_queue), 1)
-        #self.assertEqual({'config_update': {'test': 124}},
-        #                 self.fake_session.get_message(self.name, None))
-        #self.assertEqual({'version': 1, 'TestModule': {'test': 124}}, self.cm.config.data)
-        #
+        self._handle_msg_helper({ "command": [ "set_config",
+                                               [ self.name, { "test": 128 }]]},
+                                { 'result': [1, 'No answer message '+
+                                                'from TestModule']} )
+
+        # This command should leave a message to the TestModule to update its
+        # configuration (since the TestModule did not eat it)
+        self.assertEqual(len(self.fake_session.message_queue), 1)
+        self.assertEqual(
+            ccsession.create_command(ccsession.COMMAND_CONFIG_UPDATE,
+                                     { "test": 128 }),
+            self.fake_session.get_message("TestModule", None))
+
+        # Make sure queue is empty now
+        self.assertEqual(len(self.fake_session.message_queue), 0)
+
+        # Shutdown should result in 'ok' answer
         self._handle_msg_helper({ "command":
-                                  ["module_spec", self.spec.get_full_spec()]
+                                  ["shutdown"]
                                 },
                                 {'result': [0]})
-        self._handle_msg_helper({ "command": [ "module_spec", { 'foo': 1 } ] },
-                                {'result': [1, 'Error in data definition: no module_name in module_spec']})
-        self._handle_msg_helper({ "command": [ "get_module_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_full_spec() } ]})
-        self._handle_msg_helper({ "command": [ "get_module_spec",
-                                               { "module_name" : "Spec2" } ] },
-                                { 'result': [ 0, self.spec.get_full_spec() ] })
-        self._handle_msg_helper({ "command": [ "get_commands_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_commands_spec() } ]})
-        self._handle_msg_helper({ "command": [ "get_statistics_spec" ] }, { 'result': [ 0, { self.spec.get_module_name(): self.spec.get_statistics_spec() } ]})
-        # re-add this once we have new way to propagate spec changes (1 instead of the current 2 messages)
-        #self.assertEqual(len(self.fake_session.message_queue), 2)
-        # the name here is actually wrong (and hardcoded), but needed in the current version
-        # TODO: fix that
-        #self.assertEqual({'specification_update': [ self.name, self.spec ] },
-        #                 self.fake_session.get_message("Cmdctl", None))
-        #self.assertEqual({'commands_update': [ self.name, self.commands ] },
-        #                 self.fake_session.get_message("Cmdctl", None))
 
+    def test_stopping_message(self):
+        # Update the system by announcing this module
         self._handle_msg_helper({ "command":
-                                  ["shutdown"]
+                                  ["module_spec", self.spec.get_full_spec()]
                                 },
                                 {'result': [0]})
 
+        # This causes a update to be sent from the ConfigManager to the CmdCtl
+        # channel, containing the new module's name and full specification
+        self.assertEqual(ccsession.create_command(
+                            ccsession.COMMAND_MODULE_SPECIFICATION_UPDATE,
+                            [ self.spec.get_module_name(),
+                              self.spec.get_full_spec()]),
+                         self.fake_session.get_message("Cmdctl", None))
+
+        # A stopping message should get no response, but should cause another
+        # message to be sent, if it is a known module
+        self._handle_msg_helper({ "command": [ "stopping",
+                                               { "module_name": "Spec2"}] },
+                                None)
+        self.assertEqual(len(self.fake_session.message_queue), 1)
+        self.assertEqual({'command': [ 'module_specification_update',
+                                       ['Spec2', None] ] },
+                         self.fake_session.get_message("Cmdctl", None))
+
+        # but if the 'stopping' module is either unknown or not running,
+        # no followup message should be sent
+        self._handle_msg_helper({ "command":
+                                  [ "stopping",
+                                    { "module_name": "NoSuchModule" } ] },
+                                None)
+        self.assertEqual(len(self.fake_session.message_queue), 0)
+
     def test_set_config_virtual(self):
         """Test that if the module is virtual, we don't send it over the
            message bus, but call the checking function.
@@ -381,9 +458,9 @@ class TestConfigManager(unittest.TestCase):
             self.cm.set_virtual_module(self.spec, check_test)
             # The fake session will throw now if it tries to read a response.
             # Handy, we don't need to find a complicated way to check for it.
-            result = self.cm._handle_set_config_module(self.spec.
-                                                       get_module_name(),
-                                                       {'item1': value})
+            result = self.cm.handle_msg(ccsession.create_command(
+                        ccsession.COMMAND_SET_CONFIG,
+                        [self.spec.get_module_name(), { "item1": value }]))
             # Check the correct result is passed and our function was called
             # With correct data
             self.assertEqual(self.called_with['item1'], value)
@@ -415,19 +492,22 @@ class TestConfigManager(unittest.TestCase):
         self.assertEqual({"version": 2}, self.cm.config.data)
 
         self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self.cm._handle_set_config_all({"test": { "value1": 123 }})
+        self.cm.handle_msg(ccsession.create_command(
+            ccsession.COMMAND_SET_CONFIG, ["test", { "value1": 123 }]))
         self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
                           "test": { "value1": 123 }
                          }, self.cm.config.data)
 
         self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self.cm._handle_set_config_all({"test": { "value1": 124 }})
+        self.cm.handle_msg(ccsession.create_command(
+            ccsession.COMMAND_SET_CONFIG, ["test", { "value1": 124 }]))
         self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
                           "test": { "value1": 124 }
                          }, self.cm.config.data)
 
         self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self.cm._handle_set_config_all({"test": { "value2": True }})
+        self.cm.handle_msg(ccsession.create_command(
+            ccsession.COMMAND_SET_CONFIG, ["test", { "value2": True }]))
         self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
                           "test": { "value1": 124,
                                     "value2": True
@@ -435,7 +515,8 @@ class TestConfigManager(unittest.TestCase):
                          }, self.cm.config.data)
 
         self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self.cm._handle_set_config_all({"test": { "value3": [ 1, 2, 3 ] }})
+        self.cm.handle_msg(ccsession.create_command(
+            ccsession.COMMAND_SET_CONFIG, ["test", { "value3": [ 1, 2, 3 ] }]))
         self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
                           "test": { "value1": 124,
                                     "value2": True,
@@ -444,7 +525,8 @@ class TestConfigManager(unittest.TestCase):
                          }, self.cm.config.data)
 
         self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self.cm._handle_set_config_all({"test": { "value2": False }})
+        self.cm.handle_msg(ccsession.create_command(
+            ccsession.COMMAND_SET_CONFIG, ["test", { "value2": False }]))
         self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
                           "test": { "value1": 124,
                                     "value2": False,
@@ -453,7 +535,8 @@ class TestConfigManager(unittest.TestCase):
                          }, self.cm.config.data)
 
         self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self.cm._handle_set_config_all({"test": { "value1": None }})
+        self.cm.handle_msg(ccsession.create_command(
+            ccsession.COMMAND_SET_CONFIG, ["test", { "value1": None }]))
         self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
                           "test": { "value2": False,
                                     "value3": [ 1, 2, 3 ]
@@ -461,7 +544,8 @@ class TestConfigManager(unittest.TestCase):
                          }, self.cm.config.data)
 
         self.fake_session.group_sendmsg(my_ok_answer, "ConfigManager")
-        self.cm._handle_set_config_all({"test": { "value3": [ 1 ] }})
+        self.cm.handle_msg(ccsession.create_command(
+            ccsession.COMMAND_SET_CONFIG, ["test", { "value3": [ 1 ] }]))
         self.assertEqual({"version": config_data.BIND10_CONFIG_DATA_VERSION,
                           "test": { "value2": False,
                                     "value3": [ 1 ]
@@ -472,9 +556,14 @@ class TestConfigManager(unittest.TestCase):
     def test_run(self):
         self.fake_session.group_sendmsg({ "command": [ "get_commands_spec" ] }, "ConfigManager")
         self.fake_session.group_sendmsg({ "command": [ "get_statistics_spec" ] }, "ConfigManager")
+        self.fake_session.group_sendmsg({ "command": [ "stopping", { "module_name": "FooModule" } ] }, "ConfigManager")
         self.fake_session.group_sendmsg({ "command": [ "shutdown" ] }, "ConfigManager")
+        self.assertEqual(len(self.fake_session.message_queue), 4)
         self.cm.run()
-        pass
+        # All commands should have been read out by run()
+        # Three of the commands should have been responded to, so the queue
+        # should now contain three answers
+        self.assertEqual(len(self.fake_session.message_queue), 3)
 
 
 if __name__ == '__main__':
diff --git a/src/lib/python/isc/config/tests/config_data_test.py b/src/lib/python/isc/config/tests/config_data_test.py
index bede625..d10804b 100644
--- a/src/lib/python/isc/config/tests/config_data_test.py
+++ b/src/lib/python/isc/config/tests/config_data_test.py
@@ -312,6 +312,16 @@ class TestMultiConfigData(unittest.TestCase):
         self.mcd.remove_specification(module_spec.get_module_name())
         self.assertFalse(self.mcd.have_specification(module_spec.get_module_name()))
 
+    def test_clear_specifications(self):
+        self.assertEqual(0, len(self.mcd._specifications))
+        module_spec = isc.config.module_spec_from_file(self.data_path +
+                                                       os.sep +
+                                                       "spec1.spec")
+        self.mcd.set_specification(module_spec)
+        self.assertEqual(1, len(self.mcd._specifications))
+        self.mcd.clear_specifications()
+        self.assertEqual(0, len(self.mcd._specifications))
+
     def test_get_module_spec(self):
         module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec1.spec")
         self.mcd.set_specification(module_spec)
diff --git a/src/lib/python/isc/testutils/Makefile.am b/src/lib/python/isc/testutils/Makefile.am
index 5479d83..7abc1bc 100644
--- a/src/lib/python/isc/testutils/Makefile.am
+++ b/src/lib/python/isc/testutils/Makefile.am
@@ -1,4 +1,5 @@
-EXTRA_DIST = __init__.py parse_args.py tsigctx_mock.py rrset_utils.py
+EXTRA_DIST = __init__.py ccsession_mock.py parse_args.py tsigctx_mock.py \
+             rrset_utils.py
 
 CLEANDIRS = __pycache__
 
diff --git a/src/lib/python/isc/testutils/ccsession_mock.py b/src/lib/python/isc/testutils/ccsession_mock.py
new file mode 100644
index 0000000..5f88678
--- /dev/null
+++ b/src/lib/python/isc/testutils/ccsession_mock.py
@@ -0,0 +1,34 @@
+# Copyright (C) 2012  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+class MockModuleCCSession():
+    """Fake ModuleCCSession with a minimal implementation as needed by the
+       tests. Currently this module only stores whether some methods have
+       been called on it (send_stopping(), and close())"""
+    def __init__(self):
+        """Will be set to True when send_stopping() is called"""
+        self.stopped = False
+        """Will be set to True when close() is called"""
+        self.closed = False
+
+    def send_stopping(self):
+        """Fake send_stopping() call. No message is sent, but only stores
+           that this method has been called."""
+        self.stopped = True
+
+    def close(self):
+        """Fake close() call. Nothing is closed, but only stores
+           that this method has been called."""
+        self.closed = True
diff --git a/src/lib/server_common/portconfig.cc b/src/lib/server_common/portconfig.cc
index 5c9a7fe..d1e97f3 100644
--- a/src/lib/server_common/portconfig.cc
+++ b/src/lib/server_common/portconfig.cc
@@ -92,17 +92,12 @@ setAddresses(DNSService& service, const AddressList& addresses) {
     current_sockets.clear();
     BOOST_FOREACH(const AddressPair &address, addresses) {
         const int af(IOAddress(address.first).getFamily());
-        // TODO: Support sharing somehow in future.
-
-        // As for now, we hardcode the application name as dummy_app, because:
-        // * we don't have a name available in our interface, which will change
-        //   soon anyway
-        // * we use the DONT_SHARE mode, so the name is irrelevant anyway
+        // We use the application name supplied to the socket requestor on
+        // creation. So we can freely use the SHARE_SAME
         const SocketRequestor::SocketID
             tcp(socketRequestor().requestSocket(SocketRequestor::TCP,
                                                 address.first, address.second,
-                                                SocketRequestor::DONT_SHARE,
-                                                "dummy_app"));
+                                                SocketRequestor::SHARE_SAME));
         current_sockets.push_back(tcp.second);
         if (!test_mode) {
             service.addServerTCPFromFD(tcp.first, af);
@@ -110,8 +105,7 @@ setAddresses(DNSService& service, const AddressList& addresses) {
         const SocketRequestor::SocketID
             udp(socketRequestor().requestSocket(SocketRequestor::UDP,
                                                 address.first, address.second,
-                                                SocketRequestor::DONT_SHARE,
-                                                "dummy_app"));
+                                                SocketRequestor::SHARE_SAME));
         current_sockets.push_back(udp.second);
         if (!test_mode) {
             service.addServerUDPFromFD(udp.first, af);
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 3b3090d..0817928 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/src/lib/server_common/server_common_messages.mes
@@ -16,7 +16,7 @@ $NAMESPACE isc::server_common
 
 # \brief Messages for the server_common library
 
-% SOCKETREQUESTOR_CREATED Socket requestor created
+% SOCKETREQUESTOR_CREATED Socket requestor created for application %1
 Debug message.  A socket requesor (client of the socket creator) is created
 for the corresponding application.  Normally this should happen at most
 one time throughout the lifetime of the application.
diff --git a/src/lib/server_common/socket_request.cc b/src/lib/server_common/socket_request.cc
index 546de05..e471ad0 100644
--- a/src/lib/server_common/socket_request.cc
+++ b/src/lib/server_common/socket_request.cc
@@ -264,8 +264,10 @@ getSocketFd(const std::string& token, int sock_pass_fd) {
 // be closed during the lifetime of this class
 class SocketRequestorCCSession : public SocketRequestor {
 public:
-    explicit SocketRequestorCCSession(cc::AbstractSession& session) :
-        session_(session)
+    SocketRequestorCCSession(cc::AbstractSession& session,
+                             const std::string& app_name) :
+        session_(session),
+        app_name_(app_name)
     {
         // We need to filter SIGPIPE to prevent it from happening in
         // getSocketFd() while writing to the UNIX domain socket after the
@@ -278,7 +280,8 @@ public:
             isc_throw(Unexpected, "Failed to filter SIGPIPE: " <<
                       strerror(errno));
         }
-        LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, SOCKETREQUESTOR_CREATED);
+        LOG_DEBUG(logger, DBGLVL_TRACE_BASIC, SOCKETREQUESTOR_CREATED).
+            arg(app_name);
     }
 
     ~SocketRequestorCCSession() {
@@ -293,7 +296,9 @@ public:
     {
         const isc::data::ConstElementPtr request_msg =
             createRequestSocketMessage(protocol, address, port,
-                                       share_mode, share_name);
+                                       share_mode,
+                                       share_name.empty() ? app_name_ :
+                                       share_name);
 
         // Send it to boss
         const int seq = session_.group_sendmsg(request_msg, "Boss");
@@ -377,6 +382,7 @@ private:
     }
 
     cc::AbstractSession& session_;
+    const std::string app_name_;
     std::map<std::string, int> fd_share_sockets_;
 };
 
@@ -392,12 +398,14 @@ socketRequestor() {
 }
 
 void
-initSocketRequestor(cc::AbstractSession& session) {
+initSocketRequestor(cc::AbstractSession& session,
+                    const std::string& app_name)
+{
     if (requestor != NULL) {
         isc_throw(InvalidOperation,
                   "The socket requestor was already initialized");
     } else {
-        requestor = new SocketRequestorCCSession(session);
+        requestor = new SocketRequestorCCSession(session, app_name);
     }
 }
 
diff --git a/src/lib/server_common/socket_request.h b/src/lib/server_common/socket_request.h
index 5708cc1..aac95d1 100644
--- a/src/lib/server_common/socket_request.h
+++ b/src/lib/server_common/socket_request.h
@@ -161,12 +161,31 @@ public:
     /// \param port the port to which the socket should be bound (native endian,
     ///     not network byte order).
     /// \param share_mode how the socket can be shared with other requests.
-    /// This must be one of the defined values of ShareMode.
+    ///     This must be one of the defined values of ShareMode..
     /// \param share_name the name of sharing group, relevant for SHARE_SAME
-    ///     (specified by us or someone else).
+    ///     (specified by us or someone else). If left empty (the default),
+    ///     the app_name parameter of initSocketRequestor is used. If that one
+    ///     is empty as well, it is accepted, but not recommended, as such
+    ///     a non-descriptive name has a high chance of collisions between
+    ///     applications. Note that you should provide a name (by share_name
+    ///     or app_name) even when you set it to DONT_SHARE (for logs and
+    ///     debugging) and you need to provide one with SHARE_SAME (to know
+    ///     what is same) and SHARE_ANY (someone else might want SHARE_SAME,
+    ///     so it would check against this)
     /// \return the socket, as a file descriptor and token representing it on
     ///     the socket creator side.
     ///
+    /// To understand the modes better:
+    /// - If mode is DONT_SHARE, it succeeds if no one else has opened an FD
+    ///   for requested protocol, address and port.
+    /// - If mode is SHARE_SAME, it succeeds if all applications who opened an
+    ///   FD for the requested protocol, address and port provided the same
+    ///   share_name as this one and none of them had mode DONT_SHARE.
+    /// - If mode is SHARE_ANY, it succeeds if no applications who requested
+    ///   the same potocol, address and port provided DONT_SHARE and all the
+    ///   applications who provided SHARE_SAME also provided the same
+    ///   share_name as this process did.
+    ///
     /// \throw InvalidParameter protocol or share_mode is invalid
     /// \throw CCSessionError when we have a problem talking over the CC
     ///     session.
@@ -180,7 +199,7 @@ public:
     virtual SocketID requestSocket(Protocol protocol,
                                    const std::string& address,
                                    uint16_t port, ShareMode share_mode,
-                                   const std::string& share_name) = 0;
+                                   const std::string& share_name = "") = 0;
 
     /// \brief Tell the socket creator we no longer need the socket
     ///
@@ -215,8 +234,16 @@ SocketRequestor& socketRequestor();
 ///
 /// \param session the CC session that'll be used to talk to the
 ///                socket creator.
+/// \param app_name default share name if one is not provided with
+///                 requestSocket. You can leave this as empty string,
+///                 but then you should provide a reasonably descriptive
+///                 name to requestSocket. Empty names work like any others,
+///                 but have a high chance of collisions, so it is recommended
+///                 to avoid them and provide the name of the application
+///                 here.
 /// \throw InvalidOperation when it is called more than once
-void initSocketRequestor(cc::AbstractSession& session);
+void initSocketRequestor(cc::AbstractSession& session,
+                         const std::string& app_name);
 
 /// \brief Initialization for tests
 ///
diff --git a/src/lib/server_common/tests/portconfig_unittest.cc b/src/lib/server_common/tests/portconfig_unittest.cc
index ffeb322..2ea3808 100644
--- a/src/lib/server_common/tests/portconfig_unittest.cc
+++ b/src/lib/server_common/tests/portconfig_unittest.cc
@@ -133,7 +133,10 @@ TEST_F(ParseAddresses, invalid) {
 struct InstallListenAddresses : public ::testing::Test {
     InstallListenAddresses() :
         dnss_(ios_, NULL, NULL, NULL),
-        sock_requestor_(dnss_, store_, 5288)
+        // The empty string is expected parameter of requestSocket,
+        // not app_name - the request does not fall back to this, it
+        // is checked to be the same.
+        sock_requestor_(dnss_, store_, 5288, "")
     {
         valid_.push_back(AddressPair("127.0.0.1", 5288));
         valid_.push_back(AddressPair("::1", 5288));
diff --git a/src/lib/server_common/tests/socket_requestor_test.cc b/src/lib/server_common/tests/socket_requestor_test.cc
index e917e0c..829b6d9 100644
--- a/src/lib/server_common/tests/socket_requestor_test.cc
+++ b/src/lib/server_common/tests/socket_requestor_test.cc
@@ -83,7 +83,7 @@ public:
                                     ElementPtr(new ListElement),
                                     ElementPtr(new ListElement))
     {
-        initSocketRequestor(session);
+        initSocketRequestor(session, "tests");
     }
 
     ~SocketRequestorTest() {
@@ -158,35 +158,46 @@ TEST_F(SocketRequestorTest, testSocketRequestMessages) {
 
     expected_request = createExpectedRequest("192.0.2.1", 12345, "UDP",
                                              "NO", "test");
-    ASSERT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
-                                   "192.0.2.1", 12345,
-                                   SocketRequestor::DONT_SHARE,
-                                   "test"),
+    EXPECT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
+                                                 "192.0.2.1", 12345,
+                                                 SocketRequestor::DONT_SHARE,
+                                                 "test"),
                  CCSessionError);
     ASSERT_EQ(1, session.getMsgQueue()->size());
-    ASSERT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+    EXPECT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
 
     clearMsgQueue();
     expected_request = createExpectedRequest("192.0.2.2", 1, "TCP",
                                              "ANY", "test2");
-    ASSERT_THROW(socketRequestor().requestSocket(SocketRequestor::TCP,
-                                   "192.0.2.2", 1,
-                                   SocketRequestor::SHARE_ANY,
-                                   "test2"),
+    EXPECT_THROW(socketRequestor().requestSocket(SocketRequestor::TCP,
+                                                 "192.0.2.2", 1,
+                                                 SocketRequestor::SHARE_ANY,
+                                                 "test2"),
                  CCSessionError);
     ASSERT_EQ(1, session.getMsgQueue()->size());
-    ASSERT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+    EXPECT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
 
     clearMsgQueue();
     expected_request = createExpectedRequest("::1", 2, "UDP",
                                              "SAMEAPP", "test3");
-    ASSERT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
-                                   "::1", 2,
-                                   SocketRequestor::SHARE_SAME,
-                                   "test3"),
+    EXPECT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
+                                                 "::1", 2,
+                                                 SocketRequestor::SHARE_SAME,
+                                                 "test3"),
                  CCSessionError);
     ASSERT_EQ(1, session.getMsgQueue()->size());
-    ASSERT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+    EXPECT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
+
+    // A default share name equal to the app name passed on construction
+    clearMsgQueue();
+    expected_request = createExpectedRequest("::1", 2, "UDP",
+                                             "SAMEAPP", "tests");
+    EXPECT_THROW(socketRequestor().requestSocket(SocketRequestor::UDP,
+                                                 "::1", 2,
+                                   SocketRequestor::SHARE_SAME),
+                 CCSessionError);
+    ASSERT_EQ(1, session.getMsgQueue()->size());
+    EXPECT_EQ(*expected_request, *(session.getMsgQueue()->get(0)));
 }
 
 TEST_F(SocketRequestorTest, invalidParameterForSocketRequest) {
@@ -211,19 +222,19 @@ TEST_F(SocketRequestorTest, testBadRequestAnswers) {
     // Test various scenarios where the requestor gets back bad answers
 
     // Should raise CCSessionError if there is no answer
-    ASSERT_THROW(doRequest(), CCSessionError);
+    EXPECT_THROW(doRequest(), CCSessionError);
 
     // Also if the answer does not match the format
     session.getMessages()->add(createAnswer());
-    ASSERT_THROW(doRequest(), CCSessionError);
+    EXPECT_THROW(doRequest(), CCSessionError);
 
     // Now a 'real' answer, should fail on socket connect (no such file)
     addAnswer("foo", "/does/not/exist");
-    ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+    EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
 
     // Another failure (domain socket path too long)
     addAnswer("foo", std::string(1000, 'x'));
-    ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+    EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
 
     // Test values around path boundary
     struct sockaddr_un sock_un;
@@ -236,7 +247,7 @@ TEST_F(SocketRequestorTest, testBadRequestAnswers) {
         doRequest();
         FAIL() << "doRequest did not throw an exception";
     } catch (const SocketRequestor::SocketError& se) {
-        ASSERT_EQ(std::string::npos, std::string(se.what()).find("too long"));
+        EXPECT_EQ(std::string::npos, std::string(se.what()).find("too long"));
     }
 
     const std::string too_long(sizeof(sock_un.sun_path), 'x');
@@ -246,18 +257,18 @@ TEST_F(SocketRequestorTest, testBadRequestAnswers) {
         doRequest();
         FAIL() << "doRequest did not throw an exception";
     } catch (const SocketRequestor::SocketError& se) {
-        ASSERT_NE(std::string::npos, std::string(se.what()).find("too long"));
+        EXPECT_NE(std::string::npos, std::string(se.what()).find("too long"));
     }
 
     // Send back an error response
     // A generic one first
     session.getMessages()->add(createAnswer(1, "error"));
-    ASSERT_THROW(doRequest(), CCSessionError);
+    EXPECT_THROW(doRequest(), CCSessionError);
     // Now some with specific exceptions
     session.getMessages()->add(createAnswer(2, "error"));
-    ASSERT_THROW(doRequest(), SocketRequestor::SocketAllocateError);
+    EXPECT_THROW(doRequest(), SocketRequestor::SocketAllocateError);
     session.getMessages()->add(createAnswer(3, "error"));
-    ASSERT_THROW(doRequest(), SocketRequestor::ShareError);
+    EXPECT_THROW(doRequest(), SocketRequestor::ShareError);
 }
 
 // Helper function to create the release commands as we expect
@@ -286,24 +297,24 @@ TEST_F(SocketRequestorTest, testSocketReleaseMessages) {
     expected_release = createExpectedRelease("foo");
     socketRequestor().releaseSocket("foo");
     ASSERT_EQ(1, session.getMsgQueue()->size());
-    ASSERT_EQ(*expected_release, *(session.getMsgQueue()->get(0)));
+    EXPECT_EQ(*expected_release, *(session.getMsgQueue()->get(0)));
 
     session.getMessages()->add(createAnswer());
     clearMsgQueue();
     expected_release = createExpectedRelease("bar");
     socketRequestor().releaseSocket("bar");
     ASSERT_EQ(1, session.getMsgQueue()->size());
-    ASSERT_EQ(*expected_release, *(session.getMsgQueue()->get(0)));
+    EXPECT_EQ(*expected_release, *(session.getMsgQueue()->get(0)));
 }
 
 TEST_F(SocketRequestorTest, testBadSocketReleaseAnswers) {
     // Should fail if there is no answer at all
-    ASSERT_THROW(socketRequestor().releaseSocket("bar"),
+    EXPECT_THROW(socketRequestor().releaseSocket("bar"),
                  CCSessionError);
 
     // Should also fail if the answer is an error
     session.getMessages()->add(createAnswer(1, "error"));
-    ASSERT_THROW(socketRequestor().releaseSocket("bar"),
+    EXPECT_THROW(socketRequestor().releaseSocket("bar"),
                  SocketRequestor::SocketError);
 }
 
@@ -514,20 +525,20 @@ TEST_F(SocketRequestorTest, testSocketPassing) {
         // 1 should be ok
         addAnswer("foo", ts.getPath());
         socket_id = doRequest();
-        ASSERT_EQ("foo", socket_id.second);
-        ASSERT_EQ(0, close(socket_id.first));
+        EXPECT_EQ("foo", socket_id.second);
+        EXPECT_EQ(0, close(socket_id.first));
 
         // 2 should be ok too
         addAnswer("bar", ts.getPath());
         socket_id = doRequest();
-        ASSERT_EQ("bar", socket_id.second);
-        ASSERT_EQ(0, close(socket_id.first));
+        EXPECT_EQ("bar", socket_id.second);
+        EXPECT_EQ(0, close(socket_id.first));
 
         // 3 should be ok too (reuse earlier token)
         addAnswer("foo", ts.getPath());
         socket_id = doRequest();
-        ASSERT_EQ("foo", socket_id.second);
-        ASSERT_EQ(0, close(socket_id.first));
+        EXPECT_EQ("foo", socket_id.second);
+        EXPECT_EQ(0, close(socket_id.first));
     }
 
     // Create a second socket server, to test that multiple different
@@ -542,35 +553,35 @@ TEST_F(SocketRequestorTest, testSocketPassing) {
         // 1 should be ok
         addAnswer("foo", ts2.getPath());
         socket_id = doRequest();
-        ASSERT_EQ("foo", socket_id.second);
-        ASSERT_EQ(0, close(socket_id.first));
+        EXPECT_EQ("foo", socket_id.second);
+        EXPECT_EQ(0, close(socket_id.first));
     }
 
     if (timo_ok) {
         // Now use first socket again
         addAnswer("foo", ts.getPath());
         socket_id = doRequest();
-        ASSERT_EQ("foo", socket_id.second);
-        ASSERT_EQ(0, close(socket_id.first));
+        EXPECT_EQ("foo", socket_id.second);
+        EXPECT_EQ(0, close(socket_id.first));
 
         // -1 is a "normal" error
         addAnswer("foo", ts.getPath());
-        ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+        EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
 
         // -2 is an unexpected error.  After this point it's not guaranteed the
         // connection works as intended.
         addAnswer("foo", ts.getPath());
-        ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+        EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
     }
 
     // Vector is of first socket is now empty, so the socket should be gone
     addAnswer("foo", ts.getPath());
-    ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+    EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
 
     // Vector is of second socket is now empty too, so the socket should be
     // gone
     addAnswer("foo", ts2.getPath());
-    ASSERT_THROW(doRequest(), SocketRequestor::SocketError);
+    EXPECT_THROW(doRequest(), SocketRequestor::SocketError);
 }
 
 }
diff --git a/src/lib/testutils/socket_request.h b/src/lib/testutils/socket_request.h
index a1e51e2..2c14120 100644
--- a/src/lib/testutils/socket_request.h
+++ b/src/lib/testutils/socket_request.h
@@ -59,11 +59,18 @@ public:
     /// \param expect_port The port which is expected to be requested. If
     ///     the application requests a different port, it is considered
     ///     a failure.
+    /// \param expeted_app The share name for which all the requests should
+    ///     be made. This is not the usual app_name - the requestSocket does
+    ///     not fall back to this value if its share_name is left empty, if
+    ///     you want to check the code relies on the requestor to use the
+    ///     app name, you set this to empty string.
     TestSocketRequestor(asiodns::DNSService& dnss,
                         server_common::portconfig::AddressList& store,
-                        uint16_t expect_port) :
+                        uint16_t expect_port,
+                        const std::string& expected_app) :
         last_token_(0), break_rollback_(false), break_release_(false),
-        dnss_(dnss), store_(store), expect_port_(expect_port)
+        dnss_(dnss), store_(store), expect_port_(expect_port),
+        expected_app_(expected_app)
     {
         // Prepare the requestor (us) for the test
         server_common::initTestSocketRequestor(this);
@@ -141,8 +148,13 @@ public:
     /// \param protocol The protocol to request
     /// \param address to bind to
     /// \param port to bind to
-    /// \param mode checked to be DONT_SHARE for now
-    /// \param name checked to be dummy_app for now
+    /// \param mode checked to be SHARE_SAME for now
+    /// \param name checked to be the same as expected_app parameter of the
+    ///      constructor. Note that this class does not provide the fallback
+    ///      to an app_name if this is empty string. To check the code relies
+    ///      on the fallback (wants to use the app_name instead of providing
+    ///      its own share name), you need to create this class with empty
+    ///      expected_app.
     /// \return The token and FD
     /// \throw SocketAllocateError as described above, to test error handling
     /// \throw ShareError as described above, to test error handling
@@ -169,8 +181,8 @@ public:
         const std::string proto(protocol == TCP ? "TCP" : "UDP");
         const size_t number = ++ last_token_;
         EXPECT_EQ(expect_port_, port);
-        EXPECT_EQ(DONT_SHARE, mode);
-        EXPECT_EQ("dummy_app", name);
+        EXPECT_EQ(SHARE_SAME, mode);
+        EXPECT_EQ(expected_app_, name);
         const std::string token(proto + ":" + address + ":" +
                                 boost::lexical_cast<std::string>(port) + ":" +
                                 boost::lexical_cast<std::string>(number));
@@ -207,6 +219,7 @@ private:
     asiodns::DNSService& dnss_;
     server_common::portconfig::AddressList& store_;
     const uint16_t expect_port_;
+    const std::string expected_app_;
 };
 
 }
diff --git a/tests/lettuce/features/bindctl_commands.feature b/tests/lettuce/features/bindctl_commands.feature
new file mode 100644
index 0000000..872064f
--- /dev/null
+++ b/tests/lettuce/features/bindctl_commands.feature
@@ -0,0 +1,38 @@
+Feature: control with bindctl
+    Assorted tests using bindctl for the administration of BIND 10.
+
+    Scenario: Removing modules
+    # This test runs the original example configuration, which has
+    # a number of modules. It then removes all non-essential modules,
+    # and checks whether they do disappear from the list of running
+    # modules (note that it 'misuses' the help command for this,
+    # there is a Boss command 'show_processes' but it's output is
+    # currently less standardized than 'help')
+    Given I have bind10 running with configuration example.org.config
+
+    Then remove bind10 configuration Boss/components/NOSUCHMODULE
+    last bindctl output should contain Error
+
+    bind10 module Xfrout should be running
+    bind10 module Stats should be running
+    bind10 module Zonemgr should be running
+    bind10 module Xfrin should be running
+    bind10 module Auth should be running
+    bind10 module StatsHttpd should be running
+
+    Then remove bind10 configuration Boss/components value b10-xfrout
+    last bindctl output should not contain Error
+    # assuming it won't error for further modules (if it does, the final
+    # 'should not be running' tests would fail anyway)
+    Then remove bind10 configuration Boss/components value b10-stats
+    Then remove bind10 configuration Boss/components value b10-zonemgr
+    Then remove bind10 configuration Boss/components value b10-xfrin
+    Then remove bind10 configuration Boss/components value b10-auth
+    Then remove bind10 configuration Boss/components value b10-stats-httpd
+
+    bind10 module Xfrout should not be running
+    bind10 module Stats should not be running
+    bind10 module Zonemgr should not be running
+    bind10 module Xfrin should not be running
+    bind10 module Auth should not be running
+    bind10 module StatsHttpd should not be running
diff --git a/tests/lettuce/features/terrain/bind10_control.py b/tests/lettuce/features/terrain/bind10_control.py
index fdc419b..7ccd2b3 100644
--- a/tests/lettuce/features/terrain/bind10_control.py
+++ b/tests/lettuce/features/terrain/bind10_control.py
@@ -112,8 +112,63 @@ def have_bind10_running(step, config_file, cmdctl_port, process_name):
     step.given(start_step)
     step.given(wait_step)
 
+# function to send lines to bindctl, and store the result
+def run_bindctl(commands, cmdctl_port=None):
+    """Run bindctl.
+       Parameters:
+       commands: a sequence of strings which will be sent.
+       cmdctl_port: a port number on which cmdctl is listening, is converted
+                    to string if necessary. If not provided, or None, defaults
+                    to 47805
+
+       bindctl's stdout and stderr streams are stored (as one multiline string
+       in world.last_bindctl_stdout/stderr.
+       Fails if the return code is not 0
+    """
+    if cmdctl_port is None:
+        cmdctl_port = 47805
+    args = ['bindctl', '-p', str(cmdctl_port)]
+    bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
+                               subprocess.PIPE, None)
+    for line in commands:
+        bindctl.stdin.write(line + "\n")
+    (stdout, stderr) = bindctl.communicate()
+    result = bindctl.returncode
+    world.last_bindctl_stdout = stdout
+    world.last_bindctl_stderr = stderr
+    assert result == 0, "bindctl exit code: " + str(result) +\
+                        "\nstdout:\n" + str(stdout) +\
+                        "stderr:\n" + str(stderr)
+
+
+ at step('last bindctl( stderr)? output should( not)? contain (\S+)')
+def check_bindctl_output(step, stderr, notv, string):
+    """Checks the stdout (or stderr) stream of the last run of bindctl,
+       fails if the given string is not found in it (or fails if 'not' was
+       set and it is found
+       Parameters:
+       stderr ('stderr'): Check stderr instead of stdout output
+       notv ('not'): reverse the check (fail if string is found)
+       string ('contain <string>') string to look for
+    """
+    if stderr is None:
+        output = world.last_bindctl_stdout
+    else:
+        output = world.last_bindctl_stderr
+    found = False
+    if string in output:
+        found = True
+    if notv is None:
+        assert found == True, "'" + string +\
+                              "' was not found in bindctl output:\n" +\
+                              output
+    else:
+        assert not found, "'" + string +\
+                          "' was found in bindctl output:\n" +\
+                          output
+
 @step('set bind10 configuration (\S+) to (.*)(?: with cmdctl port (\d+))?')
-def set_config_command(step, name, value, cmdctl_port):
+def config_set_command(step, name, value, cmdctl_port):
     """
     Run bindctl, set the given configuration to the given value, and commit it.
     Parameters:
@@ -123,16 +178,30 @@ def set_config_command(step, name, value, cmdctl_port):
                 the command to. Defaults to 47805.
     Fails if cmdctl does not exit with status code 0.
     """
-    if cmdctl_port is None:
-        cmdctl_port = '47805'
-    args = ['bindctl', '-p', cmdctl_port]
-    bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
-                               subprocess.PIPE, None)
-    bindctl.stdin.write("config set " + name + " " + value + "\n")
-    bindctl.stdin.write("config commit\n")
-    bindctl.stdin.write("quit\n")
-    result = bindctl.wait()
-    assert result == 0, "bindctl exit code: " + str(result)
+    commands = ["config set " + name + " " + value,
+                "config commit",
+                "quit"]
+    run_bindctl(commands, cmdctl_port)
+
+ at step('remove bind10 configuration (\S+)(?: value (\S+))?(?: with cmdctl port (\d+))?')
+def config_remove_command(step, name, value, cmdctl_port):
+    """
+    Run bindctl, remove the given configuration item, and commit it.
+    Parameters:
+    name ('configuration <name>'): Identifier of the configuration to remove
+    value ('value <value>'): if name is a named set, use value to identify
+                             item to remove
+    cmdctl_port ('with cmdctl port <portnr>', optional): cmdctl port to send
+                the command to. Defaults to 47805.
+    Fails if cmdctl does not exit with status code 0.
+    """
+    cmd = "config remove " + name
+    if value is not None:
+        cmd = cmd + " " + value
+    commands = [cmd,
+                "config commit",
+                "quit"]
+    run_bindctl(commands, cmdctl_port)
 
 @step('send bind10 the command (.+)(?: with cmdctl port (\d+))?')
 def send_command(step, command, cmdctl_port):
@@ -144,15 +213,21 @@ def send_command(step, command, cmdctl_port):
                 the command to. Defaults to 47805.
     Fails if cmdctl does not exit with status code 0.
     """
-    if cmdctl_port is None:
-        cmdctl_port = '47805'
-    args = ['bindctl', '-p', cmdctl_port]
-    bindctl = subprocess.Popen(args, 1, None, subprocess.PIPE,
-                               subprocess.PIPE, None)
-    bindctl.stdin.write(command + "\n")
-    bindctl.stdin.write("quit\n")
-    (stdout, stderr) = bindctl.communicate()
-    result = bindctl.returncode
-    assert result == 0, "bindctl exit code: " + str(result) +\
-                        "\nstdout:\n" + str(stdout) +\
-                        "stderr:\n" + str(stderr)
+    commands = [command,
+                "quit"]
+    run_bindctl(commands, cmdctl_port)
+
+ at step('bind10 module (\S+) should( not)? be running')
+def module_is_running(step, name, not_str):
+    """
+    Convenience step to check if a module is running; can only work with
+    default cmdctl port; sends a 'help' command with bindctl, then
+    checks if the output contains the given name.
+    Parameters:
+    name ('module <name>'): The name of the module (case sensitive!)
+    not ('not'): Reverse the check (fail if it is running)
+    """
+    if not_str is None:
+        not_str = ""
+    step.given('send bind10 the command help')
+    step.given('last bindctl output should' + not_str + ' contain ' + name)




More information about the bind10-changes mailing list